• Keine Ergebnisse gefunden

DEVICE DRIVERS 3-31

Im Dokument INTER ACTIVE (Seite 70-74)

System Utility Functions

DEVICE DRIVERS 3-31

All spl functions return the previous priority level of the parameter passed to it. The splxO function is useful in cases where the processor priority level may have been raised already but where the driver does not know that it has been raised sufficiently to block out the proper level of interrupts. When the driver is ready to lower the priority level, it should not lower it all the way to

o

in that case but rather to the old priority level. Consider the following code:

register int s;

s = splS();

while «cp = getcb(&tp->t_rawq)) != NULL) putcf(cp) ;

tp->t_delct

=

0;

splx(s) ;

Particularly nasty race conditions can occur if spl functions are not used with the sleepO function. For example, the code segment

driver . state I = condition;

while (driver.state & condition) sleep(&driver.state, PRIORITY);

will cause the process to sleep if the condition bit is set in the field

driver. state. (Since processes could sleep on the address for several events, the sleep call is enclosed in the while loop so that when awakened, the code will again check that the condition is indeed no longer true. This is one rea-son it is best to sleep on different address values for different sleep rearea-sons.) Without use of the splO function, the process could check the condition bit, find it true, and attempt to call sleep. But if an interrupt occurred before the process called sleep and the interrupt handler checked the condition bit to determine if a process was sleeping, it would assume the process was asleep and call wakeup to awaken it. Consider the following code:

3-32 ISDG

if (driver.state &. condition) {

}

driver.state &.= -condition;

wakeup(&.driver.state) ;

By the time the interrupted process calls sleepO, it will have missed the wakeupO call, and another one may never come. By bracketing the calls to sleepO with splO function calls, the driver prevents the race condition:

splS();

driver.state 1= oandition;

while (driver.state &. condition) sleep ( &.driver. state, PRIORITY);

splO( );

Interrupt Priority Level

Another kernel characteristic, Interrupt Priority Level (IPL), interacts with the spl functions. Some processor architectures have a hardware priority scheme that defines a hierarchy of which devices can interrupt others. Since the 80386 processor does not have such a scheme, UNIX System V /386 has assignable priority levels that simulate hardware priority levels. By defining an IPL in the sdevice file, we can protect a driver's critical regions at the appropriate level. IPL8 is the highest level and is reserved for the internal clock. Drivers at this level cannot be interrupted by other devices (their inter-rupt routines execute at sp17()). A device at IPL6 can be interinter-rupted by a device at IPL7 or IPL8. In UNIX System V /386, the base system device drivers use the following IPL levels. This shows that the serial ports run at the highest priority to prevent loss of data. The lineprinter is more safely interrupted and is given a low IPL. See the section "Controller Interface Basics" for a more complete definition of the device configuration assign-ments.

DEVICE DRIVERS 3-33

DEV IPL Device attached

---clock 8 UNIX System clock

asy 7 Serial Ports

fd 6 Floppy Disk

hd 5 Hard Disk

1m 6 Keyboard

lp 3 Line printer (Parallel R:>rt)

rtc 5 Real T:ime Clock

Care must be taken not to overstate device interrupt priority and to limit the amount of time spent at high levels. For example, if any driver elevates to spl70 for more than a few milliseconds, loss of UNIX System clock time may result.

Sleep Priorities

The second parameter to the sleepO function, a scheduling parameter used when the process awakens from its sleep, must be a constant and not a vari-able. The parameter, called the sleep priority, has critical effects on the sleep-ing process's reaction to signals. If it is lower than the manifest constant PZERO (25 on most systems), that is, it is higher than PZERO (lower value priority levels mean higher priority in the UNIX System), then the system does not awaken sleeping processes on receipt of a signal. However, if it is lower than PZERO, then the system awakens sleeping processes "prema-turely." If the PCATCH bit (discussed later) is not set, the process immedi-ately finishes the system call, that is, it executes a 10ngjmpO out of the driver.

Sleep calls the 10ngjmpO function. When the system executes 10ngjmpO, it does not follow the conventional C function call/return sequence but instead resets the program counter, stack pointer, and data registers to the values they had when the most recent setjmpO function call was done.

For instance, if a signal is sent to a process sleeping in the following sleep call, the system call will end immediately without returning to the code that called sleep:

3-34 ISDG

When a driver must call sleep, how should the driver programmer determine the sleep priority? The first decision is whether the process should ignore the receipt of signals or not. If the driver puts the process to sleep for an event that is "sure" to happen, then it should ignore receipt of signals and sleep at priority greater than PZERO (numerically, less than PZERO).

An example of an event that is "sure" to happen is waiting for a locked data structure to be unlocked:

if (tp->t_state & T_IDCKED)

sleep (&tp->t_state, PZERD - 5);

In this case, another process locked the data structure and went to sleep, but it left the data structure locked so that no other process could change it before it awakened. Since that process will eventually awaken and unlock the data structure and then awaken all other processes waiting for the lock to clear, the event (the wakeup call announcing the unlock) is sure to happen. Otherwise, the driver has a bug.

If the driver puts a process to sleep while it awaits an event that may not happen, the process must sleep at a priority less than PZERO (numerically greater than PZERO). An example of an event that may not happen is wait-ing for data to arrive from a remote device. For example, when the system reads data from a terminal, the read system call sleeps in the terminal driver waiting for data to arrive from the terminal. If data never arrives, the read will sleep indefinitely. When a user at the terminal hits the <BREAK> key or even hangs up, the terminal driver interrupt handler sends a signal to the reading process still asleep, and the signal causes the reading process to finish the system call without having read any data. If the driver had slept at a priority value that ignores signals, the process could have been awakened only by a specific wakeup call. If that wakeup call could never happen (the user hung up the terminal), then the process would sleep forever, clearly an undesirable characteristic.

Priority values range between 0 (highest priority) and the constant PUSER (lowest system priority, usually around 60). When the driver programmer decides whether the process should ignore signals or not, he/she must choose the priority values so as not to affect process scheduling adversely. The sys-tem should be benchmarked using several sleep priority values to tune syssys-tem performance with the new driver.

Im Dokument INTER ACTIVE (Seite 70-74)