Jwzumwalt already covered the basic, being not much younger I might add a few minor details still present to me.
While it didn't particularly work well for games (as far as I remember), it was popular for system operators of bulletin board systems.
It was perfect - well, let me rephrase this as rather useful - for well behaving (read no direct hardware access) programs to have two programs at hand. Like a BBS operating in the background while being able to do maintenance tasks at the same time.
How did DoubleDOS work? I know that I had used it on 8086 machines back in the day which I don't believe had many of the real context switching capabilities that modern CPUs use to support multitasking.
In general the process is the same on each and every CPU ever developed. To switch all CPU-states visible to a user task need to be exchanged. Some CPUs may offer special modes for special cases (like the second register set in the Z80) or instructions to store the whole state (or most of it) in a single operation. Still, it has to be done.
Also with a primary single taking BIOS/OS combination, several memory regions need to be swapped - just think of things like cursor position.
Same with screen memory. If there's only one screen adapter, the content needs to be exchanged. Otherwise one would not be able to see the output of the background task when switched up.
Was it simply hooking up to an interrupt and storing session 1's CPU registers, context switching, restoring session 2's registers, running and then running until the next interrupt?
Exactly like that. The basic task switch was done via a timer interrupt. To make it work some additional precaution had to be taken. For example monitoring entry to certain BIOS and DOS interrupts. As well as handling BIOS for I/O related functions. This is necessary since no (at least not many) DOS/BIOS functions are designed for reentry (i.e. two tasks running thru the same code). So DoubleDOS had to keep the tasks from doing so.
For example, if the timer interrupt (08h) fires while the active task issues a DOS function, it will be ignored. Prior to DOS 2.0 DoubleDOS did this by hooking Int 21h and setting a marker of their own, while with DOS 2.0 the InDOS Flag got introduced to sign when it's safe to (re)enter DOS (*1).
Was memory space adjusted accordingly
DoubleDOS was really build around the Idea of splitting a machine into two by partitioning memory. It wasn't really a taking system with dynamic memory assignment. At startup (or if no program was running in either task) available memory was to be split um in two regions and kept that way. Beside being the prime idea, it also avoids hassles wit programs manipulating the memory they assume to be entitled to.
or did programs which didn't go simply through DOS and the BIOS fail to operate?
There are zillion ways to screw operations, and computers really enable them :)) It depends on the way of switching and what kind of 'bypassing' DOS/BIOS had been used. The most important issue here is usually the screen content and keyboard. Programs accessing both directly did only run well in foreground. In background they may at best just screw up the front tasks screen and steal input.
In contrast 'well behaving' programs, using only DOS and BIOS could even continue to produce output in background. Same for Printers and serial lines (*2). In fact, I remember that somehow up to 6 'tasks' could run at the same time. Two real tasks, while the other being specialized like print spoolers.
In reality, many programs 'less than behaving' programs fail due to direct screen access. At the same time the majority do wait for user interaction at some point and don't issue any screen update when switching. So putting them in background won't do damage beside wasting CPU time for an idling application.
Then again DoubleDOS did offer a ways to get around that to a certain degree by setting the background execution to halt. So when foreground and background switch, everything gets saved and the program stops operation (*3).
Addendum 1: Time slice handling
It might be interesting to add the ways of handling time attribution to each task.
- 2:1 Foreground/Background
- 1:2 Foreground/Background
- 1:1 (equal time)
- 2:1 Upper/Lower Program
- 1:2 Upper/Lower Program
- Full time for Foreground
Addendum 2: Multitasking (and TSR) hooks
With DOS 2 a lot of hooks where designed into DOS to enable multitasking - much the same way as the AT BIOS introduced similar measures. From there on it was rather straightforward to build a tasking layer above DOS/BIOS. And Windows is maybe not the least example to do so.
On BIOS side this includes several functions to allow an OS independent protocol to detect and handle critical sections and process task swap. With the SysReq-Key even an OS independent user interaction was designed.
DOS added not only InDOS via Int 21h Function 34h (?), but also INT 28 to give a switching point/activity entry during idle. Beside InDOS function 34h also introduced an error flag which could be used to make the active DOS function fail in a (somewhat) orderly fashion. This again could be used in conjunction with the Error Handler at Int 24h(*4). To speed up the process. Int 23H also added the
CTRL-BREAK handling as an entry point for task switchers/OSes due user request.
And then there was Int 2Fh, the MUX-Services. A collection of hooks mainly added by TSR utilities like
PRINT, but also various versions of DOS (and various DOSes). There where functions from
PRINT over network and CD handling to a countless number of services provided by Windows, VM-Ware and alike.
COMMAND.COM's command line handling being not the least :))
*1 - There are some function, especial the Character I/O which are usually safe to be used while InDOS is set.
*2 - Ofc. only if both tasks are not trying to access the same device on a character base - that is single byte (or line) output via DOS/BIOS. As soon a utility like PRINT was used even sharing a printer on job base was possible.
*3 - Not really desirable for a BBS, isn't it :))
*4 - Right, that's the one where the infamous Abort/Retry/Ignore/FAIL is displayed.