IO models in Linux device drivers---blocking and non-blocking IO "Turn"

Source: Internet
Author: User
Tags function prototype mutex readable

In the previous study of network programming, I have learned I/O model Linux System application Programming-network programming (I/O model), below to learn the I/O model in the device driver application.

Recall that there are five types of I/O models under Unix/linux, namely:

A--blocking I/O
B--Non-blocking I/O
C--I/O multiplexing (SELECT and poll)
D--signal-driven I/O (SIGIO)
E--Asynchronous I/O (posix.1 aio_ series functions)

Let's learn about blocking I/O, nonblocking I/O, I/O multiplexing (SELECT and poll), and learn the basics first

A--blocking

A blocking operation means that when a device operation is performed, if the resource is not available, the suspended process is aware that it satisfies the operational conditions and then the suspended process goes into hibernation (discarding the CPU) and is removed from the running queue of the scheduler until the waiting condition is satisfied;

B--Non-blocking

A non-blocking process is not suspended (continues to occupy the CPU) when it is unable to operate the device, it either discards or keeps querying until it can be manipulated;

The difference between the two can be seen if the application's call returns immediately!

Drivers often need to provide the ability to make system calls, such as read (), write (), if the device's resources are not available, and the user wants to access the device in a blocking manner, the driver should be in the device-driven xxx_read (), Xxx_write (), and so on The process is blocked until the resource is available , and then the application's read (), write () is returned, the entire process still has the correct device access, the user is not aware of it, and if the user accesses the device file in a non-blocking way, the device-driven operations such as Xxx_read (), Xxx_write () are returned immediately, and system calls such as read (), write ( ) are returned.

Because the blocked process goes into hibernation, you must make sure that there is a place to wake the dormant process, otherwise the process is really dead. The most likely place to wake up the process is in the interrupt, as the hardware resource is often accompanied by an interrupt .

Blocking I/O is usually implemented by the wait queue, and non-blocking I/O is implemented by polling.

One, blocking I/O implementation--waiting queue

1. Basic concept

In Linux drivers, you can use the wait queue (wait queues) to wake up a blocking process . Wait queue is very early as a basic functional unit in the Linux kernel, it is a queue-based data structure, and process scheduling mechanism tightly integrated, can implement the kernel of the asynchronous event notification mechanism . The wait queue can be used to synchronize access to system resources , and the semaphore described in the previous article is also dependent on the wait queue for the kernel.

The process of using the wait queue in the Linux kernel is simple, defining a wait_queue_headfirst, and then calling Wait_event (waiting for a queue, event) If a task wants to wait for an event.

Waiting queues are widely used, but the kernel implementation is simple. It involves two more important data structures:__wait_queue_head, which describes the chain header of the wait queue , which contains a list of links and an atomic lock, the structure is defined as follows:

struct __wait_queue_head {spinlock_t lock;         /* Protect waiting queue atomic lock */struct list_head task_list; /* Wait for queue */};
typedef struct __WAIT_QUEUE_HEAD wait_queue_head_t;

__wait_queue, the structure is an abstraction of a waiting task. Each waiting task is abstracted into a wait_queue and mounted on the wait_queue_head. The structure is defined as follows:

struct __wait_queue {unsigned int flags;void *private;             /* Usually points to the current task control block *//* task Wakeup action method, which is provided in the kernel, usually autoremove_wake_function */wait_queue_func_t func;              struct List_head task_list; /* Mount The mount point of the wait_queue_head */};

The implementation of the waiting queue in Linux shows that when a task needs to sleep on a wait_queue_head, it encapsulates its own process control block information into wait_queue and then mounts it to the Wait_queue list, performing a scheduled sleep. When certain events occur, another task (process) wakes one or all of the tasks on Wait_queue_head, and the wake-up effort is to set the tasks in the wait queue to a state that is scheduled and removed from the queue.

