Blocking and non blocking are two ways to access devices . When writing blocking and non blocking drivers , Waiting queues are often used .

One 、 Blocking and non blocking
Blocking call refers to the call result before returning , The current thread will be suspended , The function returns... Only after it gets the result .
Non blocking means before you can't get results right away , This function does not block the current process , And you'll be back right away .
There is a strong correlation between whether the object is in blocking mode and whether the function is blocking calls , But it's not a one-to-one correspondence . There can be non blocking calls on blocked objects , We can go through a certain amount of API To poll the status , Call blocking functions when appropriate , You can avoid blocking . And for non blocking objects , The called function can also enter the blocking call . function select() It's such an example .

Two 、 Waiting in line
stay linux In device driver , The blocking process can be implemented using the wait queue .
In kernel , There are many uses for waiting queues , Especially in Interrupt handling , Process synchronization , timing Etc , have access to Wait for the queue to wake up the blocking process . It's based on queues, data structures , Combined with process scheduling mechanism , It can be used to implement asynchronous event notification mechanism in kernel , Synchronize access to system resources .

1、 Waiting for the implementation of the queue :

stay linux in , The structure of the waiting queue is as follows :

struct __wait_queue_head {
spinlock_t lock; // spinlocks , Used to correct task_list The linked list plays a protective role , Achieve exclusive access to the waiting queue
struct list_head task_list; // Used to store waiting processes
};
typedef struct __wait_queue_head wait_queue_head_t;

 

2、 Waiting for the queue to be used
(1) Define and initialize the waiting queue :

wait_queue_head_t wait;// Define the waiting queue
init_waitqueue_head(&wait);// Initialize wait queue
Define and initialize the waiting queue :
#define DECLARE_WAIT_QUEUE_HEAD(name) wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)


(2) Add or remove waiting queues :

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);// Will wait for the queue element wait Add to the waiting queue header q In the waiting queue list pointed to .
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);


(3) Events wait :

 

wait_event(wq, condition);// Sleep in the waiting queue until condition It's true .
wait_event_timeout(wq, condition, timeout);
wait_event_interruptible(wq, condition) ;
wait_event_interruptible_timeout(wq, condition, timeout) ;
/* 
* queue: The waiting queue, which is the head of the waiting queue, is awakened
*    conditon: Must satisfy , Otherwise blocking
*    timeout and conditon comparison , There's a higher priority
*/

 

(4) sleep :

sleep_on(wait_queue_head_t *q);
interruptible_sleep_on(wait_queue_head_t *q);
/*
sleep_on The function is to set the state of the current process to TASK_UNINTERRUPTIBLE, Until resources are available ,q The waiting queue for the boot is awakened .
interruptible_sleep_on It's the same thing ,  It just sets the process state to TASK_INTERRUPTIBLE
*/

 

(5) Wake up waiting line :

// Can wake up in TASK_INTERRUPTIBLE and TASK_UNINTERRUPTIBLE The process of state ;
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)
// Can only wake up in TASK_INTERRUPTIBLE The process of state
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)

 

3、 ... and 、 Sleep in the operating system 、 Blocking 、 The difference between hung images explains

First of all, these terms are for threads . Controlling threads is like controlling an employee to work for you . Your control over employees is programmed .
Hung thread It means you're talking to employees :“ Go to bed , When I use you, I take the initiative to call you , And then go back to work ”.
send Thread sleep It means that you take the initiative to say :“ Go to bed , Come and report at a certain time , And then go back to work ”.
Thread blocking It means , You suddenly found out , Your employees don't know when they didn't get your permission , I sleep by myself , But you can't blame the employees , I'm sure your employer didn't notice , You used to let the employees sweep the floor , As a result, the broom was stolen or borrowed by the neighbors , You didn't let the hired man go on with other work , He had to go to bed . As for when the broom comes back , Will the hireling know , Will continue to work , You don't have to worry about it , Once the hireling finds out that the broom is back , He will go to work by himself . Because the employees are well trained . This training organization is the operating system .


Four 、 Blocking and non blocking operations

Blocking operation refers to suspending the process if the resource cannot be obtained during the execution of device operation , Until the operational conditions are met, the operation is carried out .
The process of non blocking operation does not hang when the device cannot be operated , It may be abandoned , Or keep searching , Until it's operational .

