Chapter 28. Interrupts

You’re familiar with interruptions already; you get them every day — a request for something special, right now. You might possibly delay it by locking your door, but the sources rarely go away, and you’ll have to take care of it sooner or later.

A computer interrupt is the same thing: something out there wants immediate attention. This request appears as a hardware signal delivered to the CPU. Each interrupt has with it a priority, relating to how soon the system should take care of the situation. Some interrupts are more like completion signals: “The task you gave me is finished, and I’m waiting for something else to do. “ This case is not a high priority, since neglecting it for a while won’t hurt anything (except perhaps performance). Other interrupts are more time-critical: a clock tick, for instance, won’t wait for too long. Eventually the clock will request another interrupt as time passes. Miss too many of these and your system’s notion of the time of day will be seriously off. Another example might be incoming data, which signals its arrival. Wait too long and it may be overwritten by new data — and you’ve lost it.

All of these situations require the CPU to stop what it’s doing (eventually) and go off to execute some unique code that handles this special condition. This code is known as an interrupt service routine, and there is usually a separate routine for each possible interrupt that might occur. Since each interrupt has an associated priority, the system will normally associate the same priority with the service routine. This prevents lower-priority requests from interrupting a high-priority service function.

An interrupt is generally known as an asynchronous event, since it can occur at any time unrelated to the current code being executed by the machine. The CPU will check before fetching the next instruction to see if an interrupt has been requested and if it is at a high enough priority to be recognized. If the interrupt needs to be handled, the system will immediately perform some sort of function call or trap to the appropriate kernel code. As a part of this unexpected break in the normal flow of events (and code), the CPU must ensure that no state information is ever lost: the interrupted code must be able to pick up and continue exactly where it left off, as if nothing had ever happened.

SPARC systems

On a SPARC system, an interrupt is treated just like any other trap condition. The CPU will automatically execute a special trap sequence, based on the interrupt’s priority level. The hardware will stuff a unique code into the Trap Base Register, which provides an address in kernel space. This is immediately used as the PCvalue for the next instruction. Interrupt- and trap-handling routines are invariably written in assembly language, although they may call C functions once the correct environment has been set up. These routines (both C and assembly) are generally given names reflecting the priority level of the interrupt that invoked them or the device they are intended to service. The C functions themselves are usually short and are not supposed to do much other than alert some other routine about a special event. Since they are I/O-related, they normally appear in device driver code. This means, of course, that they are part of the kernel and operate with all the rights and privileges pertaining thereto (and all the responsibilities).

Priority levels

The SPARC CPU has 16 priority levels, ranging from zero to fifteen. Level 0 is where user code executes: anything can interrupt users. Level 1 is sometimes known as “softclock”— the level at which the kernel does time-based housekeeping functions. This is a low enough priority that any hardware needing attention can get it: all devices interrupt at a higher level. The highest level of priority (15) is used to mask out everything except the highest level of interrupt, usually associated with a system shutdown or (in the case of the SPARCserver 1000 and SPARCcenter 2000) a potentially dangerous overheating condition.

SBus and VME peripheral devices have only seven levels of interrupt priority (and usually use only about four of them). These are mapped to internal CPU priority levels by hardware according to the following table.

Table 28-1. SBus and VME device interrupt priorities

CPU Priority

Interrupt/Device

Level 0

Spurious

Level 1

 

Level 2

SBus/VME level 1

Level 3

SBus/VME level 2

Level 4

On-board SCSI devices

Level 5

SBus/VME level 3

Level 6

On-board Ethernet

Level 7

SBus/VME level 4

Level 8

On-board video (retrace)

Level 9

SBus/VME level 5

Level 10

Normal clock (100 Hz)

Level 11

SBus/VME level 6, floppy drive

Level 12

Serial I/O (ZS)

Level 13

SBus/VME level 7, audio device

Level 14

