Home Up
Home Teaching Glossary ARM Processors Supplements Prof issues About

68K User and Supervisor States


An interesting aspect of the 68K ISA is its so-called user and supervisor states. Such concepts exist elsewhere with other processors, but they are often hidden from the user and are part of the processor’s memory management system and operating system. In the case of the 68K, the user/supervisor mode is explicit. Moreover, it’s interesting. This material is taken from some notes I wrote for a lower-level course.

What does a computer do when it encounters an illegal instruction or an address error? The very last thing you’d want to do is just continue executing instructions sequentially. Allowing the processor to ignore the cause of the problem, reject the faulty instruction, and then proceed as if nothing had happened is not an option. The fact that an error has occurred indicates that the program has a fatal flaw and continued execution is either meaningless or positively dangerous.

The most common course of action taken by the system after detecting an error is to generate an exception, which we can regard as a call for help to the operating system. The term exception strictly includes interrupts, which are not always generated by error conditions. Interrupts are widely used to implement a computer’s input and output operations—when a device such as disk is ready to transfer data, it sends an interrupt (i.e., a signal) to the processor. The processor detects the interrupt,  stops what it’s doing and deals with the disk’s request for attention. A computer interrupt is entirely analogous to a human interrupt—if you’re doing something and someone asks you a question, you stop whatever you’re doing, answer the question and then resume your previous activity.

The software that actually deals with an exception is called an exception handler and normally forms part of the computer’s operating system. We haven’t yet introduced the operating system formally, so a few comments might be helpful at this point. An operating system is probably the most important piece of software that runs on a computer, because it acts as the interface between the computer and the machine, and it controls the system’s resources. Essentially, the operating system is responsible for controlling the input/output devices, and the disk and memory systems. Because the operating system coordinates all the processor’s activities, the operating system is also responsible for dealing with errors. Clearly, the operating system itself should be as fault-free as possible.

When the processor detects an error such as an illegal op-code, it calls the appropriate exception handler within the operating system to deal with the situation. This action involves loading the program counter with the exception handler’s starting address. You can think of an exception as a sort of “jump to subroutine” instruction that’s jammed into the CPU whenever something goes wrong. It is the job of the exception-handler writer to take whatever action is necessary to recover from the problem.

Many computers are capable of multitasking; that is they can run several programs (or tasks) at the same time. In reality, the operating system rapidly switches between the different tasks to give the operator the impression that all tasks are running at the same time. When one of the errors we’ve described above occurs in a multitasking system running several tasks simultaneously, the exception handler suspends the task that caused the exception and runs another task. If, however, the computer is being used in a control application (e.g., process control in industry) some mechanism must be provided either to put the system in a safe state or to wind back the system to a point from which it is able to recover. Whatever the cause of the problem, it’s almost certain that the exception handler will be part of the processor’s operating system, rather than the application program that caused the exception.

The Protected State

A member of the 68000 family of processors is always in either a user state or in a supervisor state.

The figure illustrates two possible courses of action that may take place when an exception is generated (remember that the terms exception and interrupt are sometimes employed interchangeably). Both these diagrams are read from the top down. In each case, the left-hand side is shaded to represent user or applications programs running in the user state, and the right hand side is unshaded to represent the operating system running in the supervisor state.

In (a) a user program is running and an exception occurs. A jump is made to the exception handler that forms part of the operating system; the operating system deals with the problem; and a return is made to the user program. In this case, the exception handler is able to deal with the cause of the exception—the exception may have been generated by a peripheral requesting attention rather than a fatal error condition.


















In (b) an exception caused by a fatal error occurs and the exception handler is called. However, in this case, the operating system terminates the “faulted” user program. The operating system then runs another user program.

These figures show user programs and the operating system existing in separate compartments—this is not a fiction. We are now going to explain why user programs and the operating system sometimes really do live in different universes.

A processor can, at any instant, be in one of several states or levels of privilege. Here, we restrict our discussion to members of the 68000 family of microprocessors that have two states (i.e., levels of privilege). One of the 68000’s states is called the supervisor state and the other the user state. The operating system runs in the supervisor state, and applications programs running under the control of the operating system run in the user state.

In simple 68000-based systems, the processor’s supervisor and user state mechanism isn’t exploited, and all code is executed in the supervisor state. However, all reasonably sophisticated 68000-based systems with an operating system do make good use of the 68000’s user and supervisor state mechanisms. When power is first applied to the 68000, it automatically enters its supervisor state. This makes sense, because you would expect the operating system to initially take control of the computer while it sets everything up and loads the user tasks that it’s going to run.

