LWN.net Logo

The timer API: size or type safety?

The timer API allows kernel code to request that a function be called at some point in the future. At its core is the timer_list structure, which contains a few fields of interest:

    struct timer_list {
	unsigned long expires;
	void (*function)(unsigned long);
	unsigned long data;
	/* ... */
    };

To request an action in the future, a kernel function places a relative expiration time (expressed in jiffies) in expires and some sort of useful private value in data. function() is a pointer to a routine which will be called after (at least) the requested number of jiffies have passed; data will be its only parameter. After the timer_list structure has been set up, a call to add_timer() puts the request into the system.

This API has not changed much in some time; as a result, the description of timers in Chapter 7 of Linux Device Drivers is still useful for those wanting details. It may, in fact, be the only part of LDD3 which is not yet thoroughly obsolete.

That situation may change soon, however, as there are developers with their eyes on this interface. Interestingly, there are two very different ideas of how the timer API should be changed.

The conversation was started by Al Viro who, for some time now, has been working on improving the type safety of the kernel API. He notes that the unsigned long argument to timer functions is, in fact, almost always a pointer value. So there is a lot of code in the kernel which is busily casting pointers to unsigned long values and back - or engaging in lazy trickery to avoid having to do those casts. Casts like this make compile-time type checking almost impossible, so every one is an opportunity to introduce hard-to-find bugs.

Al would like to fix this problem by creating a more type-safe interface to the kernel timer subsystem. His approach involves changing the type of the timer function argument to void *, reflecting the fact that it's usually a pointer type. He then has a SETUP_TIMER() macro which involves the following bit of code:

    typeof(*data) *p = data;
    timer->function = (void (*)(void *)) func;
    timer->data = (void *) p;
    (void)(0 && (func(p), 0));

The middle two lines are simply initializing the relevant fields of the timer_list structure. What the last line is doing, however, is creating a call to the timer function with the provided argument; if there is a type mismatch between that argument and the function's prototype, the compiler will complain. The call is written in such a way that it will be optimized out, so that call does not make it through to the kernel image. But, in the running kernel, it will be known that the timer function is receiving an appropriately-typed argument.

There are a lot of timers in the kernel, so this is the sort of change which makes people nervous. Al's plan involves creating the SETUP_TIMER() macro, but leaving the callback function's prototype unchanged. Then parts of the kernel could be converted at leisure, with the callback function prototype being changed once the conversion of in-kernel code is complete.

Thomas Gleixner joined in with an alternative suggestion: remove the data value from struct timer_list altogether, and pass a pointer to the timer_list structure into the callback function. If that structure is embedded within some other structure which has the information the callback really needs, a simple recast with container_of() will yield the needed pointer. The result would be a smaller timer_list structure. This approach mirrors the proposed workqueue API changes discussed here last week.

Al doesn't like that idea. He has been working to get rid of casts in the kernel, but this API would require the introduction of hundreds more of them. There is little type safety built into container_of(). To him, the space required for a pointer is more than justified by the extra compile-time checking that comes from its use.

Ingo Molnar, in disagreeing, makes the tradeoff clear:

The question is: which is more important, the type safety of a container_of() [or type cast], which if we get it wrong produces a /very/ trivial crash that is trivial to fix - or embedded timers data structure size all around the kernel? I believe the latter is more important.

Not too many other developers have joined the discussion so far. It's an important one, though; how this decision goes could shape how kernel APIs are designed in the future. Perhaps somebody will come up with a way to have both type safety and smaller size. Until such a time, however, there is a tradeoff to be made, and it's not clear which way the decision will go.


(Log in to post comments)

The timer API: size or type safety?