Review the simple character device driver , We see how to achieve read and write Method . Here it is , however , We skipped an important question : How should a driver respond to a request when it cannot satisfy it immediately ?  One right read The call to may come when there is no data , And more data will be expected in the future . Or a process might try to write , But your device is not ready to accept data , Because your output buffer is full . The calling process often doesn't care about this ; The programmer just wants to call read or write And make the call return , After the necessary work has been done . such , In such a situation , Your drive should be ( By default ) Blocking process , Put it to sleep until the request can continue .

When we look at full-featured read and write Before the implementation of the method , The last point we touch is to decide when to sleep the process . 
(1) In blocking drive ,read Realization way : If a process calls read But there's no data available , This process has to block . The process wakes up as soon as data arrives , And that data is returned to the caller , Even if it's less than what you're giving me count Number of requests in parameter .
(2) In blocking drive ,write Realization way : If a process calls write And there's no space in the buffer , This process has to block , And it has to be used in a relationship with read In different waiting queues . When some data is written to the hardware device , And the space in the output buffer becomes free , The process wakes up and the write call succeeds , Although the data may only be partially written if there is no space in the buffer for the requested count byte .
(3) Sometimes an operation is required not to block , Even if it doesn't go on completely . The application element can call filp->f_flags Medium O_NONBLOCK Flag to manually set read and write operations to non blocking mode . This symbol is defined by <linux/fcntl.h>, By <linux/fs.h> Automatically include .

 

5、 ... and 、 Blocking driver test program :

1.memdev.h

 

#ifndef _MEMDEV_H_
#define _MEMDEV_H_
#ifndef MEMDEV_MAJOR
#define MEMDEV_MAJOR 0   /* Preset mem The master number of the device */
#endif
#ifndef MEMDEV_NR_DEVS
#define MEMDEV_NR_DEVS 2    /* Number of devices */
#endif
#ifndef MEMDEV_SIZE
#define MEMDEV_SIZE 4096
#endif

 

/*mem Device description structure */
struct mem_dev                                     
{                                                        
  char *data;                      
  unsigned long size; 
  wait_queue_head_t inq;      
};
#endif /* _MEMDEV_H_ */

 

 

2.memdev.c

 

 

#include <linux/module.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 "memdev.h"

 

static mem_major = MEMDEV_MAJOR;
bool have_data = false; /* Indicates that the device has enough data to read */
module_param(mem_major, int, S_IRUGO);
struct mem_dev *mem_devp; /* Device structure pointer */
struct cdev cdev; 
/* File open function */
int mem_open(struct inode *inode, struct file *filp)
{
    struct mem_dev *dev;
    
    /* Get the secondary device number */
    int num = MINOR(inode->i_rdev);
    if (num >= MEMDEV_NR_DEVS) 
            return -ENODEV;
    dev = &mem_devp[num];
    
    /* Assign device description structure pointer to file private data pointer */
    filp->private_data = dev;
    
    return 0; 
}
/* File release function */
int mem_release(struct inode *inode, struct file *filp)
{
  return 0;
}
/* Read function */
static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
  unsigned long p =  *ppos;
  unsigned int count = size;
  int ret = 0;
  struct mem_dev *dev = filp->private_data; /* Get the device structure pointer */
  /* Judge whether the read position is valid */
  if (p >= MEMDEV_SIZE)
    return 0;
  if (count > MEMDEV_SIZE - p)
    count = MEMDEV_SIZE - p;
    
