One for engineering students , Keen on the underlying technology development , Have a strong curiosity , I'm interested in MCU , The embedded Linux,Uboot etc. , Welcome to study and exchange !
I like running , To play basketball , sleep .
Welcome to join me QQ1500836631( remarks CSDN), Learn and communicate with each other , Sharing various learning materials , electronic text , Learning videos, etc .


List of articles


Blocking / Introduction to non blocking

Blocking operation refers to when performing equipment operation , Without access to resources , be Suspend process Do not operate until the operational conditions are met . The suspended process enters Sleep state , Removed from the scheduler's run queue , Until the waiting conditions are met . When a non blocking process cannot perform device operations , It doesn't hang up , It either gives up , Or keep searching , Until it can be operated .

Blocking / Non blocking routines

Blocking mode

int fd;int data = 0;fd = open("/dev/xxx_dev", O_RDWR); /*  Blocking mode on  */ret = read(fd, &data, sizeof(data)); /*  Reading data  */

Non blocking mode

int fd;int data = 0; fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /*  Open in non blocking mode  */ ret = read(fd, &data, sizeof(data)); /*  Reading data  */

Waiting queue Introduction

Waiting queue is an important data structure in kernel . Blocking access to devices , If the device is not operational , Then the process goes into Sleep state . The waiting queue is a data structure to complete the process sleep operation .

Wait for queue related functions

Define the waiting queue

wait_queue_head_t my_queue;

wait_queue_head_t yes __wait_queue_head One of the structures typedef.

Initialize the waiting queue header

void init_waitqueue_head(wait_queue_head_t *q)

Parameters q It is the waiting queue header to be initialized , You can also use macros **DECLARE_WAIT_QUEUE_HEAD (name)** To initialize the definition of the waiting queue header at one time .

Define and initialize a wait queue item

DECLARE_WAITQUEUE(name, tsk)

name Is the name of the waiting queue item ,tsk Indicates which task process this waiting queue item belongs to , Generally set as current, stay Linux The kernel current It's equivalent to a global variable , Express The current process . So macro DECLARE_WAITQUEUE Is to create and initialize a waiting queue item for the currently running process .

Add the waiting item to the queue

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)

q: The head of the waiting queue to which the waiting queue item is to join
wait: Waiting queue items to join
Return value : nothing

Remove the queue item from the waiting queue header

void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)

q: The head of the waiting queue where the waiting queue item to be deleted is located
wait: Waiting queue items to delete .
Return value : nothing

Waiting to wake up

void wake_up(wait_queue_head_t *q) void wake_up_interruptible(wait_queue_head_t *q)

q: It's the waiting queue head to wake up , This function wakes up all the processes in the first two queues
wake_up The function wakes up in TASK_INTERRUPTIBLE and TASK_UNINTERRUPTIBLE The process of state , and wake_ up_ interruptible Function can only wake up in TASK_INTERRUPTIBLE The process of state

Events wait

wait_event(wq, condition)

Wait for wq The waiting queue for the waiting queue header is awakened , Premise is condition Conditions must be met ( It's true ), Otherwise, it will be blocked all the time . This function sets the process to TASK _UNINTERRUPTIBLE state

wait_event_timeout(wq, condition, timeout)

Function and wait_event similar , But this function can add Timeout time , With jiffies In units of . This function has a return value , If you return 0 It's time out , and condition For false . by 1 The words of condition It's true , That is, the conditions are met .

wait_event_interruptible(wq, condition)

And wait event Function similar to , But this function sets the process to TASK_INTERRUPTIBLE, Namely Can be interrupted by a signal .

wait_event_interruptible_timeout(wq, condition, timeout)

And wait event timeout Function similar to , This function also sets the process to TASK_INTERRUPTIBLE, Can be interrupted by a signal .

polling

When an application accesses a device in a non blocking way , We will check whether our devices can be accessed over and over again , This query operation is called polling . The kernel provides poll,epoll,select Function to handle polling operations . When the application passes through the upper layer poll,epoll,select Function to query the device , In the driver poll,epoll,select Function is to implement the query at the bottom , If it works , Data will be read from or written to the device .

select

The function prototype

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)

