11

Nowadays, it's easy to take for granted the system() call (as defined in POSIX), which allows a user program to easily execute a child process and wait for it to complete. Obviously, this is a trivial (and useful!) API to implement on a multitasking OS, such as the original Unix. I'm curious about the same capability being available to applications running on early consumer operating systems1, which were not based on Unix and didn't support multitasking2.

I would stipulate that the OS would need to provide an API that makes it just as trivial to invoke a child process as the system() call. Therefore, it would also transparently protect the memory of the calling program from being corrupted by a well-behaved child process (not meaning hardware enforced protected memory here). Multitasking systems have a general approach to sharing memory. This would, I assume, be a more limited mechanism that restores memory to the way it was when the call returns to the parent.

Did such an API exist on early single-tasking OS's like CP/M, DOS, MacOS, Atari TOS, etc.? Is there a more obscure consumer system with OS that provided it earlier?

1 I'm talking about an OS for a low-cost "personal computer" as one might have found in common use by small business or home computer hobbyists.

2 The Amiga was released in 1985 and had the Execute() API for this—it doesn't count, because it's also a multitasking OS.

18
  • 1
    On the original UNIX, I'm not sure it would have been that easy. ISTR it had a single process slot per terminal and the program switch was more like MSDOS where exit() simply loaded the shell back over the current program in the slot.
    – paxdiablo
    Apr 28, 2022 at 22:38
  • 2
    @paxdiablo I thought fork() and its variations for process management were there pretty early in Unix history. I haven't found a reference for when the actual system() function appeared.
    – Brian H
    Apr 28, 2022 at 22:43
  • 4
    Trivial? I do not think it means what you think it means. 🙂 Apr 29, 2022 at 14:57
  • 4
    The constraint about protecting the calling program pretty much eliminates, well, everything that this post is asking about. Since none of those systems had any memory protection at all. Even OS/9 couldn't due that, even though it was, indeed, a mutli-tasking system. But programs still had to be well behaved to get along with others. Apr 29, 2022 at 18:09
  • 2
    nitpick: You know system() is a library function, not a "system call" on normal Unix systems, right? The implementation of system on a system like Linux involves multiple system calls, to at least fork(2), execve(2), and some flavour of wait. "system() call" is a fun and compact way to write it, but not accurate. Apr 30, 2022 at 11:39

5 Answers 5

20

MS-DOS allows an application to invoke a nested application. Processing of the former application will be suspended until the latter application returns, whereupon the former application will continue following the function call that invoked the other application.

The way this typically worked revolved around the fact that MS-DOS programs may be easily relocated to any 16-byte boundary when they are loaded, and often use storage near the end of their code space before they use storage that's higher up. Launching a nested application simply involves having it invoked with its "low-water mark" set to equal the parent program's "high-water mark".

MS-DOS 2.0 (October 1983) and later provided this as a system call, interrupt 0x21 service 0x4B. Prior to that, program loading had to be implemented manually, albeit with some help from interrupt 0x21 service 0x26 to create a PSP; see COMMAND.COM’s implementation for one example (this was trivial for .COM-style programs, more complex for MZ programs). Programming environments typically provided their own wrappers for service 0x4B; see for example Turbo C’s spawn functions, or GW-BASIC’s SHELL statement.

5
  • 1
    Back in the DOS days I had an emacs-like editor where you could run DOS commands, compilers (and make) in one of the buffers. Not quite sure just how it worked, but it did.
    – Jon Custer
    Apr 28, 2022 at 22:05
  • 1
    @JonCuster My similar recollection is about Norton Commander, but I think that used some fancy programming to manage/resume its state.
    – Brian H
    Apr 28, 2022 at 22:22
  • I just checked my copy of the MSDOS FoxBase+ manual dated July 1987, and the RUN/! command existed, which allows you to run another executable from within a FoxBase program. Not multitasking of course; the Foxbase program stopped until the other command returned.
    – LAK
    Apr 29, 2022 at 15:09
  • And to circle back to the original question, just a few pages later on in the Turbo C manual, we indeed have an entry for system(), which acts as more or less a wrapper around spawn(). Apr 30, 2022 at 20:08
  • @JonCuster I recall Turbo Pascal's Turbo Vision also has some demo that pause the text editor and return to DOS then resume
    – phuclv
    May 4, 2022 at 4:22
13

All versions of Atari's GEMDOS (first released in 1985) supported Pexec(). Flags permitted launching the child process and waiting for it to finish, or merely loading the child into memory (for overlays and debugging).

