5

Once you have an operating system that provides services callable from programs, you need to provide a way for programs to request those services.

These days the general approach only requires a single instruction that will trap from user mode into kernel mode; it can either be an instruction intended for that exact purpose (like SYSENTER on some Intel x86) or a more generic instruction (like INT 2E on earlier Intel) that will serve the purpose. Such mechanisms generally don't look like the rest of the instruction set, e.g., the opcode does not itself uniquely say what operation is intended by the programmer; any operands have to be separately specified, rather than being part of the syscall instruction.

But an earlier fashion was to provide hardware support for making system calls look more like the rest of the instruction set. This typically included providing many different opcodes, so that (as with hardware) one opcode invoked one system function. Maybe any operand(s) of the instruction would have the usual processing (such as effective address computation - modification and indirection) performed before the kernel was entered, and so on. I'll name this approach the 'extracode' approach, to use the English term of the period.

Several systems I've used or read about adopted the extracode approach: Atlas (which coined the term), the FP6000 and its offspring the ICL 1900 range, the SDS 9xx series ("programmed operators"), PDP-10 ("unimplemented user operations"), and probably more.


To give a TOPS-10 (PDP-10) example: to look up a file, the LOOKUP instruction is used. The instruction format is "LOOKUP ac, addr" - this is a single instruction, one word (not some macro sequence). The opcode for LOOKUP (assigned by the kernel design) is not used for anything else. The 'ac' field contains a channel number, the 'addr' points to a filename. The instruction may also specify index and indirection in the usual manner. The hardware computes the effective address of the filename from addr, index, indirection just as it does for all instructions, including validity checking. Then the trap is taken.

This is in contradistinction to other systems where the system-call sequence requires loading (in this case) the effective address of the filename into an agreed place, an indication of what function we want to perform in some other place, and then issuing the trap instruction that is common to all system calls.

The assembly-level programmer is conscious of the difference.


So, why don't we do it that way any more?

My own guesses at an answer is that thinking about each system call as a separate machine instruction is tied to an assembly-language view of programming, a view that the kernel was providing user mode with a virtual machine that "improved" on the hardware, and also to an era when the service repertoire was quite small.

For this question, I'm not interested in the case of emulation of missing instructions - where lower-end machines implemented in software operations that were done in hardware (or microcode!) on higher-end systems. I also recognize that there might be a grey area around features such as Alpha's PALcode or Prism's epicode, which I'll view an adaptation layer between hardware provisions and OS kernel requirements. And there was a grey area in some systems - e.g., TOPS-10 used separate UUO opcodes for many different functions, but also multiplexed others on top of a single CALL/CALLI UUO. Nevertheless, I'm hoping that there's enough of a distinction to provoke a discussion here.

10
  • 2
    Re, "...opcode does not itself uniquely say what operation is intended..." You're mixing different levels of abstraction. A generic "system call" or software interrupt opcode says exactly what the programmer wants at the hardware level: The programmer wants to invoke a privileged handler. It doesn't say what the handler will do. That's defined at a higher level of abstraction, just like how a "function call" instruction says that the programmer wants to call a function at such-and-such address, but doesn't say what that function will do. Feb 8, 2020 at 20:40
  • 1
    I disagree. The virtual machine implementer wishes to provide, and the programmer wishes to use, an instruction that is (say) "print string on console". The instruction set in the virtual machine is largely identical in format with the instruction set, except for the "print string on console" instruction which instead of being a single machine instruction (opcode="print string", operand=the string, in the same format as used on the physical machine) has an opcode="trap to kernel", no string operand in the instruction, string pointer set up in register or stack by a previous instruction. Feb 8, 2020 at 21:18
  • Sorry, I guess I didn't make it clear that I was responding to your second paragraph: The one about how we do it "these days." If I'd had an answer to your actual question, I would have posted it as an actual answer. But I can only guess at why things changed. Feb 8, 2020 at 21:21
  • 1
    @another-dave Are you asking “when” or “why” or both? Your title and body are different.
    – wizzwizz4
    Feb 9, 2020 at 13:13
  • I'll go with "why", Feb 9, 2020 at 13:22

2 Answers 2

5

I would guess that the biggest argument against such opcodes is that there are generally better uses to which the opcodes could be put. The 68000 reserved a range of opcode bytes from 0xA000-0xAFFF for the purpose, which the original Macintosh OS used extensively, but a single-word A-line-trap instruction wouldn't really offer much advantage over a two-word instruction to perform a call to one of 4096 addresses. It would save a word of opcode space at the call site, and maybe 2-3 words at some of the destinations, of course, but for most programs the fraction of instructions that were A-line traps would be too small for that to be a meaningful savings given modern memory prices.

