Linux Kernel & Device Driver Programming

Ch 7 - Time, Delays, and Deferred Work

 

Topics


Clocks vs. Timers


Many Time References (Clocks)


jiffies


Do you understand what the symbol "volatile" means in the declaration of a variable in the C programming language, and why that is critical here?

There will be variables in drivers that also need to be declared as volatile, but people often tend to forget about it. The results can be serious.


Dealing with jiffies wrap

example: to compute how many milliseconds elapsed between two clock readings

t1 = jiffies;
 ...other intervening computations...
t2 = jiffies;
diff = (long)t2 - (long)t1;
msec = diff * 1000 / HZ;

The key here is wrap-around. In general, if the clock may have wrapped around more than once, there is no way to tell how much time has elapsed. However, if the times t1 and t2 are known to be fairly close, we can reliably compute the difference in a way that takes into account the possibility that the clock may have wrapped between times.

To see the effect of using signed arithmetic to compare jiffy time values, look at the following program. The variable T1 is set to the largest possible signed integer, so that adding one to it will result in wrap-around to zero.

#include <stdio.h>
#include <limits.h>
int main() {
  unsigned long T1, T2;
  T1 = ULONG_MAX;
  T2 = T1 + 1;
  fprintf (stderr, "unsigned  (%lx < %lx) = %d, (%lx - %lx) = %lx, (%lx - %lx) = %lx\n",
     T1, T2, T1 < T2, T2, T1, T2 - T1, T1, T2, T1 - T2);
  fprintf (stderr, "signed    (%ld < %ld) = %d, (%ld - %ld) = %ld, (%ld - %ld) = %ld\n",
     (long) T1, (long) T2, (long) T1 < (long) T2, 
     (long) T2, (long) T1, (long) T2 - (long) T1,
     (long) T1, (long) T2, (long) T1 - (long) T2);
  return 0;  
}

output:

unsigned  (ffffffff < 0) = 0, (0 - ffffffff) = 1, (ffffffff - 0) = ffffffff
signed    (-1 < 0) = 1, (0 - -1) = 1, (-1 - 0) = -1

The unsigned comparison comes out wrong (for a circular clock) but the signed comparison comes out correctly. Note that the unsigned subtraction comes out right if we are interested in modular arithmetic, but does not tell us the direction of the difference, to the signed subtraction is ordinarily more useful.

Earlier versions of Linux did not deal in this way with the problem of jiffies wrap-around. The problem is similar to the "Y2K" problem. People tended to assume that it would not wrap around, and so there was risk writing incorrect code. These new functions/macros are intended to avoid such errors. use them.


ix86 Timestamp Counter


See definition of rdtsc in include/asm-i386/msr.h. Like the MIPS example in the textbook, it uses gcc inline assembly code.

Beware:

Clock and timer wrap is a tricky problem. For example, the above code using rdtscl is strictly incorrect. The output will be garbage if ini is fetched before the wrap-around and end is fetched after.


get_cycles


do_gettimeofday

See module jit.c for example of calling do_gettimeofday.


current_kernel_time

See module jit.c for example of calling current_kernel_time.


Directly accessing xtime is unreliable because the value generally cannot be fetched atomically in one instruction. That means one may get garbage if the fetch occurs just as the value is being increment, in the case that the low-order part has just wrapped around and the high-order part has not yet been incremented for the "carry". It is worth taking a look at the implementation of current_kernel_time and/or do_gettimeofday to see how subtle this is. Earlier Linux kernels did not do it correctly. There are still incorrect usages in the Linux system, that have not yet been updated. For example, see net/ipv4/route.c, or use the on-line search engine to search for other references to xtime.


Controlling the Time that Something Happens


Delaying by Looping - the naive ways

See example of a delay loop using jiffies.

One version uses cpu_relax, a new addition to better support symmetric multithreaded hardware architectures.

See also the following example in the same file, calling schedule(); to allow preemption


In the 2nd edition of the text this example was not correct, in that it did not treat wrap-around correctly. That was typical of the kernel at that time also. You can expect there to be residual errors of that kind in device drivers and other kernel components. Do not imitate such errors.

This is a general problem. There are some bad coding practices in the Linux source code, along with a number of very good ones. It is difficult to know what to imitate and what not to imitate. The text generally shows the best way to do things. A good rule would be to avoid copying anything you do not see in the text

In any case, you should not write timed delay loops of either of the above forms, and you should understand why this is a bad idea.

If the time delay is short, use one of the functions ndelay, udelay(), and mdelay().These are standard ways of implementing a timed delay loop.

If the time delay is long, use one of the "sleeping" timer mechanisms mentioned further below, or arrange to have the job done later via a kernel timer, tasklet, or work queue.


Short Busy-Waiting Delays


None of these is a good idea for a long delay. Why?


Historical: Delaying using "sleep" Calls

Uses of the "sleep" calls are deprecated, because they are unreliable. However, they are still used. See example of a delay using interruptible_sleep_on_timeout in sound/core/rawmidi.c driver.


Delaying by Scheduling a Timeout Directly

See example of delay using schedule_timeout.


Kernel Timers

See examples in jiq.c:


Interrupt Context


Implementation of Kernel Timers


Tasklets

See examples in mis-modules/jiq.c:


The tasklet is declared and initialized with a DECLARE_TASKLET macro, and then the task is scheduled with a simple function call.

The declaration of struct tasklet_struct includes a field state. If you look at the __tasklet_schedule (in interrupt.h), you will see an example of how a testand_set_bit instruction is used to atomically set this field.


Workqueues


Workqueue Operations


The Shared Workqueue

See examples in jiq.c:


In Kernel 2.6 it seems some of the Kernel 2.4 low-level scheduling mechanisms have been eliminated, including "task queues" and "bottom halves".


Connection to Class Lab Assignments

For your LED display programming assignment you will probably want to use a timer task that reschedules itself. You may also be able to use a kernel timer, whose function reschedules itself.

© 2003, 2004, 2005, 2006 T. P. Baker. ($Id)