5
  • I'm curious why the same page (which might be about FreeMint) also documents a Pfork() call. fork()/exec() style process management is quite "Unixy" and I thought GEMDOS was a close relative of CP/M-68k.
    – Brian H
    Apr 28, 2022 at 22:28
  • 2
    Those calls were added in later GEMDOS / TOS improvements, including MultiTOS and MiNT. I assumed your question pertained to the earliest versions of consumer operating systems, when the ST was single-tasking.
    – Jim Nelson
    Apr 28, 2022 at 23:23
  • GEMDOS is closer to MS-DOS in spirit than to CP/M. I suppose DR used their DR-DOS code for the porting more than their CP/M 68 code. Apr 29, 2022 at 5:53
  • 1
    GEMDOS was apparently originally a private project by Jason Loveman, a Digital Research developer. This was before DR-DOS existed; DR's DOS-compatible operating systems at the time were DOS emulators running atop CP/M kernels.
    – john_e
    Apr 29, 2022 at 23:16
  • CP/M 68K could do it
    – Alan Cox
    May 19, 2022 at 18:13
11

Since you mentioned CP/M: At least in its original 8080 incarnation, CP/M does not have any system call that allows a program to launch another and regain control when the program it launched terminates. Under CP/M-80, programs are assembled to run at address 0100h, so loading the 'child' process would inevitably overwrite the memory of the 'parent'. While it's possible to imagine workarounds (such as the 'parent' process relocating itself higher in memory and reporting a reduced memory size to the 'child') there is no simple system call that does this.

The closest approximation is the P_CHAIN call under CP/M 3, which takes a command line and executes it as if typed at the A> prompt. However this terminates the calling program before executing the command, for the reason given above.

10
  • This really isn't an answer, as it is proving that CP/M couldn't do it.
    – DrSheldon
    Apr 29, 2022 at 16:54
  • But it could be done...Turbo Pascal had this capability. It used it run both it's own programs, and external ones. But it was not a built-in CP/M service. It doesn't hurt that CP/M had 2 ways to exit a program. Return to the CCP, or warm start the entire OS. Of course, you could only return to the CCP if your code didn't stomp it out in memory first. Apr 29, 2022 at 18:06
  • XSUB and DESPOOL were official utilities using this technique. Apr 29, 2022 at 19:37
  • 1
    @DrSheldon: The question was "Did such an API exist on early single-tasking OSs like CP/M, DOS, MacOS..." For CP/M, at least, "No" is the correct answer.
    – john_e
    Apr 29, 2022 at 23:08
  • 1
    @ThorbjørnRavnAndersen If you use the eXecute command from the Turbo Pascal menu, it will run the program and return you to Turbo Pascal. It does this even if the program you run does a warm start of the system to exit. Apr 29, 2022 at 23:18
4

The Cambridge Z88 (1987) has a number of system calls that allow it to

  • enumerate installed programs on inserted flash cards in its 4MB address range
  • Select one of the enumerated applications to run
  • create and forward "Mailbox content" to applications being run (which can contain a "come back to (and restart) me after done")

So, essentially, all functions required to implement a system() call functional equivalent are present. It's obviously not presented as a POSIX-compliant implementation, though.

2

I'm not sure that it would have been useful on Mac OS unless you were writing a launcher in the brief period before the MultiFinder as applications always present at least some UI and do not exit of their own volition.

With System 7 Apple introduced AppleScript, which allows applications to expose scriptable actions and thereby allows a third-party application to interface with a scriptable application e.g. to open an audio editor, ask it to open a file, delete the first five seconds, normalise audio elsewhere, save and exit. But that's in the era where cooperative multitasking was enabled for all, rather than being a third-party extension (prior to System 5) or optional (in Systems 5 and 6).

3
  • 2
    AppleScipt is textual and can launch other programs, but isn't even close to implementing the system() call. When a question asks about the first instance of a POSIX behavior, the answer is never classic MacOS.
    – DrSheldon
    Apr 29, 2022 at 16:52
  • 1
    I wonder how the shell utility worked in Macintosh Programmer's Workshop.
    – Brian H
    Apr 29, 2022 at 19:48
  • 1
    @DrSheldon agreed, but it is part of the correct answer to "Did such an API exist on early single-tasking OS's like ... MacOS"?
    – Tommy
    Apr 29, 2022 at 23:47

You must log in to answer this question.

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