In the last article 《 teach-by-doing Linux drive 8-Linux IO Model 》 We've learned about congestion 、 Non blocking 、 Synchronous and asynchronous concepts , This article mainly explains how to block the process by waiting for the queue .

Application scenarios :

 

 

 

 

     When a process wants to acquire certain resources ( For example, reading data from the network card ) When , But the resources are not ready ( For example, the network card has not received the data yet ), At this time, the kernel must switch to other processes to run , Wake up the process until the resource is ready .

    waitqueue ( Waiting in line )  The kernel is used to manage processes waiting for resources , When a process gets resources that are not ready , You can call   add_wait_queue()  Function to add a process to   waitqueue in , Then switch to another process to continue . When resources are ready , The resource provider calls   wake_up()  Function to wake up the waiting process .

 

 


 

 

 

Define header file :

 

#include <linux/wait.h>

Define and initialize the waiting queue header (workqueue):

Static , Using macros :

#define DECLARE_WAIT_QUEUE_HEAD(name) \
    wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

Dynamic , Also use macro :

#define init_waitqueue_head(q)              \
    do {                        \
        static struct lock_class_key __key; \
                            \
        __init_waitqueue_head((q), #q, &__key); \
    } while (0)

Define examples

wait_queue_head_t wq;
init_waitqueue_head(&wq);

Blocking interface :

wait_event(wq, condition)
wait_event_timeout(wq, condition, timeout)
wait_event_interruptible(wq, condition)
wait_event_interruptible_timeout(wq, condition, timeout)
wait_event_hrtimeout(wq, condition, timeout)
wait_event_interruptible_hrtimeout(wq, condition, timeout)
wait_event_interruptible_exclusive(wq, condition)
wait_event_interruptible_locked(wq, condition)
wait_event_interruptible_locked_irq(wq, condition)
wait_event_interruptible_exclusive_locked(wq, condition)
wait_event_interruptible_exclusive_locked_irq(wq, condition)
wait_event_killable(wq, condition)
wait_event_lock_irq_cmd(wq, condition, lock, cmd)
wait_event_lock_irq(wq, condition, lock)
wait_event_interruptible_lock_irq_cmd(wq, condition, lock, cmd)
wait_event_interruptible_lock_irq(wq, condition, lock)
wait_event_interruptible_lock_irq_timeout(wq, condition, lock,  timeout)

Parameters

wq         Waiting queue header defined ,
condition  For conditional expression , When wake up after ,condition To true , Wake up blocked processes , When it is false , Keep sleeping .

Functional specifications

There are many interface versions , Each has its own application , But the first four are commonly used .

wait_event: Uninterrupted sleep , The conditions have not been met , I sleep all the time .
wait_event_timeout: Don't interrupt sleep , When more than the specified timeout( The unit is jiffies) Time , With or without wake up, Or the conditions are not met , Wake up the process , Back at this point is 0. stay timeout If the condition is satisfied in time, the return value is timeout perhaps 1;
wait_event_interruptible: Sleep that can be interrupted by a signal , Wake up when interrupted by a signal , Returns a negative value -ERESTARTSYS;wake up when , The conditions are met , return 0. except wait_event no return value , The others have returned , If there is a return value, it is generally necessary to judge the return value . Here's an example :
    int flag = 0;
    if(wait_event_interruptible(&wq,flag == 1))
        return -ERESTARTSYS;
wait_event_interruptible_timeout: yes wait_event_timeout and wait_event_interruptible_timeout The combined version of , There are two characteristics of them .

Other interfaces , With a few , If you are interested, you can have a look .

Unblock the interface ( Wake up the )

Interface definition :

#define wake_up(x)          __wake_up(x, TASK_NORMAL, 1, NULL)
#define wake_up_nr(x, nr)       __wake_up(x, TASK_NORMAL, nr, NULL)
#define wake_up_all(x)          __wake_up(x, TASK_NORMAL, 0, NULL)
#define wake_up_locked(x)       __wake_up_locked((x), TASK_NORMAL, 1)
#define wake_up_all_locked(x)       __wake_up_locked((x), TASK_NORMAL, 0)
#define wake_up_interruptible(x)    __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
#define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
#define wake_up_interruptible_all(x)    __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
#define wake_up_interruptible_sync(x)   __wake_up_sync((x), TASK_INTERRUPTIBLE, 1)

Functional specifications

wake_up: Only one process hanging on the head of the waiting queue can be awakened at a time
wake_up_nr: A wake-up call nr A process ( Waiting in the same wait_queue_head_t There are many )
wake_up_all: All waiting at once in the same wait_queue_head_t All the processes on the Internet
wake_up_interruptible: Corresponding wait_event_interruptible Version of wake up
wake_up_interruptible_sync: Guarantee wake up The action atomicity of ,wake_up This function , It's very likely that the function is not finished yet , I was called up and the process was preempted , This function guarantees that wak up Complete execution of the action .

Others are also corresponding to the corresponding blocking interface .

Using examples

Take the character device for example , When there's no data , stay read Function to achieve read blocking , When writing data to the kernel , Wake up all tasks blocking the waiting queue .

Read operations

static ssize_t hello_read(struct file *filp,char __user *buf,size_t size,loff_t *poss)  
{  
    wait_event_interruptible(rwq,flage!=0);
    ……………
    flage=0;
    wake_up_interruptible(&wwq);
    return size;  
}

Write operations

static ssize_t hello_write(struct file *filp,const char __user *buf,size_t size,loff_t *poss)  
{  
    wait_event_interruptible(wwq,flage!=1);
    ……………
    flage=1;
    wake_up_interruptible(&rwq);
    return size;  
}

How to support non blocking synchronization ?

Although the above operation realizes the blocking function , But when we open a character device in an application , Sometimes we want operations to be nonblocking , such as :

fd=open("/dev/hello",O_RDONLY|O_NONBLOCK);

So how does the driver get this tag ?

Reference resources 《 teach-by-doing Linux drive 6-inode,file,file_operations Relationship 》, The tag is stored in the structure struct file Of f_flags Among members .

So the program can be modified as follows :

static ssize_t hello_read(struct file *filp,char __user *buf,size_t size,loff_t *poss)  
{  
int ret = 0; if(flage==0)
{
if(filp->f_flags & O_NONBLOCK)
{
  return -EAGAIN;
}
wait_event_interruptible(rwq,flage!=0);
} ……………
flage=0;
wake_up_interruptible(&wwq);
    return size;  
}

 


A flexible way to add and delete the waiting queue in the waiting queue header :

(1) Definition :
static state :

#define DECLARE_WAITQUEUE(name, tsk)                    \
    wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)

