24

Common wisdom seems to be that MS-DOS was an exclusively single-threaded OS. Of course, implementing a scheduler would be possible, but the OS itself did not provide any facilities for multithreading, at least to my knowledge.

For this reason, I was surprised while perusing the manual for the v5.1 MS C compiler:

7.2.2 Re-entrant Functions

The existing run-time library is designed primarily for single-thread execution. Most of the functions are not re-entrant, and therefore cannot be executed by more than one thread at a time. The functions below are re-entrant and therefore may be used in multi-thread programs:

(Microsoft C Optimizing Compiler for MS® OS/2 and MS-DOS® Operating Systems Version 5.1 Update, Page 26)

The table below contains a list of functions and the minor changes made to them.

Table 7.1
Minor Changes to Functions

Function Change
exit, _exit Terminates all threads of the calling program.

(idem, pg 27)

This seems to imply that there was at least some common framework for multithreading considered in the design of MS-DOS. I guess making (some of) the functions re-entrant is just a matter of ensuring they are not accessing any shared data, but the library would need to be aware of the threads in order to terminate them, wouldn't it? The documentation does not reference DOS 4.0 (multitasking), but instead it says it's targetting DOS 3.x (and OS/2):

Welcome to the Microsoft® C Optimizing Compiler Version 5.1 Update. This update discusses new features of the C language, in particular the ability to program within OS/2 systems. The pages that follow use the term “OS/2” to refer to both Microsoft Operating System/2 (MS® OS/2) and IBM® OS/2. Similarly, the term “DOS” is used to refer to both MS-DOS® and IBM Personal Computer DOS.

Here are the major new features of Version 5.1:

  • OS/2 support. Version 5.1 includes new libraries, language features, and enhanced development tools that allow you to write programs for OS/2 protected mode as well as the DOS 3.x (OS/2 real-mode) environment.

(idem, pg 1)

So my question is, did the regular (non-multitasking) DOS have any multithreaded facilities, or not?

9
  • 6
    This usually indicates that variables are not placed in an area local to the current context (whatever that might be) but instead as globals. Consider it a warning to the experienced developer using the compiler, not an indication of the underlying operating system. Commented Mar 17, 2023 at 15:04
  • 11
    OS/2 had threads (though they are not what threads are e.g. in Linux), so I'd assume those run-time library features were based on OS/2.
    – dirkt
    Commented Mar 17, 2023 at 15:05
  • 12
    Armed with only a compiler, it is possible to implement threads. I have done so in the past, though not on DOS. It's therefore useful to know when your runtime library is non-reentrant.
    – dave
    Commented Mar 17, 2023 at 17:04
  • 3
    @tofro - see mention of 'thread' in this 1966 tech report from Multics work. It's true that at least in this report it was mentioned in the sense of 'process', but the term was apparently known.
    – dave
    Commented Mar 18, 2023 at 1:56
  • 7
    Re-entrancy is important for interrupt service routines (that could be considered a form of multithreading in a very limited way).
    – Frog
    Commented Mar 18, 2023 at 21:09

7 Answers 7

40

The fact that a C library functions were made to support re-entrancy has got nothing to do with DOS. Or any other OS for that matter. It may be needed when doing bare metal programming without any OS, or writing the OS itself.

DOS itself has no support for multitasking.

Any reference to threads or having a OS or filesystem function like mkdir() supporting re-entrancy are OS/2 only features that are not available on DOS.

However a DOS program (or a game) may need support for re-entrant functions such as strcpy or memset. The program may run in a main loop doing some things, and it may set up a timer interrupt routine to do some other things periodically regardless of what the main loop was executing.

It means that the main code can be interrupted while it was executing memset and the interrupt code can also call memset. That is not possible unless memset is made re-entrant.

Same applies if you write a time-slicing multitasking program with the C compiler, that switches the executed task in a timer interrupt between multiple tasks. You need re-entrant C library functions.