When using the wait queue, you first need to define a wait_queue_head, which can be done by Declare_wait_queue_head macros, which is a statically defined method. The macro defines a wait_queue_head and initializes the locks in the structure and waits for the queue. Of course, the method of dynamic initialization is also very simple, initialize the lock and the queue can be.

When a task waits for an event to occur, it is usually called wait_event, which defines a wait_queue, describes the wait task , initializes the wait_queue with the current process description block, and then Wait_ The queue is added to the wait_queue_head.

The function implementation process is described as follows:

A--Initializes a wait task described by Wait_queue with the current process description block (PCB).

B--Waits for the task to join the wait queue under the protection of the waiting queue lock resource.

C--Determine if the wait condition is satisfied, and if so, wait for the task to move out of the queue and exit the function.

D-if the condition is not satisfied, then the task is dispatched, and the CPU resources are handed to other tasks.

E--When the sleep task is awakened, you need to repeat the B and C steps, and exit the Wait event function if the confirmation condition is met.

2. Wait for the queue interface function

1. Define and initialize

/* define "Wait queue header" */
wait_queue_head_t My_queue;
/* Initialize "Wait queue header" */init_waitqueue_head (&my_queue);

defined and initialized directly. The Init_waitqueue_head () function initializes the spin lock to an unlocked, waiting queue to initialize to an empty, doubly-looped list.

Declare_wait_queue_head (My_queue); Defined and initialized, can be used as a shortcut to define and initialize the wait queue header.

2. Define the wait queue:

Declare_waitqueue (Name,tsk);

Defines and initializes a wait queue named name.

3. Add/Remove the wait queue (from the waiting queue header):

/* Add_wait_queue () function, set the waiting process to be a non-mutex process, and add it into the queue header (q) of the */void Add_wait_queue (wait_queue_head_t *q, wait_queue_t * wait);/* The function is basically the same as the Add_wait_queue () function, except that it sets the waiting process (wait) to a mutually exclusive process. */void add_wait_queue_exclusive (wait_queue_head_t *q, wait_queue_t *wait);

4. Wait for the event:

(1) wait_event () macro:

/** * Wait_event-sleep until a condition gets true * @wq: The waitqueue to wait on * @condition: a C expression for the Event to wait for * * The process was put to sleep (task_uninterruptible) until the * @condition evaluates to True. The @condition is checked each time * The Waitqueue @wq are woken up. * * WAKE_UP () have to is called after changing any variable that could * change the result of the wait condition. */#define WAIT_EVENT (Wq, condition) do                   {                                    if (condition) break                          ;                           __wait_event (Wq, condition);                    } while (0)

Wait for the column to sleep until condition is true. During the wait, the process is set to task_uninterruptible into sleep until the condition variable becomes true. The value of condition is checked each time the process is awakened.

(2) Wait_event_interruptible () function:

The difference from wait_event () is that the current process is set to the Task_interruptible state while the macro is being called. In each wake, first check if the condition is true, if True then return, otherwise check if the process is awakened by the signal, The-erestartsys error code is returned. If condition is true, returns 0.

(3) Wait_event_timeout () macro:

It is similar to Wait_event (). However, if the given sleep time is negative, return immediately. If you wake up during sleep and the condition is true, return the rest of your sleep time, or continue to sleep until you reach or exceed a given sleep time, and then return 0
(4) wait_event_interruptible_timeout () macro :
Similar to Wait_event_timeout (), but returns the Erestartsys error code if interrupted by a signal during sleep.
(5) wait_event_interruptible_exclusive () macro
Same as wait_event_interruptible (), but the sleep process is a mutex process.

5. Wake-up queue

(1) wake_up () function

