如何在DOS中获取额外的段?

7

我想写一个小的DOS程序(我的第一个),但我有点缺乏经验。

对于这个程序,我需要超过64千字节的(传统的)内存。如何获得额外的内存?理想情况下,我希望为程序获得两个额外的64k内存块。我可以直接开始将数据写入地址空间的某个位置吗?还是需要请求额外的内存?


你在使用哪个汇编器? - Ross Ridge
@RossRidge 我使用GNU汇编器,因为我熟悉它的语法。 - fuz
啊,这很不幸。GNU汇编器不支持段并且不能用于创建MS-DOS EXE文件。否则,您可以在源代码中定义两个64k的内存块。 - Ross Ridge
@RossRidge 我想创建一个简单的COM二进制文件。这个练习的一部分是尝试理解DOS如何管理内存,所以我想在运行时“分配”内存,即使我事先知道我最多需要多少内存。我将来会放弃GAS,但现在我比其他汇编器更熟悉它。 - fuz
4个回答

9

我最近偶然发现了这个问题。虽然它已经几年了,但我觉得除了当前答案之外,一些额外的信息可能对未来的读者有用。


这个问题实际上是:我可以在DOS分配的程序范围之外任意写入内存吗?该问题针对DOS COM程序,但其中大部分信息也适用于DOS EXE程序。
GNU汇编器的局限性在于它不生成16位DOS EXE程序,因此您必须生成DOS COM程序。DOS COM程序的起始点为0x100。代码、数据和堆栈不能超过64KiB的内存(在加载时)。一旦被DOS加载器加载到内存中,DOS COM程序将具有以下特征:
  • 程序进入时,DS=ES=SS=CS
  • 程序可重定位到任何段,并且不包含加载时间的修复/重定位。
  • 尽管DOS COM程序在加载时被限制为<=64KiB的内存,但该程序被分配了DOS内存池中最大的连续空闲块。DOS加载器有效地将整个空闲池分配给您的COM程序。
  • DOS加载器总是设置SS = CS,但如果我们程序的可用空间小于64KiB,SP可能以0x00001之外的值开始。
  • DOS加载器在转移控制到CS:0x0100启动我们的程序之前,总是将值0x0000推送到堆栈上。CS:0x0000是PSP的起点,而PSP以2字节指令(0xcd 0x20)Int 20h开始。 Int 20h终止当前程序。这是允许DOS COM程序使用ret来终止程序的机制。
  • 有一个名为Program Segment Prefix(PSP)的程序控制块,DOS将其放置在CS:0x0000和CS:0x0100之间的内存中。
  • COM程序从CS:0x0100开始执行。
首先要问的问题是:我的DOS COM程序实际上有多少内存?简单的答案是:它会根据可用的常规内存量而变化(IBM PC通常配备64KiB、128KiB、256KiB、512KiB或640KiB)。另一个答案中引用的Dr. Dobbs Journal文章发表于1988年,内存映射缺少一些关键内容。

1987年IBM发布了IBM PS/2系列电脑。为了保存鼠标相关信息,IBM意识到位于中断向量表上面的BIOS数据区没有足够的空间,因此他们创建了一个扩展BIOS数据区(EBDA)。这个内存由BIOS保留,而IBM PS/2 BIOS开始报告比实际少1KiB的内存(639KiB而不是640KiB)。EBDA的大小取决于BIOS制造商。BIOS Int 12h调用将返回不包括EBDA区域在内的常规内存(<=640KiB)的数量。DOS依靠这个来确定可用的内存量。

更糟糕的是,当基于386SL的系统发布时,它包括运行在环-2的System Management Mode,可以完全访问您的PC。这些系统也开始使用EBDA中的空间。一些系统需要超过1KiB的空间。理论上,可以有128KiB的EBDA空间,但我不确定是否有任何系统真正达到了这个数值!最终,该区域被用于电源管理(APM)、ACPI、SMBIOS,而且System Management Mode随时可以对这个区域进行写入。因此,该区域通常被操作系统视为保留区域。实际发生的情况取决于BIOS和机器制造商。
除了EBDA外,一些DOS程序(和恶意软件)截获BIOS Int 12h,并报告较少的内存,以隐藏(或使其常驻)DOS不应碰触的代码/数据。Dr. Dobbs内存映射可能需要增加一些内容:
mmmm:mmmm                Environment block #1
mmmm:mmmm                Application program #1
     .                        .      .                        .      . 
