92

Windows in its earliest days was simply a shell that ran on top of MS-DOS, which means that Windows 3.1 itself was actually just a standard MS-DOS application like any other.

Yet, MS-DOS is not a multitasking operating system, and at the same time, Windows applications were compiled native-code binaries that ran without any form of managed environment. So, how exactly was multitasking of Windows binaries achieved if Windows 3.1 was simply a regular old MS-DOS program itself? Are there any old technical documents still floating around that describe the early Windows architecture internally?

Ola Ström
  • 327
  • 1
  • 2
  • 9
Mike Nielsen
  • 2,897
  • 3
  • 14
  • 11
  • 23
    Not entirely true. Windows 3.1 would switch the processor to protected mode, which allowed memory segmentation and other goodies that helped manage a multi-process environment. It was not simply a "MS-DOS application like any other." (Thus, it required at least an 80286 processor for those features) – Joe Jun 07 '16 at 23:43
  • 1
    What @Joe said. It could actually multitask, just not in the way modern computers implement multitasking. – Retro Gamer Jun 07 '16 at 23:56
  • 7
    Joe, windows 2.x and 1.x don't switch to protected mode. Even on windows 3.0 was "optional". – Zardoz89 Jun 08 '16 at 05:22
  • 12
    @Joe Windows actually emulated "protected mode" if it wasn't available. It only really went "hardware protected" with preëmptive multi-tasking on a 386 CPU (not 286), and even then, it consisted of multiple virtual machines, some of which were "real" (for MS DOS applications) and some of which were "protected". Depending on your hardware and software configuration, Windows came all the way from "a thin GUI client on top of DOS" to "a full blown preëmptively multi-tasked OS that just happens to be started from a DOS loader instead of a bootloader". – Luaan Jun 08 '16 at 08:57
  • 12
    Raymond Chen has written a lot about how old versions of Windows worked on his blog, The Old New Thing. I recommend a perusal of the history tag on The Old New Thing. – user Jun 08 '16 at 13:37
  • 3
    @RetroGamer Not only "modern" computers. For example, the Amiga OS used pre-emptive multitasking before even Windows 1.0 existed, and I'm sure there were at least one other home computer with a similar feature. Not to mention the big UNIX guys of course.. – pipe Jun 08 '16 at 20:18
  • 5
    MS-DOS doesn't help programs multitask, but why do you think MS-DOS programs can't have their own multitasking? – user253751 Jun 08 '16 at 23:07
  • 7
    Interestingly, TopView and DESQview went even above and beyond Windows and had full preemptive multitasking for (well behaved) DOS programs! The lack of memory protection (especially the interrupt table) meant that errant programs could wreak havoc. However, there's nothing that stops multitasking under DOS. There's just a lack of things to make it easy. – Brian Knoblauch Jun 11 '16 at 17:57
  • 5
    @BrianKnoblauch Indeed. In fact there was also Concurrent DOS, which allowed multitasking and multiuser options compatible with standard DOS programs. link – mnem Jun 12 '16 at 22:24
  • 2
    DOS itself was single-tasking on paper, but it did have support for TSR programs, that would stay in the background until triggered by a hardware interrupt. MS-DOS version 4.0 supported full preemptive multitasking. And now you can use Real-Time Multitasking Kernel for DOS which give you complete control over MT. – Overmind Feb 03 '17 at 06:37
  • 3
    @pipe just off the top of my head, 1983's Apple Lisa had both protected memory and preemptive multitasking, and 1984's Sinclair QL had preemptive multitasking, both before the Amiga and correspondingly before Windows 1.0. So it had definitely started filtering down into micros at both ends of the price spectrum before Microsoft had tried any approach. – Tommy Dec 04 '17 at 19:33
  • 2
    One word: "poorly". – Mark Dec 04 '17 at 19:53
  • You should not think of Windows as a DOS-program, but MS-DOS as a Windows-bootstrap program. – Thorbjørn Ravn Andersen Jun 30 '19 at 16:23
  • @Tommy: Did the Lisa really have both of those things? By my understanding, the Lisa was in every way except RAM capacity and screen pixel width less powerful than the original Macintosh, which couldn't preemptively multitask Macintosh applications until the late 1980s or early 1990s. – supercat Sep 27 '20 at 19:09
  • @supercat on further review, it seems to be borderline. On preemptive multitasking, this documentation covers processes, skip to the bottom of page 35 for the ways a process may be suspended; none is time-based but the '... causes code to be swapped or its stack to be expanded' is at least implicit. On protected memory Page 49 covers 'private data segments' after some brief discussion of the MMU. – Tommy Sep 28 '20 at 16:25
  • @Tommy: The Macintosh XL was essentially a rebranded Lisa adapted to run Mac OS; I don't think the Mac OS would make any use of an MMU when running on a Macintosh XL, but I could easily imagine that Mac OS might just set up the MMU in an "allow access to everything state", but Lisa OS would configure it to actually provide protection. – supercat Sep 28 '20 at 16:27
  • @Tommy: Even if the Lisa did have protected memory, I don't know that it would be capable of doing anything other than terminating a program that attempts out-of-bounds access. Memory paging would require the ability to suspend the execution of an instruction, retrieve data from the paging store, and resume execution--something that prior to the 68010 could only be done by having a second CPU (so the CPU which tried to load memory which was unavailable would be frozen while the other CPU fetched the required data, whereupon the first CPU could resume execution). – supercat Sep 28 '20 at 16:31
  • @supercat yeah, I don't think the source I found substantiates the claim. I'm going to look further into it. Also I don't think it's the Lisa, but I think you can actually do real memory paging on a vanilla 68000 if you restrict the instructions permitted for accessing memory banks and force the processor unnaturally into a world of near and far jumps, in order to force the programmer into using only instructions from which recovery is possible upon an exception. That's getting kind of semantic though; it's getting quite a distance from natural 68000 code and towards a software implementation. – Tommy Sep 28 '20 at 17:14
  • 1
    @Tommy: I wonder if it would have been practical to use some latches, a counter, and a little control logic to capture a memory-fault address as well as the last user-mode first-instruction-word code fetch, as well as a count of how many cycles had elapsed since that code fetch? A page fault within a multi-word load or store could be handled by letting the instruction run to completion (while either fetching from a dummy address or storing data to nothing) before going to a trap handler, and then having the trap handler use the address of the instruction and the cycle count... – supercat Sep 28 '20 at 17:32
  • ...to infer how many words had been transferred before the fault, and thus what would need to happen to unwind the operation. – supercat Sep 28 '20 at 17:33
  • Regarding your question about documentation that explains the old Windows versions. You could read Charles Petzold's books. E.g. "Programming Windows 3.0", "Programming Windows 3.1" or "Programming Windows 95" from Microsoft Press. – Coder Mar 25 '24 at 00:10