#define WAKE_UP (x)          __wake_up (x, Task_normal, 1, NULL)/** * __wake_up-wake up threads blocked on a waitqueue. * @q:t  He waitqueue * @mode: Which threads * @nr_exclusive: How many wake-one or Wake-many threads to wake up * @key: is directly Passed to the Wakeup function */void __wake_up (wait_queue_head_t *q, unsigned int mode,            int nr_exclusive, void *key) {    unsigned long flags;     Spin_lock_irqsave (&q->lock, flags);    __wake_up_common (q, Mode, nr_exclusive, 0, key);    Spin_unlock_irqrestore (&q->lock, flags);} Export_symbol (__WAKE_UP);

Wake-up wait queue. You can wake up processes in task_interruptible and task_uninteruptible states, and wait_event/wait_event_timeout in pairs. (2) wake_up_interruptible () function:

#define WAKE_UP_INTERRUPTIBLE (x) __wake_up (x, task_interruptible, 1, NULL)

and Wake_up () The only difference is that it only wakes up the task_interruptible state of the process., with Wait_event_interruptible/wait_event_interruptible_timeout/wait_ Event_interruptible_exclusive is used in pairs.

Let's look at an example:

Static ssize_t hello_read (struct file *filep, char __user *buf, size_t len, loff_t *pos) {/*    when implementing the application process read, if no data is blocked */    if (len>64)    {        len =64;    }    Wait_event_interruptible (Wq, Have_data = = 1);    if (Copy_to_user (Buf,temp,len))    {        return-efault;    }        Have_data = 0;    return Len;} Static ssize_t hello_write (struct file *filep, const char __user *buf, size_t len, loff_t *pos) {    if (len >) 
   
    {        len = +;    }    if (Copy_from_user (Temp,buf,len))    {        return-efault;    }    PRINTK ("Write%s\n", temp);    Have_data = 1;    Wake_up_interruptible (&WQ);    return Len;}
   

Note the two concepts:

A--crazy herd

WAKE_UP, all the processes that block in the queue will be awakened, but because of the condition limit, only one process gets the resources, and the other processes hibernate again, if the number is large, called the crazy herd.

B--Exclusive wait

Waiting for the queue's entry to set a wq_flag_exclusive flag that will be added to the end of the wait queue, without setting the settings to add to the head, wake up when encountering the first one with Wq_flag_ The process that exclusive this flag stops waking other processes.

Second, non-blocking I/O implementation-multiplexing

1. The concept and role of polling

in user programs,Select () and poll () are also topics that are closely related to device blocking and non-blocking access. Applications that use non-blocking I/O typically use the Select () and poll () system call queries for non-blocking access to the device. The Select () and poll () system calls will eventually cause the poll () function in the device driver to be executed.

2. Polling programming in the application

In the user program, select () and poll () are essentially the same, except that they are introduced in the same way that the former is introduced in BSD Unix, which is introduced in System v. A more extensive use of the select system is called. The prototype is as follows :

int select (int Numfds, fd_set *readfds, Fd_set *writefds, Fd_set *exceptionfds, struct timeval *timeout);

where Readfs,writefds,exceptfds is a collection of file descriptors for read, write, and exception handling for select () monitoring, the Numfds value is the highest file description that needs to be checked multibyte 1,timeout is a time-bound value , when the value is exceeded, it is returned even if there is still no descriptor ready.

struct Timeval
{int tv_sec;    //sec int tv_usec; microseconds

There are several main operations that involve the collection of file descriptors:

1) Clear a File Description descriptor Fd_zero (Fd_set *set);

2) Add a file descriptor to the file descriptor set fd_set (int fd,fd_set *set);

3) Clears a file descriptor from the file descriptor set FD_CLR (int fd,fd_set *set);

4) Determine if the file descriptor is set fd_isset (int fd,fd_set *set);

Finally, we use the relevant file descriptor set above to write a verification to add the device polling driver, the upper two pieces of the link together

3. Polling programming in device driver

The poll () function prototype in the device driver is as follows

unsigned int (*poll) (struct file *filp, struct poll_table * wait);