nfds: Number of file descriptors to operate .
readifds、 writefds and exceptfds: These three pointers point to Descriptor set , These three parameters indicate which descriptors to care about 、 What conditions need to be met and so on , These three parameters are fd_set Type of , fd_set Each bit of a type variable represents a file descriptor . readfds Used to monitor a specified set of descriptors Reading change , That is to monitor whether these files can be read , As long as there is a file in these collections that can be read , that seclect Will return a value greater than 0 The value of indicates that the file can be read . If there are no files to read , Then it will be based on timeout Parameter to determine whether the timeout . Sure take reads Set to NULL, I don't care about the change of any file . writefds and reads similar , It's just writers Used to monitor whether these files can be written . exceptfds Exceptions used to monitor these files
timeout: Timeout time , When we call select Function to wait for some file descriptors to set a timeout , Timeout uses structure timeval Express , The structure is defined as follows :

struct timeval { long tv_sec; /*  second  */long tv_usec; /*  subtle  */ };

When timeout by NULL It means Waiting indefinitely for the return value .0, It means that the timeout occurs , But there are no file descriptors to operate on ;-1, An error occurred ; Other values , The number of file descriptors that can be operated on .
operation fd_set Functions of variables

void FD_ZERO(fd_set *set) void FD_SET(int fd, fd_set *set) void FD_CLR(int fd, fd_set *set) int FD_ISSET(int fd, fd_set *set)

FD_ZERO Is used to fd set Variable All the bits are cleared , FD_SET Is used to fd_set A bit of a variable Set up 1, That is to say, to fd_set Add a file descriptor , Parameters fd Is the file descriptor to be added . FD_CLR Users will fd_set Variable A bit cleared , That is to say, a file descriptor is removed from fd_set Delete in , Parameters fd Is the file descriptor to delete . FD_ISSET be used for test fd_set Of Whether a bit is set to 1, That is to say, to judge a file Whether it can be operated , Parameters fd It's the file descriptor to judge .

void main(void) {  int ret, fd; /*  File descriptor to monitor  */ 
    fd_set readfds; /*  Read operation file descriptor set  */struct timeval timeout; /*  Timeout structure  */ 
    fd = open("dev_xxx", O_RDWR | O_NONBLOCK); /*  Nonblocking access  */ FD_ZERO(&readfds); /*  eliminate readfds */ FD_SET(fd, &readfds); /*  take fd Add to readfds Inside  */ 
     /*  Construct timeout  */ 
     timeout.tv_sec = 0; 
     timeout.tv_usec = 500000; /* 500ms */ 
     ret = select(fd + 1, &readfds, NULL, NULL, &timeout); 
     switch (ret) { case 0: /*  Overtime  */ printf("timeout!\r\n");break; case -1: /*  error  */ printf("error!\r\n"); break; default: /*  Can read data  */ if(FD_ISSET(fd, &readfds))   /*  Judge whether it is fd File descriptor  */ 
          {   /*  Use read Function to read data  */ 
          } 
         break; } 
 }

poll

In a single thread , select Function can monitor the maximum number of file descriptors , It's usually 1024, The kernel can be modified to increase the number of file descriptors to be monitored , But it reduces efficiency ! It can be used at this time poll function , poll Functions are essentially the same as select There's no big difference , however poll function There is no maximum file descriptor limit ,Linx In the application poll The function prototype is as follows :

int poll(struct pollfd *fds, nfds_t nfds, int timeout)

Function parameters and return values have the following meanings
fds: The collection of file descriptors to be monitored and the events to be monitored , Is an array , Array elements are structs polled Type of , pollfd The structure is as follows

struct pollfd 
{ 
int fd; /*  File descriptor   File descriptor   File descriptor  */ 
short events; /*  Requested event   Requested event   Requested event  */short revents; /*  Events returned   Events returned   Events returned  */ };

fd Is the file descriptor to monitor , If f If it doesn't work, then events Monitoring events is ineffective , also revents return 0. events It's an event to watch , The types of events that can be monitored are as follows

