Linux Device Drivers 2nd Edition (779877), страница 37
Текст из файла (страница 37)
Any code that sleepsshould do so in a loop that tests the condition after returning from the sleep, asdiscussed in “A Sample Implementation: scullpipe” later in this chapter.A Deeper Look at Wait QueuesThe previous discussion is all that most driver writers will need to know to gettheir job done.
Some, however, will want to dig deeper. This section attempts toget the curious started; everybody else can skip to the next section without missing much that is important.The wait_queue_head_t type is a fairly simple structure, defined in<linux/wait.h>. It contains only a lock variable and a linked list of sleepingprocesses. The individual data items in the list are of type wait_queue_t, andthe list is the generic list defined in <linux/list.h> and described in “LinkedLists” in Chapter 10. Normally the wait_queue_t structures are allocated on thestack by functions like interruptible_sleep_on; the structures end up in the stackbecause they are simply declared as automatic variables in the relevant functions.In general, the programmer need not deal with them.Some advanced applications, however, can require dealing with wait_queue_tvariables directly.
For these, it’s worth a quick look at what actually goes on insidea function like interruptible_sleep_on. The following is a simplified version of theimplementation of interruptible_sleep_on to put a process to sleep:void simplified_sleep_on(wait_queue_head_t *queue){wait_queue_t wait;init_waitqueue_entry(&wait, current);current->state = TASK_INTERRUPTIBLE;add_wait_queue(queue, &wait);schedule();remove_wait_queue (queue, &wait);}14422 June 2001 16:36http://openlib.org.uaBlocking I/OThe code here creates a new wait_queue_t variable (wait, which gets allocated on the stack) and initializes it.
The state of the task is set to TASK_INTERRUPTIBLE, meaning that it is in an interruptible sleep. The wait queue entry isthen added to the queue (the wait_queue_head_t * argument). Then scheduleis called, which relinquishes the processor to somebody else. schedule returnsonly when somebody else has woken up the process and set its state toTASK_RUNNING.
At that point, the wait queue entry is removed from the queue,and the sleep is done.Figure 5-1 shows the internals of the data structures involved in wait queues andhow they are used by processes.KEYwait_queue_head_tspinlock_t lock;Wait Queues in Linux 2.4No process is sleeping on the queuestructlist_head task_list;wait_queue_tstruct task_struct *task;struct list_head task_list;The current process is sleeping on the device’s queueThe device structurewith itswait_queue_head_tThe structwait_queue itselfThe currentprocess andits associatedstack pageSeveral processes are sleeping on the same queueAnotherprocess andits associatedstack pageFigur e 5-1.
Wait queues in Linux 2.4A quick look through the kernel shows that a great many procedures do theirsleeping ‘‘manually’’ with code that looks like the previous example. Most of those14522 June 2001 16:36http://openlib.org.uaChapter 5: Enhanced Char Driver Operationsimplementations date back to kernels prior to 2.2.3, before wait_event was introduced. As suggested, wait_event is now the preferred way to sleep on an event,because interruptible_sleep_on is subject to unpleasant race conditions.
A fulldescription of how that can happen will have to wait until “Going to Sleep Without Races” in Chapter 9; the short version, simply, is that things can change in thetime between when your driver decides to sleep and when it actually gets aroundto calling interruptible_sleep_on.One other reason for calling the scheduler explicitly, however, is to do exclusivewaits. There can be situations in which several processes are waiting on an event;when wake_up is called, all of those processes will try to execute. Suppose thatthe event signifies the arrival of an atomic piece of data. Only one process will beable to read that data; all the rest will simply wake up, see that no data is available, and go back to sleep.This situation is sometimes referred to as the ‘‘thundering herd problem.’’ In highperformance situations, thundering herds can waste resources in a big way.
Thecreation of a large number of runnable processes that can do no useful work generates a large number of context switches and processor overhead, all for nothing.Things would work better if those processes simply remained asleep.For this reason, the 2.3 development series added the concept of an exclusivesleep. If processes sleep in an exclusive mode, they are telling the kernel to wakeonly one of them. The result is improved performance in some situations.The code to perform an exclusive sleep looks very similar to that for a regularsleep:void simplified_sleep_exclusive(wait_queue_head_t *queue){wait_queue_t wait;init_waitqueue_entry(&wait, current);current->state = TASK_INTERRUPTIBLE | TASK_EXCLUSIVE;add_wait_queue_exclusive(queue, &wait);schedule();remove_wait_queue (queue, &wait);}Adding the TASK_EXCLUSIVE flag to the task state indicates that the process is inan exclusive wait.
The call to add_wait_queue_exclusive is also necessary, however. That function adds the process to the end of the wait queue, behind all others. The purpose is to leave any processes in nonexclusive sleeps at thebeginning, where they will always be awakened. As soon as wake_up hits the firstexclusive sleeper, it knows it can stop.14622 June 2001 16:36http://openlib.org.uaBlocking I/OThe attentive reader may have noticed another reason to manipulate wait queuesand the scheduler explicitly. Whereas functions like sleep_on will block a processon exactly one wait queue, working with the queues directly allows sleeping onmultiple queues simultaneously. Most drivers need not sleep on more than onequeue; if yours is the exception, you will need to use code like what we’veshown.Those wanting to dig even deeper into the wait queue code can look at<linux/sched.h> and kernel/sched.c.Writing Reentrant CodeWhen a process is put to sleep, the driver is still alive and can be called byanother process.
Let’s consider the console driver as an example. While an application is waiting for keyboard input on tty1, the user switches to tty2 andspawns a new shell. Now both shells are waiting for keyboard input within theconsole driver, although they sleep on different wait queues: one on the queueassociated with tty1 and the other on the queue associated with tty2.
Each process is blocked within the interruptible_sleep_on function, but the driver can stillreceive and answer requests from other ttys.Of course, on SMP systems, multiple simultaneous calls to your driver can happeneven when you do not sleep.Such situations can be handled painlessly by writing reentrant code. Reentrantcode is code that doesn’t keep status information in global variables and thus isable to manage interwoven invocations without mixing anything up.
If all the status information is process specific, no interference will ever happen.If status information is needed, it can either be kept in local variables within thedriver function (each process has a different stack page in kernel space wherelocal variables are stored), or it can reside in private_data within the filpaccessing the file. Using local variables is preferred because sometimes the samefilp can be shared between two processes (usually parent and child).If you need to save large amounts of status data, you can keep the pointer in alocal variable and use kmalloc to retrieve the actual storage space.
In this case youmust remember to kfr ee the data, because there’s no equivalent to ‘‘everything isreleased at process termination’’ when you’re working in kernel space. Using localvariables for large items is not good practice, because the data may not fit the single page of memory allocated for stack space.You need to make reentrant any function that matches either of two conditions.First, if it calls schedule, possibly by calling sleep_on or wake_up. Second, if itcopies data to or from user space, because access to user space might page-fault,and the process will be put to sleep while the kernel deals with the missing page.14722 June 2001 16:36http://openlib.org.uaChapter 5: Enhanced Char Driver OperationsEvery function that calls any such functions must be reentrant as well.