The Complete Magazine on Open Source

Waiting and Blocking in a Linux Driver

SHARE
/ 290 0

Stop symbol

This is the second article on waiting and blocking in a Linux driver. As we’ve discovered earlier, waiting and blocking are as inevitable in Linux drivers as death and taxes in real life, so let us try to understand the process better with this insightful article.

In the last article, we implemented a basic wait mechanism in the Linux driver and as stated, such an implementation is very primitive and prone to bugs. Also, the process was being put to an unconditional sleep, which is obviously not the case in real life scenarios. So, how do we make the process wait on an event? Read on to dive further into the implementation of wait mechanisms in Linux drivers.

Waiting/blocking on an event
In real life scenarios, a process never waits without any event. The event can be a specified period before which the device is available for use. It could even be the arrival of data from some device such as the disk. Or the event could very well be waiting for some shared resource to be available. Since we don’t have any physical event to wait upon, let’s simulate the event by using a global variable flag. Shown below is the code snippet for this:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/sched.h>

#define FIRST_MINOR 0
#define MINOR_CNT 1

static dev_t dev;
static struct cdev c_dev;
static struct class *c1
static struct task_struct *sleeping_task;
static char flag = ‘n’;

int open(struct inode *inode, struct file *filp)
{
printk(KERN_INFO “Inside open \n”);
return 0;
}
int release(structi node *inode, struct file *filp)
{
printk(KERN_INFO “Inside close \n”);
return 0;
}

ssize_t read(struct file *filp, char *buff, size_t count, loff_t *offp)
{
printk(KERN_INFO “Inside read\n”);
sleeping_task = current;
slp:
if (flag != ‘y’)
{
printk(KERN_INFO “Scheduling out\n”);
set_current_state(TASK_INTERRUPTIBLE);
schedule();
}
if (flag == ‘y’)
printk(KERN_INFO “Woken up\n”);
else
{
printk(KERN_INFO “Interrupted by signal\n”);
gotoslp;
}
flag = ‘n’;
return 0;
}

ssize_t write(struct file *filp, const char *buff, size_t count, loff_t *offp)
{
printk(KERN_INFO “Inside Write\n”);
if (copy_from_user(&flag, buff, 1) != 0)
{
printk(KERN_INFO “Failed to read from user space\n”);
}
wake_up_process(sleeping_task);
return count;
}

struct file_operations fops = {
.read = read,
.write = write,
.open = open,
.release = release
};

int schd_init(void)
{
int ret;
struct device *dev_ret;
if ((ret = alloc_chrdev_region(&dev, FIRST_MINOR, MINOR_CNT, “wqd”)) < 0)
{
return -1;
}
printk(“Major Nr: %d\n”, MAJOR(dev));
cdev_init(&c_dev, &fops);
if ((ret = cdev_add(&c_dev, dev, MINOR_CNT)) < 0)
{
unregister_chrdev_region(dev, MINOR_CNT);
return ret;
}
if (IS_ERR(cl = class_create(THIS_MODULE, “chardrv”)))
{
cdev_del(&c_dev);
unregister_chrdev_region(dev, MINOR_CNT);
return PTR_ERR(cl);
}
if (IS_ERR(dev_ret = device_create(cl, NULL, dev, NULL, “mychar%d”, 0)))
{
class_destroy(cl);
cdev_del(&c_dev);
unregister_chrdev_region(dev, MINOR_CNT);
return PTR_ERR(dev_ret);
}
return 0;
}
void schd_cleanup(void)
{
printk(KERN_INFO “ Inside cleanup_module\n”);
device_destroy(cl, dev);
class_destroy(cl);
cdev_del(&c_dev);
unregister_chrdev_region(dev, MINOR_CNT);
}

module_init(schd_init);
module_exit(schd_cleanup);

MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“Pradeep Tewani”);
MODULE_DESCRIPTION(“Demo for Waiting on an Event”);

The above example is the same as that of previous articles, except that the flag variable is used to signal the event. sched_init() and sched_exit() are initialisation and exit functions for the driver, respectively. Any process invoking the read on the device file would be blocked. Let’s have a demo on the same, assuming that the module is compiled as wait.ko:

$ insmod wait.ko
Major Nr: 250
$ cat /dev/mychar0
Inside open
Inside read
Scheduling out

As seen above, upon loading the driver, we get the device file with the name mychar0 in the /dev directory. Executing the cat command will invoke the read function in the driver. So, this will get the process blocked. Let’s open another shell and try to wake up the process by invoking the write on the device file.

$ echo 1 > /dev/mychar0
Inside open
Inside write
Interrupted by signal
Scheduling out
Inside close

As seen above, executing the echo command on the device file will invoke the write function in the driver, which in turn will wake up the process. So, does this get our process unblocked? Not really. The process is still blocked as the condition for waking up is not satisfied. echo will set the flag to ‘1’ and this doesn’t meet our wake up condition of flag being set to ‘y’. So, how do we wake the process up? No points for guessing the right answer –- just set the flag to ‘y’. Let’s have a look at the following sample run:

$ echo y > /dev/mychar0
Inside open
Inside write
Woken up
Inside close
Inside close

This will wake up the process as the condition of flag being set to value ‘y’ is satisfied.
So this seems to be more realistic as our process is waiting on some event, rather than unconditional waiting. But this is still manual waiting, wherein everything is being managed by a programmer and, as such, is prone to several synchronisation bugs. So, does the kernel provide any programming constructs to make the programmer’s life simpler? Of course it does. To explore more on the wait mechanism in the Linux kernel, stay tuned to the next article on this subject.