Note that on some architectures, all multi-word instructions require that extension words be a form of "no-op", making it possible to examine code at any address and determine what the instruction containing that address will do, without having to know whether execution would start at that word or a previous word. On such architectures, the advantages of having certain operations be recognizable as single instructions may be greater, but the architectural limitations posed by reserving chunks of address space would also be greater. Personally, I like the concept of an architecture where any range of code would have a single meaning regardless of where one starts parsing. Among other things, if code can only run from read-only storage marked as "executable", and certain kinds of I/O operations can only be done via dedicated opcodes, it becomes trivial to statically guarantee that a piece of code won't perform those kinds of I/O. I don't know if anyone's tried to design an architecture that's like a cross between ARM and Thumb, where every 32-bit word could either contain one 32-bit instruction or a pair of other operations, but such a design would make certain kinds of static code analysis easier and--with a good compiler--might not necessarily lose too much code density versus a design that freely mixes 16-bit and 32-bit instructions.

7
  • Downvoter: care to explain? The 68000 F-line traps were intended for FPU emulation, but Motorola made clear that operating systems would be free to use A-line traps, and that range of opcodes would not be used by Motorola for any purpose other than to invoke the A-line trap handler, so it wouldn't be "emulating" anything other than the OS that was designed to use it.
    – supercat
    Feb 8, 2020 at 21:05
  • 1
    Indeed, some of the later MacOS toolbox calls also used a selector (in register D0 or pushed on the stack) to specify which tool it really was. Feb 8, 2020 at 21:41
  • Except, Motorola itself called it emulation. Check section 6.3.6 of the UM: "Word patterns with bits 15–12 equaling 1010 or 1111 are distinguished as unimplemented instructions, and separate exception vectors are assigned to these patterns to permit efficient emulation." I guess that's authoritative, isn't it?
    – Raffzahn
    Feb 8, 2020 at 23:54
  • 1
    @Raffzahn: The documentation lumped A-line and F-line traps together, but both the intended purpose and the way they were used in practice was very different. Aside from the use of the same word in the documentation for A-line and F-line traps, I don't think the A-lines were really "emulating" anything in the same sense that the F-line traps were.
    – supercat
    Feb 9, 2020 at 3:14
  • 1
    @rackandboneman: The difference between a Harvard Architecture and a Von Neuman architecture is whether code and data occupy the same address space. I don't know of any term to distinguish architectures where a word of code would be processed one way if it's the start of an instruction, but in a different way if it's part of a multi-word instruction.
    – supercat
    Apr 4, 2021 at 16:44
2

The PDP-10 calling sequence should be contrasted with the VAX calling sequence to get a good handle on two very different approaches. The PDP-10 requires the program to issue an extra code, while the VAX does not. This reflect changes in system designs between about 1965 and 1975.

The PDP-10 used unused opcodes (UUOs) to request services from the operating system. It used instructions like PUSHJ or JSR to invoke library proedures that had been linked into the program's memory image. The hardware (or the microcode for the KL-10) was set up to trap UUOS. The VAX worked quite differently. It used the same instruction and the same calling sequence for system services and for library procedures. Instead of the instruction triggering a trap, it was the address. System service requests used an address in system space, validated by the operating system. This made life easier for high level languages to make use of both system services and qlibrary procedures.

10
  • How does this answer the question?
    – wizzwizz4
    Feb 9, 2020 at 13:21
  • I'm going to differ on the VAX. The programmer wrote CALLS @#SYS$FOO, but SYS$FOO was just a stub in S0 space (later in P1 space to support per-process filtering) and would be entered in the mode of the caller. SYS$FOO would then issue a CHMK instruction which is "trap to kernel mode", transferring control to some address previously set up in system initialization, which would then dispatch to the real implementation of the FOO service. Feb 9, 2020 at 13:21
  • Feel free to edit my answer in order to improve the description. It remains the case that the CHMK instruction allows VMS to limit system calls to starting at valid entry points. Without that, a rogue program could wreak havoc. Feb 9, 2020 at 14:17
  • I'm not sure what you mean though. Hardware-wise, any (Macro-32) program could execute a CHMK. Are you saying that the CHMK handler in the kernel checked that the address of the CHMK instruction was in the S0 stub? I don't see how that helped. You still have to probe the arguments. Feb 9, 2020 at 14:24
  • Also, the VMS mechanism is the same as what happens in (say) a Unix program written in C. The programmer calls 'write'. That's a simple stub, in this case in the C RTL, which adjusts any calling convention and then executes some "trap to kernel" instruction. The difference for VMS was that this was the only documented and supported entry. Feb 9, 2020 at 14:27

You must log in to answer this question.

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