mmmm.mmmm                Environment block #n
mmmm:mmmm                Application #n
xxxx:xxxx                Transient COMMAND.COM
hhhh:hhhh                Hidden/Resident programs and data
eeee:eeee                Extended BIOS Data Area
A000:0000                Video buffers and ROM
FFFF:000F                Top of 8086 / 88 address space
故事寓意: 你不应该假设可用于你的内存量在CS:0x00000xa000:0x00002之间。要回答如何确定内存区域是专属于你的程序,可以查看PSP,特别是偏移量为CS:0x0002处的WORD值:

02h-03h word (2 bytes) 分配给程序的内存后面第一个字节所在的段

通过读取此值,您可以获得程序分配的第一个字节之后的段(我们称其为NEXTSEG)。通常,NEXTSEG将是0xA000或0x9FC0(具有1KiB EBDA的系统将具有此值)。由于之前讨论的原因,它会在硬件上有所不同。该区域将重叠MS-DOS的COMMAND.COM的瞬态部分。实际上,我们加载后可以保证独占的内存区域是我们可以自由使用CS:0x0000NEXTSEG:0x0000之间的所有物理内存。

COM程序分配128KiB

由于20位段偏移地址的重叠性,每个段指向内存中一个不同的16字节区域的起始位置,称为段落。将段增加1会在内存中前进16字节,而将其减少则会后退16字节。这在计算所需内存量并确保有足够的内存来满足请求时非常重要。

128KiB等于128*1024/16=8192个段落。我们的COM程序加载到的实际区域大小(以及堆栈放置的位置)由CS:0x0000和堆栈(SP)指向的段之间的边界限制。由于DOS总是为COM程序推送一个2字节值(ret返回的返回地址),因此可以通过将SP除以16(或SHR除以4)并加1来计算下一个段落(我们将其称为SEGAFTERSTACK)。

最简单的方法是将我们的128KiB数据放置在堆栈的上限之后(SEGAFTERSTACK)。我们只需要确保在SEGAFTERSTACKNEXTSEG(DOS分配给我们的程序区域的范围)之间有足够的空间。如果该值大于等于8192个段,则我们有足够的内存,可以自由地访问它。如果我们有足够的内存,我们可以使用Int 21h/AH=4ah请求DOS将我们的COM程序调整为所需的确切空间大小。我们不需要调整DOS已经为我们分配的内存,但如果您的代码需要使用DOS的Exec函数Int 21h/AH=4bh加载/运行子程序,则可能会有用。

注意: DOS < 2.0 不支持 内存控制块 ,这意味着 Int 21h 函数无法分配、释放和调整大小。在 DOS < 2.0 上调用它们会悄无声息地失败。当调整大小减少程序在内存中的大小时,函数不应该失败,因此我们应该忽略任何错误。

使用 GNU 汇编器的一个版本,确保我们在堆栈之后有 128KiB 的空闲空间,程序可能如下所示:

EXTRA_SIZE      = 128*1024     # Allocate 128KiB above stack
PARA_SIZE       = 16           # A paragraph = 16 bytes
EXTRA_SIZE_PARA = (EXTRA_SIZE+PARA_SIZE-1)/PARA_SIZE
                               # Extra Size in Paragraphs
COM_ORG         = 0x100        # Origin point for COM  program is 0x100

.code16
.global _start
.section .text

_start:
    # In a COM program CS=DS=ES=SS=0x0000. IP=0x100. The PSP is a 0x100 byte structure
    # between CS:0x0000 and CS:0x0100. DOS allocates the largest free block of
    # contiguous conventional memory from the DOS memory pool to our COM program. 
    # SS:SP grows down from the last paragraph allocated to us OR the top of the
    # 64kb segment, whichever is lower.
    #
    # At (DS:[0x0002]) is the segment (NEXTSEG) of the first byte  beyond the memory
    # allocated to our program. This means our program has been allocated all memory
    # between CS:0x0000 and NEXTSEG:0x0000

    # Get the next segment just above the top of the stack
    mov %sp, %bp               # BP = Current stack pointer
    mov $4, %cl                # Compute the segment just above top of stack
                               # Where extra data will be placed
    shr %cl, %bp               #     Divide BP by 16
    inc %bp                    #     and add 1

    # Compute a new program size including extra data area we want and
    # place it above the stack
    lea EXTRA_SIZE_PARA(%bp), %bx
                               # BX = Size (paragraphs) of Code/Data+Stack+Extra Data
    mov 0x0002, %ax            # Get the segment above last allocated
                               #     paragraph of our program from PSP @ [DS:0002]
    sub %bx, %ax               # Do we have enough memory for the extra data?
    jb .no_mem                 #     If not  display memory error and exit
    mov $0x4a, %ah             # Request DOS resize our program's memory block
    int $0x21                  #     to exactly the # of paragraphs we need.
    push %cs
    pop %bx                    # BX = CS (first segment of our program)
    add %bx, %bp               # BP = segment at the start of our extra data

    # Do stuff. Just an example:
    lea 0x0000(%bp), %si       # SI=segment of first 64KiB segment we allocated
    lea 0x1000(%bp), %di       # DI=segment of second 64KiB segment we allocated

    jmp .exit