(2) dynamic :

wait_queue_t wa;
init_waitqueue_entry(&wa,&tsk);

tsk It's a process structure , It's usually current(linux This is what the current process uses to get ). You can also use the following , Set a custom waiting queue callback function , The above is linux A default callback function default_wake_function(), But the default is the most used :

wait_queue_t wa;
wa->private = &tsk;
int func(wait_queue_t *wait, unsigned mode, int flags, void *key)
{
    //
}
init_waitqueue_func_entry(&wa,func);

( What does a callback do ?)
Use the following function to wait for the queue , Join the waiting queue head ( belt remove The most important thing is to delete the work queue from the work queue header ):

extern void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
extern void add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait);
extern void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

Blocking and unblocking interfaces above , It can only block the current process / unblocked , With these flexible interfaces , We can define a waiting queue separately , Just get the process task_struct The pointer , We can add any process to the waiting queue , Then join the waiting queue head , We can take any other process ( It's not just the current process ), Hang up sleep , Of course, when you wake up , If you use wake_up_all Version! , It's going to evoke . This situation , I can't use the interface above , We need to use the interface described in the next section (schedule()), Unblocking can be done with wake_up,wake_up_interruptible etc. .

More advanced and flexible blocking :

The principle of blocking the current process : Use functions set_current_state() Modify the current process to TASK_INTERRUPTIBLE( Don't interrupt sleep ) or TASK_UNINTERRUPTIBLE( Can interrupt sleep ) state , And then call schedule() Tell the kernel to reschedule , Because the current process state is already in sleep state , Naturally, it won't be scheduled .schedule() In short, it tells the kernel that the current process actively gives up CPU control power . Come on , It can be said that the current process sleeps here , That is, the obstruction is here .

In the last section “ Flexibly add and delete the waiting queue in the waiting queue header ”, Add any process to waitqueue, And then it's similar to :

task_struct *tsk;
wait_queue_t wa;
// hypothesis tsk Has pointed to a process control block
p->state = TASK_INTERRUPTIBLE;//or TASK_UNINTERRUPTIBLE
init_waitqueue_entry(&wa,&tsk);

Can suspend any process , Of course , Also need to wa, Hang to the head of the waiting queue , And then use wait_event(&wa), The process exits from the ready queue , Enter the sleep queue , until wake up when , The suspended process state is modified to TASK_RUNNING, Will be rescheduled .( Mainly schedule() We will talk about ).


wait_event Realization principle :

Let's take a look at wait_event Realization :

#define __wait_event(wq, condition)                     \
do {                                    \
    DEFINE_WAIT(__wait);                        \
                                    \
    for (;;) {                          \
        prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);    \
        if (condition)                      \
            break;                      \
        schedule();                     \
    }                               \
    finish_wait(&wq, &__wait);                  \
} while (0)
#define wait_event(wq, condition)                   \
do {                                    \
    if (condition)                          \
        break;                          \
    __wait_event(wq, condition);                    \
} while (0)

DEFINE_WAIT:

     Define a work queue .

prepare_to_wait:

 Definition :void prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)