The three big questions we’ve now got to answer are: “How does the 68000 know what state it’s in?”, “How is a transition made from one state to another?”, and “What does it matter anyway?”

The answer to the first question is easy. The 68000 has a special flag-bit in its status register, called an S-bit, that indicates what state it’s currently operating in. If S = 1, the 68000 is in its supervisor state and if S = 0, the 68000 is in its user state. The S-bit is located in the 68000’s 16-bit status register, SR, that is divided into two halves. The lower-order byte of the status register is a conventional condition code register (CCR) with a carry bit, an overflow bit, a negative bit, and a zero bit. The CCR is used in conjunction with conditional branch instructions. The upper byte of the status register is called the system byte and contains the S-bit that defines the operating state of the processor.

The next question was “How is a transition made from one state to another?” The next figure provides a state diagram describing the relationship between the 68000’s user and supervisor states. The lines with arrows indicate the transitions between states (and the text against the line explains the action that causes the corresponding transition). This figure also demonstrates that a transition can be from the current state back to the current state.














The state transition diagram shows that a transition from the supervisor state to the user state is made by clearing the S-bit of the status register. You can execute the instruction MOVE #0,SR to the S-bit (and the other bits) of the status byte and put the 68000 in the user state. When the operating system wishes to execute an applications program in the user state, it simply clears the S-bit and executes a jump to the appropriate program; that is, the operating system is able to invoke the less privileged user state simply by executing any instruction that sets the S-bit to 0.

If you inspect this figure, you will see that once you’re in the user state, you can’t get back to the supervisor state by setting the S-bit in the status register to 1. If you could do this, anyone would be able to access the supervisor state’s privileged features and any security mechanism it provides would be worthless. The only way in which a transition can be made from the user state to the supervisor state is by means of an exception, any exception. Let’s say that again: a program running in the user state cannot deliberately invoke the supervisor state directly. Indeed, an attempt by the user state programmer to modify the S-bit would result in a privilege violation. A privilege violation causes an exception that forces the 68000 into the supervisor state, where the exception handler deals with the problem.

We can now answer the third question we asked earlier. How can the 68000’s two-state mechanism be of benefit? First, some instructions can be executed only in the supervisor state and are said to be privileged. Typical privileged instructions are STOP and RESET. The STOP instruction brings the processor to a halt and the RESET acts on external hardware such as disk drives. You might not want the applications programmer to employ these powerful instructions that may cause the entire system to crash if used inappropriately. Other privileged instructions are those that operate on the system byte (including the S-bit) in the status register. If the applications programmer were permitted to access the S-bit, he or she could change it from 0 (user state) to 1 (supervisor state) and bypass the processor’s security mechanism.

If the 68000’s user/supervisor mode mechanism were limited to preventing the user-state programmer executing certain instructions, it would be a nice feature of the processor, but not of any earth-shattering importance. The user/supervisor state mechanism has two important benefits; the provision of dual stack pointers, and the support for memory protection. These two features can be used to protect the operating system’s memory from either accidental or deliberate modification by a user application. We are now going to describe how the 68000’s supervisor state protects its most vital region of memory, its stack.

Security and the Stack

When students are first introduced to assembly language programs, their early efforts often crash and bring the processor to a halt. The first place they should look for the cause of such a crash is the stack. Most computers maintain a stack to manage subroutine return addresses. When a subroutine is called by means of an instruction like BSR XYZ, the address immediately after the subroutine call (i.e., the return address) is pushed on the stack. The final instruction of a subroutine, RTS (return from subroutine), pulls the return address off the stack and loads it in the program counter. The stack is, therefore, a region of memory used to save subroutine return addresses.

The processor has a special register, the stack pointer, that points to the current top item on the stack. This stack pointer is automatically updated as elements are pushed onto the stack or are pulled off the stack.

If you corrupt the contents of the stack by overwriting the return address, or if you corrupt the stack pointer itself, the RTS instruction will load an undefined address into the program counter. Instead of making a return to the calling point, the processor will make a jump to a random point in memory and start executing code at that point. The result might lead to an illegal instruction error or to an attempt to access non-existent memory.

Consider the following fragment of code (which is both badly written and contains a serious error). Don’t worry about the fine details, it’s the underlying principles that matter. The 68000’s stack pointer is address register A7.

     MOVE.W  D3,-(A7)   Push the parameter in register D3 onto the stack

     BSR     Sub_X      Call a subroutine

     .                  Return here

     .

Sub_X ADDA.L  #4,A7      Step over the return address on the top of the stack

     MOVE.L  (A7)+,D0   Read the parameter from the stack

     SUBA.L  #6,A7      Restore the stack pointer

     .                  The body of the subroutine goes here....

     RTS                Return from subroutine


The programmer first pushes the 16-bit parameter in data register D3 onto the stack by means of the instruction MOVE.W D3,-(A7), and then calls a subroutine at location Sub_X. The next figure illustrates the state of the stack at this point. As you can see, the stack contains the 16-bit parameter and the 32-bit return address.











When the subroutine is executed, the programmer first attempts to retrieve the parameter from the stack stepping past the return address at the top of the stack. The instruction ADDA.L #4,A7 adds 4 to the stack pointer in order to skip past the return address on the top of the stack, (b). This is a terrible way of accessing the parameter—do remember that we are providing an example of how not to do things.

The programmer then reads the parameter from the stack by means of the operation MOVE.L (A7)+,D0, that first increments the stack pointer by the size of the operand (4 for a 4-byte value), (c). Since the stack pointer has been moved down by first stepping past the return address and then pulling the parameter off the stack, it must be adjusted by 6 to point to the subroutine’s return address once more (i.e., a 4-byte return address plus a 2-byte parameter). Finally, the return from subroutine instruction, RTS, at the end of the subroutine pulls the 32-bit return address off the stack and loads it in the program counter.

This code fails because it contains an error. The parameter initially pushed on the stack is a 16-bit value, but the parameter read from the stack in the subroutine is a 32-bit value. The programmer should have written the instruction MOVE.W (A7)+,D0 rather than MOVE.L (A7)+,D0; the error in the code is just a single letter. The effect of this error is to leave the stack pointer pointing at the second word of the 32-bit return address, rather than the first word. The RTS instruction therefore loads the program counter with an erroneous return address resulting in a jump to a random region of memory. We have demonstrated that this tiny error not only gives the wrong result, but generates a fatal error. We are now going to demonstrate how the user/supervisor mechanism helps us to deal with such a situation.

The 68000’s Stack User and Supervisor Stack Pointers

There’s very little the computer designer can do to prevent programming errors that corrupt either the stack or the stack pointer. What the computer designer can do is to limit the effects of possible errors. The 68000 microprocessor approaches the problem of stack security by providing two identical stack pointers—each of which is called address register A7. However, both stack pointers can’t be active at the same time—either one or the other is in use (it’s a bit like Clark Kent and Superman; you never see them together).
















One of the 68000’s two stack pointers is also called the supervisor stack pointer and is active whenever the processor is in the supervisor state. The other stack pointer, the user stack pointer, is active when the processor is in the user state. Since the 68000 is always in either the user state or the supervisor state, only one of the two stack pointers is available at any instant. The supervisor stack pointer is entirely invisible to the user programmer—there’s no way in which the user programmer can modify (or even read) the supervisor stack pointer. The supervisor state programmer (i.e., the operating system) can use a special privileged instruction to read or modify the user stack pointer.

Let’s summarize what we’ve just said. When the 68000 is operating in its supervisor state, its S-bit is 1 and the supervisor stack pointer is active. This stack pointer points at the stack used by the operating system to handle subroutine return addresses. When the 68000 is operating in its user state, its S-bit is 0 and the user stack pointer is active. This stack pointer points at the stack used by the current applications program.

Consider the previous example of the faulty applications program running in the user state. When the return from subroutine instruction is executed, an incorrect return address is pulled off the stack and a jump to a random location is made. Consequently, an illegal instruction error (or a similar error) will eventually occur and an exception handler will be called. The effect of an exception is to force a change of state from user to supervisor mode. The exception handler runs in the supervisor state, whose own stack pointer has not been corrupted. That is, the applications programmer can corrupt his or her own stack pointer and crash the program, but the operating system’s own stack pointer will not be affected by the error. You could almost say that when a user program crashes, the operating system mounts a rescue attempt.

The 68000’s two-stack architecture doesn’t directly prevent the user programmer from corrupting the contents of the stack. Instead, it separates the stack used by the operating system and all exception-processing software from the stack used by the applications programmer. Whatever the user does in his or her own environment cannot prevent the supervisor stepping in and dealing with the problem. We are now going to demonstrate that it is possible to go even further and protect memory space itself.