The first parameter is the file struct pointer, the second parameter is a polling table pointer, and the poll device method accomplishes two things:

A--to call the Poll_wait () function on a waiting queue that may cause changes in the device file status, add the corresponding wait queue header to poll_table, and if no file descriptor is available to perform I/O, The kernel causes the process to wait on the waiting queue that corresponds to all file descriptors that are passed to the system call.

B--Returns a mask that indicates whether the device is capable of non-blocking read, write access.

Bitmask: Pollrdnorm, Pollin,pollout,pollwrnorm

Device readable, usually returned: (Pollin | Pollrdnorm)

Device writable, usually returned: (Pollout | Pollwrnorm)

 

poll_wait () function : Used to register the wait queue to poll_table

The poll_wait () function does not cause blocking, and its work is to add the current process to the wait list (poll_table) specified by the wait parameter.

The real blocking action is done in the upper Select/poll function . Select/poll calls their own poll support function in a loop for each device that needs to be monitored so that the current process is added to the waiting list for each device. If no device is currently being monitored, the kernel will dispatch (call schedule) to let the CPU go into a blocking state, and when schedule returns, it loops again to see if any operations can proceed, so repeat; otherwise, if any one of the devices is ready, select/ Poll all returned immediately.

The process is as follows:

A-- the user program invokes select or poll for the first time, drives the call to poll_wait and causes two queues to join the poll_table structure as a condition for the next call to the driver function poll, a mask return value indicates whether the device is operable, 0 is not prepared, and if the file descriptor is not ready to be read or writable, the user process is added to the write or read waiting queue to go to sleep.

B-- when the driver performs certain operations, such as a write buffer or read buffer, the write buffer wakes the read queue, the read buffer wakes the write queue , and the Select or poll system call calls the driver function poll again when it is going to be returned to the user process. The driver still calls Poll_wait and makes two queues join the poll_table structure and determines if the writable or readable condition is met if mask returns Pollin | Pollrdnorm or Pollout | Pollwrnorm indicates either readable or writable, when select or poll actually returns to the user process, and if mask returns 0, the system calls Select or poll to continue without returning

Here is a typical template:

static unsigned int xxx_poll (struct file *filp, poll_table *wait) {    unsigned int mask = 0;        struct Xxx_dev *dev = filp->private_data;     Get device structure pointers ...    Poll_wait (FILP, &dev->r_wait, wait);    Read-up waits on the column header    poll_wait (FILP, &dev->w_wait, wait);    Add write wait queue header        if (...) Readable    {          Mask |= Pollin | Pollrdnorm;    The identity data can be obtained     }    if (...) Writable    {          Mask |= pollout | Pollwrnorm;    Identity data can be written to     }.    .    return mask;}

4. Call Process:

The procedure for a select call under Linux:

1. User-level application calls Select (), bottom call poll ())
2. Core layer call Sys_select ()------> Do_select ()

The final call to the file descriptor fd corresponds to the struct file type variable of the struct file_operations *f_op of the poll function.
The function that poll points to returns information that is currently read and written.
1) returns read and write information if it is currently readable or writable.
2) If it is not currently read/write, block the process and wait for the driver to wake up, call the poll function again, or return the timeout.

3, the driver needs to implement the poll function
When the driver discovers that there is data to read and write, the core layer is notified, and the kernel layer re-invokes the function query information pointed to by poll.

Poll_wait (filp,&wait_q,wait)///Here joins the current process to the wait queue, but does not block

Use Wake_up_interruptible (&wait_q) to wake up the wait queue in interrupts.

4. Example Analysis

1, Memdev.h

/*mem Device Description Structural body */struct mem_dev                                     {                                                          char *data;                        unsigned long size;   wait_queue_head_t inq;  }; #endif/* _memdev_h_ */

