Multitasking MS-DOS 4

Not to be confused with the widespread version of MS-DOS 4 produced in 1988.
Multitasking MS-DOS 4
Version of MS-DOS
Multitasking MS-DOS 4 First Boot.png
Version4.00
Release date1986
Replaces
MS-DOS 3

Multitasking or European MS-DOS 4[a] refers to an intermediate operating system between MS-DOS and OS/2 released in the mid-1980s. Unlike its predecessors and even contemporary versions of Microsoft Windows, it implements preemptive multitasking with a time-sliced scheduler. It was only offered by certain original equipment manufacturers located in Europe.

The project was intended to be the successor to MS-DOS 2 and later MS-DOS 3, although it does not share much code with it. The system natively supports the New Executable format from Windows 1.0 in addition to older COM and MZ executables. However, only NE format executables can be multitasked, while running legacy MS-DOS applications will suspend all background tasks. A large part of the operating system was also rewritten in C, including the entirety of the command interpreter (COMMAND.COM), several utilities (such as FDISK.EXE, the disk partition tool) and parts of the DOS kernel (IBMDOS.COM). Documents suggest that many features, such as threading and named pipes, were also planned but removed or otherwise scrapped during development.

Most vendors did not adapt this version of MS-DOS due to IBM's lack of interest in the operating system, which preferred a multi-tasking environment that took advantage of the 80286 protected mode.[1] This led to IBM and Microsoft signing a Joint Development Agreement in August 1985, under which both companies cooperated on further development of the MS-DOS 4 codebase. The project, occasionally called "DOS 5.0" as the successor to MS-DOS 4,[2] would ultimately become OS/2.[3][4]

Several OEMs shipped the multitasking MS-DOS 4 with their computers: namely, International Computers Limited,[5][6] Siemens,[7] and SMT Goupil. It was also at the very least evaluated by Wang Laboratories (and possibly released) in the US, and was possibly also released by DataPac in Australia, where it was used in a "large government department"[8] Most of these are European-based companies (hence the unofficial "European MS-DOS 4" name); it may have also been internally used by the French post at one point in the late 1980s.[9] The operating system was considered lost until disk images of an internal build were uploaded to PCjs.org (then called JSMachines.net) on 11 March 2013. The Goupil OEM version was found and uploaded by a reader of the OS/2 Museum on 9 April 2014.

Known releases[edit | edit source]

There are currently five publicly available releases of Multitasking MS-DOS 4.0. The disk images originally uploaded to JSMachines.net contain an internal Microsoft build, most components of which have been compiled on 26 November 1985, shortly after the release of Windows 1.0. IBMDOS.COM contains the version string "[I]nternal revision 6.7, 85/11/26", although it has been patched to not display in output.

The second release is a SMT Goupil OEM version released in May 1986. While most of the OS is identical to the previous build, parts of the operating system were recompiled on 26 March 1986. It is largely the same as the non-OEM version albeit with extra utilities, which include PS.EXE (a Unix-style process status command), DEBUGDD.SYS (the kernel debugger), and DOSSIZE.exe (a utility to determine the layout of conventional memory and the amount taken up by DOS). The session manager (SM.EXE) is absent in this release and so there is no way to visually multitask, although the one from the previous build works fine if set as the default shell via the SHELL= statement in CONFIG.SYS.

The third release is dated 17 November 1986 and is likely to be the ICL OEM version, or less likely, an original dump of the Goupil version. There are substantial changes from the previous two releases – the kernel was cut down in size, making it possibly the "scaled down" version that Osterman references in blog posts. The hard error daemon (HE_DAEM.EXE) and session manager are not included, and attempting to run applications from the earlier builds results in a * User Error 914F: Dynamic link loaded * error, possibly indicating that dynamic linking was removed from this version. Nevertheless, the system still retains support for preemptive multitasking. A native version of MS-Net for multitasking MS-DOS is also included, the only commercial product known to be released for the system. This is the only available release that is able to run Windows 1.0, which it achieves by applying custom patches to the Windows kernel when loading the fast boot blob (WIN100.BIN).

The fourth release, an external beta release compiled on 29 May 1984, was officially released by Microsoft, as well as internal documentation dated 30 October 1984 and the source code of MS-DOS 4.00, on 25 April 2024.

A fifth release, another beta release from 28 May 1985 was uploaded to a warez BBS on 2 May 1987 and recovered on 23 June 2024.

An updated version, Multitasking MS-DOS 4.10, was developed for the ICL DRS Professional Workstation and is known to exist in private collections.[5][6] It is currently unknown what changes it may include.

Wang Laboratories, an American computer company, apparently tested a version of Multitasking MS-DOS 4.0, described as an "alpha version", for an unnamed laptop project in 1985, but decided not to bundle it with the laptop because of poor compatibility with "ill-behaved" MS-DOS apps.[9] A beta tester was quoted as saying that "the only thing it would run was COMMAND.COM".

List[edit | edit source]

Emulator compatibility[edit | edit source]

There are some issues with booting this OS on some emulators, with some configurations; issues with Multitasking DOS 4 are possible even on emulators where a "regular" MS-DOS version will run and work perfectly fine.

Emulator compatibility
Emulator Result
86Box 4.0 Will not boot on 808x configurations. Requires an 80286 or later to boot. Otherwise, seems to run fine.
PCjs Runs fine on 8086, 80286 and 80386 configurations.
MartyPC

Internal revision 6.7 and later: Requires at least version 0.2.0 due to inaccurate PIC ICW2 (initialization command word 2) emulation causing a hang midway through boot, after displaying the "MS-DOS Version 4.00" screen, waiting for an interrupt to arrive that never does due to aforementioned inaccurate emulation., as well as separately failing to boot during a hard disk read (most likely to discover the drives in the system) if the Xebec IBM 5150 HDD controller is configured – a later commit fixes it, and **the latest commit will run under any configuration**.
Internal revision 4.11 and earlier: Does not work. A bug report has been filed on stable 0.2.2. 2.06 experiences a hard error and 4.11 spews multicoloured garbage all over the screen.

PCem v17 Runs fine on 8086, 80286 and 80386 configurations.

Status and relationship to OS/2[edit | edit source]