Protected Memory Space

All stored-program digital computers include a central processing unit and a random access memory that holds programs and data. Computer scientists regard memory as a sequence of locations that can be accessed by the processor, and refer to these locations collectively as memory space. For example, you can say that a 68000 microprocessor with a 24-bit address bus has a 224 byte (i.e., 16 Mbyte) address space.

First-generation microprocessors treated all memory locations equally in the sense that an access to memory location X was performed in exactly the same way as an access to memory location Y. You can, however, divide a processor’s memory space into separate regions, each of which has a common characteristic. There are several ways of partitioning memory space into distinct regions; you can divide the memory space into read/write and read-only regions, or into program and data regions, or into user and supervisor regions. Memory space can be partitioned according to ownership so that an individual task or program has exclusive rights to a region of memory—this region belongs to the word processor and that region belongs to the database. You can even combine these attributes and speak of, for example, a read-only supervisor program space.

It is possible to build a 68000-based microprocessor system with a 4 Mbyte block of read-only memory that holds the operating system, a 2 Mbyte block of read/write memory that makes up the operating system’s working space, and a 4 Mbyte block of read/write memory dedicated to user programs.

Partitioning Memory Space

Memory space isn’t simply an academic abstraction. When the 68000 processor accesses memory during a read or a write cycle, it sends out signals on some of its pins to tell the rest of the system what type of memory space it is accessing. The state of its read/write signal, its S-bit, and the nature of the address (i.e., instruction or data) define the type of memory space currently being accessed. With a little cunning you can make good use of this information. Suppose you divide memory space into a number of regions according to their type, as the next figure demonstrates. If you then design the hardware so that, for example, a program running in the user state cannot access memory belonging to the operating system, you have a very powerful means of protecting system software from user errors. You can even protect one user’s memory space from access by another user.

In order to control the access to memory, you have to place a special hardware subsystem between the CPU and its memory. This hardware is called a memory management unit, MMU. Today’s processors employ an internal on-chip MMU. When the 68000 was first introduced, it used a separate memory management unit.

Memory Space

The MMU contains a table that describes the properties of each block of memory. A system with 16 Mbytes of memory space might divide the memory into 256 blocks of 64 Kbytes (256 x 64K = 16M). This arrangement would require a table with 256 entries, one for each of the possible blocks of memory space. When an address appears on the processor’s address bus, the contents of one of the 256 entries in the MMU’s table are read to determine the type of access permitted to this block of address space. The MMU then compares the information provided by the MMU for the current memory space with the signals from the processor indicating the actual memory space being accessed. If they match, the access is legal, otherwise an illegal memory access is reported to the processor.
















The MMU acts as a type of monitor or watchdog that observes addresses on the address bus and checks whether the current CPU access is legal or not. The next figure illustrates the dialog that takes place between the CPU, the MMU, and the memory system during a read or a write cycle. At the start of a memory access the CPU generates an address and sends it to the MMU together with the control signals that define the type of the access (i.e., read or write, program or data, user or supervisor mode). If the location being accessed is illegal (e.g., if it is assigned to supervisor space and a user program is attempting to read it), the MMU sends an error message to the CPU to abort the current access and to begin exception processing (and error recovery).












.



Dialog between the CPU, MMU, and Memory

The next figure illustrates the structure of a memory management unit that checks whether the address space currently being accessed is legal. Once again we must emphasize that the MMU performs functions other than the security mechanism that is the subject of this chapter. Moreover, the way in which memory is actually partitioned is more complex than we’ve just described.

By dividing memory space into regions of different characteristics, it is possible to provide a considerable measure of security. A user program cannot access memory space belonging to the operating system, because an attempt to access this memory space would result in the MMU generating an interrupt. Not only does the 68000 protect the supervisor stack pointer from illegal access by a user program, the 68000 + MMU combination protects the supervisor stack (and any other address space allocated to the supervisor) from illegal access.

In summary, the 68000’s user/supervisor modes, exception handling facilities, and memory management make it a very robust processor. Errors in a user program that would otherwise bring the system to a halt, force a switch to the 68000’s supervisor state and allow the operating system to either repair the damage or to terminate the faulty program. The memory management mechanism protects the operating system from illegal access by applications programs and even protects one user program from access by another. In the next section we are going to look at an interesting implication of the 68000’s user/supervisor modes.