function : Put the work queue wait Join the work queue header q, And set the current process to state Specified state , It's usually TASK_UNINTERRUPTIBLE or TASK_INTERRUPTIBLE state ( There are calls in this function set_current_state).
     The first parameter : Work queue header
     The second parameter : Work queue
     The third parameter : The state to be set by the current process 

finish_wait:    

     It was used prepare_to_wait after , When you exit , Be sure to use this function to clear the waiting queue .

function :

  1.   This function first calls prepare_to_wait, Modify the process to sleep ,

  2.   Condition not satisfied ,schedule() Just give up CPU control power , sleep ,

  3.   When wake up When , Blocked in wq( It can also be said that the blockage is in wait_event It's about ) Waiting for the process at the head of the queue , It's running again , Then perform schedule() Later code , here , It's obviously a cycle ,prepare_to_wait Set the current process to sleep again , Then judge whether the conditions are met ,

  4. If you are satisfied, you exit the cycle ,finish_wait Restore the current process to TASK_RUNNING state , Which means the blockage is removed . dissatisfaction , Keep sleeping . So repeatedly waiting for conditions to be established .

 

 

Understand the process , use prepare_to_wait and schedule() To achieve more flexible blocking , It's easy , It's the same as before wake_up,wake_up_interruptible etc. .

Here is wake_up and wait_event flow chart :

wait_event and wake_up technological process

 

Exclusive waiting


   When calling wake_up when , All processes waiting on the queue are awakened , If only one process has access to resources , here , Other processes will go into hibernation again , If the quantity is large , go by the name of ” Crazy selling group ”. This will take up a lot of system resources .

 

resolvent :

wait_queue_t member flage There's an important sign WQ_FLAG_EXCLUSIVE, Express :

 When a waiting queue entry has  WQ_FLAG_EXCLUSEVE  Mark set ,  It's added to the end of the waiting queue .  Entry items without this flag ,  Add to start .
When  wake_up  Called on a waiting queue ,  It's awakening the first to have WQ_FLAG_EXCLUSIVE  Mark the process after stopping .

wait_event The default is always waitqueue Join the start , and wake_up Always wake up one by one from the beginning , If you keep having waitqueue Join in , So the first one to join , I can't wake up all the time , There's this sign , This situation is avoided .

prepare_to_wait_exclusive() It's the one with the logo .

Add

Linux Describe the process state as the following five :
1. TASK_RUNNING: Operational state . The process in this state can be scheduled to execute and become the current process .
2. TASK_INTERRUPTIBLE: An interruptible sleep state . The process in this state is awakened when the required resource is valid , It can also wake up by signal or timing interrupt ( Because there is signal_pending() function ).
3. TASK_UNINTERRUPTIBLE: Uninterrupted sleep . A process in this state is awakened only when the required resource is valid .
4. TASK_ZOMBIE: Zombie state . Indicates that the process has ended and the resource has been released , But it's task_struct Not released yet .
5. TASK_STOPPED: Pause state . A process in this state can only be awakened by signals from other processes 

Linux Through the structure task_struct Maintain all running threads 、 process , Tasks in different states , Will be maintained by different queues ,schedule() Function is responsible for scheduling these tasks according to these state changes . About process scheduling , In the follow-up meeting, a new article will be introduced in detail .

 

example


 

The main functions of the following examples are based on our previous courses 《 teach-by-doing Linux drive 3- Detailed explanation of character device architecture , This is enough 》 The last code example , On this basis, the function of write blocking is added .

  1. There is buffered memory in the kernel , And whether it can be accessed ;

int flage=0;  //1: Data readable   0: No data , Do not read
char kbuf[128];

  1. In the initial state flage by 0, No data ;

  2. When the application process reads the data, it calls the kernel function hello_read(), If flage by 1, Then read the data directly , And it will be changed flage Set up 1, If flage by 0, The process is blocked , Until a process writes the data flage Set up 1;

  3. Every time the application process writes data, it calls the kernel function hello_write(), If flage by 0, Write the data directly , And set up flage by 1, If 1, The block , Until another process calls the read function hello_read() take flage Set up 0.

drive

/********************************************* *hellodev.c *********************************************/  #include <linux/module.h>  #include <linux/ioctl.h>  #include <linux/wait.h> #include <linux/poll.h> #include <linux/device.h> #include <linux/types.h>  #include <linux/fs.h>  #include <linux/errno.h>  #include <linux/mm.h>  #include <linux/sched.h>  #include <linux/init.h>  #include <linux/cdev.h>  #include <asm/io.h>  #include <asm/system.h>  #include <asm/uaccess.h>   #include<linux/slab.h>   static int hello_major = 250;  static struct class *hello_class;#define  DEV_NAME "hello_cls" module_param(hello_major,int,S_IRUGO);  dev_t devno;struct cdev cdev;  int num;int flage=0;char kbuf[128];wait_queue_head_t rwq; //read wqwait_queue_head_t wwq;  //write wqint hello_open(struct inode *inode,struct file *filp)  {     return 0;  }  int hello_release(struct inode *inode,struct file *filp)  {      return 0;  }  static ssize_t hello_read(struct file *filp,char __user *buf,size_t size,loff_t *poss)  {    int ret = 0;  if(flage==0)  {    if(filp->f_flags & O_NONBLOCK)    {            return -EAGAIN;    }    wait_event_interruptible(rwq,flage!=0);  }    if(copy_to_user(buf,kbuf,size))   {    return -EFAULT;  }  flage=0;  wake_up_interruptible(&wwq);    return size;  }   static ssize_t hello_write(struct file *filp,const char __user *buf,size_t size,loff_t *poss)  {      int ret = 0;  if(size>128||size<0)  {    return -EINVAL;  }  wait_event_interruptible(wwq,flage!=1);   if(copy_from_user(kbuf,buf,size))   {    return -EFAULT;  }  flage=1;  wake_up_interruptible(&rwq);    return size;  }   static const struct file_operations hello_fops =  {      .owner  = THIS_MODULE,      .read   = hello_read,      .write  = hello_write,      .open   = hello_open,      .release = hello_release,  };   static int hellodev_init(void)  {    int result;    int i;    struct device *hello_dev;  devno = MKDEV(hello_major,0);    if(hello_major)        result = register_chrdev_region(devno,2,"hello");    else      {        result = alloc_chrdev_region(&devno,0,2,"hello");        hello_major = MAJOR(devno);    }    if(result < 0)        return result;      cdev_init(&cdev,&hello_fops);    cdev.owner = THIS_MODULE;    cdev.ops = &hello_fops;      cdev_add(&cdev,MKDEV(hello_major,0),1);    init_waitqueue_head(&rwq);  init_waitqueue_head(&wwq);  hello_class = class_create(THIS_MODULE, DEV_NAME);// Class name    if (IS_ERR(hello_class)) {    printk(KERN_WARNING "class_create faihello ! \n");    goto err_3;  }   hello_dev = device_create(hello_class, NULL, devno, NULL, "hello");  if (IS_ERR(hello_dev)) {    printk(KERN_WARNING "device_create faihello! \n");    goto err_4;  }  return 0; err_4:  class_destroy(hello_class);err_3:  cdev_del(&cdev);  unregister_chrdev_region(MKDEV(hello_major,0),2);     return 0;  }  static void hellodev_exit(void)  {   device_destroy(hello_class, devno);    class_destroy(hello_class);  cdev_del(&cdev);  unregister_chrdev_region(MKDEV(hello_major,0),2);  }    MODULE_LICENSE("GPL");MODULE_DESCRIPTION("yikoulinux");module_init(hellodev_init);  module_exit(hellodev_exit);

The test program

read.c

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <fcntl.h>#include <sys/select.h>int main(){  int fd= 0;  char buf[128];  int num;//  fd=open("/dev/hello",O_RDONLY);  Block mode read    fd=open("/dev/hello",O_RDONLY|O_NONBLOCK);  // Non blocking      if(fd<0)   {         printf("open memdev failed!\n");         return -1;                 }       read(fd,buf,sizeof(buf));      printf("num:%s\n",buf);    close(fd);  return 0;       }

 

write.c

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <fcntl.h>#include <sys/select.h>int main(){  int fd =0;  char buf[128]="hello yikouLlinux";  int num;  fd=open("/dev/hello",O_RDWR);       if(fd <0)       {         printf("open device failed!\n");         return -1;                 }       write(fd,buf,sizeof(buf));    close(fd);  return 0;       }

 

  Master the usage of waiting queue , Later, we can explain the interruption .