Linux Device Drivers 2nd Edition (779877), страница 49
Текст из файла (страница 49)
Italso runs very quickly—a task that resubmits itself to the scheduler queue can runhundreds or thousands of times within a single timer tick. Even on a very heavilyloaded system, the latency in the scheduler queue is quite small.The timer queueThe timer queue is different from the scheduler queue in that the queue(tq_timer) is directly available.
Also, of course, tasks run from the timer queueare run in interrupt mode. Additionally, you’re guaranteed that the queue will runat the next clock tick, thus eliminating latency caused by system load.The sample code implements /pr oc/jiqtimer with the timer queue. For this queue,it must use queue_task to get things going:int jiq_read_timer(char *buf, char **start, off_t offset,int len, int *eof, void *data){jiq_data.len = 0;jiq_data.buf = buf;jiq_data.jiffies = jiffies;jiq_data.queue = &tq_timer;/*/*/*/*nothing printed, yet */print in this place */initial time */reregister yourself here */queue_task(&jiq_task, &tq_timer);interruptible_sleep_on(&jiq_wait);/* ready to run *//* sleep till completion */*eof = 1;return jiq_data.len;}19622 June 2001 16:37http://openlib.org.uaTask QueuesThe following is what head /proc/jiqtimer returned on a system that was compilinga new kernel:time delta interrupt pid cpu command450848451187830 cc1450848461187830 cc1450848471187830 cc1450848481187830 cc1450848491187840 as450848501187581 cc1450848511187890 cpp450848521187581 cc1450848531187581 cc1450848541187581 cc1450848551187581 cc1Note, this time, that exactly one timer tick goes by between each invocation of thetask, and that an arbitrary process is running.The immediate queueThe last predefined queue that can be used by modularized code is the immediatequeue.
This queue is run via the bottom-half mechanism, which means that oneadditional step is required to use it. Bottom halves are run only when the kernelhas been told that a run is necessary; this is accomplished by ‘‘marking’’ the bottom half. In the case of tq_immediate, the necessary call is mark_bh(IMMEDIATE_BH). Be sure to call mark_bh after the task has been queued; otherwise, thekernel may run the task queue before your task has been added.The immediate queue is the fastest queue in the system—it’s executed soonestand is run in interrupt time. The queue is consumed either by the scheduler or assoon as one process returns from its system call.
Typical output can look like this:time delta interrupt pid cpu command451294490188830 head451294534100 swapper45129453016010 X45129453016010 X45129453016010 X45129453016010 X451294541100 swapper45129454016010 X45129454016010 X45129454016010 X45129454016010 X45129454016010 X45129454016010 X45129454016010 XIt’s clear that the queue can’t be used to delay the execution of a task—it’s an‘‘immediate’’ queue.
Instead, its purpose is to execute a task as soon as possible,19722 June 2001 16:37http://openlib.org.uaChapter 6: Flow of Timebut at a safe time. This feature makes it a great resource for interrupt handlers,because it offers them an entry point for executing program code outside of theactual interrupt management routine. The mechanism used to receive networkpackets, for example, is based on a similar mechanism.Please note that you should not reregister your task in this queue (although we doit in jiqimmed for explanatory purposes). The practice gains nothing and may lockthe computer hard if run on some version/platform pairs. Some implementationsused to rerun the queue until it was empty. This was true, for example, for version2.0 running on the PC platform.Running Your Own Task QueuesDeclaring a new task queue is not difficult.
A driver is free to declare a new taskqueue, or even several of them; tasks are queued just as we’ve seen with the predefined queues discussed previously.Unlike a predefined task queue, however, a custom queue is not automatically runby the kernel. The programmer who maintains a queue must arrange for a way ofrunning it.The following macro declares the queue and expands to a variable declaration.You’ll most likely place it at the beginning of your file, outside of any function:DECLARE_TASK_QUEUE(tq_custom);After declaring the queue, you can invoke the usual functions to queue tasks.
Thecall just shown pairs naturally with the following:queue_task(&custom_task, &tq_custom);The following line will run tq_custom when it is time to execute the task-queueentries that have accumulated:run_task_queue(&tq_custom);If you want to experiment with custom queues now, you need to register a function to trigger the queue in one of the predefined queues.
Although this may looklike a roundabout way to do things, it isn’t. A custom queue can be useful whenever you need to accumulate jobs and execute them all at the same time, even ifyou use another queue to select that ‘‘same time.’’TaskletsShortly before the release of the 2.4 kernel, the developers added a new mechanism for the deferral of kernel tasks. This mechanism, called tasklets, is now thepreferred way to accomplish bottom-half tasks; indeed, bottom halves themselvesare now implemented with tasklets.19822 June 2001 16:37http://openlib.org.uaTask QueuesTasklets resemble task queues in a number of ways. They are a way of deferring atask until a safe time, and they are always run in interrupt time.
Like task queues,tasklets will be run only once, even if scheduled multiple times, but tasklets maybe run in parallel with other (different) tasklets on SMP systems. On SMP systems,tasklets are also guaranteed to run on the CPU that first schedules them, whichprovides better cache behavior and thus better performance.Each tasklet has associated with it a function that is called when the tasklet is to beexecuted. The life of some kernel developer was made easier by giving that function a single argument of type unsigned long, which makes life a little moreannoying for those who would rather pass it a pointer; casting the long argumentto a pointer type is a safe practice on all supported architectures and pretty common in memory management (as discussed in Chapter 13). The tasklet function isof type void; it returns no value.Software support for tasklets is part of <linux/interrupt.h>, and the taskletitself must be declared with one of the following:DECLARE_TASKLET(name, function, data);Declares a tasklet with the given name; when the tasklet is to be executed (asdescribed later), the given function is called with the (unsigned long) datavalue.DECLARE_TASKLET_DISABLED(name, function, data);Declares a tasklet as before, but its initial state is ‘‘disabled,’’ meaning that itcan be scheduled but will not be executed until enabled at some future time.The sample jiq driver, when compiled against 2.4 headers, implements /pr oc/jiqtasklet, which works like the other jiq entries but uses tasklets; we didn’t emulatetasklets for older kernel versions in sysdep.h.
The module declares its tasklet asvoid jiq_print_tasklet (unsigned long);DECLARE_TASKLET (jiq_tasklet, jiq_print_tasklet, (unsigned long)&jiq_data);When your driver wants to schedule a tasklet to run, it calls tasklet_schedule:tasklet_schedule(&jiq_tasklet);Once a tasklet is scheduled, it is guaranteed to be run once (if enabled) at a safetime.
Tasklets may reschedule themselves in much the same manner as taskqueues. A tasklet need not worry about running against itself on a multiprocessorsystem, since the kernel takes steps to ensure that any given tasklet is only running in one place. If your driver implements multiple tasklets, however, it shouldbe prepared for the possibility that more than one of them could run simultaneously. In that case, spinlocks must be used to protect critical sections of the code(semaphores, which can sleep, may not be used in tasklets since they run in interrupt time).19922 June 2001 16:37http://openlib.org.uaChapter 6: Flow of TimeThe output from /pr oc/jiqtasklet looks like this:time delta interrupt pid cpu command454723770189040 head454723781100 swapper454723791100 swapper454723801100 swapper454723833100 swapper45472383016010 X45472383016010 X45472383016010 X45472383016010 X454723896100 swapperNote that the tasklet always runs on the same CPU, even though this output wasproduced on a dual-CPU system.The tasklet subsystem provides a few other functions for advanced use of tasklets:void tasklet_disable(struct tasklet_struct *t);This function disables the given tasklet.
The tasklet may still be scheduledwith tasklet_schedule, but its execution will be deferred until a time when thetasklet has been enabled again.void tasklet_enable(struct tasklet_struct *t);Enables a tasklet that had been previously disabled. If the tasklet has alreadybeen scheduled, it will run soon (but not directly out of tasklet_enable).void tasklet_kill(struct tasklet_struct *t);This function may be used on tasklets that reschedule themselves indefinitely.tasklet_kill will remove the tasklet from any queue that it is on. In order toavoid race conditions with the tasklet rescheduling itself, this function waitsuntil the tasklet executes, then pulls it from the queue.
Thus, you can be surethat tasklets will not be interrupted partway through. If, however, the taskletis not currently running and rescheduling itself, tasklet_kill may hang.tasklet_kill may not be called in interrupt time.Kernel TimersThe ultimate resources for time keeping in the kernel are the timers. Timers areused to schedule execution of a function (a timer handler) at a particular time inthe future. They thus work differently from task queues and tasklets in that youcan specify when in the future your function will be called, whereas you can’t tellexactly when a queued task will be executed.
On the other hand, kernel timersare similar to task queues in that a function registered in a kernel timer is executedonly once—timers aren’t cyclic.20022 June 2001 16:37http://openlib.org.uaKernel TimersThere are times when you need to execute operations detached from any process’s context, like turning off the floppy motor or finishing another lengthy shutdown operation. In that case, delaying the return from close wouldn’t be fair to theapplication program. Using a task queue would be wasteful, because a queuedtask must continually reregister itself until the requisite time has passed.A timer is much easier to use.