我想写一个小DOS程序(我的第一个程序),而且我有点没经验。
对于程序,我需要超过64千字节的(常规)内存。我怎样才能获得额外的记忆?理想情况下,我希望为程序增加两个64k的内存块。我可以开始在地址空间的某处写入数据,还是需要额外的内存?
答案 0 :(得分:4)
我最近偶然发现了这个问题。尽管有几年的历史了,但我认为当前答案之外的一些其他信息可能对将来的读者有用
这个问题实际上可以归结为:我可以在DOS分配的程序范围之外任意写入内存吗?这个问题是针对DOS COM程序的,但是很多信息也适用于DOS EXE程序。
GNU汇编程序受到限制,因为它不会生成16位DOS EXE程序,因此您必须生成DOS COM程序。原始点为0x100的DOS COM程序。代码,数据和堆栈(在加载时)不能超过64KB。 DOS COM程序一旦被DOS加载程序加载到内存中,便具有以下特征:
Int 20h
开始。 Int 20h
终止当前程序。这是允许DOS COM程序执行ret
来终止程序的机制。一个人应该问的第一个问题是:我的DOS COM程序实际上有多少内存?简单的答案是:它有所不同。它可能会根据可用的常规内存量而有所不同(IBM PC通常随附64KiB,128KiB,256KiB,512KiB或640KiB)。另一个答案中引用的《多布斯博士杂志》文章发表于1988年,内存图缺少一些关键的东西。
1987年,IBM发布了IBM PS / 2系列计算机。为了保存与鼠标有关的信息,IBM意识到中断向量表上方的BIOS Data Area中没有足够的空间,因此他们创建了Extended BIOS Data Areas(EBDA)。此内存由BIOS保留,IBM PS / 2 BIOS开始报告少1KiB的内存(639KiB而不是640KiB)。 EBDA的大小可能有所不同,具体取决于BIOS制造商。 BIOS Int 12h
调用将返回EBDA区域以外的常规内存量(<= 640KiB)。 DOS依靠此来确定它可以使用多少内存。
更糟糕的是,当基于386SL的系统发布时,它包含System Management Mode,其运行在-2环上,可以完全访问您的PC。这些系统也开始使用EBDA中的空间。一些系统需要超过1KiB。从理论上讲,您可以拥有128KiB的EBDA空间,尽管我不确定是否有很多系统具有该功能!该区域最终被用于电源管理(APM),ACPI,SMBIOS,并且该区域可以随时通过系统管理模式写入。因此,操作系统通常认为该区域是保留的。实际发生的情况取决于BIOS和计算机的制造商。
除EBDA以外,某些DOS程序(和恶意软件)会拦截BIOS Int 12h并报告较少的内存,以便隐藏(或驻留)一段DOS不应接触的代码/数据。 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:0x0000
和0xa000:0x0000
2 之间。< / p>
要回答有关如何分辨程序的内存区域的问题,可以通过查看PSP来解决,尤其是偏移量CS:0x0002
处的WORD值:
02h-03h字(2个字节)超出分配给程序的内存的第一个字节的段
通过读取该值,您可以得到第一个字节的段,恰好超出程序已分配的段(我们将其称为NEXTSEG
)。 NEXTSEG
通常为0xA000或0x9FC0(具有1KiB EBDA的系统将具有该值)。由于前面讨论的原因,它在硬件上会有所不同。该区域将与MS-DOS的COMMAND.COM的瞬态部分重叠。实际上,在加载后我们可以保证COM程序专有的内存区域是,我们可以自由使用CS:0x0000
和NEXTSEG:0x0000
之间的所有物理内存。
由于20-bit segment:offset addressing的重叠性质,一个段指向内存中另一个称为 paragraph 的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
)的最上方。我们只需要确保SEGAFTERSTACK
和NEXTSEG
之间有足够的空间(DOS给我们的程序区域的范围)。如果该值是> = 8192段落,那么我们有足够的内存,我们可以自由地访问它。如果确实有足够的内存,我们可以要求DOS使用Int 21h/AH=4ah
将COM程序的大小调整为所需的确切空间。我们不需要调整已经为我们分配的DOS内存的大小,但是如果您的代码需要使用DOS的Exec函数Int 21h/AH=4bh
加载/运行子程序,则它很有用。
注意:DOS <2.0不支持Memory Control Blocks,这意味着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程序(它们具有MZ header)。 MZ标头包含程序信息,重定位表,堆栈,入口点,以及最小和最大内存分配要求,超出了可执行文件中实际存在的数据。具有完全未初始化数据的段(包括但不限于BSS和Stack段)不会在可执行文件中占用空间,但是DOS加载程序被告知要通过 MINALLOC 和分配额外的内存MAXALLOC 标头字段:
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约定
[snip]
/ 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
0xa000:0x0000
通常被视为DOS可用的连续常规内存的高端,但不一定非要那样。一些内存管理器(JEMMEX,QEMM,386Max等)及其工具可以成功移动EBDA(在不会引起问题的设备上),并且可以得知VGA / EGA内存从0xa000:0x0000到0xa000 :0xffff未被使用,可以将DOS分配的连续内存的高端移至0xb000:0x0000。在无头(无视频)配置中,甚至可能拥有更多。这样做的386内存管理器通常在v8086模式下运行DOS,并将扩展内存(使用386的分页支持)重新映射到0xa000:0x0000和0xf000:0xffff之间的未使用区域。答案 1 :(得分:3)
在DOS下,是的,您可以开始使用另一段内存。但是,有一个重要的警告!
查看您正在使用的DOS版本的内存映射。您希望确保您没有选择实际为其他目的保留的内存区域。这是Dr. Dobb's期刊中的一个:
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
&#34;官方&#34;内存分配机制是通过内存控制块(MCB)和DOS中断0x21使用0x48来分配和0x49来释放内存。有关这方面的详细讨论可以在Microsoft support document中找到。
有关中断方法的文档,you might look here.
答案 2 :(得分:2)
如果我们启动程序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
答案 3 :(得分:0)
您可以通过将其中一个段寄存器设置为所需的值来获取所需的任何段。但请记住