High-level clock (kernel profiling

Level 15

Asynchronous error (memory errors)

Devices that interrupt at SBus/VME level 2 or 3 are normally disk or tape devices (the IPI disk controller, for example, interrupts at level 2). Bus level 4 is used for the somewhat smart ALM-2 serial I/O card; this does have some buffering capability and does not need attention quite as promptly.

Unfortunately, the mappings are not linear or consistent, but at least they are in order.

Note the difference between disk/tape and character input. Generally, any response to a CPU request is a low priority; it’s just informative. Any unsolicited input occurs at a fairly high priority, since more may be arriving at any time. Also, unsophisticated hardware is generally handled at a higher priority, partly because the smarter stuff can take care of itself and requires little attention from the main processor.

For several reasons, the high-priority interrupt service routines are generally very restricted in what they can do. First, since they will most likely block out other requests that might occur at the same time or while the service routine is executing, they need to do their thing and get out fast. Second, since they may be associated with devices that are fairly high speed, they must avoid doing any extensive time-consuming processing or they will lose succeeding interrupts and possibly data. As a general rule, interrupt service functions, regardless of their priority, will do little more than save data in a safe place, signal some other function that the data is there, and possibly start up the next device request if one is pending.

Serial devices

TTY ports (serial input lines) are handled uniquely. Since the data is fairly volatile, it needs to be handled quickly. However, since it may involve some extensive work, especially when dealing with STREAMS, it can’t all be done at a high-priority level. This results in a two-phased scheme.

Every incoming character for the standard serial ports (the ZS devices, which stands for Zilog Serial — the chip that handles them) results in an interrupt request. The hardware has a very small, two-character “silo,” which it uses to store the character until the software function can get to it. If the CPU doesn’t get there in time, the data will be overwritten, resulting in a “silo overflow” condition and an error message to that effect on the console. This situation means data was lost, although normally it’s associated with the mouse and is not catastrophic. The interrupt service routine doesn’t want to spend much time worrying about character processing, so it just grabs the character(s) from the silo and stuffs the data into a small circular “ring buffer,” which normally holds about 256 characters. Very high speed input on the serial lines may result in the ring buffer being filled before the STREAMS code can get to it; this may result in a “ring buffer overflow” message. The interrupt service routine now sets a flag noting that data is available, and one of the housekeeping functions done at clock-tick time will take the incoming data and parcel it out to the correct Stream. The STREAMS code, also run periodically based on clock ticks, does whatever processing is necessary, such as recognition of special characters like Control-C, echoing back to the originator, or sending full lines of text to the user programs.

If the system is locked up at a priority level higher than 1, this may result in the L1-A (or Stop-A) key sequence apparently being ignored. It’s not really ignored, but the system won’t get around to processing it until the STREAMS code (at level 1) is allowed to run. This may never happen: the system looks (and is) hung. A “break” on an ASCII terminal used as a console (or unplugging the normal keyboard and plugging it back in again) may have better results, because this is actually a line condition rather than an incoming character and, as such, can be recognized and handled by the higher-priority ZS interrupt service routine.

Vectored interrupts

On some systems, especially those that are VME-based, the interrupt response sequence performed by the hardware may look something like this:

   Device: Interrupt request  -->>  CPU 
              Acknowledgment  <<--  CPU 
               Vector number  -->>  CPU 

As a part of the hardware signal sequence, the device will supply a vector number to the CPU. This vector is an ID number associated with the device or card and helps the CPU get to the appropriate service routine quickly. Assuming that each device has a unique ID, the vector number is used as an index into an array of function pointers, and the interrupt recognition hardware in the CPU will actually make a call directly through the vector slot to the appropriate function. This way there’s no overhead involved in identifying the device associated with the interrupt request and in locating the correct function to call. It’s all done in hardware.

This does lead to interesting problems if more than one device attempts to use the same vector number. Hopefully, this is caught by the system during initialization. The system administrator must be very careful to make sure that conflicts do not arise when adding new hardware.

Polled interrupts

A simpler form of interrupt response has essentially one vector associated with each interrupt priority level. Interrupt recognition is faster and easier (in hardware) but requires the software to check each device that might interrupt at that particular priority level to see if it was the one(s) issuing the request. This is known as polling.

Some software complications result. Since the hardware cannot uniquely identify a device, the driver code for each device must have a function that checks to see if that particular piece of hardware was responsible. This is normally done by the interrupt service routine, which can be called at any time, whether or not an interrupt was pending. Thus, the interrupt service routine in a driver must first check to see if there really was an interrupt before proceeding.

Polling is the method used by SPARC. The CPU does not support a large vector; there is one per interrupt priority level, giving 16 slots. The first one, level 0, is not used—it actually holds a pointer to a spurious error function (no device should be able to interrupt at level 0!). For SPARC machines that handle VME boards (older Sun-4 systems and the 600 series), additional hardware receives and holds the vector information provided by the devices. This is made available to the software and is used to locate an interrupt service routine — but the hardware doesn’t do it all for you, this time.

Interrupts in tracebacks

An interrupt is just another form of trap, so any interrupt response is going to operate the same way something like a page fault would. A special trap frame is stored on the stack, and an interrupt service routine is called from the vector. This function then polls various pieces of hardware at that interrupt level.

You can usually identify an interrupt by noticing a driver interrupt service function on the stack. These almost universally end in “int” or “intr,” so something like zsint() would refer to the interrupt service function for the ZS device driver. Just before this in time (just below it on the stack trace), you should find a low-level assembly function that does the polling. Look for something like level10() as a function name, which would correspond to interrupt priority level 10 processing. Just below this on the stack will appear the stack frame that contains the PC/ nPC values from the code being executed when the interrupt occurred.

Some interrupt priority levels are devoted to one device, just as if it were a real vectored interrupt system. This means the kernel software need not poll; it knows exactly which device caused the interrupt. The clock is the main device for which this is done, since the great frequency of clock interrupts (100 per second) demands efficiency in handling them. The function that handles the high-priority clock hardware “tick” is normally known as “hardclock”; “softclock” is the lower-priority housekeeping function invoked by hardclock when and if there is something to be done that is time-based (required periodically or after a certain elapsed time). This could include STREAMS service procedures, character data handling, callback functions (also known as callout functions), or signal generation, such as alarm clock signals delivered to user programs.

Callout functions are also known as delay functions or deadman timers; a driver or kernel routine will request that a function be invoked after a certain amount of time has passed. This may be a cleanup function, to be called if a device has not answered within a certain period of time, or merely some sort of delay function to send certain data out after a specified time has elapsed.

For an example of an interrupt from the keyboard appearing in a traceback, look at the third case study, “Hanging Instead of Swapping”, which examines a hung system. In this case, recognition of the L1-A stop sequence appears on the top part of the stack. The interrupt itself occurred during the idle function; the trap frame contains a PC and nPCvalue from within that routine.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.6.194