Posted Dec 7, 2006 5:47 UTC (Thu) by thedevil (subscriber, #32913) [Link]

"if we get it wrong produces a /very/ trivial crash that is trivial to fix"

Perhaps it's just me, but this sounds quite wrong. If the timer_list passed to add_timer is not embedded in any larger structure but the callback expects it to be (or if it is embedded in a larger structure of the wrong type), the callback will simply use whatever random garbage follows the timer_list in kernel memory. This is actually the *worst* kind of bug. It may appear to work most of the time because the following data is zero and the callback interprets zero as some kind of default. Ouch! I am with Viro all the way on this one.

The timer API: size or type safety?

Posted Dec 7, 2006 16:10 UTC (Thu) by liljencrantz (guest, #28458) [Link]

I really hope that correctness and verifiability is given priority over memory usage and performance. In all but the most pathological cases I've seen, internal kernel memory use has not had a noticable effect on overall memory usage, but the frequency of kernel panics has been steadily increasing the last few years.

The timer API: size or type safety?

Posted Dec 7, 2006 18:58 UTC (Thu) by gnb (subscriber, #5132) [Link]

>internal kernel memory use has not had a noticable effect on overall
>memory
Depends on your target system: for a lot of embedded platforms memory is
a lot smaller, and userspace is also much reduced, so the kernel can
account for a much larger proportion of the memory used.

The timer API: size or type safety?

Posted Dec 7, 2006 23:12 UTC (Thu) by liljencrantz (guest, #28458) [Link]

True, but I think there should be a limit to the kinds of tradeoffs that are made while trying to make Linux scale up and down. Stability is very important to me.

The timer API: size or type safety?

Posted Dec 7, 2006 16:16 UTC (Thu) by pimlott (guest, #1535) [Link]

Wow--what Al has done is write a type-safe parametrically polymorphic function in C! The signature of SETUP_TIMER is effectively
    void SETUP_TIMER((*function)(T), T data)
where the T can be any (pointer) type. While you can't write such a signature in C, Al guarantees that the two instances of T are in fact the same with a clever bit of code that has no run-time impact. Lovely!

The timer API: size or type safety?

Posted Dec 7, 2006 20:34 UTC (Thu) by jzbiciak (✭ supporter ✭, #5246) [Link]

Is typeof() standard C or a GNU extension?

The timer API: size or type safety?

Posted Dec 8, 2006 18:20 UTC (Fri) by giraffedata (subscriber, #1954) [Link]

Is typeof() standard C or a GNU extension?

I'ts GNU C. But it looks like it isn't necessary for this technique.

The timer API: size or type safety?

Posted Dec 8, 2006 19:42 UTC (Fri) by jzbiciak (✭ supporter ✭, #5246) [Link]

Are you certain? Let's look at that closely:

    typeof(*data) *p = data;
    timer->function = (void (*)(void *)) func;
    timer->data = (void *) p;
    (void)(0 && (func(p), 0));

I guess your statement is that the last line, (func(p)) could be rewritten as (func(data)) instead. I can see that.

The timer API: size or type safety?

Posted Jan 3, 2007 19:48 UTC (Wed) by rjbell4 (guest, #35764) [Link]

The reason you wouldn't necessarily do that is that "data" may actually be an expression that has a side effect, so you don't want to reference it twice.

The timer API: size or type safety?

Posted Jan 3, 2007 19:59 UTC (Wed) by jzbiciak (✭ supporter ✭, #5246) [Link]

So, I guess typeof(*data) doesn't evaluate data then? I guess that makes sense.

The timer API: size or type safety?

Posted Jan 3, 2007 20:01 UTC (Wed) by jzbiciak (✭ supporter ✭, #5246) [Link]

Actually, hold on... (0 && func(data)) shouldn't evaluate data a second time under any circumstances anyway.

The timer API: size or type safety?

Posted Dec 8, 2006 11:46 UTC (Fri) by viro (subscriber, #7872) [Link]

Heh... The current version in my tree actually gets you a constant
arithmetic expression. It's built around the following:
#define check_callback_type(f,x) (sizeof((0 ? (f)(x) : (void)0), 0))
Feel free to torture students with it... It actually checks even
more - namely, that f returns void. And since it never evaluates
f or x, we are free to use it in macros without any concerns about
side effects. Can be used in global initializers, too (and yes,
it _is_ valid C99; we get past the constraints since the expression
that appears to be problematic is an argument of sizeof and doesn't
have a variably-modified type).

The timer API: size or type safety?

Posted Dec 8, 2006 17:10 UTC (Fri) by liljencrantz (guest, #28458) [Link]

That is so clever it hurts.

Am I correct in thinking that since check_callback_type never evaluates f or x, we also don't need to use typeof anymore, as we only truly reference f or x once? I.e. we can write this as:

check_callback_type( func, data );
timer->function = (void (*)(void *)) func;
timer->data = (void *) data;

The timer API: size or type safety?

Posted Dec 7, 2006 16:33 UTC (Thu) by rfunk (subscriber, #4054) [Link]

Sounds like Linus will have to make the deciding call on this. I'm
betting (and hoping) that his taste runs toward type safety.

The timer API: size or type safety?

Posted Dec 7, 2006 17:33 UTC (Thu) by bronson (subscriber, #4806) [Link]

Could a static check be created? You could have type safety AND low memory usage if sparse would whinge whenever you improperly embedded a timer_list. It would require some hinting but that doesn't seem too bad.

One of the reasons the Linux kernel is noticeably faster than Windows nowadays is because the kernel guys have been aggressively pursuing small optimizations like this. 10 small optimizations == 1 large one. That said, I agree: the anti-type-safety guys should show a noticeable runtime difference if they want to justify chucking stricter compiler checks out the window.

The timer API: size or type safety?

Posted Dec 7, 2006 21:47 UTC (Thu) by malor (subscriber, #2973) [Link]

"[...]which if we get it wrong produces a /very/ trivial crash that is trivial to fix[....]"

On production systems, there's no such thing as a trivial crash.

The timer API: size or type safety?

Posted Dec 7, 2006 23:09 UTC (Thu) by iabervon (subscriber, #722) [Link]

On production systems, a trivial crash is one that crashed the test system and got fixed before it went into production. The argument is that it would be difficult to write code which would pass a cursory code review and would ever work (particularly with debugging enabled), so chances are that such bugs wouldn't get anywhere.

The timer API: size or type safety?

Posted Dec 8, 2006 0:42 UTC (Fri) by liljencrantz (guest, #28458) [Link]

Fail-fast bugs are certainly better than fail-slow bugs. But I belive you place far to much trust in test coverage. In a complex, concurrent system like a kernel, there will always be code paths that aren't covered by any test cases, even ones using long test periods and real use cases.

The timer API: size or type safety?

Posted Dec 8, 2006 15:57 UTC (Fri) by iabervon (subscriber, #722) [Link]

Oh, the error paths get terrible coverage. This particular case, however, is looking up the data required for a timer callback. If the timer fires at all, it is almost certain to trigger any bug of this form immediately, because it's standardly unconditional at the beginning of the callback. If this isn't getting tested, a bug is more likely to be that the function does something totally wrong (since it wasn't tested at all) than that the type of the argument is wrong.

Copyright © 2006, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds