When the execution of a COM program begins, DOS jumps to address 100h. But at what address is DOS stored in RAM while the COM program is executing? Is DOS stored in conventional memory? If so, isn't there a risk that the COM program may overwrite DOS itself? How does DOS recover when it is overwritten?
DOS is a very un-restricted and "open" operating system with basically no protection on (benevolent or not) intrusion. That means, it's perfectly possible for a .COM program to take over the system and boot into something else (Linux loaders that allowed to boot Linux from a DOS command prompt did stuff like that and earlier versions of Windows (95, 98) did the same, as well as some games that took over the machine completely), so one answer to your question could be "possibly nowhere". And the answer to your recovery question is just as straightforward: If you kill it, it's dead. There is no recovery other than a reboot.
The "normal" case, however, is that a loaded program "behaves" and keeps DOS intact. In this normal case, DOS versions before 5.0 (when DOS in UMB or HMA was introduced), DOS used to live in the lower memory area, directly after the interrupt vector table and BIOS data area (typically at 0:0600h) with a sequence of (I'm using the original IBM file names, they obviously vary greatly between vendors)
- DOS interface to ROM-BIOS (IBMBIO.COM)
- Main portion of DOS (IBMDOS.COM)
- Resident portion of the command processor (COMMAND.COM)
The transient portion of COMMAND.COM is normally located in the upper end of the memory map and may be overwritten when a program is loaded and restored afterwards.
Later, (5 and onwards) parts of DOS, command processor, and drivers could be moved to the UMB or HMA area, with smaller parts remaining in lower memory.
Your question seems to assume that .COM programs are loaded to the absolute address 0:100h - no, they aren't. When executing a .COM (or .EXE or any other program, for that matter) program, DOS allocates code and data segments somewhere in free memory and loads CS/DS with that segment addresses. .COM programs thus start in fact at CS:0100h (with CS being an arbitrary value determined by DOS depending on the memory usage), so, approximately at flat addresses beyond 64k, depending on the DOS version and loaded drivers.
DOS kernel will be loaded somewhere in memory. Where exactly is loaded depends on if you have HMA driver (e.g. HIMEM.SYS) and additionally EMM driver (e.g. EMM386.EXE) loaded to provide UMBs and where the CONFIG.SYS settings tell what memory areas to use (e.g. DOS=HIGH,UMB).
Please note that wherever DOS is loaded, it does not matter. DOS will load a program to a free area of memory so DOS will not overwrite itself. The COM program gets all the free memory starting from the segment it is loaded to. So a COM program is loaded to some free segment within the first megabyte of memory, and DOS jump to offset 100h of that segment where the program is loaded.
A proper program only should use memory that is allocated for it by DOS, as if the program even accidentally writes to areas outside what is allocated to it, it may overwrite something important, like DOS or memory driver or other TSR driver.
To add to the existing answers, it does in fact matter where in conventional memory DOS is loaded, and this influenced the design decisions.
Anyone reading this far down on a retrocomputing site already knows the story of how MS-DOS was a quick and dirty clone of CP/M. A DOS
.COM file, the only format available in version 1.0, attempts to emulate the 16-bit memory space of CP/M, such as adding a Program Segment Prefix similar to the Low Storage area of CP/M (which also took up 256 bytes), file control blocks that work like CP/M’s, and a jump instruction at address CS:0005 to enable the
CALL 5 interface of CP/M to work with a near
CALL. (The combination of this and another CP/M compatibility feature at offset 6 in the PSP mean that addresses 000C0 and 1000C0 still, on 32-bit Windows, are set up in a particular way to support this interface.)
DOS used the segmentation registers of the Intel 8086 and 8088 to give each
.COM program its own 16-bit memory space that looked a lot like the 64 KiB memory space of an 8080 machine running CP/M. In x86 real mode, these can start on any 16-byte boundary in the first megabyte of system memory. If the program needs more than that, it is supposed to request it from the OS. Most programs even did, because it was very common to have drivers and Terminate-and-Stay_Resident (TSR) programs waiting in the background that must not be overwritten.
DOS 2.0 introduced support for programs larger than 64 KiB. These programs, however, could only be loaded into consecutive regions of memory. This was why DOS had its famous 640K barrier: IBM had chosen to put video memory at address A0000 in hex, which is 640 kilobinary, and that could never be moved because most programs wrote directly to video memory. There were also some regions of memory at the bottom that were used either by the CPU (such as the 1 KiB interrupt vector table) or by the original IBM PC design (such as the BIOS transfer area). An indeterminate amount of the memory above the video RAM (but always including at least segment FFFF, which many programs relied on) were reserved for system ROM, and this varied from machine to machine. Programs could only fit either above or below video memory, and the space below was larger.
Therefore, early versions of DOS, designed to run on computers that would not even have 640K of RAM, always tried to load themselves at the lowest possible address, immediately above the memory used by the BIOS. This left the largest possible block of free RAM between itself and the video memory to load programs. Later versions of DOS, starting with MS-DOS 5, attempted to load things like drivers, TSRs and DOS above video memory, leaving as much of the 640 KiB of “conventional” memory as possible free for programs. Versions 5.0 and above included a
LOADFIX.EXE command, for compatibility with old programs that assumed they would always be loaded above where MS-DOS used to be.
One of the most important techniques was to toggle the bit on the motherboard that caused all but the first sixteen bytes of segment FFFF to address an additional 64 KiB of memory, which would not have worked on the original IBM PC hardware. (If you had tried that, the address would have wrapped around to 00010h.) Later versions of DOS almost all load themselves into this “high memory area,” as far out of the way of any other programs as possible. They additionally gave executables such as TSRs and drivers a way to request that they be loaded as high in memory as possible, leaving the largest possible block of memory below them, and added a
LOADHI command to load older TSRs into upper memory, rather than conventional memory.
Finally, most DOS games these days probably run in emulators. These get to cheat a bit, and almost always leave more memory free than real hardware from the ’80s and ’90s were able to.
There might be some confusion about address/memory layout
Some Generic View
When programming real mode x86 (aka 80(1)86/88 ISA) it's best to think of segmentation as a basic way of memory management and virtualization. Segments located within existing memory are used to organized processes. As a result, there is an address of 100h in absolute memory, as well as within each segment.
Like with any other memory management, as long as a process stays with the segments/memory assigned by DOS, no problems arise (*1). As soon as program tries to escape these, a protected memory environment would halt that process and report a privilege violation.
Except, DOS is not a protected memory OS and the 8086 does not provide any support for privilege management, No way to enforce separation of 'ordinary' programs from 'OS-level' programs. Each and every program can perform each and every operation - including manipulation of segment registers and killing off any other process including the OS itself.
When the execution of a COM program begins, DOS jumps to address 100h
That 100h is within the COM programs address space, not the absolute address range. That is the potion of memory the code segment register points to.
But at what address is DOS stored in RAM while the COM program is executing?
From the point of view of a COM program it's outside of it's address space ... that is, unless it changes any segment register to point to a segment where DOS is located.
Is DOS stored in conventional memory?
Unless we're talking about some 'hacks/extensions' with later CPU's (286&up), then the first megabyte (aka conventional memory) is all DOS is about. It's an 8086 OS.
If so, isn't there a risk that the COM program may overwrite DOS itself?
Once a COM (or an EXE for that matters) runs it has absolute control over the CPU. It can change any segment register to any value, thus it's able access any location, including all of DOS, BIOS or any other tools code and/or dater of any other component.
How does DOS recover when it is overwritten?
Not at all.
As a non protected OS, there is no way to prevent a malicious program to overwrite whatever would be needed to recover.
The story of recovering COMMAND.COM from the end of memory is probably a cut/paste mistake between CP/M documentation and DOS documentation. On CP/M, the entire OS was located between the last 16 bit address offered to a COM program and the end of physical memory. If a program needed extra memory, it could overwrite some of the OS code, then exit by jumping to the "soft reset" routine located right after the CP/M kernel. This would then reload the CP/M kernel and command processor from hidden sectors on disk. In theory, a program that overwrote only the CP/M command processor could return to the CP/M kernel via function 0, and then hope the kernel would detect the overwritten command processor and reload it from disk.
Because 8080 CPUs booted by executing ROM code at address 0000, a running CP/M system put a jump to that soft boot function at address 0000 (The destination of that jump instruction was the end of CP/M kernel memory). 8080 series interrupt vectors were hardware coded to execute addresses i times 8 and the system call interface was via a jump instruction squeezed in just before interrupt IRQ1 at address 5, while command line parameters were stored right after interrupt IRQ7 from address 005C to 00FF. Under CP/M, a program could determine available program memory by reading the destination of that jump instruction. MS-DOS COM files get their last allowed 16 bit address from a different field or register and can get their last allowed 20 bit address by making an MS-DOS call.
P.S. CP/M manuals referred to their driver portion as the "BIOS" and the kernel as "BDOS". Those names inspired Microsoft to name the files IBMBIO.COM and IBMDOS.COM, despite the PC BIOS being actual ROM code between address F0000 and FFFFF, called by the drivers in IBMBIO.COM / IO.SYS.
Also the 640KB limit is a result of address 640K (A0000) being the lowest address reserved for I/O cards, such as the VGA display buffer. 640KB is also the result of starting with a 128KB IBM PC and plugging in a 512KB memory card, because memory modules were sold in powers of 2. 640KB is not a result of anything done in any DOS version.
Here's an example program that generates a random number. As shown, it is a .COM program running under DOS. (You will need to disable any HTTPS-everywhere tool for this site as I haven't enabled HTTPS for it.)
The reason I'm including it here is because of all the comments I've included in the source code. Please do take the time to access this source code and read through it. It will be worth some time.
When DOS loads up, it loads itself into the lowest segments available from the BIOS memory allocator. (Meaning... it loads LOW.) DOS will also read up a file that tells it what drivers to load up. It allocates those into HIGH memory (the highest available, which is just below the video memory areas.) Once all that is done, the BIOS holds whatever is left (minus DOS and minus drivers.)
When a .COM program is loaded up, it is placed into the first segment available from BIOS's memory allocation scheme. This is, of course, a 64k segment.
However, DOS also chooses (for .COM programs only) to allocate all of the available BIOS memory -- just in case. DOS assumes that a .COM program, despite it's small load size, may want to use all of the available RAM in the system. Another reason for this is because memory allocation functions (wrapping the BIOS memory allocation functions) were only available after DOS 1.0. Since .COM programs were the only kind of programs that DOS 1.0 could run and since DOS 2.0+ had to support this older model, DOS 2.0+ pretty much was forced into allocating all of memory in order to be compatible with DOS 1.0 .COM programs.
For .EXE programs (only existing at and after DOS 2.0), since the file format for those includes enough information to know what memory to allocate, DOS doesn't have to assume worst case. So in the .EXE case DOS allocates only what is necessary, leaving the rest in the BIOS memory pool for later allocation, if needed.
Once a .COM program has been loaded into a segment of memory (the lowest available, but obviously also not at segment 0 since DOS occupies that place), DOS then prepares the first 0100h bytes as the "Program Segment Prefix" or PSP. This PSP is essentially identical in most ways to the older CP/M header. Once that preparation is completed, DOS sets up all the segment registers to point to the same place (a non-zero segment just above the last segment used by DOS, which varies by version) and then jumps into the .COM code starting at offset 0100h, bypassing the PSP. The code must obviously start after this point.
This is why a COM program starts at 100h. It bypasses the PSP and starts execution just after that point.
Keep in mind that there are offset pointers that implicitly use the segment registers. Since DOS sets up all of the segment pointers in a .COM program to point at the same place in memory, address pointers held in registers like the BX, SI, or DI are "relative" to these segment registers. So a .COM program, which only uses offsets (usually), doesn't need to care exactly where it is loaded. It uses offsets and these offsets are relative to where ever it was loaded by DOS. So the code can be loaded pretty much anywhere in memory and still run just fine.
If you carefully read through the above-provided assembly source code, most of your questions should be answered. Feel free to ask more. I won't mind. But hopefully that places a lot of detail in front of you. Best wishes.