while (!have_data) /*  No data to read , Think about why not if, While using while, Interrupt signal wakes up  */
{
        if (filp->f_flags & O_NONBLOCK)
            return -EAGAIN;
    
    wait_event_interruptible(dev->inq,have_data);
}
  /* Read data into user space */
  if (copy_to_user(buf, (void*)(dev->data + p), count))
  {
    ret =  - EFAULT;
  }
  else
  {
    *ppos += count;
    ret = count;
   
    printk(KERN_INFO "read %d bytes(s) from %d\n", count, p);
  }
  
  have_data = false; /*  Indicates that there is no longer data to read  */
  return ret;
}
/* Write function */
static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
  unsigned long p =  *ppos;
  unsigned int count = size;
  int ret = 0;
  struct mem_dev *dev = filp->private_data; /* Get the device structure pointer */
  
  /* Analyze and get effective write length */
  if (p >= MEMDEV_SIZE)
    return 0;
  if (count > MEMDEV_SIZE - p)
    count = MEMDEV_SIZE - p;
    
  /* Write data from user space */
  if (copy_from_user(dev->data + p, buf, count))
    ret =  - EFAULT;
  else
  {
    *ppos += count;
    ret = count;
    
    printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);
  }
  
  have_data = true; /*  There's new data to read  */
    
    /*  Wake up the reading process  */
    wake_up(&(dev->inq));
  return ret;
}
/* seek File location function  */
static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
{ 
    loff_t newpos;
    switch(whence) {
      case 0: /* SEEK_SET */
        newpos = offset;
        break;
      case 1: /* SEEK_CUR */
        newpos = filp->f_pos + offset;
        break;
      case 2: /* SEEK_END */
        newpos = MEMDEV_SIZE -1 + offset;
        break;
      default: /* can't happen */
        return -EINVAL;
    }
    if ((newpos<0) || (newpos>MEMDEV_SIZE))
        return -EINVAL;
        
    filp->f_pos = newpos;
    return newpos;
}
/* File operation structure */
static const struct file_operations mem_fops =
{
  .owner = THIS_MODULE,
  .llseek = mem_llseek,
  .read = mem_read,
  .write = mem_write,
  .open = mem_open,
  .release = mem_release,
};
/* Device driver module loading function */
static int memdev_init(void)
{
  int result;
  int i;
  dev_t devno = MKDEV(mem_major, 0);
  /*  Static application equipment number */
  if (mem_major)
    result = register_chrdev_region(devno, 2, "memdev");
  else  /*  Dynamically assign device numbers  */
  {
    result = alloc_chrdev_region(&devno, 0, 2, "memdev");
    mem_major = MAJOR(devno);
  }  
  
  if (result < 0)
    return result;
  /* initialization cdev structure */
  cdev_init(&cdev, &mem_fops);
  cdev.owner = THIS_MODULE;
  cdev.ops = &mem_fops;
  
  /*  Register character device  */
  cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);
   
  /*  Allocate memory for the device description structure */
  mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
  if (!mem_devp)    /* Application failed */
  {
    result =  - ENOMEM;
    goto fail_malloc;
  }
  memset(mem_devp, 0, sizeof(struct mem_dev));
  
  /* Allocate memory for devices */
  for (i=0; i < MEMDEV_NR_DEVS; i++) 
  {
        mem_devp[i].size = MEMDEV_SIZE;
        mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
        memset(mem_devp[i].data, 0, MEMDEV_SIZE);
  
      /* Initialize wait queue */
     init_waitqueue_head(&(mem_devp[i].inq));
  }
   
  return 0;
  fail_malloc: 
  unregister_chrdev_region(devno, 1);
  
  return result;
}
/* Module unload function */
static void memdev_exit(void)
{
  cdev_del(&cdev);   /* Log off the device */
  kfree(mem_devp);     /* Free device structure memory */
  unregister_chrdev_region(MKDEV(mem_major, 0), 2); /* Release the device number */
}
MODULE_AUTHOR("David Xie");
MODULE_LICENSE("GPL");
module_init(memdev_init);
module_exit(memdev_exit);


3.app-write.c

#include <stdio.h>
int main()
{
    FILE *fp = NULL;
    char Buf[128];
    
    
    /* Open device file */
    fp = fopen("/dev/memdev0","r+");
    if (fp == NULL)
    {
        printf("Open Dev memdev0 Error!\n");
        return -1;
    }
    
    /* Write device */
    strcpy(Buf,"memdev is char dev!");
    printf("Write BUF: %s\n",Buf);
    fwrite(Buf, sizeof(Buf), 1, fp);
    
    sleep(5);
    fclose(fp);
    
    return 0;    
}

 

4.app-read.c

#include <stdio.h>
int main()
{
    FILE *fp = NULL;
    char Buf[128];
    
    /* initialization Buf*/
    strcpy(Buf,"memdev is char dev!");
    printf("BUF: %s\n",Buf);
    
    /* Open device file */
    fp = fopen("/dev/memdev0","r+");
    if (fp == NULL)
    {
        printf("Open memdev0 Error!\n");
        return -1;
    }
    
    /* eliminate Buf*/
    strcpy(Buf,"Buf is NULL!");
    printf("Read BUF1: %s\n",Buf);
    
    /* Read the data */
    fread(Buf, sizeof(Buf), 1, fp);
    
    /* detection result */
    printf("Read BUF2: %s\n",Buf);
    
    fclose(fp);
    
    return 0;    
}