in DOS, the C library is statically linked into the program executable, so even if you made a TSR that runs in the background, and a program that runs as the main program, they would each have their own copy of the library and thus they are separate instances so the re-entrancy is not a problem between two programs.

8
  • What about the newly-advertised ability of exit() to "terminate all threads"? Is it just a side effect of returning control to the (single-threaded) OS with 4c.21, from where it's guaranteed control will never return to the multithreaded program's code? Commented Mar 17, 2023 at 21:43
  • 5
    What newly-advertized, where? A DOS libc is likely unable to start threads anyway unless it supports that without DOS, and if it does support multiple threads, it likely supports terminating all threads regardless of how the program exits, because DOS surely doesn't.
    – Justme
    Commented Mar 17, 2023 at 22:35
  • 1
    @Justme Table 7.1 in the question above. Commented Mar 18, 2023 at 7:11
  • 6
    The compiler supports OS/2 which supports threads. So anything about threads being created or killed or handling filesystem function like mkdir means OS/2. Not about DOS.
    – Justme
    Commented Mar 18, 2023 at 8:46
  • 1
    There was no standard for threading in 1988 C compiler. The compiler simply had wrapper functions for OS/2 system calls to create threads and terminating the program is also a system call which terminated the threads as well.
    – Justme
    Commented Mar 18, 2023 at 22:08
18

Short answer: No.

Most likely, the mentions of threads are there because, as you mentioned in your question, this version of the compiler supported OS/2, which supported multithreading.

On the other hand, the MS-DOS API was just a suggestion. With no built-in memory protection or security mechanisms, programs had unrestricted direct access to the underlying hardware. This provided a path for third-party applications to provide “operating system” level features like memory management or the various low-level I/O things that Norton Utilities did. So you could implement some type of multitasking system on top of MS-DOS. TSRs were a popular (albeit limited) way of doing this.

But the most popular multitasking environment for MS-DOS was called Microsoft Windows. Today, we think of Windows as a separate operating system, but in its original form, it was just a shell that ran on top of DOS, providing an environment for Windows-specific programs with GUI support, and cooperative multitasking.

9
  • 1
    You can't really say "you could implement some type of multitasking system on top of MS-DOS" without mentioning the most widely used multitasking system on top of MS-DOS i.e. Windows (before Windows 95 / Windows NT).
    – Peteris
    Commented Mar 17, 2023 at 23:30
  • 2
    @Joshua yes, it used ooperative multitasking, but I'm not sure how how that relates to my point.
    – PC Luddite
    Commented Mar 18, 2023 at 5:57
  • 2
    @Joshua I don’t think threads have to be preëmptively scheduled by definition, especially not by a definition that had been used back in the 1980s. Commented Mar 18, 2023 at 13:57
  • 2
    @user3840170: In this specific case (asking about re-entrant functions that don't have callbacks back to user code) cooperative multitasking is equivalent to single threaded.
    – Joshua
    Commented Mar 18, 2023 at 23:45
  • 2
    Although there's actually more to it. Win3.1 scheduler is preemptive of itself, and if you started multiple 32 bit processes it could preempt between them; however you could not start more than one thread per process. And if you have Win32s you can run some Win32 GUI processes. One of the API calls that doesn't work is CreateThread. No multi-threaded processes for you.
    – Joshua
    Commented Mar 19, 2023 at 0:29
14

TL;DR: No ...

DOS did not provide any measures for concurrent threads.

Anything presenting parallel tasks was implemented on top. Either by Windows, or by some run time environment like possibly provided by above C compiler.


But ...

DOS did support a very limited level of concurrent programming in form of background programs called TSR (Terminate and Say Resident) programs(*1). Since they are may be activated asynchronous, they need to have a way to detect if they interrupted DOS activity, thus not being wise to issue any DOS call, or not

Starting with 2.0 DOS provided were two Flags

  • InDOS Flag, a byte being non zero when inside a non-reentrant section and
  • Critical Error Flag, a byte holding a non zero value while 'processing' a critical error.

Both are present since DOS 2.0 and both had to be Zero, otherwise some kind of exception handling (usually waiting) had to be done.

Access to the InDOS flag is given via INT 21h Function 34h Return Address of InDOS Flag, introduced with 2.0 and stable since. The importance of the Critical Error Flag was only recognized with Version 3.0, when it was placed on the byte thereafter. For DOS 2.x some wiggling was recommended (or looking at Ralf Browns List :))

The whole workings are described in detail for example in the MS-DOS Encyclopedia of 1988 on page 353..357.


For any scheduler on top of DOS this simply means that a thread switch should only happen when both flags are zero. If not, it needs to wait until DOS has finished whatever it was doing. So either extend the time slice, or hooking the exit to postpone the thread switch until then.

Independent of that, any compiler creating parallel code would have better made sure that its code, either direct or via libraries, avoids doing parallel DOS calls as a general measure. The usual solution was to let user code not do any direct DOS calls, but only call its library, which in turn would nicely prepare extended time slices ahead of calling DOS as well as inserting schedule points afterwards.


*1 - And Critical Error Handlers

8
  • 1
    FWIW, this C compiler's runtime does not implement parallel tasks either, it just mentions the concept in the documentation. Commented Mar 17, 2023 at 15:18
  • 1
    I agree. The quoted text should be read as "the library has no support for multithreading, even when used on a platform that implements it". I'd add that a lot of what we know about the In-DOS flag etc. has emerged due to analysis well after DOS became obsolescent: when it was a live product MS was extremely aggressive when it came to refusing to discuss- let alone support- this sort of thing even if it was mentioned in a book from MS Press. Commented Mar 18, 2023 at 7:02
  • 2
    @MarkMorganLloyd While MS was secretive about many internals, the In-DOS flag was never one of them. They published meaning and details of the Flag starting right with their DOS 2.0 programming documentation as well as in later books. The 1988 MS-DOS Encyclopedia (the go-to source for everything DOS) for example describes locating and using of both flags (and what stack logic uses) on p.353-357. After all, without those no TSR or critical error handler could be programmed. Support for In-DOS was stable since 2.0 all the way to 7.0 (Critical Error Flag since 3.0).
    – Raffzahn
    Commented Mar 18, 2023 at 14:14
  • @Raffzahn I was supporting this stuff, and talking direct to MS on a fairly regular basis. As I have said, they were extremely aggressive about disowning any technical information published by MS Press, insisting that it was a completely separate company. My recollection is that MS, themselves, did not make info about InDos etc. public, i.e. to anybody except card-carrying OEMs. It's easy to be wise after the event, but those of us labouring at the coalface frequently got our information by disassembling and tracing execution... I learnt a lot by ripping an IBM realtime kernel to pieces. Commented Mar 18, 2023 at 19:34
  • @MarkMorganLloyd Of course we all did our part of disassembling. Except Information about INDOS I get read from MS documentation provided for for DOS 2.11, the first I did write some background stuff for - that's predating even the very first MS-Press books (which were all about the Mac). Don't have that docs anymore, so can't tell how secretive they were. I'm not sure what kind of distinction you want to create. Microsoft Press is an imprint by Microsoft. Not an independent company of any kind. As a customer I do not really care what department of MS provides me with that information.
    – Raffzahn
    Commented Mar 18, 2023 at 20:13
3

Actually, yes, MS-DOS did have some facilities for running multi-threaded programs. Other people have mentioned TSRs taking advantage of these. MS-DOS supported multiple stacks internally, and could run up to 2 threads of system-level execution (on separate stacks). This was used by networking extensions, such as microsoft's "net share" capability, along with 3rd party networking programs like Lantastic and NetWare Lite. The MS-DOS kernel supported hooks that a TSR could register with it, to get scheduled on interrupts and scheduling actions. I am not aware of any "regular" programs (non-TSRs) that utilized these, but it was theoretically possible.