The operating system has numerous similarities to OS/2, to the point where it almost certainly is based on the same codebase for it:

  • Identical strings in the bootloader, kernel, shell, and apps (such as the Top level process aborted, cannot continue string) with early builds of OS/2, including the May 1987 SDK build and revision 7.68.
  • The same version number format (Internal revision...) and even date format in the version number as OS/2.
  • C Library strings in numerous parts of the OS, indicating that the OS was largely rewritten in C. This is the case for the shell, parts of the kernel, and many utilities such as FDISK.
  • Identical source file names in the bootloader, IBMBIO.COM (msinit.asm 6.7 85/11/26 in this build, msinit.asm 8.11 86/10/21 in IBM OS/2 1.0 revision 7.68)
  • Identical names for operating system concepts (PS.EXE in the Goupil OEM version of this build references the Per-Task Data Area, PTDA, which is the primary process information structure in OS/2)
  • The same New Executable binary format is used.
  • The same manner of calling system APIs (exports from New Executable files) is used, although Multitasking MS-DOS 4.0 uses INT 21h as well.
  • The same directory name for shared memory (/SHAREMEM) internally in the kernel.
  • The same signal system used for killing processes as OS/2, with some signal numbers being the same (signal 1 for Ctrl + C is identical, but other signals were moved around)
  • Pre-emptive multitasking and child process support using a quantum scheduler.
  • DOSCALLS being used for kernel exported APIs, identically to OS/2 (although actually as a part of IBMDOS.COM and not "farmed out" to DOSCALLS.EXE).
  • IBM OS/2 having the same bootloader and kernel filenames as Multitasking MS-DOS 4.0, and by proxy legacy MS-DOS.
  • The source code in the slack space of IBM OS/2 1.0 revision 7.68.17 directly mentioning "MT-MSDOS" and being copyrighted 1983 to 1984.
  • Approximately forty percent of the functions with a substantial implementation in the kernel (18 NE import functions are ignored) have been matched with IBM OS/2 1.0 revision 7.68.17; these primarily deal with the FAT driver (and disk management in general), utility functions (such as the string library*
PAGE    ,132 
TITLE    
LOADSTE - segment loading routines 
NAME    LOADSTE 

;***    MT-MSDOS Segment loading routines 
; 
;    Copyright (c) 1983, 1984 by Microsoft 
; 
;    SCCSID = @(#)loadste.asm    10.3 87/02/26 
; 
;    
;    The segment loading routines handle the loading of 
;    new .EXE file format segments. 
; 
;    The module table entry is allocated in the file LOAD.ASM

This evidence is further cemented by a quote from Gordon Letwin, the primary architect of both operating systems in part one, chapter one of Inside OS/2:[2]

Although MS-DOS 4.0 was released as a special OEM product, the project—now called MS-DOS 5.0—continued. [...] Soon, Microsoft and IBM signed a Joint Development Agreement that provided for the development of DOS 5.0 (now called CP/DOS). The agreement is complex, but it basically provides for joint development and then subsequent joint ownership, with both companies holding full rights to the resulting product.

As the project neared completion, the marketing staffs looked at CP/DOS, nee DOS 5, nee DOS 4, nee DOS 3, and decided that it needed... you guessed it... a name change. As a result, the remainder of this book will discuss the design and function of an operating system called OS/2.

Another quote supporting this is from an internal meeting with Ray Ozzie of Iris Software on 1 April 1985 (the meeting notes of which were released by Ray Ozzie in 2010), a question on the upcoming changes for DOS 5.0 was asked. Microsoft's response was:

DOS 5.0 will include: protected mode, pipes, threads, asynchronous I/O, demand execution, possibly installable file systems, sound services, >32MB file systems.

All of these, notably the protected mode nature, were a part of OS/2 1.0 (except installable file systems, which were added in OS/2 1.2). Therefore, it is effectively certain that Multitasking MS-DOS 4.0 was the predecessor project to, is based on the same codebase as, and eventually morphed into what would become OS/2. This also implies that OS/2 is a direct derivative of MS-DOS, as MS-DOS 4.0 was originally forked from MS-DOS 2.0, although almost all of the code would be eventually rewritten by the time OS/2 2.0 was in development.

It is also explicitly stated by Larry Osterman, a long-time Microsoft employee who worked on Multitasking DOS 4.0 and 4.1, as well as numerous versions of LAN Manager and Windows NT-based operating systems, in a comment on a 2004 blog post that Multitasking MS-DOS 4.0 became OS/2:[1]

The product known as multi-tasking MS-DOS 4.0 lived on as OS/2, while the product known as MS-DOS 3.1 continued on in parallel through DOS 3.2, DOS 3.3, DOS 4.0, DOS 5.0 and DOS 6.0.

He restated this in a 2009 blog post:[3]

So IBM and Microsoft started negotiations to take the MS-DOS 4.0 code base and turn it into what eventually turned into OS/2.

And, most succinctly and directly, again in a 2020 tweet:[4]

1986: MS-DOS 4 forked into OS/2, but other customers were left behind who needed support. One of those was Goupil in France, so while the rest of MSFT was shut down moving to a new campus, a co-worker and I traveled to Paris for a weekend to fix bugs for Goupil.

Architectural changes[edit | edit source]

Multitasking[edit | edit source]

The single-tasking nature of the operating system was replaced by a preemptive scheduler with priorities, child process support, and a UNIX and OS/2-style signaling system (although the signal numbers themselves are not identical to UNIX, with signal 8 being the equivalent of Unix SIGTERM). This also means that the operating system is now reentrant (albeit only for applications compiled as New Executables intended for DOS 4), as this is a requirement for a preemptive design. The first app that runs has a parent process ID (Ppid) of zero and is referred to as a "top-level process" by the kernel. If it quits, the operating system will simply freeze, although COMMAND.COM has code to print a message (Top-level process aborted, cannot continue).

The multitasking abilities of the operating system are exposed to the user via the Session Manager (SM.EXE), which is configured as the shell via the SHELL statement in CONFIG.SYS. The actual shell is also spawned by the Session Manager, using COMMAND.COM by default, although it is also possible to use a custom shell by providing its path as an argument to SM.EXE. If SM.EXE is ever unable to start a program, the message Unable to start program, press a key to continue. is printed to the screen, and the SM menu is returned to if any key is pressed. The kernel provides slots for a maximum of 31 processes; attempting to start another process after 31 have been started (the internal limit is likely 32, but the kernel may count itself as a process) causes the following User Error to be issued:

* User Error E9CE *
No Proc Slots

It is worth noting that these 31 processes must share a maximum of sixteen console devices; these console devices can be optionally grouped using the concept of the "screen group" – a "screen group" is a set of processes that all share the same console device – for example, if a nested instance of command.com was launched, that instance will share the same "screen group" as the instance that created it, even if it created a new console device (which is simply referred to as a "screen device"). However, if a new instance of COMMAND.COM were launched through a different application such as SM.EXE, that instance will be allocated a new screen group. If there are already sixteen console devices (as opposed to logical screen groups) and an attempt to create a new one is made, the screen group will not be created, and the following User Error will be issued:

* User Error 7691 *
DOS Screen Limit

This is not possible in an stock install of Multitasking DOS 4.0 using only the provided applications as SM.EXE only allows the starting of six different apps (which only take six different screen groups), possibly due to its limited screen space. Additionally, attempting to start a process with an ID of 65,535, or 0xFFFF – this is also the 65,536th process started, as Process IDs are never reclaimed after they are used, will simply lead to a EXEC failure message – the next process then appeared to have a PID of 41, instead of wrapping around to zero as expected due to the two-byte value nature of Process IDs. The kernel was not skipping already used process IDs, as there were only five processes running on the system, with a maximum Process ID of 8)

As ordinary DOS apps are not written with multitasking in mind, the internal command MEMSET allows a partition of foreground memory to be reserved for "old" apps (either temporarily with the MEMSET OVERRIDE=<value> parameter, or permanently with the DEFAULT=<value>, with <value> being the number of kilobytes to dedicate to old applications when in use. The default value of this, 127 KB, is far too small for almost all DOS applications made after the mid-1980s, and it should be increased to allow newer applications to run correctly. When an "old" app is running, it permanently takes up the size of the "foreground" partition multitasking is suspended; the entirety of the CPU is being used by the legacy app, and the legacy app is immediately suspended as soon as another app is switched to.

Process management[edit | edit source]

Regular MS-DOS has a basic process information structure (the Program Segment Prefix) that is only suitable for running a single task at a time; this structure has been extended and replaced with the kernel's 16-byte Module Table Entry (MTE), an entry in a linked list, the Module Table, containing the list of all loaded segments (one process can have several segments, which all get their own MTEs), and the process' Per-Task Data Area (PTDA); these structures were carried on into OS/2 1.x. The PTDA ("TD" header, for Task Data) is a very long (at least 400 bytes, and possibly up to 1172) structure containing almost all information about a running process, as well as a pointer to the process' MTE; both the MTE and PTDA are always referenced by a segment address and therefore can only exist on a paragraph boundary; the kernel has an internal structure with a list of all PTDAs in the system (called a "process slot").

When a new (NE) program is loaded into memory (the kernel being an exception to this rule), it is typically split up – the NE header is loaded into memory (and a module table entry is created for it, being placed immediately before), which is immediately followed by the module table entry for the code segment and PTDA of the process (the NE header can be accessed again by the kernel by using the segment address stored at 0x17 in the PTDA structure); in the case where an instance of the process already exists, the code segment of the new instance is discarded and the first instance's code segment is used in order to save memory (although a new data segment is still allocated for every new instance of a process). The PTDA is then followed by the process' data segment, which is pointed to by the DS register at startup, as one would expect) – the stack is then stored immediately after the data segment, with the value of the register pair SS:SP starting one byte past the end of the program's data segment – presumably, each data segment used by the program (in the case of compact or large model applications) is loaded one after the other. The program's code segment is split off from its data segment and loaded in a separate location, usually in upper memory – it seems program code segments are designed to load in from the bottom of RAM up, and data segments, headers, and PTDAs from the top of memory down (in order to reduce memory fragmentation?); a "placeholder" PTDA is located within the kernel binary and copied from its data segment to the running application (both during kernel initialization, for POPUP.EXE, and by the DOS EXEC call), containing placeholder values that are later replaced with values obtained from the application's NE header; any segment can be relocated anywhere by the memory manager, possibly partially dictated by the relocation table in the NE header generated by LINK4.EXE.

The first and last module table entries is always a dummy and has a type of 0xFF; it seems to always be created by the kernel.

The value of the AX register on entry to any New Executable program is set to the word contained at 0x01. of the PTDA – this is then typically copied into the DX register in order to allocate a segment; it seems to be related to the amount of memory (in paragraphs), taken up by the application's code segment the significance of this is presently unknown.

MTE format
Offset Type
0x00 Magic ("MT", sometimes "M")
0x01 – 0x05 0x01 may indicate type (often "T" – seems to be used before the header, code, and data (primary segments only?) segments of NE programs, 0xFF, 0x00 and 0x28 also seen); the significance of the rest of the bytes unknown.
0x06 Pointer (segment address) to previous module table entry. If this is the same as the current, we are at the start of the module table; the first entry in the list seems to be a dummy entry (possibly for the kernel itself).
0x08 Pointer (segment address) to next module table entry. If this is the same as the current, we are at the end of the module table
0x0A – 0x0F Presently unknown.

Shell[edit | edit source]

The shell (COMMAND.COM) is an entirely different codebase from the previous MS-DOS versions and is now written in C and compiled using Microsoft C 3.0; despite its filename (which is likely to remain COMMAND.COM for compatibility reasons – in OS/2 it was renamed to CMD.EXE), the shell is a native New Executable binary. The codebase continued into OS/2, where by IBM OS/2 1.0 revision 7.68 in October 1986 it was already renamed to CMD.EXE; as the OS/2 shell was ported to NT, it may have eventually become (although almost certainly not a huge amount of code would have been left by this time) CMD.EXE in Windows NT. It is also capable of nesting instances of itself, detaching applications (using the DETACH command) and managing memory for old apps (using the MEMSET command). autoexec.bat is also now parsed and run on the starting of any instance of the shell, even long after the system has booted; this is not indicated to the user in any way, even when it affects system-wide settings. CONFIG.SYS is parsed by the kernel and is independent of any shell.

There is a string for a EXTPROC command, but it does not appear to be referenced anywhere or recognized in any way by the shell (presumably it would do something related to "external processes"; it is implemented, or at least recognized as a valid command, in IBM OS/2 1.0 build 7.68.17) a SHIFT command also exists and is recognized, but its functionality has not been determined.

Memory manager[edit | edit source]

The kernel memory manager has been rewritten due to the requirement for a multitasking system – a "segment loader" (in this case a "segment", unlike the real-mode x86 memory concept, is just an arbitrary region of memory that can be allocated for the use of an application) – has been implemented to handle memory allocation for DOS 4 applications. Segments can be managed using kernel system calls (ALLOCSEG, REALLOCSEG and FREESEG are all present for allocation, reallocation and freeing of segments). In order to use a segment, it must be "locked" in order to allow the memory to be accessed, much like "locking" a graphics primitive in graphics APIs like SDL – this was most likely done in order to prevent other processes from using them in a crude, and likely ineffective due to the nature of the 808x processors without an MMU Multitasking DOS is designed for, form of memory protection) using LOCKSEG and UNLOCKSEG. It is also possible to discard a code segment allocated to the current app (DOSDISCARDCODE). Freed memory seems to be overwritten in some cases with the string ms (for Microsoft?); this is usually used in debugging environments and would presumably further indicate that build 6.7 is a pre-release build.

