I’ve been doing quite a bit of kernel hacking recently and am learning a lot, but for some reason it only just occurred to me that I should be documenting some of the things I’m learning. Learning to hack the kernel is not easy and often there isn’t much documentation (I have Linux Kernel Development by Robert Love [Amazon link] which is really useful – it’s by Novell press, so please buy it second-hand rather than give Novell your money).
I should really start at the beginning, but I’m not going to, since I can’t remember that far back and I’m not sure that there is a sensible place to start in any case. I’m currently working with tasklets, so that’s where I’m going to start.
When writing an interrupt handler in the kernel, there’s one key rule: you need your interrupt handler function to return as quickly as possible. To make that possible, we split the interrupt handler in half: a top-half and a bottom-half. The top half is our main interrupt handler function which does the bare minimum: work out what we need to do in response to the interrupt. The most-used way to implement a bottom-half is a tasklet. A tasklet is a function much like any other in C, except that you don’t call it, you schedule it. When a tasklet is scheduled, it is not run immediately but scheduled to run at some point in the future. This allows your interrupt handler top-half to return quickly without having to wait for the bottom-half to finish.
In a tasklet, we do the actual work that we need to do in response to the interrupt we received – this could be manipulating data structures or reading/writing to hardware.
Here’s a simplified example of an interrupt handler and a tasklet bottom-half based roughly on the code I’m currently working on:
#include <linux/interrupt.h>
…
// declare interrupt handler function
static irqreturn_t interrupt_handler(int irq, void *dev_id);
// declare tasklet function
static void handle_disk_removal(unsigned long data);
// declare tasklet
// prototype:
// DECLARE_TASKLET(tasklet_name, function_name, unsigned long data)
DECLARE_TASKLET(disk_removed, handle_disk_removal, 0);
…
static irqreturn_t interrupt_handler(int irq, void *dev_id)
{
// read interrupt source
u8 interruptregister = i2c_read_8574(CTRL_ADDR);
// acknowledge the interrupts to the interrupt controller
i2c_write_8574(CTRL_ADDR, 0xFF);
// determine the source of the interrupt
// NOTE: this is not the right way to determine the source; this is a simplified example
switch (interruptregister) {
case 0xFE:
// we know that when the interrupt register is 0xFE it means that a hard
// disk has been hot-swap removed
// schedule our disk_removed tasklet to run.
tasklet_schedule(&disk_removed);
break;
}
…
}
static void handle_disk_removal(unsigned long data) {
// manipulate our disks data structure
// print a kernel message
// whatever else we need to do
…
}
You don’t need to worry about most of the code in the interrupt handler (since every one is different), it is the tasklet_schedule function that is important.
When we declare our tasklet, we give it a name (disk_removed) and give it a function to call (handle_disk_removal). We also have the option to pass it some data in the form of an unsigned long but we don’t need to, so we just pass 0. Incidentally, I don’t think there is one place in the kernel where someone actually passes a value to a tasklet – most often you’ll need to access something that isn’t an unsigned long, so you’ll use a (properly locked) global variable or structure instead.
Now we’ve declared the tasklet, getting it to run is a simple case of calling tasklet_schedule and passing the tasklet name. This will cause the tasklet to run in the future – we don’t know (or care) when, but we can be sure that it will be run. If it gets scheduled more than one before it gets run, it will only be run once.
So tasklets are actually very simple to use. The hard part comes when you need to share data between regular functions, tasklets and your interrupt handler. You have to use proper locking to make sure nothing nasty happens, but locking deserves a post of it’s own, I think.