To utilize the multi-threaded features, you had to interface with the kernel very tightly, interacting with specific memory locations and call sites, where the control interfaces for the multi-threading existed. There was an excellent reference book that described the API for this, called "Undocumented DOS". I don't know if it's still available. See https://archive.org/details/undocdos2ed. (I was one of the authors of NetWare Lite, which used the multi-threaded capabilities of MS-DOS for its network server TSR.)

1
  • I think, to be more specific, you are talking about Swappable Data Area (SDA), the Get/Set Current PSP functions, Int28, etc – is that right? Commented Jun 27, 2023 at 0:35
3

exit, _exit Terminates all threads of the calling program.

Well, given that the manual you are citing, was called "Microsoft C Optimizing Compiler for MS® OS/2 and MS-DOS®", no wonder it was mentioning threads. OS/2 had threads, and these C functions were supposed to be portable. They were documented similar for OS/2 and DOS, with "threads" obviously applying to OS/2 only.

So my question is, did the regular (non-multitasking) DOS have any multithreaded facilities, or not?

It didn't have a scheduler, but it provided some means (separate stacks, InDOS flag) to write one. But that has nothing to do with the aforementioned C functions.

2

The question isn't really well-posed because DOS is not the kind of operating system that provides such facilities. It's a glorified program loader with minimal filesystem/IO handling facilities. If you wanted to do multi-threaded programming on DOS, it would be perfectly possible and reasonable to do so, either with explicit/cooperative task switching or by receiving the timer interrupt yourself and doing preemptive task switching.

The DOS software interrupt based facilities are not necessarily (and not) reentrant, so if you were doing a preemptive scheduler, you'd want to suspend the timer interrupt handler around calls to these facilities, or use a global lock so that only one thread could be using them at a time. But it was (and still is) totally possible to do this.

1

Even Windows didn't provide preemptive multi-tasking until Windows 95.

Most multitasking in this era is cooperative, essentially just sticking multiple programs tasks in the same overall event loop.

As other answers mention about the closest thing to other threads or processes running simultaneously is interrupt code. A TSR (Terminate and Stay Resident) like a driver could leave some of its code resident in memory and use it for interrupt handlers. This is how many device drivers worked but could also be used for purely-software background tasks if a timer was used to generate the interrupt. If that code used a DOS API for something it was much more likely to need worry about reentrancy for any output from the function's TEXT section for example. Especially since MMUs were barely a thing in DOS.

Essentially modern preemptive multitasking is based on the same kind of timer interrupt.

8
  • 7
    You don’t need a re-entrant C library on a multitasking system; it’s only relevant for multithreaded programs. Commented Mar 17, 2023 at 23:45
  • 2
    @PCLuddite not quite, DOS tasks were pre-emptively multi-tasked even under Windows 3. Commented Mar 18, 2023 at 6:18
  • 3
    @StephenKitt Re-entrancy is required whenever a task can be interrupted by anything running in the same memory space. For example, Unix signal handlers have always been restricted in what C library functions they can use because they share memory with the process.
    – JeremyP
    Commented Mar 18, 2023 at 10:20
  • 3
    @JeremyP yes, but that’s not tied to multitasking. Ah, I get what you’re saying — my first comment above isn’t comprehensive enough, yes; interrupt handlers etc. also need to be careful. Commented Mar 18, 2023 at 10:23
  • 2
    essentially just sticking multiple programs tasks in the same overall event loop not quite. I have just converted my embedded scheduler from an event dispatcher to cooperative multithreading, and the main advantage of doing this is that threads can suspend in arbitrary places by calling the scheduler, whereas dispatched handlers have to return. The disadvantage is I need multiple stacks and memory is very tight.
    – Rodney
    Commented Mar 20, 2023 at 12:21

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .