Linux Device Drivers 2nd Edition (779877), страница 46
Текст из файла (страница 46)
To this aim, we’ll implement a rdtscl function for MIPS processors that works in the same way as the x86one.We’ll base the example on MIPS because most MIPS processors feature a 32-bitcounter as register 9 of their internal ‘‘coprocessor 0.’’ To access the register, only18322 June 2001 16:37http://openlib.org.uaChapter 6: Flow of Timereadable from kernel space, you can define the following macro that executes a‘‘move from coprocessor 0’’ assembly instruction:*#define rdtscl(dest) \_ _asm_ _ _ _volatile_ _("mfc0 %0,$9; nop" : "=r" (dest))With this macro in place, the MIPS processor can execute the same code shownearlier for the x86.What’s interesting with gcc inline assembly is that allocation of general-purposeregisters is left to the compiler. The macro just shown uses %0 as a placeholder for‘‘argument 0,’’ which is later specified as ‘‘any register (r) used as output (=).’’ Themacro also states that the output register must correspond to the C expressiondest.
The syntax for inline assembly is very powerful but somewhat complex,especially for architectures that have constraints on what each register can do(namely, the x86 family). The complete syntax is described in the gcc documentation, usually available in the info documentation tree.The short C-code fragment shown in this section has been run on a K7-class x86processor and a MIPS VR4181 (using the macro just described). The formerreported a time lapse of 11 clock ticks, and the latter just 2 clock ticks. The smallfigure was expected, since RISC processors usually execute one instruction perclock cycle.Knowing the Current TimeKernel code can always retrieve the current time by looking at the value ofjiffies.
Usually, the fact that the value represents only the time since the lastboot is not relevant to the driver, because its life is limited to the system uptime.Drivers can use the current value of jiffies to calculate time intervals acrossevents (for example, to tell double clicks from single clicks in input devicedrivers). In short, looking at jiffies is almost always sufficient when you needto measure time intervals, and if you need very sharp measures for short timelapses, processor-specific registers come to the rescue.It’s quite unlikely that a driver will ever need to know the wall-clock time, sincethis knowledge is usually needed only by user programs such as cr on and at. Ifsuch a capability is needed, it will be a particular case of device usage, and thedriver can be correctly instructed by a user program, which can easily do the con-* The trailing nop instruction is required to prevent the compiler from accessing the targetregister in the instruction immediately following mfc0.
This kind of interlock is typical ofRISC processors, and the compiler can still schedule useful instructions in the delay slots.In this case we use nop because inline assembly is a black box for the compiler and nooptimization can be performed.18422 June 2001 16:37http://openlib.org.uaKnowing the Current Timeversion from wall-clock time to the system clock. Dealing directly with wall-clocktime in a driver is often a sign that policy is being implemented, and should thusbe looked at closely.If your driver really needs the current time, the do_gettimeofday function comes tothe rescue. This function doesn’t tell the current day of the week or anything likethat; rather, it fills a struct timeval pointer — the same as used in the gettimeofday system call—with the usual seconds and microseconds values.
The prototype for do_gettimeofday is:#include <linux/time.h>void do_gettimeofday(struct timeval *tv);The source states that do_gettimeofday has ‘‘near microsecond resolution’’ formany architectures. The precision does vary from one architecture to another,however, and can be less in older kernels. The current time is also available(though with less precision) from the xtime variable (a struct timeval);however, direct use of this variable is discouraged because you can’t atomicallyaccess both the timeval fields tv_sec and tv_usec unless you disable interrupts.
As of the 2.2 kernel, a quick and safe way of getting the time quickly, possibly with less precision, is to call get_fast_time:void get_fast_time(struct timeval *tv);Code for reading the current time is available within the jit (‘‘Just In Time’’) module in the source files provided on the O’Reilly FTP site. jit creates a file called/pr oc/currentime, which returns three things in ASCII when read:•The current time as returned by do_gettimeofday•The current time as found in xtime•The current jiffies valueWe chose to use a dynamic /pr oc file because it requires less module code—it’snot worth creating a whole device just to return three lines of text.If you use cat to read the file multiple times in less than a timer tick, you’ll see thedifference between xtime and do_gettimeofday, reflecting the fact that xtime isupdated less frequently:morgana%gettime:xtime:jiffies:gettime:xtime:jiffies:gettime:xtime:jiffies:cd /proc; cat currentime currentime currentime846157215.937221846157215.9311881308094846157215.939950846157215.9311881308094846157215.942465846157215.941188130809518522 June 2001 16:37http://openlib.org.uaChapter 6: Flow of TimeDelaying ExecutionDevice drivers often need to delay the execution of a particular piece of code for aperiod of time—usually to allow the hardware to accomplish some task.
In thissection we cover a number of different techniques for achieving delays. The circumstances of each situation determine which technique is best to use; we’ll goover them all and point out the advantages and disadvantages of each.One important thing to consider is whether the length of the needed delay islonger than one clock tick. Longer delays can make use of the system clock;shorter delays typically must be implemented with software loops.Long DelaysIf you want to delay execution by a multiple of the clock tick or you don’t requirestrict precision (for example, if you want to delay an integer number of seconds),the easiest implementation (and the most braindead) is the following, also knownas busy waiting:unsigned long j = jiffies + jit_delay * HZ;while (jiffies < j)/* nothing */;This kind of implementation should definitely be avoided.
We show it herebecause on occasion you might want to run this code to understand better theinternals of other code.So let’s look at how this code works. The loop is guaranteed to work becausejiffies is declared as volatile by the kernel headers and therefore is rereadany time some C code accesses it.
Though ‘‘correct,’’ this busy loop completelylocks the processor for the duration of the delay; the scheduler never interrupts aprocess that is running in kernel space. Still worse, if interrupts happen to be disabled when you enter the loop, jiffies won’t be updated, and the while condition remains true forever. You’ll be forced to hit the big red button.This implementation of delaying code is available, like the following ones, in thejit module. The /pr oc/jit* files created by the module delay a whole second everytime they are read.
If you want to test the busy wait code, you can read /pr oc/jitbusy, which busy-loops for one second whenever its read method is called; acommand such as dd if=/proc/jitbusy bs=1 delays one second each time it reads acharacter.As you may suspect, reading /pr oc/jitbusy is terrible for system performance,because the computer can run other processes only once a second.18622 June 2001 16:37http://openlib.org.uaDelaying ExecutionA better solution that allows other processes to run during the time interval is thefollowing, although it can’t be used in hard real-time tasks or other time-critical situations.while (jiffies < j)schedule();The variable j in this example and the following ones is the value of jiffies atthe expiration of the delay and is always calculated as just shown for busy waiting.This loop (which can be tested by reading /pr oc/jitsched ) still isn’t optimal.
Thesystem can schedule other tasks; the current process does nothing but release theCPU, but it remains in the run queue. If it is the only runnable process, it willactually run (it calls the scheduler, which selects the same process, which calls thescheduler, which . . . ). In other words, the load of the machine (the average number of running processes) will be at least one, and the idle task (process number0, also called swapper for historical reasons) will never run. Though this issue mayseem irrelevant, running the idle task when the computer is idle relieves the processor’s workload, decreasing its temperature and increasing its lifetime, as well asthe duration of the batteries if the computer happens to be your laptop.
Moreover,since the process is actually executing during the delay, it will be accounted for allthe time it consumes. You can see this by running time cat /proc/jitsched.If, instead, the system is very busy, the driver could end up waiting rather longerthan expected. Once a process releases the processor with schedule, there are noguarantees that it will get it back anytime soon.
If there is an upper bound on theacceptable delay time, calling schedule in this manner is not a safe solution to thedriver’s needs.Despite its drawbacks, the previous loop can provide a quick and dirty way tomonitor the workings of a driver. If a bug in your module locks the system solid,adding a small delay after each debugging printk statement ensures that everymessage you print before the processor hits your nasty bug reaches the system logbefore the system locks. Without such delays, the messages are correctly printed tothe memory buffer, but the system locks before klogd can do its job.The best way to implement a delay, however, is to ask the kernel to do it for you.There are two ways of setting up short-term timeouts, depending on whether yourdriver is waiting for other events or not.If your driver uses a wait queue to wait for some other event, but you also want tobe sure it runs within a certain period of time, it can use the timeout versions ofthe sleep functions, as shown in “Going to Sleep and Awakening” in Chapter 5:sleep_on_timeout(wait_queue_head_t *q, unsigned long timeout);interruptible_sleep_on_timeout(wait_queue_head_t *q,unsigned long timeout);Both versions will sleep on the given wait queue, but will return within the timeout period (in jiffies) in any case.
They thus implement a bounded sleep that will18722 June 2001 16:37http://openlib.org.uaChapter 6: Flow of Timenot go on forever. Note that the timeout value represents the number of jiffies towait, not an absolute time value. Delaying in this manner can be seen in theimplementation of /pr oc/jitqueue:wait_queue_head_t wait;init_waitqueue_head (&wait);interruptible_sleep_on_timeout(&wait, jit_delay*HZ);In a normal driver, execution could be resumed in either of two ways: somebodycalls wake_up on the wait queue, or the timeout expires. In this particular implementation, nobody will ever call wake_up on the wait queue (after all, no othercode even knows about it), so the process will always wake up when the timeoutexpires.