The last article in this series focused on implementing the basic wait mechanism. It was a manual waiting where everything, starting from putting the process to the sleep to checking for the wake up event, was done by driver writer. But, such kind of manual waiting is error prone and may at times result in synchronization bugs. So, does kernel provide some robust wait mechanism? No points for guessing the right answer, yes it does. So, read on to explore more on wait mechanism in kernel.
Wait Queues
Wait queue is a mechanism provided in kernel to implement the wait. As the name itself suggests, wait queue is the list of processes waiting for an event. Below are the data structures for wait queues:
#include <linux/wait.h> // Data structure: wait_queue_head_t // Created statically DECLARE_WAIT_QUEUE_HEAD(wait_queue_name); // Created dynamically wait_queue_head_t my_queue; init_waitqueue_head(&my_queue);
As seen above, wait queues can be defined and initialized statically as well as dynamically. Once the wait queue is initialized, next step is to add our process to wait queue. Below are variants for this:
// APIs for Waiting wait_event(queue, condition); wait_event_interruptible(queue, condition); wait_event_timeout(queue, condition, timeout); wait_event_interruptible_timeout(queue, condition, timeout);
As seen, there are two variants – wait_event() and wait_event_timeout(). The former is used for waiting for an event as usual, but the latter can be used to wait for an event with timeout. Say, if the requirement is to wait for an event till 5 milliseconds, after which we need to timeout.
So, this was about the waiting, other part of the article is to wake up. For this, we have wake_up() family of APIs as shown below:
// Wakes up all the processes waiting on the queue wake_up(wake_queue_head_t *); // Wakes up only the processes performing the interruptible sleep wake_up_interruptible(wait_queue_head_t *);
Below is modified code from the last article where we use wait queues:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/errno.h> #include <asm/uaccess.h> #include <linux/wait.h> #include <linux/sched.h> #include <linux/delay.h> #define FIRST_MINOR 0 #define MINOR_CNT 1 static char flag = 'n'; static dev_t dev; static struct cdev c_dev; static struct class *cl; static DECLARE_WAIT_QUEUE_HEAD(wq); int open(struct inode *inode, struct file *filp) { printk(KERN_INFO "Inside open\n"); return 0; } int release(struct inode *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"); printk(KERN_INFO "Scheduling Out\n"); wait_event_interruptible(wq, flag == 'y'); flag = 'n'; printk(KERN_INFO "Woken Up\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)) { return -EFAULT; } printk(KERN_INFO "%c", flag); wake_up_interruptible(&wq); return count; } struct file_operations pra_fops = { read: read, write: write, open: open, release: release }; int wq_init (void) { int ret; struct device *dev_ret; if ((ret = alloc_chrdev_region(&dev, FIRST_MINOR, MINOR_CNT, "SCD")) < 0) { return ret; } printk("Major Nr: %d\n", MAJOR(dev)); cdev_init(&c_dev, &pra_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 wq_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(wq_init); module_exit(wq_cleanup); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Pradeep"); MODULE_DESCRIPTION("Waiting Process Demo");
As seen, the earlier manual waiting has been replaced by single statement wait_event_interruptible() which is more robust.
Below is the sample run of the above program, assuming that the module is compiled as wait.ko:
$ insmod wait.ko Major Nr: 250 $ cat /dev/mychar0 Inside open Inside read Scheduling out
This gets our process blocked. Open another shell to wake up the process:
$ echo 'y' > /dev/mychar0 Inside open Inside write y Inside close Woken up Inside close
As seen above, this will wake up the process, since the condition of flag being ‘y’ is satisfied.
Pingback: Waiting / Blocking in Linux Driver Part – 2 | Playing with Systems
Pingback: Waiting / Blocking in Linux Driver Part – 4 | Playing with Systems
nice tutorials
Very nice article sir thx for sharing!
I have a question. Its something that i dont understand on this topic.
when disk issues an interrupt that data requested are ready how he knows which process to wake up, the wake_up wakes all processes ? or the one that requested the data that got ready? I am so confused. Can u plz explain what happens? Its confusing because a disk can handle one request at a time so the wait_queue must have only one process waiting, if that queue is unique per event.
To understand it simply, you can think of each request having a corresponding spinlock. Requester takes the lock & blocks, and the interrupt handler unlocks the lock corresponding to its request.
i am facing some problem can you please tell me when will we face a kernel panic where i am using wait_event_timeout.
i am encountering a kernel panic in prepare_to_wait_event.
can you please help me.
Possibly due to uninitiatialized wait queue or some issue with wait flag (invalid pointer). What does the panic message mention?
What is the scope of the ‘condition’ inside ‘wait_event_interruptible’? Can you for example test some variable stored inside ‘private_data’ of the file parameter?
The condition is just a usual C code piece. So, whatever makes sense from C perspective can be put there. For example, you may use the variables, fields, … visible in the context, where you are putting the call to wait_event_interruptible.