POLLIN // There's data to read .POLLPRI // There's urgent data to read .POLLOUT // You can write data POLLERR Error in specified file descriptor POLLHUP The specified file descriptor is suspended POLLNVAL Invalid request POLLRDNORM Equate to  POLLIN

revents: Returns the parameter , That is, the returned event , Yes Linux The kernel sets specific return Events .
nfds:poll Number of file descriptors to be monitored by function
timeout: Timeout time , Unit is ms
Return value : return revents Domain is not 0 Of polled Number of structures , That is, the number of file descriptors where an event or error occurred ;0, Overtime ;-1, An error occurred , And set up errno For error type

void main(void){ 
   int ret; 
   int fd; /*  File descriptor to monitor  */
   struct pollfd fds;
   fd = open(filename, O_RDWR | O_NONBLOCK); /*  Nonblocking access  */ /*  Structural bodies  */ 
   fds.fd = fd; 
   fds.events = POLLIN; /*  Monitor whether the data can be read  */
   ret = poll(&fds, 1, 500); /*  Is the polling file operational , Overtime 500ms */ if (ret) { /*  The data is valid  */ 
      /*  Reading data  */ 
     } else if (ret == 0) 
     { 
         /*  Overtime  */ 
     } else if (ret < 0) 
     {  /*  error  */ 
     } 
 }

epoll

Conventional selcet and poll Functions will follow the listening fd An increase in quantity , There is a problem of inefficiency , and poll The function must traverse all descriptors each time to check for ready descriptors , It's a waste of time . So ,epoll Born of luck ,epoll It's for dealing with big concurrency , It is often used in network programming epoll function . The application needs to use epoll_create Function to create a epoll Handle , epoll create The original function is as follows .

int epoll_create(int size)

Function parameters and return values have the following meanings :
size; from Linux2.6.8 At first, this parameter is meaningless , Fill in any one greater than 0 The value of
Return value :epoll Handle , If -1 The creation failed ,epoll After the handle is created successfully, use ,epoll ctl Function to add file descriptors to be monitored and things to be monitored ct The function prototype is as follows

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

Function parameters and return values have the following meanings
epfd; To operate epoll Handle , That is to use epoll_create Function created epoll Handle .
p: It means to be right about epfd( epoll Handle ) Operations performed , It can be set to

EPOLL CTL ADD // Xiang Yin fd Add file parameters d The descriptor of the representation EPOLL CTL MOD Modify the parameters fd Of  event event .EPOLL CTL DEL // from f Has been deleted from l The descriptor 

fd: Description of the file to be monitored
event: The type of event to monitor , by epoll_event Structure type pointer , epoll_event The types of structures are as follows

struct epoll_event 
{
  uint32_t events; /* epoll event  */ 
epoll_data_t data; /*  User data   User data  */ };

Structure epoll_event Of events Member variables represent events to be monitored , The optional events are as follows

EPOLLIN // There's data to read EPOLLOUT You can write data EPOLLPRI // There's urgent data to read EPOLLERI Error in specified file descriptor .EPOLLHUP // The specified file descriptor is suspended POLLET Set up epo Trigger for edge , The default trigger mode is horizontal trigger king POLLONESHOT // One time surveillance , When the monitoring is finished, you need to monitor another fd, So you're going to have to fd Re add to  epoll  Inside 

These events can take place “ or ” operation , That is to say, you can set multiple event return values :0, success ;-1, Failure , And set up errno The value of is the corresponding error code . After everything is set up, the application can go through epoll_wait Function to wait for an event to occur , similar select function . epoll_wait The function prototype is as follows

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

Function parameters and return values have the following meanings
epfd: To wait for epoll
events: Point to epoll_event An array of structs , When something happens Iimx The kernel will fill in events, The caller can call according to events Judge what happened .
prevents:events Array size , Must be greater than 0
timeout: Timeout time , Unit is ms Return value :0, Overtime ;-1, error ; Other values , Number of file descriptors ready .
epoll It's more used on large-scale concurrent servers , Because on this occasion select and poll Not suitable for . When designing file descriptors (fd It's suitable for less time selcet and pl In this chapter we use sellect and poll These two functions

Asynchronous notification concept

Blocking and non blocking access 、poll Function provides a better mechanism to solve the problem of device access , But if you have asynchronous notification , The whole mechanism is more complete .
Asynchronous notification means : Once the device is ready , The application is actively notified , In this way, the application does not need to query the device status at all , This is very similar to hardware “ interrupt ” The concept of , The more accurate title is “ Signal driven asynchrony I/O”. Signal is a simulation of interrupt mechanism at software level , In principle , A process receiving a signal is the same as a processor receiving an interrupt request . The signal is asynchronous , A process does not have to wait for a signal to arrive through any operation , in fact , The process doesn't know when the signal will arrive .
Blocking I/O It means waiting until the device is accessible , Non blocking I/O Use in poll() It means to query whether the device is accessible , Asynchronous notification means that the device notifies the user that it is accessible , And then the user does I/O Handle . thus it can be seen , These kinds of I/O Ways can complement each other .

Linux The signal

The core of asynchronous notification is signaling , stay arch/xtensa/include/uapi/asm/signal.h It's defined in the file Linux All the signals supported

#define SIGHUP      1/*  The terminal is suspended or the control process is terminated  */ #define SIGINT      2/*  Terminal interrupt (Ctrl+C Composite key ) */ #define SIGQUIT     3 /*  Terminal exit (Ctrl+\ Composite key ) */#define SIGILL      4/*  Illegal order  */ #define SIGTRAP     5/* debug Use , Instruction generation with breakpoint  */#define SIGABRT     6/*  from abort(3) The exit command issued  */ #define SIGIOT      6 /* IOT Instructions  */ #define SIGBUS      7 /*  Bus error  */ #define SIGFPE      8 /*  Floating point operation error  */ #define SIGKILL     9 /*  Kill 、 Terminate the process  */ #define SIGUSR1     10 /*  User defined signal 1 */ #define SIGSEGV     11 /*  It's a violation ( Invalid memory segment ) */#define SIGUSR2     12 /*  User defined signal 2 */ #define SIGPIPE     13 /*  Write data to the unread pipe  */ #define SIGALRM     14 /*  alarm clock  */#define SIGTERM     15 /*  Software termination  */#define SIGSTKFLT   16 /*  Stack exception  */#define SIGCHLD     17 /*  End of subprocess  */#define SIGCONT     18 /*  The process continues  */#define SIGSTOP     19 /*  Stop the execution of the process , It's just a pause  */#define SIGTSTP     20 /*  Stop the process (Ctrl+Z Composite key ) */ #define SIGTTIN     21 /*  The background process needs to read data from the terminal  */ #define SIGTTOU     22 /*  The background process needs to write data to the terminal  */#define SIGURG      23 /*  Yes " emergency " data  */#define SIGXCPU     24 /*  exceed CPU Resource constraints  */ #define SIGXFSZ     25 /*  File size exceeded  */ #define SIGVTALRM   26 /*  Virtual clockticks  */ #define SIGPROF     27 /*  Clock signal description  */#define SIGWINCH    28 /*  Window size changes  */ #define SIGIO       29 /*  You can input / Output operation  */#define SIGPOLL SIGIO 
 /* #define SIGLOS 29 */ #define SIGPWR      30 /*  Breakpoint restart  */ #define SIGSYS      31 /*  Illegal system call  */ #define SIGUNUSED   31 /*  No signal used  */

Asynchronous notification code

When we use interrupts, we need to set interrupt handling functions , alike , If you want to use signals in an application , Then it is necessary to set the signal used by Signal processing functions , Use... In an application signal Function to set the processing function of the specified signal , signal The function prototype is as follows

void (*signal(int signum, void (*handler))(int)))(int);

The function prototype is difficult to understand , It can be broken down into :

typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler));

The first parameter specifies the value of the signal , The second parameter specifies the processing function for the previous signal value , if SIG_IGN, Indicates that the signal is ignored ; if SIG_DFL, Indicates that the signal is processed in the system default mode ; If it is a user-defined function , After the signal is captured , This function will be executed .
If signal Successful call , It returns the last signal signum Bound to the processing function handler value , Failure returns SIG_ERR.

Signal processing in driving

fasync_struct Structure

First of all, we need to define a fasync_struct Structure pointer variable , fasync_struct The structure is as follows

struct fasync_struct 
{ spinlock_t fa_lock; int magic; int fa_fd; struct fasync_struct *fa_next; struct file *fa_file; struct rcu_head fa_rcu; };

Generally will fasync_struct Structure pointer variables are defined into the device structure , For example xxx_dev Add a fasync_struct Structure pointer variable , The results are shown below

struct xxx_dev 
{ 
struct device *dev; 
struct class *cls;struct cdev cdev;
 ...... 
 struct fasync_struct *async_queue; /*  Asynchronous related structures  */
 };

fasync function

If you want to use asynchronous notification , It needs to be implemented in the device driver file_ operations The operation is centralized fasync function , The format of this function is as follows :

int (*fasync) (int fd, struct file *filp, int on)

fasync Functions are usually called fasync_helper Function to initialize the previously defined fasync_struct Structure pointer , fasync_helper The function prototype is as follows

int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)

fasync_helper The first three arguments to the function are fasync The three parameters of the function , The fourth parameter is to be initialized fasync_ struct Structure pointer variable . When an application pointer to a variable through a struct . When the application passes “ fcntl(fd, F_SETFL, flags | FASYNC)” change fasync When marking , The driver file_operations The operation is centralized fasync The function will execute .

struct xxx_dev 
{ 
......struct fasync_struct *async_queue; /*  Asynchronous related structures  */ }; static int xxx_fasync(int fd, struct file *filp, int on){
 struct xxx_dev *dev = (xxx_dev)filp->private_data; 
 if (fasync_helper(fd, filp, on, &dev->async_queue) < 0) 
 return -EIO; 
 return 0; 
 } 
 static struct file_operations xxx_ops =
 { 
    ...... 
   .fasync = xxx_fasync,
  ...... 
 };

When closing the driver file, you need to file_ operations The operation is centralized release Function fasyn_fasync struct The same release function is fasync_helper, release Reference examples of function parameters are as follows

static int xxx_release(struct inode *inode, struct file *filp) 
 { 
  return xxx_fasync(-1, filp, 0); /*  Delete asynchronous notification  */
 }

static struct file_operations xxx_ops =
 { 
...... 
.release = xxx_release, 
 };

The first 3 Line by calling the sample code xxx_fasync Function to complete fasync_struct The release of work , however , In the end, it passed fasync_helper Function to complete the release .

kill_fasync function

When the device is accessible , The driver needs to signal the application , It's equivalent to producing “ interrupt ” kill_fasync The specified signaling function is responsible for , kill_fasync The function prototype is as follows

void kill_fasync(struct fasync_struct **fp, int sig, int band)

Function parameters and return values have the following meanings :
fasync struct Pointer to the file to operate on
sig: The signal to send
band: When readable, set to POLL IN, When writable, set to POLL OUT.
Return value : nothing .

Application processing of asynchronous notifications

The processing of asynchronous notification by application includes the following three steps
1、 Register signal processing functions the application sets the signal processing functions based on the signals used by the driver , Application usage signal Function to set the signal processing function . I have already talked about it in detail , I won't go into details here .
2、 Tell the kernel the process number of this application fcntl(fd, F_SETOWN, getpid) Tell the kernel the process number of this application
3、 To turn on asynchronous notification, use the following two-line program to turn on asynchronous notification :

flags = fcntl(fd, F_GETFL); /*  Get the current process state */ fcntl(fd, F_SETFL, flags | FASYNC); /*  Turn on the asynchronous notification function of the current process  */

The point is through fcntl Set the process state function to FASYNC, So you go through this , In the driver fasync The function will execute .

Everyone's encouragement is the driving force for me to continue to create , If you think it's good , Welcome to your attention , give the thumbs-up , Collection , forward , thank you !