3 Answers3

91

For Win16 programs, Windows implemented co-operative multitasking. Its implementation was based upon the "message loop" architecture of every Windows program.

The duty of every program was to endlessly run in a loop in which a call to the GetMessage function was performed. This function call looks whether a message to this process is in the queue. If there is one, it is retrieved (GetMessage), optionally translated (TranslateMessage, done to convert keyboard shortcuts into menu actions) and finally, passed to the window procedure (DispatchMessage).

If there is no message available in the queue, Windows suspends the current task and gives the CPU to another task. This task will try to retrieve a message from its queue as well, yielding the CPU to another task if no message is present and so on.

If a program needed to perform background tasks while there were no pending messages in its queue, it would call PeekMessage instead of GetMessage. This didn't make the task relinquish the CPU, as PeekMessage would immediately return to the caller task to inform it whether a message is available or not. This was used to time-multiplex message processing with another time-consuming task (think of a 3D program rendering but letting the user cancel that render by pressing a "Cancel" button).

If this time-consuming task was actually very time-consuming, a well-behaved program should call the Yield function from time to time, to relinquish the CPU and let other tasks run.

A badly-behaved program could easily hog the CPU by not retrieving messages too often or by spending too much time in a window procedure function.

The situation was quite different for MS-DOS boxes. They ran using the V8086 mode of the 80386 (if Windows was running in enhanced mode). Windows-aware programs ran in the so-called System VM (Virtual Machine 1). DOS boxes ran from VM 2 upwards.

DOS programs usually were badly-behaved programs, so Windows assigned each DOS box a different virtual machine. Virtual machines used pre-emptive multitasking in Windows 3.1, so each DOS box could run concurrently to others and to any Windows-aware program.

user3840170
  • 23,072
  • 4
  • 91
  • 150
mcleod_ideafix
  • 18,784
  • 2
  • 70
  • 102
  • 1
    MS-DOS programmes could be made aware of CPU sharing. The TSR functionality enabled a form of co-operative multi-tasking. – Chenmunka Jun 08 '16 at 10:01
  • 5
    In MSDOS Int 28h worked as an idle call when it was waiting for input. Also when running with DPMI hosts (such as windows 3.x) you could call int 2fh function 1680h (Release current virtual machines time slice) I added these to a Dos based application to improve the performance when running under Windows. (exact details may be wrong it's over 20 years ago now) – PeterI Jun 08 '16 at 14:07
  • 8
    A TSR program didn´t have to do anything with multitasking. They usually intercepted some DOS or BIOS vector so they would be triggered by some event (press a hot key, the timer interrupt, activity on the disk, etc). About int 28h and int 2Fh, those mechanisms came into scene when Windows was marketed. Before that, many DOS programs were made so no one of them was aware of such added functionality. Instead, programmers learned soon how to directly write on the screen, read keyboard, etc, making the DOS world a wilder place than the old west... – mcleod_ideafix Jun 08 '16 at 17:04
  • 2
    ...Those system calls of course helped DOS programs to behave a little better in the new Windows world, but the majority of them, just didn´t use them. Anyway, my answer has been edited to not to cause misinformation :) – mcleod_ideafix Jun 08 '16 at 17:04
  • 2
    Remember that DOS+Windows 3.1 also had Standard Mode, which was somewhat different to the 386 Enhanced Mode that you describe here. – JdeBP Jun 08 '16 at 18:31
  • 1
    @JdeBP The message pump loop and cooperative multitasking basics were the same, going back all the way to the real mode Windows 1.x and 2.x. For that reason, it isn't that difficult to run applications written for Windows 1.x on any 16-bit-capable Windows (shows applications originally bundled with Windows 1.01 running on Windows 8.1), or running some semi-modern software under Windows 1.x (shows the ReactOS and Wine "WineMine" minesweeper running under Windows 1.01). – user Jun 10 '16 at 17:52
  • 3
    As someone who tried to take advantage of that multiple DOS box thing - it wasn't as stable as one might like. I presume due to all the weirdness that DOS programs were wont to commit, the system had a tendency to get crash when running multiple DOS boxes. – Michael Kohne May 21 '17 at 02:21
  • @MichaelKohne: DR-DOS would likely have been more stable, Windows' startup "error" message notwithstanding, since its functions were designed to be re-entrant. If a DOS function was running in one window when some other window needed to access the disk, it would be necessary to stall the second operation until the first could succeed, but recognizing all the places where that might be necessary was difficult. – supercat Sep 27 '20 at 19:11