There is also a "handle" API that seems to be separate to the normal DOS file API (which is still implemented via the legacy INT 21h method), but it seems to be part of the memory manager due to function names such as GETDSHANDLE. . The kernel incorporates a garbage collector for internal use, implemented using the internal garbage_collect function located at 0xB7B5 (0xB5B5 in IDA) in IBMDOS.COM.

The MS-DOS 4 memory manager was also adapted for use in Windows 1.0.[1] The part of the memory manager that deals with global allocations is nearly identical between Windows 1.0 and MS-DOS 4, while other parts were changed to accommodate for the different multitasking model. Despite the documentation for Development Release #4 states that the memory manager would be rewritten "in the October release", suggessting that that the first release with the new memory manager was Development Release #5 from October 1984, the reality is that this version has a completely separate memory manager seemingly unique to it implemented in its KERNEL, with the Alpha release from January 1985 being the first with a version of the Multitasking DOS 4 memory manager.

API[edit | edit source]

The DOS API has been massively extended, with at least 31 additional system calls (most of which take up the 80h through 99h values in the AH register when calling the INT 21h API – a far higher value than any other DOS version, where INT 21h services only ever went up to 63h); these APIs have been implemented for tasks added for tasks such as running and blocking processes, modifying the amount of memory given to legacy DOS apps, sending signals to processes, blocking and running processes, getting process IDs and handles, and various other functionality.

A list of "4.0 new multitasking calls", containing most (but not all) of the system calls implemented for Multitasking MS-DOS 4.0, dated 30 August 1986, was found in the slack space of an early build of OS/2 1.0.

Multitasking MS-DOS 4.0 API (Legacy INT 21)
Register values Name Description
AH AL BX
44h 0Ch EXEC Unknown IOCTL (Screen switching?), not defined in regular DOS. Listed as "Generic character device request" on Ralf Brown's Interrupt List, but listed as different on "European MS-DOS 4.0, OS/2".

Category (indicated by CH register) 04h), function (indicated by CL register) 06h ("reserved for keyboard" in regular DOS) seems to be used to create a new console device (not screen group as PS.EXE is in the same screen group as its parent process – screen groups seem to be managed by the kernel); PS.EXE only calls this function if its -S (fullscreen) option is used, where it creates a new console device (but one that can have the same screen group).

Needs more testing.

4Bh 04h EXEC Load and execute program in background (legacy MS-DOS only defines subfunctions 00 to 03h)
4Bh 08h EXEC Unknown subfunction, used by SM.EXE
4Bh 0Ch EXEC Unknown subfunction, used by SM.EXE; seems to be the default option used by COMMAND.COM to launch programs.
4Bh 0Fh EXEC Unknown subfunction; EXEC checks for this and errors out if the subfunction is any higher – presumably this is the highest subfunction.
59h 0001h Get Hard Error Returns a pointer to kernel memory containing the information about the last hard error that occurred.
Format of European MS-DOS 4.0 hard error information packet
Offset Size Description
00h WORD contents of AX at system entry
02h WORD Process ID which encountered error
04h WORD contents of AX at time of error
06h BYTE error type
  • 00h physical I/O error
  • 01h disk change request
  • 02h file sharing violation
  • 03h FCB problem
  • 04h file locking violation
  • 05h bad FAT
  • 06h network detected error
07h BYTE INT 24 error code
08h WORD extended error code (see #01680)
0Ah DWORD pointer to associated device
61h ??? Listed as "stubbed, intended for network" or "DOS 3.x internal, UNUSED FUNCTION" for all other functions, but the function points to code that is clearly not stubbed in Multitasking DOS 4.0. Needs more research.
80h AEXEC "European MS-DOS 4.0 - AEXEC - EXECUTE PROGRAM IN BACKGROUND" according to Ralf Brown's Interrupt List, but not in OS/2 syscall list, and not used in COMMAND, POPUP, PS, SWAPPER, HE_DAEM or SM in build 6.7. Needs more research!
81h FREEZE "European MS-DOS 4.0 - FREEZE - FREEZE A PROCESS" according to Ralf Brown's Interrupt List, but not in OS/2 syscall list, and the entry in the syscall table that would point to this function points to a null function; most likely it was replaced with the PBLOCK export API.
82h RESUME "European MS-DOS 4.0 - RESUME - RESUME A PROCESS" according to Ralf Brown's Interrupt List, but not in OS/2 syscall list, and the entry in the syscall table that would point to this function points to a null function; most likely it was replaced with the PRUN export API. Needs more research!
83h 00h DOSChildCtl (RBIL refers to this as 'PARTITION', maybe it changed over time) Get foreground memory partition for old apps. The carry bit is used to indicate success (clear on success and set on failure); if the function was successful, AX is zero and BX contains the current size of foreground memory in paragraphs (see below). If the function was not successful, AX includes an error code, which according to RBIL can only be 01h, 07h, and 0Dh (which, interpreted as DOS extended errors, correspond to "Invalid function", "Memory control block destroyed" and "Data invalid"); the last two would seemingly indicate severe system corruption if the value is simple being acquired. Needs more research!
83h 01h DOSChildCtl Set foreground memory partition for old apps. BX contains new size in paragraphs (the smallest unit a segment can be moved around due to the nature of how segment registers work in x86-16). According to RBIL, if BX is zero, "no partition management is done and all memory allocations become compatible with DOS 3.2", but that seems extremely suspect as that would only allow one task to run and it can be trivially proven with a single command (MEMSET DEFAULT=1) that rigidly sized memory partitions are only used for legacy apps. When this command is run, you can only run "new" DOS 4 apps (as 1 KB is far too small for any legacy DOS app to run). The MEMSET command in COMMAND.COM internally uses this API to change the partition size.
84h DOSSharMem This function (which is used internally by SM.EXE to create two shared memory areas, SMDATA1 and SMDATA2), is used to manage the concept of shared memory between processes, which can be accessed using a virtual \SHAREMEM folder; this function has several subfunctions, which are selected using the value of the AL register:
84h 00h DOSSharMem Create a shared memory area, with BX containing a size – providing a size of zero is interpreted by the kernel as FFFFh. (65,536 bytes). The BX CX register contains flags – the only known value used for this is82h, which implies that bits 0 and 6 are used; the DS:DX register pair is used to point to a null-terminated (ASCIZ) string containing the name of the shared memory area; it must be in the pseudo-folder SHAREMEM or the operating system kernel will not allow the shared memory area to be created.

The carry flag CF is used to indicate success upon returning; if the function returned successfully, CF is unset and AX contains a segment address to the start of the shared memory structure; otherwise CF is set and AX contains an error code (which according to RBIL, can only ever be 06h, "Invalid handle", or 08h, "insufficient memory".)

84h 01h DOSSharMem Used to access a shared memory area created by another process; a shared memory area is referred to by its name (which is pointed to by the register pair DS:DX when calling the function, with the CX register being used for the same flag setup as AL=01h). When a process obtains access to a shared memory area, the shared memory area has its reference count increased.

The carry flag CF is used to indicate success upon returning; it is unset when the function is successful, with AX containing a segment address to the requested shared memory area, and CX its size in bytes. If the function fails, CF is set and AX contains an error code, which is apparently always set to the DOS extended error code of "invalid name".

84h 02h DOSSharMem Used to remove the current process from a shared memory area; this will decrease its reference count. As is usual for reference counting, when the reference reaches zero, the shared memory area is freed.

Used internally by SM.EXE; two separate shared memory areas (\SHAREMEM\SMDATA1) and (\SHAREMEM\SMDATA2) are created.

85h 00h-04h DOSSysSem Most likely an internal API used to handle semaphore primitives for the kernel; there are functions and code referring to some kind of "loader semaphore", which may be related. There are five subfunctions (AL=00h through AL=04h), but further testing is required.
86h SETFILETABLE "European MS-DOS 4.0 - SETFILETABLE - INSTALL NEW FILE HANDLE TABLE" according to Ralf Brown's Interrupt List, but not in OS/2 syscall list, and not used in COMMAND, POPUP, PS, SWAPPER, HE_DAEM or SM in build 6.7; RBIL claims this changes the size of the file handle table for the current process (and notes that only the first twenty, the default per process, and that the total number of file handles still cannot exceed the system-wide limit enforced by the FILES= directive in CONFIG.SYS), not included in the early OS/2. Requires testing.
87h DOSGetPid Returns the process ID for the process that called the API in AX, its parent process ID in BX and its Command Subgroup ID (CSID) in CX. Called by the getpid() function in Microsoft C 4.0.
88h DOSRlock Unknown, but obviously has something to do with locking a primitive so it cannot be used by other processes. Requires testing.
89h DOSSleep Found in header files in some Microsoft C versions, such as 5.0. Sleeps for CX milliseconds, or if the value in CX is zero the process will be completely frozen (the scheduler will no longer provide it a time slice). Presumably an interrupt or signal handler could be used to recover from this. HE_DAEM.EXE installs a signal handler(?) for hard errors and waits 65,280 seconds in a loop, however.
8Ah DOSCwait Waits, likely for a child process. Requires testing. Used internally by SM.EXE.
8Bh DOSFinalArena Possibly frees a memory arena – a term for a region of memory Microsoft used for 16-bit Windows as well as Multitasking DOS 4.0.
8Ch DOSSet_Signal_Handler Sets a signal handler to a function within the code segment of the current executable, represented by a function pointer in AL. Known signal IDs used by Multitasking DOS 4.0 are zero (unknown), one (SIGINTR, which is sent to the currently executing process when the user executes a Ctrl + C or Ctrl + Break key combination to interrupt the process), four (unknown), and eight (which RBIL claims as SIGTERM, but MSC 4.0 defines as SIGFPE for floating point exceptions – ongoing testing has so far failed to actually terminate processes using signal #8, all attempts leading to errors). The function in PS.EXE that installs that processes' signal handler for signal #1 (the Ctrl + C signal, which sets an internal global variable that, the next time PS.EXE draws the screen, causes it to exit instead), returns a failure code if the signal ID is above 10h, or 16, which may indicate a Unix-style signal number system, with a total of fifteen signals – but this has not been determined for certain. SM.EXE installs handlers for signals 4 and 8 (process termination?). SWAPPER.EXE additionally installs handlers for signals 1 and 8.

RBIL also claims that signal 9 is used for a broken pipe (despite named pipes apparently being removed from DOS 4.0), and 12 and 13 are user-definable signals. The process can also specify an action to perform on receiving the signal – known actions are 0, or SIG_DFL (terminate process), 1, or SIG_IGN (ignore), and RBIL also claims that 2 (SIG_GET, acknowledge), 3 (SIG_ERR, signify error, and 4 (SIG_ACK, acknowledge but do not change current signal handler), exist.

If the function was successful, AX is zero and the carry bit is clear. If it is set, AX indicates an error code. Additionally, ES:BX holds the previous signal handler and AL the previous action; if it was not successful, DOS extended error codes 01h (invalid function number, "Invalid SigNumber or Action" per RBIL) and 06h (invalid handle) have been observed to be returned during testing.

8Dh DOSSend_Signal Sends a signal.
  • AL is the signal number and BH, according to RBIL, is apparently a "signal argument".
  • If BL is zero, only the process explicitly specified is sent the signal; if it is one, all of its child processes are also sent the signal (this feature makes it trivial to implement a "kill process tree" style utility, for example).
  • DX contains the process ID to send the signal to.

The carry bit is clear and AX is zero if the function was successfully executed. If it was not, the carry bit is set and the same error codes as for DOSSet_Signal_Handler are returned.

8Eh DOSSetpri Set process priority for the process ID indicated by CX. According to RBIL, if the BL register is zero, only the process ID indicated by CX has its priority changed, whereas if it is one, the entire tree of child processes of the current process has the priority changed (this has not been confirmed by testing yet). In all cases, when DL is zero the priority is returned, otherwise it is interpreted as a signed value and used to change the priority level of the specified process ID (which is an eight-bit number running from 0 to 255, with new processes being observed to assume values around 190).
8Fh DOSSet_Global_Vector Presumably sets an interrupt vector. Untested.
90h DOSUnset_Vector Presumably unsets an interrupt vector. Untested.
91h DOSPtrace Unknown, untested. Likely to do with tracing a process.
92h N/A Does not exist in the system (the DOS API dispatch table entry for AH=92h points to the "invalid function" code); listed in Ralf Brown's Interrupt List as "European MS-DOS 4.0 - ???" according to Ralf Brown's Interrupt List. Considering nearly all other intended features are accounted for in this list, may have at some point related to the removed multithreading capability.
93h DOSPipe (removed) Does not exist in the system (the DOS API dispatch table entry for AH=93h points to the "invalid function" code) but listed in the OS/2 sycall list as. Has something to do with named pipes, which were removed from the system around April 1985 due to memory constraints.

Although the function does not exist in the versions of the OS that are currently available, the RBIL documentation may be useful in case a version of the OS containing it is found: RBIL claims that this function takes a size in bytes for the pipe in CX. The carry bit is used to indicate success. If the function succeeds, AX contains a read and BX contains a write handle to the pipe. If it fails, AX containing an error code (apparently only one case can cause this, insufficient memory, which as you would generally expect returns 08h, "insufficient memory").

Not tested.

94h THREAD (removed) Does not exist in the kernel dispatch table (the DOS API dispatch table entry for AH=94h points to the "invalid function" code), but internally used by SWAPPER.EXE, so may be internal to it.

Alternatively, it could be installed at runtime by the swapper when active. October 1984 design documents indicate that this system call was intended to create or destroy threads, with 00h destroying and 01h creating threads

95h 00h/01h DOSCritErr According to RBIL, sets hard error processing mode, but the name suggests something else; the AL register controls the new state – zero enables HE_DAEM.EXE hard error processing and one turns it off, with hard errors leading to INT 24. However, on build 6.7 it doesn't seem to have any effect on HE_DAEM, although the function definitely exists – it seems to return zero, regardless of if AL is 00h or 01h, but this may be due to other registers being set up wrong (it seems to use the SS register to set a byte in an internal system structure – it may be not suitable for single-stepping, or require the SYSVARS table or something similar to be obtained first). It seems strange that a program would want to turn it off, but it might be useful if you needed to handle hard errors yourself, for example to handle conditions HE_DAEM doesn't.

This function is internally used by SWAPPER.EXE with AL=01.

96h ??? Unknown but confirmed to exist by the kernel syscall dispatch table. Untested.
97h 00h ??? Unknown but confirmed to exist by the kernel syscall dispatch table, and internally used by SM.EXE, so may be internal to it. Seems to have only one subfunction, but presently untested.
98h ??? Unknown but confirmed to exist by the kernel syscall dispatch table. Untested.
99h ??? "European MS-DOS 4.0 - PBLOCK" according to Ralf Brown's Interrupt List, but not in OS/2 syscall list, and not used in COMMAND, POPUP, PS, SWAPPER, HE_DAEM or SM in build 6.7. It claims that DS:BX contains a pointer to the memory address to block on, and CX maximum number of milliseconds to sleep. Additionally, if DH is zero, the process will awake upon being given a special "restart" signal. This may be how HE_DAEM jumps into action when given an error – the kernel sends it a restart signal and tells it an error has occurred. The return values are apparently:
  • CF clear if "awakened by event", otherwise the wakeup is "unusual".
  • ZF clear if timed out, otherwise a signal told the

Although the maximum syscall number in the kernel in build 6.7 is 99h, having a PBLOCK syscall without a corresponding PRUN syscall seems strange (although you could certainly use the RUN function to wake up the process, or send a restart signal). These calls seem to be duplicated variants of functions 80h (FREEZE) and 81h (RESUME), as well as 89h (SLEEP), with more functionality and may have replaced them, or have something to do with the internal operations of the PBLOCK and PRUN functions. This function may exist in Multitasking MS-DOS 4.1 (undumped) or the OEM versions of Multitasking MS-DOS 4.0 but have not yet been tested under build 6.7.

Untested.

"PBLOCK" is the name of a DOSCALLS export in build 6.7.

9Ah ??? "European MS-DOS 4.0 - PRUN" according to Ralf Brown's Interrupt List, but not in OS/2 syscall list, and confirmed to not exist in the system – the maximum syscall number is 99h according to the INT 21 handler in the kernel, and while a 9Ah function is defined in the syscall dispatch table, it is set to a function that immediately returns (even if it was set to call a function that did something useful, the INT 21 function dispatcher rejects any function above 99h immediately, with nothing else being done prior to this check, so there would be no way for it to ever be called); the PBLOCK and PRESUME functions may exist in the form of INT 21 APIs in Multitasking MS-DOS 4.1, the OEM versions of Multitasking MS-DOS 4.0, but this system call for certain does not exist in build 6.7. As with AH=99 (PBLOCK), these calls seem to be duplicated variants of functions 80h (FREEZE) and 81h (RESUME), as well as 89h (SLEEP), with more functionality – it's possible that these are older versions of these exported functions.

However, "PRUN" is the name of a DOSCALLS export in build 6.7; after a function prologue it jumps into the middle of CRITLEAVE/FCRITLEAVE related code (more reversing needed).

All API calls above are followed by a call to INT 21, the standard DOS API handler.

Additionally, there is a second set of API functions, primarily for managing multitasking primitives such as critical sections (implemented using the CRITENTER and CRITLEAVE and the similarly named, but with unknown differences, FCRITENTER and FCRITLEAVE functions), managing segments (which are not identical to the 16-bit x86 architectural concept of "segments", but instead an arbitrarily-sized area of memory) and general memory, and other general utility functions (such as "subscreens" to theoretically allow multiple apps to display on the screen at once). These are implemented through the DOSCALLS library in the kernel as New Executable exports (there is a dummy NE header at offset 0x1190 of IBMDOS.COM whose sole purpose is providing said exports to other applications and nothing else), although several other functions are additionally exported from POPUP.EXE, which is a dynamic-link library (the .DLL extension not existing until 1987) for providing various types of popups. It allows several predefined types of popups (or "subscreens") to be drawn to the screen (using the SSAP, SSHE, SSLO, and SS_WRITE functions, as well as several undocumented functions exported only by ordinal).

Import resolution is done at runtime. The linker encodes calls to imported function libraries as far calls, starting at the segment-offset address 0123h:0001h for the first import from the first imported module (or more correctly, the first imported *NE library*). For each module imported, the segment address is incremented by one (i.e. the second module would start at 0124h:0001h, the third at 0125h:0001h and so on); for each function imported from that module, the offset part of the address is incremented by one – so 0123h:0002h, 0123h:0003h and so on for functions imported from the "first" library (usually the kernel's DOSCALLS library); for example, a call to the 7th import from the third module would be encoded as a far jump to 0125h:0007h in hexadecimal. While the kernel is loading the binary, it reads the NE header for the imports, determines which dummy calls correspond to which function, loads the library being imported if required, and replaces those far jumps with far jumps to the actual code the program is intending to call; in the kernel's case, the kernel is always loaded at a static segment address of 0506h:0000h, or a linear address of 0x5060, so all kernel calls will use that segment address of 506h after they have been fixed up.

NE exports
File Function name Description
IBMDOS.COM ALLOCSEG Allocates a segment, similar to C malloc – needs more research.
IBMDOS.COM REALLOCSEG Reallocates a segment (presumably changes its size), similar to C realloc – needs more research.
IBMDOS.COM LOCKSEG "Locks" a segment so that it can actually be used by the current program; returns a pointer to the first byte of the provided memory block. The dx register may contain the size in paragraphs of the segment – 16 (but in an NE this would usually be a C argument, so it needs to be matched to an argument); on return the segment seems to be pointed to by the es register, presumably returning a C void* (or char* in this era of C) and increments its lock count – needs more research.
IBMDOS.COM UNLOCKSEG "Unlocks" a segment when use is done (the DX register, in compiled programs, seems to contain a pointer to the segment); decrements its lock count – needs more research.
IBMDOS.COM GETSEGSIZE Returns the size of a segment – needs more research.
IBMDOS.COM GETDSHANDLE Returns a handle to a data segment.
IBMDOS.COM CRITENTER Enters a critical section – needs more research.
IBMDOS.COM CRITLEAVE Leaves a critical section – needs more research.
IBMDOS.COM FCRITENTER Similar to CRITENTER (possibly related to INT 21h,AH=93h "FinalAlloc" function)
IBMDOS.COM FCRITLEAVE Similar to CRITENTER (possibly related to "FinalAlloc" INT 21h,AH=93h function)
IBMDOS.COM PBLOCK Blocks a process from running – possibly replaced the INT 21h,AH=99h legacy DOS API; requires more research.
IBMDOS.COM PRUN Allows a process to run – possibly replaced the INT 21h,AH=99h legacy DOS API; requires more research.
IBMDOS.COM SUBSCREEN Manages subscreens (always called internally by the SS* functions located within POPUP.EXE; requires more research.
IBMDOS.COM GETPIDS Gets a list of process IDs running in the current system; after returning in a compiled application, the DS is set to some kind of internal kernel structure (the pointer to which is located at 0x562 from the kernel code segment start, with AX, BX, and CX all having words (equivalent to 0x08, 0x09 and 0x0C from the start of the kernel code segment; requires more research.
IBMDOS.COM DOSDISCARDCODE Discards a previously obtained code segment; requires more research.
IBMDOS.COM DOSGETHANDLE Gets a handle to a code segment (as code segment sharing is supported); requires more research.
IBMDOS.COM DOSHANDLEJUMP Unknown (presumably handles a jump); requires more research.
POPUP.EXE SSLO Subscreen utility function (take lower half of screen?); requires more research.
POPUP.EXE SSHE Subscreen utility function (take upper half of screen?); requires more research.
POPUP.EXE SSAP Subscreen utility function; requires more research.
POPUP.EXE SS_WRITE Possibly writes the content of a subscreen; requires more research!
POPUP.EXE PREAD Presumably takes user input from a popup.
POPUP.EXE Ordinal 7 Unknown
POPUP.EXE Ordinal 8 Unknown
POPUP.EXE Ordinal 9 Unknown
POPUP.EXE Ordinal 10 Unknown
POPUP.EXE Ordinal 11 Unknown

The MS-DOS Terminate & Stay Resident functionality (INT 27H or INT 21H, AH=31h) has been disabled by default (a setting can be set in the Program Information File for a program to re-enable it), with an attempt to call one resulting in a User Error code 983F being issued, and the following information being printed to the current shell:

* User Error 983F *
Cannot set private vector

and the current process being terminated. This is most likely due to TSRs not making sense in a multitasking system – it would be preferable to simply start a child process, with a system call (CWAIT, INT 21H, AH=81H) allowing the current process to block until the child process terminates itself; they would almost certainly not take very well to being preempted by the kernel while running.

Hard Error Daemon and system crash[edit | edit source]

MDOS 4 implements the concept of a hard error, as opposed to DOS 3.0+ "extended errors". The last hard error can be acquired using the INT 21h, AH=59h, BL=01h API. It appears to replace the standard INT 24h handling, which has been moved from the kernel to its own dedicated binary that is started and installed at boot by the kernel, called the Hard Error Daemon (HE_DAEM.EXE). Upon being started, the daemon will call the SLEEP system call, INT 21h, AH=89h for 65000 seconds in a loop until a hard error (an error that would usually cause a Abort, Retry, Fail? to be delivered to the user in regular MS-DOS) occurs (possibly using an INT 24h handler or some kind of special invocation by the kernel). When this occurs, the daemon determines the error information based on some kind of system structure and seven broad "error categories" (category zero being a general I/O error, one not an error but instead a disk change request, two a file sharing violation, three an issue with a DOS File Control Block, four a file locking violation, five a bad FAT and six a network error), provides an error description (which may include what the system was trying to do when the error occurred), the process ID of the process that caused the error, what the process was doing when the error occurred (which, in some cases, can simply print that the program was ???ing a device, for example when attempting to use the shell to echo to the built-in SCREEN$ device), the hexadecimal number of the system call that triggered it. The user is then provided with a Abort Retry Ignore Fail (or in some cases, just Abort Retry Fail) prompt. If an invalid entry is selected, Huh? is printed to the screen and the prompt repeated. An exception for this is the ? character, which then prints more detailed information about what each option does.

If the system crashes, for example by corrupting kernel memory, the kernel will stop the system and print a string of the format * Internal Error XXXX *, with XXXX replaced by the four byte position within the kernel code segment where the error string starts (this was likely intended to be where the call to the IntErr or UsrErr strings came from, but due to a bug or oversight the start of the error string is used instead). There is a similar (using the same scheme for error codes), but non-fatal (the current process is terminated) "User Error" string, as well as a message with what went wrong, which has experimentally been triggered by processes attempting to call a system call with invalid parameters and running out of process slots. If the hard error daemon itself is not found it will simply print Hard Error daemon not found. to the console upon trying to call it. This function is located at 0x9581 (0x9381 in IDA) for internal errors (IntErr) and 0x9595 (0x9395 in IDA) for user errors (UsrErr), with the shared error code, the ErrPrnt function, located at 0x95C1 (0x93C1 in IDA), which is called by both functions in order to display the error and/or halt the system if required. Most of the calls to this function are for impossible situations within the multitasking code inside the kernel. Interestingly, there is often a 24h byte after this, as if calls to INT 24h were overwritten with calls to this function.

COMMAND.COM implements its own mechanism for fatal errors, with "COMMAND Internal Error", followed by a single character (F and S have been seen in the code) determined by the function causing the error (which has no unified function, but is implemented in several different places). COMMAND.COM then, instead of terminating, simply jumps into an endless loop, which seems rather strange for a multitasking system; however, the fact that the code seems to check for MS-DOS 3.0, as well as 4.0, and allows itself to run in that case, may imply it was either intended to also run on legacy DOS or, more likely, that the code stems back from when MS-DOS 4.0 was called MS-DOS 3.0Template:Pfn

Built-in DOS devices[edit | edit source]

While most of the default built-in MS-DOS devices (such as CON, PRN, AUX and NUL) are still accessible by using their classic MS-DOS names (albeit the LPT4 throughLPT9 and COM3 through COM9 devices, representing the third through ninth communications and fourth through ninth printer port respectively, have been removed from the operating system – most likely due to such a high number of ports being an incredibly unrealistic configuration), it is also possible to access them via UNIX-style names (for example, /dev/con, /dev/prn, /dev/aux, and /dev/nul). There are also several additional devices that do not exist in regular MS-DOS, accessible by both the legacy and \dev mechanisms – SCREEN$ (representing the screen, likely for the detachable console feature), KEYBD$ (for the keyboard), and PRNTARB$, possibly for managing printing between multiple processes at the same time. These device names are located in the bootloader and driver blob, IBMBIO.COM instead of the kernel where almost all other operating system functionality is. This is due to the fact that it deals with interfacing directly with the hardware – it makes up the "BIOS" in IBMBIO.COM. The addition of the ability to access these devices through a Unix-style /dev folder is likely related to Microsoft's long-term plan, prior to the signing of the Joint Development Agreement with IBM to develop OS/2, to gradually enhance MS-DOS into a single-user variant of their Unix-based XENIX operating system, called XEDOS, gradually integrating UNIXisms to make the two OSes more similar until they were effectively identical; various other UNIXisms can be seen throughout this era of Microsoft, such as the \bin directory early Windows 1.0 builds, such as DR5 and Alpha, as well as Multitasking DOS 4.0 itself, dump many of their binaries into.

Detachable consoles & batch changes[edit | edit source]

Support for multiple consoles has been added to the OS. This is primarily exposed to the user the command DETACH (or DET), followed by a program to run – this program will now run "headless" and not attached to any shell. The shell instance that launched the application will resume when the application exits. Multiple instances of COMMAND.COM can be running at any one time and using multiple console devices. As a side effect of the rewriting of the shell (COMMAND.COM) in C, a new batch parser was written. This batch parser has a limitation that the regular DOS batch file parser does not: batch files will stop running (the system will ask you to provide the location of the batch file) if they are on a floppy disk and the floppy disk is changed. This is unlike regular DOS, where they will continue running on a disk change as long as they are in memory.

Furthermore, this is the first Microsoft OS, and apparently the only version of MS-DOS, to implement the SETLOCAL and ENDLOCAL batch commands to temporarily change environment variables while a batch file is running.

PIF file support[edit | edit source]

Multitasking MS-DOS 4.0 adds PIF file support to the operating system for managing the behavior of legacy MS-DOS apps, similar to 16-bit Windows (in fact, it uses a similar "extended PIF" format to Windows, as opposed to the original 253-byte TopView format); in fact, they are used for some of the OSes inbuilt apps (and, for some reason, HE_DAEM.EXE, which is a New Executable and therefore should not need a PIF file). There is a text-mode PIF editor, PIFEDIT.EXE, which allows users to graphically edit PIF files (which is described in full detail later in the "Included utilities" section).

Kernel debugger[edit | edit source]

The kernel debugger (DEBUGDD.SYS, which identifies itself as System Debug 070185) included in the Goupil OEM release can be installed by adding a DEVICE= statement to CONFIG.SYS, and will only function if attached via passthrough or a real serial port to the second COM port (COM2) of the system; it has been successfully tested with the default settings and 9600 baud speed using 86Box passthrough. It is similar to and therefore has a similar command set to MS-DOS DEBUG, real-mode Windows SYMDEB, and the OS/2 KDB debugger (which is the direct successor of System Debug); although, due to its older heritage its command set and overall functionality is between the former and the latter two, and there appears to be no commands to bring up a command list or any other type of online help. Additionally, some of DEBUG.EXE's functionality has been removed – it is impossible to quit the kernel debugger, and the disk read/write functionality of MS-DOS DEBUG has also been removed. Like DEBUG, only 808x instructions are supported. However, several other commands to manage and deal with breakpoints have been added, as well as descriptions of the type (near or far) when a pointer is being used and support for assigning types (such as floating-point numbers) to variables. While the debugger is enabled, the system will automatically break into the debugger during boot (after the sign-on message is printed), which means the system will appear to freeze; the g must be issued to allow the system to run at all while the kernel debugger is loaded. A g command must be sent to continue booting – the system can then be frozen at any time by either executing an INT 3 instruction or pressing Ctrl + C on the serial console while the system is running; the kernel debugger will automatically be broken into upon an invalid instruction being executed while it is loaded. In this state, attempting to continue will simply cause the OS to hang – attempting to break into the kernel debugger again will result in nothing happening.

For all commands except for bp, you must refer to the breakpoints by their numbers; you can view the breakpoint numbers with the bl command, which will list the ID, position, and number of each breakpoint. You can set a maximum of 10 breakpoints, with a BR Error message being printed if you attempt to add a new breakpoint after already adding ten. You can also use the wildcard * to affect all breakpoints, such as using the bc * to clear all breakpoints.

DEBUGDD.SYS additional commands
Command Description
bc <breakpoint numbers> Clears a breakpoint; providing more than one number (separated by a space) will clear more than one breakpoint.
bd <breakpoint numbers> Disables a breakpoint; providing more than one number (separated by a space) will clear more than one breakpoint.
be <num> <num> Enables a breakpoint; providing more than one number (separated by a space) will clear more than one breakpoint.
bl Lists breakpoints – their number, if they are disabled or enabled (the former represented by "d" and the latter "e"), the position and several others.
bp <seg:off> Sets a new debug breakpoint at segment:offset address.

All other commands are identical to MS-DOS DEBUG.EXE, except for the aforementioned l (load from disk), n (set filename for disk write), q (quit) and w, (write to disk), which have been removed when compared to regular DEBUG. Research into any additional commands that may exist is ongoing.

Oddities[edit | edit source]

DEBUGDD.SYS uses – but only in some parts of the code (other parts use traditional MS-DOS "$" string terminators) – the extremely obscure (primarily only used on machines made prior to 1980) method of "high bit set" (also known as "Microsoft Basic", as it was used in Microsoft Disk Basic) for string termination, where the highest bit of the last character is set to indicate a string being terminated; the reason this was no longer used is the transition of computers from 7-bit to 8-bit ASCII based character sets, as high bits being used for string termination would effectively limit the number of characters that can be used to 128, which is not sufficient to represent many languages (as IBM needed to do on the PC). They are also used in the IBM PC-DOS 1.0 boot sector but are removed from all future versions. The command list itself is stored with all bytes having their high bit set, which makes it unreadable in a normal hex editor; this was most likely done to save space, as not having null-terminated strings saves one byte per string (which in memory-constrained environments was quite a lot); the terminator is only used on strings that needed to be duplicated substantially, such as instruction names and register names – this was in fact inherited directly from MS-DOS DEBUG, which DEBUGDD.SYS seems to be forked from. The kernel debugger additionally seems to hook INT 31H, which if executed while DEBUGDD.SYS is loaded to the system and attached causes an immediate system reboot. However, executing this without prior setup restarts the system unexpectedly and leaves it in a corrupted state where it will likely immediately crash.

Application compatibility[edit | edit source]

For a legacy DOS app to work on Multitasking DOS 4.0 without issues, it must:

  • Exclusively use the documented INT 21h APIs except for functions that launch other apps and are not the functions with subtle differences listed below.
  • Not use Terminate-and-Stay-Resident functionality, which is disabled by default (unless a special bit is flipped in a PIF file attached to the application that allows it to use TSRs).
  • Not launch any other apps (although chaining as practiced by the Microsoft C Compiler appears to work)
  • Not use the internal INDOS flag to determine when to call DOS APIs (the OS becoming reentrant means this flag is effectively deprecated and is always set to the value FFh, which legacy apps will interpret as DOS always performing a system call – which means from their perspective it is never safe to call DOS APIs and they therefore will not call them)
  • Not depend on internal DOS structures or undocumented DOS INT 21 APIs.
  • Not attempt to attach itself to another program. This is because the EXEC system call (INT 21h, AH=4Bh) is completely different and partially incompatible with other versions of DOS.
  • Not use the handles to standard in, standard out and standard error, as well as the first serial and parallel ports (COM1 and LPT1 respectively), normally found within the Job File Table of the Program Segment Prefix (PSP), the process information structure, of legacy MS-DOS applications. These are all NULL in Multitasking DOS 4 and therefore all attempts to access them will lead to reads to and writes from null, and the program crashing.
  • Not write directly to the disk. This will cause User Error DAF3 – "System cannot write directly to disk."; this, like the TSR restriction, can be negated using a PIF file for the application.
  • Not be a mixed (or "family" mode) DOS/NE executable, of any type – even the "This program requires Microsoft Windows" / "DOS mode" stub does not work. The DOS 4 binary loader cannot deal with these applications (the programs built-in with DOS 4 do not have stubs) and the EXEC system call will fail, leading to COMMAND.COM issuing a EXEC failure message.
  • Not be dependent on the MS-DOS SYSVARS table, which has changed from other DOS versions (normally, newer versions of MS-DOS); notably, there are extra device pointers (for the new included devices), and the pointer to the CDS (current directory structure) structure (usually located at 1Ah) is always set to NULL; this may be due to how a program's current directory is represented being completely rewritten (it appears to be stored per-process in each process' Per-Task Data Area) and therefore not requiring a position in the SYSVARS table.
  • Not be dependent on the INT 21h, AH=58h "Set Allocation Strategy" function (which allows the user to modify how MS-DOS allocates memory blocks – a first-fit, best-fit and last-fit, which is basically first-fit but starting from high memory and not low, can be used) – in Multitasking MS-DOS 4, this function is stubbed and its entry in the kernel syscall dispatch table simply points to the global "invalid function" code; this is most likely done as the operating system is designed to handle the memory allocation strategy "on its own".
  • Not be dependent on the SYSVARS table's pointer to the CDS (Current Drive Structure) internal system structure; this has been replaced with a per-process pointer in each task's PTDA, and the system-wide pointer is replaced with a null pointer (any attempt to access it causes a null pointer to dereference)

These restrictions mean that regular DOS apps that depend on internal DOS structures, most DOS extender apps (such as later versions of IDA for DOS and many DOS apps made during and after the early 1990s), 16-bit Windows, and the Undocumented DOS tools, do not work (with either an EXEC failure message being printed to the screen, garbage being printed instead of their intended output, nothing happening, the program exiting immediately, a system crash, or a User Error message being issued). Additionally, old apps, in particular IDA 0.1, that are not aware of written to "cooperate" with Multitasking DOS R are known to corrupt system structures, which may lead to a crash.

There are also subtle differences with how some other MS-DOS API calls, such as INT 21H,AH=25h to set interrupt vectors – which no longer directly sets interrupt vectors – and the undocumented functions at INT 21H,AH=1Fh (get pointer to current drive parameter table) and INT 21H,AH=32h (get pointer to arbitrary drive parameter table), which directly copy the BIOS Parameter Block (a data structure on legacy BIOS based systems that describes the layout of a volume) into the memory address represented by the register pair DS:BX, instead of returning a pointer to DOS's copy of that structure using the register pair DS:BX. Additionally, the INT 12H BIOS API (which returns the amount of conventional memory in the system) is trapped by the operating system and modified to only return the amount of memory provided by the MEMSET command; these changes further worsen application compatibility.

Due to all of these factors, application compatibility between older (and newer) versions of MS-DOS and Multitasking MS-DOS 4.0, due to the fact that large amounts of the operating system were rewritten, is very poor, and many apps have severe issues that prevent their use, refuse to start at all, or even crash the operating system (either causing an Internal Error, or simply freezing – in both cases this essentially means a hard reset).

Tested applications[edit | edit source]

Microsoft C[edit | edit source]

Microsoft C 3.0 (the operating system appears to be itself compiled with some version of Microsoft C 3.0, with the C Library string having a 1985 copyright) produces working object files that can be linked using LINK4.EXE. Requires MEMSET DEFAULT=256 or more to increase the amount of foreground memory allocated to "old" apps. Requires header files (that are not provided) for MDOS functionality, as well as for DOSCALLS.LIB to be extracted from the kernel using a library manager. Basic apps have been compiled, but DOS 4 specific functionality has not successfully been used and there are issues with instability in the built-in libraries and headers. For more complex functionality, it can be attempted to set the size of the stack to 46000 bytes or larger as a workaround for unknown memory management issues. However, this stack segment will be permanently leaked and after several runs, no more applications can be launched. Interestingly, at least trivial apps compiled for Multitasking DOS 4 appear to not cause an error message and not crash the system when run on Windows 1.0.

Retail Microsoft C 4.0 is not compatible with the version of LINK4.EXE provided by the operating system and will not produce object files compatible with it (and even if it was, DOS 4's LINK4.EXE cannot link against the provided MSC 4.0 libraries). The version of LINK4.EXE on Disk 8 (which is listed as being intended to compile for Windows 1.0) must be used to produce functioning executables at all, and even then a EXEC failure message is produced upon trying to run the program.

Macro Assembler (MASM)[edit | edit source]

MASM 4.0 works as long as an Automatic Data Group segment of 512 bytes is declared in the application's code, which is required by the New Executable spec.

Interactive Disassembler (IDA)[edit | edit source]

IDA 0.1 appears to work and can be used for disassembly but is very slow (due to being written long after this OS). It may also corrupt system structures leading to Internal Errors and system instability. IDA 2.8 does not work (EXEC failure), as it uses DOS extenders.

"Legacy" MS-DOS built in utilities[edit | edit source]

DEBUG.EXE from MS-DOS 3.21 partially works, but it can only attach to legacy DOS apps (not New Executable apps, which cause DEBUG to issue a EXEC failure message) cannot be used to trace through the OS's INT 21h handler for unknown reasons (it somehow ends up returning to the wrong location after the initial processing, possibly due to DEBUG.EXE somehow interfering with the OS scheduler), and may at any time pin 100% of the CPU, leading to extreme system slowdown.

CHOICE.COM from MS-DOS 6.0 works. The "country-specific file name" call that requires "normal" MS-DOS 4.0 seems to not inhibit the operation of the program.

Symbolic Debugging Utility (SYMDEB)[edit | edit source]

The Microsoft Symbolic Debugging Utility, used for debugging early versions of Windows and a predecessor of the Windows 3.0 and Windows 3.1 enhanced mode, as well as Windows 9x, debugger, WDEB386.EXE, works except when attached to another program. Attempting to attach to another app will completely freeze the OS, requiring a hard reset.

Microsoft Windows[edit | edit source]

There were plans to have Windows 1.0 running as a GUI layer on top of this operating system, with Windows using the preemptive scheduler of multitasking MS-DOS instead of its original cooperative scheduler. The release of the combined environment ("Win+4") was scheduled for Q2 1986, but it was eventually cancelled, likely due to the signing of the Joint Development Agreement between Microsoft and IBM to develop what would ultimately become OS/2.

No version of Windows is known to run on build 6.7 of Multitasking MS-DOS (the 17 November 1986 build patches KERNEL.EXE, and possibly other parts of the system during initialization in order to remove incompatibilities), as most versions fail at boot with "Cannot find Windows startup files." or a similar error message. This is caused by DOS not recognizing the Windows kernel (KERNEL.EXE), after it is loaded from (WIN100.BIN) as a valid executable file when WIN.COM tries to run it using the EXEC system call; this initially happens because according to the version of the New Executable specification understood by build 6.7 of Multitasking DOS 4.0, the flag word in the NE header set by the Windows kernel indicates it to be a dynamic link library (which were not distinguished by file extension until later builds of OS/2 1.0) being executed as if it was a executable file, which causes the DOS EXEC function to refuse to load it; if this is patched, binary loading still fails for presently unknown reason. Since WIN.COM does not account for the EXEC system call failing for this reason, it ends up triggering a code path that prints out a misleading error message.

However, even if the 1985 builds of Multitasking DOS 4.0 were to recognize the Windows kernel as a valid binary, the kernel immediately terminates if it detects a DOS major revision above 3 (or below 2). This is why, on later versions of MS-DOS, SETVER is required to run these versions of Windows; forcing the kernel to run anyways results in the system crashing (with garbage being printed to the screen) early in KERNEL initialization, pointing to serious, OS-level incompatibility between Windows and Multitasking DOS 4.0. This, as well as the description of the kernel NE being Microsoft Windows Kernel Environment for 2.x and 3.x, as well as the function that calls int 21h being explicitly labelled DOS3CALL (as well as commit logs from 1986 in the Windows NT 3.5 build 782 source code referencing support for DOS 5 and 286DOS, aka OS/2), may indicate that support for Multitasking DOS 4 on Windows was set by some kind of compile option.

A version of Windows 1.03 was bundled with the ICL DRS Professional Workstation alongside multitasking MS-DOS 4.10, which suggests that the EXEC system call was fixed in the later revision to allow Windows to run.

Larry Osterman, in a 2004 blog post, recollected why KERNEL.EXE patches were required for Windows 1.0 to work:

As a simple example, when Windows started up, it increased the size of MS-DOS’s internal file table (the SFT, that’s the table that was created by the FILES= line in config.sys). It did that to allow more than 20 files to be opened on the windows system (a highly desirable goal for a multi-tasking operating system). But it did that by using an undocumented API call, which returned a pointer to a set of "interesting" pointers in MS-DOS. It then indexed a known offset relative to that pointer, and replaced the value of the master SFT table with its own version of the SFT. When I was working on MS-DOS 4.0, we needed to support Windows. Well, it was relatively easy to guarantee that our SFT was at the location that Windows was expecting. But the problem was that the MS-DOS 4.0 SFT was 2 bytes larger than the MS-DOS 3.1 SFT. In order to get Windows to work, I had to change the DOS loader to detect when win.com was being loaded, and if it was being loaded, I looked at the code at an offset relative to the base code segment, and if it was a “MOV” instruction, and the amount being moved was the old size of the SFT, I patched the instruction in memory to reflect the new size of the SFT! Yup, MS-DOS 4.0 patched the running windows binary to make sure Windows would still continue to work.

— AARDvarks in your code. blog post[10]

Undocumented DOS, 2nd Edition developer tools[edit | edit source]

Nearly all of these tools (except the INTRVIEW.EXE tool, used to view Ralf Brown's Interrupt List, which requires MEMSET DEFAULT=640 to run) do not run correctly – they crash the system, hog the CPU, print garbage, loop endlessly, or freeze halfway through and must be terminated. All of the provided TSRs in the toolkit fail to install with User Error 983F, as TSR functionality is disabled by default.

Additionally, the operating system is not recognized as MS-DOS by the MSDETECT.EXE tool, which recreates Microsoft's AARD test code to determine if it is running on "real" MS-DOS or a clone of it.

QEdit[edit | edit source]

QEdit 3.0 works perfectly, except for an issue where the state of the NumLock key is somehow tied to the state of the Shift key (when Shift is set, NumLock is off, and vice versa); the tool can still be used for editing, with minor complications.

Provided utilities[edit | edit source]

Multitasking MS-DOS 4.0 comes with numerous utility applications. Many of these are very unique and are not included with any other version of MS-DOS or OS/2; only utilities that are unique to Multitasking DOS or have other noteworthy changes will be covered in this section.

Standard changes[edit | edit source]

  • APPEND, ASSIGN, ATTRIB, JOIN, LABEL, PRINT, and SUBST have been rewritten in C and are native New Executables intended for Multitasking DOS 4.0.
  • FDISK is a special case – while it identifies as "Version 0.01", is clearly a new codebase, and has been rewritten in C like most of the other utilities, it is not a New Executable and has been compiled as a regular DOS binary.

Included in build 6.7[edit | edit source]

pifedit.exe (PIF Editor)[edit | edit source]

A text-mode version of the PIF Editor often included with 16-bit Windows installations; it can either be provided a PIF file as a command-line argument, where it will enter a screen where settings can be modified, or it can be provided a /a parameter where several more advanced settings, such as the program having the ability to directly write to disk, using the "old" signal system, and being allowed to terminate and stay resident. It also supports batch operations with the /h option. Interestingly, unlike the Windows PIF editor (which is a native Windows application); the PIF Editor included in Multitasking DOS 4.0 is a legacy MS-DOS application (although written in C like almost all of the new applications written for Multitasking DOS 4) and in fact is explicitly allowed to run on any version of regular MS-DOS from 2.0 on upwards.

swapper.exe (Swapper Daemon)[edit | edit source]

In theory, allows swapping to disk; processes that are presently swapped to disk PS.EXE show a status of [ Swapped ] after reading from an app's PTDA, but in practice (despite the existence of a SWAPPER.DOC file instructing how to start the swapper), the swapper cannot work. This is because it calls the int 21h,AH=94h ("init swapping?") API, which while being defined as a valid INT 21 API, it points to the "invalid function" code, so the function will always fail with DOS error code 01h ("Invalid function"), so the swapper will remove its signal handlers and terminate.

queuer.exe (Print Queuer)[edit | edit source]

A utility that implements a print queue; it has currently not been coaxed into running, but it uses shared memory areas.

Only included in Goupil OEM version[edit | edit source]

ps.exe (Process Status)[edit | edit source]

A Unix-style "Process Status" utility (in fact, it may be a direct port from XENIX or some other Unix distribution, as it exclusively uses Unix-style command-line switches (-) and does not even recognize MS-DOS style ones (/).

By default, only process ID, process status (which can be Run (currently running), Ready (ready to run), Wait, processes that are waiting for a scheduler timeslice, Froze, usually for "old" processes that are not swapped in, Dead, for processes that have crashed, and [ Swapped ] , used for processes currently swapped to disk), and process name are shown for every process running on the system except the POPUP.EXE DLL, which runs at all times and is started by the kernel's boot code (with a static Process ID of 0).

Command-line options[edit | edit source]
Caption
Option Result
-S Detach PS.EXE; run fullscreen and take exclusive use of the current console device.
-s Adds timeslice information (Slice), process priority (Pri), "Bmod", "Upri", "Use", and "Deny" (currently unknown), screen number (Scrn), and tics use, TicsUsed, to every process.
-l Adds child process ID, Csid (unknown, child process ID?), priority (again), screen group (as opposed to screen number – screen number is the number of console devices, screen group is a logical kernel concept), segment address of the processes' PTDA, command-line (Cmd) and Misc (unknown).
-t Seems to provide all command-line options provided to an app, instead of just the first one; requires -s or -t to have any visual effect.
-q Seems to have something to do with printing the process type (new or old), but no visual effect has been identified; definitely implemented in the code.

dossize.exe (DOS Size)[edit | edit source]

Prints the amount of memory currently taken up by DOS and running programs, across various parts of the system, how much space is free, and how much total RAM exists in the system.

dossize.exe categories
Category Purpose
"BIOS size" Amount of space taken up by the bootloader and drivers contained within IBMBIO.COM.
"DOS size" Amount of space taken up by the kernel.
"DOS tables" Amount of space taken up by the kernel's internal system structures, such as the scheduler and list of lists (which has a somewhat non-standard layout).
"Old exe program space" Amount of space taken up by non-native NE binaries; this is usually equivalent to the memory reserved for them using the MEMSET command.
"New exe program space" Amount of space taken up by all native MDOS4 apps running on the system.
"Free space size" The amount of space free on the system.
"Total space size" The total amount of system memory.

Note that the total amount of system memory cannot exceed 640 kilobytes due to the system's real-mode nature and the fact that 360 kilobytes of the memory map is taken up by various peripherals.


Bugs[edit | edit source]

  • The system will sometimes search all drives for files, even if they have no disk inserted. This will sometimes lead to "Sector not found" errors from the Hard Error Daemon.
  • The MKDIR command will sometimes print "Unable to create directory" even if the directory was successfully created.
  • Very rarely, when switching drives, the system may maintain the current directory, even if it does not exist on the current disk.
  • Trying to DETACH COMMAND.COM will lead to endless Ctrl + Z's being issued by the new COMMAND.COM instance, as well as the Process ID of COMMAND being printed.
  • Although autoexec.bat is run for each new COMMAND.COM instance created (and this can have effects on system-wide settings such as the old app memory partition set by the MEMSET command), there is no visual indication of this.

Gallery[edit | edit source]

Notes[edit | edit source]

  1. Sometimes shortened to MT-DOS or M/T-MSDOS in internal documentation.

References[edit | edit source]

  1. 1.0 1.1 1.2 Osterman, Larry. Did you know that OS/2 wasn't Microsoft's first non-Unix multi-tasking operating system?, Larry Osterman's WebLog. 22 March 2004.
  2. 2.0 2.1 Letwin, Gordon. Inside OS/2 (pp. 11).
  3. 3.0 3.1 Osterman, Larry. 24 years ago today (1985), Larry Osterman's WebLog. 26 August 2009.
  4. 4.0 4.1 Osterman, Larry. "1986: MS-DOS 4 forked into OS/2, but other customers were left behind who needed support. One of those was Goupil in France, so while the rest of MSFT was shut down moving to a new campus, a co-worker and I traveled to Paris for a weekend to fix bugs for Goupil." – via Twitter. 28 August 2020.
  5. 5.0 5.1 MS-DOS Version 4.10 Fujitsu ICL OEM, http://www.16BitOS.com. Archived from the original on 6 October 2020.
  6. 6.0 6.1 ICL DRS PWS M80 c1988, The ICL Computer Museum.
  7. Brown, Ralf. x86/MS-DOS Interrupt List, Release 61.
  8. https://groups.google.com/g/comp.os.cpm/c/-1n4L4gT3dg/m/ulXs38XPlw4J
  9. 9.0 9.1 Williams, Dave. Programmer's Technical Reference for MSDOS and the IBM PC (Version 3.3) (ch. 4, sec. 11). 20 January 1994.
  10. Osterman, Larry. AARDvarks in your code., Larry Osterman's WebLog. 12 August 2004.