2. Driver 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 <linux/ poll.h> #include "memdev.h" static mem_major = Memdev_major;bool Have_data = false; /* Indicates that the device has sufficient data to read */module_param (mem_major, int, s_irugo); struct Mem_dev *mem_devp; /* Device Structural body 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 the device description structure pointer to the 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 */* To determine if the read position is valid */if (P >= memdev_size) return 0;      if (Count > Memdev_size-p) count = memdev_size-p; while (!have_data)/* has no data to read, consider why not use the IF, while */{if (Filp->f_flags & O_nonblock) Return-eaga        In;  Wait_event_interruptible (Dev->inq,have_data);  }/* Read data to 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 no more data readable */* wake-up Write process */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 struct pointer */* parse and get valid 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 is a new data readable */* Wakeup Read process */WAKE_UP (& (DEV-&GT;INQ)); return ret;}    /* Seek file Locator 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;} unsigned int mem_poll (struct file *filp, poll_table *wait) {struct MEM_DEV *dev = filp->private_data;       unsigned int mask = 0;         /* Add the wait queue to poll_table */poll_wait (FILP, &dev->inq, wait); if (have_data) Mask |= Pollin |  Pollrdnorm; /* readable */return mask;} /* File operation struct */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,. Poll = mem_poll,};/* device driver module load function */static int Memdev_ini  T (void) {int result;  int i;  dev_t Devno = MKDEV (mem_major, 0);  /* Static application device number */if (mem_major) result = Register_chrdev_region (Devno, 2, "Memdev");    else/* Dynamically assign device number */{result = Alloc_chrdev_region (&devno, 0, 2, "Memdev");  Mem_major = Major (Devno);  } if (Result < 0) return result;  /* Initialize 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 device description structure */MEM_DEVP = KMALloc (Memdev_nr_devs * sizeof (struct mem_dev), gfp_kernel);    if (!MEM_DEVP)/* Request failed */{result =-enomem;  Goto Fail_malloc;    } memset (MEM_DEVP, 0, sizeof (struct mem_dev));        /* Allocate memory for device */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 the wait queue */Init_waitqueue_head (& (MEM_DEVP[I].INQ));  Init_waitqueue_head (& (MEM_DEVP[I].OUTQ));  } return 0;    Fail_malloc:unregister_chrdev_region (Devno, 1); return result;}   /* Module unload function */static void memdev_exit (void) {Cdev_del (&cdev);     /* Logout device */Kfree (MEM_DEVP); /* release device structure in vivo */unregister_chrdev_region (MKDEV (mem_major, 0), 2); /* Release device number */}module_author ("David Xie"); Module_license ("GPL"); Module_init (Memdev_init); Module_exit (Memdev_exit);

3. Application app-write.c

#include <stdio.h>int main () {    FILE *fp = NULL;    Char buf[128];            /* Open the device file *    /fp = fopen ("/dev/memdev0", "r+");    if (fp = = NULL)    {        printf ("Open Dev memdev 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. Application APP-READ.C

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/ioctl.h> #include < sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/select.h> #include <sys/ Time.h> #include <errno.h>int main () {    int fd;    Fd_set RDS;    int ret;    Char buf[128];        /* Initialize buf*/    strcpy (Buf, "Memdev is char dev!");    printf ("BUF:%s\n", BUF);        /* Open the device file *    /fd = open ("/dev/memdev0", O_RDWR);        Fd_zero (&rds);    Fd_set (FD, &rds);    /* Clear buf*/    strcpy (Buf, "Buf is null!");    printf ("Read BUF1:%s\n", Buf);    ret = SELECT (FD + 1, &rds, NULL, NULL, NULL);    if (Ret < 0)     {        printf ("Select error!\n");        Exit (1);    }    if (Fd_isset (FD, &rds))         read (FD, Buf, sizeof (BUF));                    /* Test Results *    /printf ("Read BUF2:%s\n", Buf);        Close (FD);        return 0;    }

IO models in Linux device drivers---blocking and non-blocking IO "Turn"

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.