46

Found a great answer on Super User that explains it really well!

Windows 3.1 uses cooperative multi-tasking – meaning that each application that is in the process of running is instructed to periodically check a message queue to find out if any other application is asking for use of the CPU and, if so, to yield control to that application. However, many Windows 3.1 applications would check the message queue only infrequently, or not at all, and monopolize control of the CPU for as much time as they required. A pre-emptive multi-tasking system like Windows 95 will take CPU control away from a running application and distribute it to those that have a higher priority based on the system’s needs.

Source: https://superuser.com/a/726367/541767

Retro Gamer
  • 2,435
  • 3
  • 17
  • 38
  • 10
    "A pre-emptive multi-tasking system like Windows 95 will take CPU control away from a running application and distribute it to those that have a higher priority based on the system’s needs." -- this bit is only half-true, windows 95 premptively multitasked 32-bit code but it cooperatively multitasked 16-bit code and it was sufficiently reliant on 16-bit code that freezing the 16-bit side would freeze the whole UI. – Peter Green Jun 08 '16 at 13:30
  • 2
    @Peter: »reliant« is a string word here. Windows 95 tried hard to keep 16-bit applications in the illusion that they are in complete control of the computer. That's why there were a lot of hooks that routed Windows-native things through old 16-bit layers when necessary to allow old things (e.g. DOS device drivers) to still work. Those were often the places where a 16-bit application could stall or crash the OS. However, it's not so much that Windows needed those 16-bit parts to run, nor did 32-bit Windows applications. As far as I know they were only active when a 16-bit program was running. – Joey Jun 09 '16 at 07:43
  • 4
    @Joey - no, the graphics subsystem on Windows 95 was (entirely) still implemented in the 16-bit GDI and User DLLs; the 32-bit APIs were implemented through thunks to the 16-bit libraries. That also meant every GUI interaction required the global lock for running 16-bit code. – Jonathan Cast Mar 07 '17 at 07:12
  • 5
    @joey The entire 16 bit subsystem ran in a single thread in Windows 95. and the GDI was also 95% identical to the Win3.1 GDI and therefore 16 bit. In order to give the GDI and the 16 bit apps the illusion that they were running in Win 3.1, there was a single global lock that was taken by 16 bit apps and 32 bit apps while they were calling into the GDI.If any process hung while holding the lock, the entire graphics display would freeze, giving the illusion that the machine had frozen. – JeremyP Dec 05 '17 at 09:02
5

A "regular old MS-DOS program" can do absolutely anything. Modern operating systems run processes in sandboxes with limited privileges, where many things are either impossible or allowed only through OS services. MS-DOS wasn't like that. Once it transferred control to your code, you were in complete control of the machine. MS-DOS remained resident in RAM as what amounted to a library of useful subroutines. You could ignore it, or even overwrite it with Linux.

Earlier answers said that Win16 tasks were cooperatively multitasked, which is true, but I think it misses the point of the question. The question was how Windows could do something (multitasking) that is usually seen as the domain of the OS, and wasn't supported by MS-DOS. The answer is that Windows did whatever it wanted. The reasons it used cooperative rather than preemptive multitasking for Win16 programs were completely unrelated to limitations of MS-DOS.

benrg
  • 1,957
  • 11
  • 15
  • Very true. One can think of any program running under MS-DOS as a "potential operating system". If the program has the routines to assume control over the interrupt facility, set up paging, and directly interact with devices, DOS won't stop it from doing so. Along those lines, I'd note it's also possible to start Linux from an MS-DOS real mode environment. The loader just sets up a similar environment to which the bootloaders do. When Linux starts there's nothing to stop it from switching the machine into protected mode and taking over. Windows 3.x did basically the same thing. – RETRAC Jul 31 '22 at 15:28