.no_mem:
    mov $no_mem_str, %dx       # Have DOS print an error and exit.
    mov $9, %ah
    int $0x21

.exit:
    ret                        # We're done

no_mem_str: .asciz "Out of memory\n\r$"

_end:

稍微复杂一点的变体是将默认给定的堆栈大小调整为适合我们工作的大小,然后将额外的128KiB数据放在堆栈之后。我们需要计算代码和数据的范围,以便将堆栈放置在其后,接着是128KiB数据的内存。以下代码使用4096字节的堆栈来实现这一点:
STACK_SIZE = 4096              # Stack size = 4KiB
EXTRA_SIZE = 128*1024          # Allocate 128KiB above stack
PARA_SIZE  = 16                # A paragraph = 16 bytes
COM_ORG    = 0x100             # Origin point for COM  program is 0x100

.code16
.global _start
.section .text

_start:
    # In a COM program CS=DS=ES=SS=0x0000. IP=0x100. The PSP is a 0x100 byte structure
    # between CS:0x0000 and CS:0x0100. DOS allocates the largest free block of
    # contiguous conventional memory from the DOS memory pool to our COM program. 
    # SS:SP grows down from the last paragraph allocated to us OR the top of the
    # 64kb segment, whichever is lower.

    # At (DS:[0x0002]) is the segment (NEXTSEG) of the first byte  beyond the memory
    # allocated to our program. This means our program has been allocated all memory
    # between CS:0x0000 and NEXTSEG:0x0000
    
    push %ds
    pop %cx                    # CX = Segment at start of our program
    mov %cx, %bp               # BP = A copy (for later) of program starting segment
    mov $PROG_SIZE_PARA, %bx   # BX = number of paragraphs of EXTRA memory to allocate 
    add %bx, %cx               # CX = total number of paragraphs our program needs
    mov 0x0002, %ax            # AX = next segment past end of our program
                               #     retrieved from our program's PSP @ [DS:0002]
    sub %cx, %ax               # Do we have enough memory to satisfy the request?
    jb .no_mem                 #     If not  display memory error and exit
    mov $0x4a, %ah             # Request DOS resize our programs memory block
    int $0x21                  #     to exactly the # of paragraphs we need.

    mov $STACK_TOP_OFS, %sp    # Place the stack after non-BSS code and data
                               #     and before the BSS (Extra) memory
    xor %ax, %ax               # Push a 0x0000 return address as DOS does for us
    push %ax                   #     when initializing our program. Memory address
                               #     CS:0x0000 contains an Int 20h instruction to exit
    add $EXTRA_SEG, %bp        # BP = segment where our extra data areas starts

    # Do stuff. Just an example:    
    lea 0x0000(%bp), %si       # SI=segment of first 64KiB segment we allocated
    lea 0x1000(%bp), %di       # DI=segment of second 64KiB segment we allocated

    jmp .exit

.no_mem:
    mov $no_mem_str, %dx       # Have DOS print an error and exit.
    mov $9, %ah
    int $0x21

.exit:
    ret                        # We're done

no_mem_str: .asciz "Out of memory\n\r$"

_end:

# Length of non-BSS Code and Data
CODE_DATA_LEN   = _end-_start

# Segment number after the PSP/code/non-BSS data/stack relative to start of program
EXTRA_SEG       = (CODE_DATA_LEN+COM_ORG+STACK_SIZE+PARA_SIZE-1)/PARA_SIZE

# Size of the total program in paragraphs
PROG_SIZE_PARA  = EXTRA_SEG+EXTRA_SIZE_PARA

# New Stack offset(SP) will be moved just below extra data
STACK_TOP_OFS   = EXTRA_SEG*PARA_SIZE

# Size of the extra memory region in paragraphs
EXTRA_SIZE_PARA = (EXTRA_SIZE+PARA_SIZE-1)/PARA_SIZE

这些示例可以使用以下命令组装并链接到名为myprog.com的程序中:

as --32 myprog.s -o myprog.o
ld -melf_i386 -Ttext=0x100 --oformat=binary myprog.o -o myprog.com

在DOS EXE程序中分配128KiB内存

DOS加载器也加载EXE程序(它们具有MZ头部)。MZ头包含程序信息、重定位表、堆栈、入口点以及除可执行文件中实际存在的数据外所需的最小和最大内存分配要求。完全未初始化数据的段(包括但不限于BSS和堆栈段)不占用可执行文件中的空间,但是DOS加载器通过MINALLOCMAXALLOC头字段被告知要分配额外的内存:

MINALLOC。 这个字表示程序开始执行所需的最小段数。这是除了需要容纳载入模块的内存之外的要求。此值通常代表链接在程序末尾的任何未初始化数据和/或堆栈段的总大小。由于没有特定的初始化值,这些空间没有直接包含在载入模块中,否则它将只浪费磁盘空间。

MAXALLOC。这个词表示程序在开始执行之前希望分配给它的段落数量的最大值。这表示除了加载模块所需的内存和MINALLOC指定的值之外的额外内存。如果无法满足请求,则分配程序可用的所有内存。

MINALLOC是EXE本身代码和数据上面所需的段数。MAXALLOC始终至少等于MINALLOC,但如果(MAXALLOC>MINALLOC),则DOS将尝试满足对额外段(MAXALLOC-MINALLOC)的请求。如果无法满足该请求,则DOS将分配其可用的所有空间。通常,MAXALLOC和MINALLOC之间的额外内存被许多工具和编程语言称为HEAP

值得注意的是,最终链接过程生成可执行文件时设置MINALLOC和MAXALLOC。通常情况下,链接器默认将MAXALLOC设置为0xffff,从而要求HEAP占用尽可能多的连续空间以供DOS分配。EXEMOD程序旨在允许更改此设置:

EXEMOD

EXEMOD显示或更改DOS文件头中的字段。要使用此实用程序,您必须了解文件头的DOS约定。

[省略]

/MIN n 将最小分配值设置为n,其中n是十六进制值,设置段落数。如果需要调整以容纳堆栈,则实际设置的值可能与请求的值不同。

/MAX n

设置最大分配量为n,其中n是十六进制值,用于设置段落数。最大分配值必须大于或等于最小分配值。此选项与链接器参数ICPARMAXALLOC具有相同的效果。
在没有内存控制块概念的DOS <2.0中,使用EXEMOD是更改DOS可执行文件的附加内存要求的方法。在DOS 2.0+中,程序(在运行时)可以通过DOS Int 21h函数分配新内存、调整内存大小和释放内存。
对于本讨论,128KiB的额外内存由程序需要,因此示例将把该数据放在未初始化的数据中。链接/可执行文件生成过程将通过添加所需的额外段来调整MZ头中的MINALLOC字段。
第一个想要分配128KiB(两个64KiB段依次放置)的DOS程序的示例是用FASM汇编编写的:
format MZ                      ; DOS EXE Program

stack 4096                     ; 4KiB stack. FASM puts stack after BSS data

entry code:main                ; Program entry point (seg:offset)

segment code
main:
    push ds
    pop ax
    mov bx, EndSeg
    sub bx, ax                 ; BX = size of program in paragraphs (EndSeg-DS)
    mov ah, 4ah                ; Resize to the number of paragraphs we need
    int 21h                    ;     because the DOS loader sometimes allocates slightly
                               ;     more than our actual program requirements

    ; Do Stuff. Just an example:    
    mov si, ExtraSeg1          ; SI=segment of first 64KiB segment we allocated
    mov di, ExtraSeg2          ; DI=segment of second 64KiB segment we allocated

    mov ax, 4c00h              ; We're done, have DOS exit and return 0
    int 21h

segment ExtraSeg1
rb 65536                       ; Reserve 65536 uninitialized "bytes" in BSS area

segment ExtraSeg2
rb 65536                       ; Reserve 65536 uninitialized "bytes" in BSS area

segment EndSeg                 ; Use this segment to determine last segment of our program
                               ;     Segments with no data will be put in BSS after
                               ;     other BSS segments

一种适用于大多数MASM/JWASM/TASM版本的版本如下所示:
.model compact, C              ; Multiple data segments, one code segment
.stack 4096                    ; 4KiB stack

; fardata? are uninitialized segments (like BSS)
.fardata? ExtraSeg1            ; Allocate first 64KiB in a new far segment
db 65535 DUP(?)                ; Some old assemblers don't support 65536! Set to 65535
                               ; The next segment will be aligned to a paragraph boundary
                               ; Uninitialized data `?` will not be physically in our EXE

.fardata? ExtraSeg2            ; Allocate second 64KiB in a new far segment after first
db 65535 DUP(?)                ; Some old MASM assemblers don't support 65536! Set to 65535
                               ; The next segment will be aligned to a paragraph boundary
                               ; Uninitialized data `?` will not be physically in our EXE


.fardata? EndSeg               ; Use this segment to determine last segment of our program
                               ;     Segments with no data will be put in BSS after
                               ;     other BSS segments
.code
main PROC
    push ds
    pop ax
    mov bx, EndSeg
    sub bx, ax                 ; BX = size of program in paragraphs (EndSeg-DS)
    mov ah, 4ah                ; Resize to the number of paragraphs we need
    int 21h                    ;     because the DOS loader sometimes will allocate 
                               ;     slightly more than our actual program requirements

    ; Do Stuff. Just an example:
    mov si, ExtraSeg1          ; SI=segment of first 64KiB segment we allocated
    mov di, ExtraSeg2          ; DI=segment of second 64KiB segment we allocated

    mov ax, 4c00h              ; We're done, have DOS exit and return 0
    int 21h
main ENDP

END main                       ; Program entry point is main

脚注:

  • 1当DOS可用的空闲内存小于64KiB时,SP将被设置为从一个在DOS可用空闲内存顶部以下偏移处向下增长的位置。当有64KiB或更多的可用空闲内存时,DOS加载器将SP设置为0x0000。在有>=64KiB可用空闲内存的情况下,第一个数据推送(返回地址0x0000)会将SP包装到段顶部的0xfffe(0x0000-2)。这是实模式的一个怪异之处:如果将SS:SP设置为SS:0x0000,则第一个推送的值将被放置在段SS顶部的SS:0xFFFE处。
  • 2尽管0xa000:0x0000通常被视为DOS可用的连续常规内存的上限,但不一定非得如此。一些内存管理器(例如JEMMEX、QEMM、386Max等)及其工具可以成功移动EBDA(在设备上不会引起问题的情况下),并且可以告诉系统VGA/EGA内存在0xa000:0x0000至0xa000:0xffff之间未使用,可以将DOS分配的连续内存的上限移动到0xb000:0x0000。即使在没有视频的无头(headless)配置中,也可以有更多的内存。执行此操作的386内存管理器通常在v8086模式下运行DOS,并将扩展内存(使用386对分页的支持)重新映射到0xa000:0x0000和0xf000:0xffff之间未使用的区域。

感谢您提供这么详细的答案。您第四个要点的意思是,如果机器内存不足,程序启动时SS ≠ CS可能是一种情况吗?我一直认为在这种情况下,SS = CS,但SP的初始值不是FFFE - fuz
@fuz:我需要稍微整理一下,以消除歧义。当COM程序被加载到内存中时,DOS加载器总是设置SS = DS = ES = CS。只有SP会改变。实际上,在完整的64KiB空闲时,堆栈值从0x0000开始,但是DOS会自动为我们推送一个2字节的返回值(0x0000),因此在我们看到它时,SP似乎是0xFFFE(0x0000-2)。 - Michael Petch
太好了,谢谢! - Leon Derczynski

7
在DOS下,是的,你可以开始使用另一个内存段。然而,有一个重要的注意事项!请查看您所使用的DOS版本的内存映射。确保您没有选择实际上为其他用途保留的内存区域。这里是一篇来自Dr. Dobb's Journal的文章:
Address (Hex)                 Memory Usage

0000:0000                Interupt vector table
0040:0000                ROM BIOS data area
0050:0000                DOS parameter area
0070:0000                IBMBIO.COM / IO.SYS *
mmmm:mmmm                BMDOS.COM / MSDOS.SYS *
mmmm:mmmm                CONFIG.SYS - specified information
                         (device drivers and internal buffers
mmmm:mmmm                Resident COMMAND.COM
mmmm:mmmm                Master environment
mmmm:mmmm                Environment block #1
mmmm:mmmm                Application program #1
     .                        .      .                        .      .                        .
mmmm.mmmm                Environment block #n
mmmm:mmmm                Application #n
xxxx:xxxx                Transient COMMAND.COM
A000:0000                Video buffers and ROM
FFFF:000F                Top of 8086 / 88 address space

“官方”的内存分配机制是通过内存控制块(MCB)和DOS中断0x21使用0x48来分配和0x49来释放内存。关于这个问题的讨论可以在这篇Microsoft支持文档中找到。
有关中断方法的文档,您可以在这里查看。

在所有DOS版本中都保证可用的区域吗? - fuz
1
我增加了一些有关使用DOS中断来分配内存的详细信息。老实说,除了零页和视频内存之外,我无法记住其他任何关于DOS下哪里有什么的内容。 :) - David Hoelzer
3
未来的读者:您可以覆盖“瞬态COMMAND.COM”区域。当用户进程覆盖该区域时,DOS会重新加载它。这意味着您可以将从CS:0000到A000:0000(不包括A000:0000)的所有内存用于您的进程。 - fuz
@fuz:那不正确,从代码段开始并延伸到0A_0000h以下的内存并不总是可用的。其他程序可以使用低内存区域的“顶部”内存。EBDA也可以放置在那里。即使如此,如果将UMB分配给DOS的内存链(假设没有其他用户),则在段9FFFh处,第一个UMCB将使用一个段落。此外,使用loadhigh,代码段甚至可以0A_0000h以上开始。最好使用int 21.4A将应用程序的内存块缩小为64 KiB,然后使用21.48分配块。 - ecm
微软支持文档 - 错误链接 - Joshua

5

如果我们启动一个程序,DOS会把所有的可用内存都分配给该程序,因此在请求新的内存之前,我们必须将其还给DOS。第一步是计算我们的程序需要多少内存,并将剩余的内存还给DOS。这部分代码必须放在我们的程序开头,在SS、SP和ES被操作之前。

mov      bx, ss
mov      ax, es
sub      bx, ax
mov      ax, sp
add      ax, 0Fh
shr      ax, 4
add      bx, ax
mov      ah, 4Ah
int    21h

下一步是请求新的内存。
mov      bx, 2000h ; 128 KB
mov      ah, 48h
int    21h
jc  NOSPACE
; AX = segment address

谢谢您的回答。在COM程序中,启动时cs = ds = es = ss,所以第一个片段实际上是必需的吗? - fuz
@FUZxxl:在一个.COM程序中不行,因为.COM程序默认只有一个段。只有.EXE程序可以有多个段。所以.COM被限制在64kb内。 - zx485
计算是针对包含代码、数据和堆栈段不同段地址的*.exe (MZ)文件进行的,但对于COM程序,我们必须在请求新内存之前归还剩余内存。 - Dirk Wolfgang Glomp
@DirkWolfgangGlomp 你为什么要请求已经拥有的内存?没有必要先释放内存,然后再请求它。原帖作者最好保留开始时获得的128KB内存。 - Sep Roland
2
在使用48h int 21h请求新内存之前,我们必须使用4Ah int 21h将空闲内存返回给DOS,因为我们不确定哪些段是空闲的。程序后面的128 KB空间并不安全。只有使用48h int 21h并且调用后未设置进位标志,我们才能保证所请求的内存数量是可用的。 - Dirk Wolfgang Glomp
显示剩余2条评论

0

通过将段寄存器之一设置为所需值,您可以获取任何想要的段。但请记住:

  • 每个段都从16字节边界开始,这意味着0400(偏移量:0000)的段将等于0040(偏移量:3c00)的段以及另一个0000(偏移量:4000)的段,依此类推。
  • 这些范围重叠,这意味着段寄存器增加1会使绝对内存地址增加16。
  • BIOS或其他外围设备预设和使用哪些范围在David Hoelzer的其他答案中有详细说明。
  • 确保您的段是64kb大小,并且不与其他段重叠。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接