linux blocking and non blocking IO experiments

Posted by interface on Wed, 29 Dec 2021 08:55:58 +0100

1, Blocking and non blocking IO

Blocking and non blocking IO are two common device access modes in Linux driver development. Blocking and non blocking must be considered when writing drivers.

In this chapter, we will learn about blocking and non blocking IO, how to deal with blocking and non blocking in the driver, and how to use wait queue and poll mechanism in the driver.

1. Introduction to blocking and non blocking

IO here refers to Input/Output, that is, Input/Output. It is the application's Input/Output operation on the driving device.

When the application operates on the device driver, if the device resource cannot be obtained, the blocking IO will suspend the thread corresponding to the application until the device resource can be obtained. For non blocking IO, the thread corresponding to the application will not hang. It will either poll and wait until the device resources are available, or give up directly.


The application program calls the read function to read data from the device. When the device is unavailable or the data is not ready, it will enter the sleep state. When the device is available, it will wake up from sleep, and then read data from the device and return it to the application.

It can be seen that the application uses non blocking access to read data from the device. When the device is unavailable or the data is not ready, it will immediately return an error code to the kernel, indicating that the data reading failed. The application will re read the data again, which will cycle back and forth until the data is read successfully.

The application can implement blocking access using the following example code:

1 int fd;
2 int data = 0;
3
4 fd = open("/dev/xxx_dev", O_RDWR); /* Blocking mode on */
5 ret = read(fd, &data, sizeof(data)); /* Read data */

The default reading method for device driver files is blocking, so I
All of our previous routine test apps use blocking IO.

If the application wants to access the driver device file in a non blocking way, you can use the following code:

1 int fd;
2 int data = 0;
3
4 fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK);  /* Open in non blocking mode */
5 ret = read(fd, &data, sizeof(data)); /* Read data */

When using the open function to open the "/ dev/xxx_dev" device file, the parameter "O_NONBLOCK" is added to indicate that the device is opened in a non blocking manner, so that the data is read from the device in a non blocking manner

2. Waiting queue

1. Waiting queue header

The biggest advantage of blocking access is that when the device file is inoperable, the process can enter the sleep state, which can free up CPU resources. However, when the device file can be operated, it must wake up the process. Generally, the wake-up work is completed in the interrupt function.

The Linux kernel provides a wait queue to wake up the blocking process. If we want to use the wait queue in the driver, we must create and initialize a wait queue header, which uses the structure wait_queue_head_t means,

wait_ queue_ head_ The T structure is defined in the file include / Linux / wait H, the structure is as follows:

39 struct __wait_queue_head {
40 spinlock_t lock;
41 struct list_head task_list;
42 };
43 typedef struct __wait_queue_head wait_queue_head_t;

After defining the waiting queue header, you need to initialize and use init_ waitqueue_ The head function initializes the waiting queue header. The prototype of the function is as follows:

void init_waitqueue_head(wait_queue_head_t *q)

The parameter q is the waiting queue header to be initialized.
You can also use the macro DECLARE_WAIT_QUEUE_HEAD to complete the initialization of the definition of the waiting queue header at one time

2. Waiting for queue items

The waiting queue header is the header of a waiting queue. Each process accessing the device is a queue item. When the device is unavailable, the corresponding waiting queue items of these processes should be added to the waiting queue.
Structure wait_queue_t indicates the waiting queue item. The structure is as follows:

struct __wait_queue {
unsigned int flags;
void *private;
wait_queue_func_t func;
struct list_head task_list;
};
typedef struct __wait_queue wait_queue_t;

Using macro DECLARE_WAITQUEUE defines and initializes a waiting queue item. The contents of the macro are as follows:

DECLARE_WAITQUEUE(name, tsk)

Name is the name of the waiting queue item. tsk indicates which task (process) the waiting queue item belongs to. It is generally set to current. In the Linux kernel, current is equivalent to a global variable, indicating the current process. Therefore, the macro DECLARE_WAITQUEUE creates and initializes a waiting queue item for the currently running process.

3. Add / remove queue items to / from the waiting queue header

When the device is inaccessible, the corresponding waiting queue item of the process needs to be added to the previously created waiting queue header. The process can enter the sleep state only after it is added to the waiting queue header. When the device is accessible, you can remove the waiting queue item corresponding to the process from the waiting queue header,
The API functions added to the waiting queue items are as follows:

void add_wait_queue(wait_queue_head_t *q,
wait_queue_t *wait)

Function parameters and return values have the following meanings:

q: The wait queue header to which the wait queue item is to be added.
wait: the waiting queue item to join.
Return value: none.

Wait queue item removal API functions are as follows:

void remove_wait_queue(wait_queue_head_t *q,
wait_queue_t *wait)

Function parameters and return values have the following meanings:

q: The waiting queue header of the waiting queue item to delete.
wait: the waiting queue entry to delete.
Return value: none.

4. Wait for wake-up

When the device is available, wake up the process entering the sleep state. The following two functions can be used to wake up:

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

Parameter q is the waiting queue header to wake up. These two functions will wake up all processes in the waiting queue header. wake_ The up function wakes up the TASK_INTERRUPTIBLE and TASK_UNINTERRUPTIBLE state of the process, while wake_up_ The interruptible function can only wake up a task_ Process in interruptible state.

5. Waiting events

In addition to active wake-up, you can also set the waiting queue to wait for an event. When the event is satisfied, the process in the waiting queue will be automatically awakened. The API functions related to waiting events are shown in the table:

3. Polling

If the user application accesses the device in a non blocking way, the device driver should provide a non blocking processing method, that is, polling.

Poll, epoll and select can be used to process polling. The application queries whether the device can operate through select, epoll or poll functions. If so, it reads or writes data from or to the device. When the application calls the select, epoll or poll functions, the poll function in the device driver will execute, so you need to write the poll function in the device driver.
Let's first look at the three functions used in the application: select, poll and epoll.

1.select function

The prototype of the select function is as follows:

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

Function parameters and return values have the following meanings:

nfds: add 1 to the maximum file descriptor in the set of three types of file descriptions to be monitored.
readfds, writefds and exceptfds: these three pointers point to the descriptor set. These three parameters indicate which descriptors are concerned and what conditions need to be met. These three parameters are FD_ Of type set, FD_ Each bit of a set type variable represents a file descriptor. readfds is used to monitor the read changes of the specified descriptor set, that is, to monitor whether these files are changed
It can be read. As long as there is a file in these collections that can be read, secect will return a value greater than 0, indicating that the file can be read. If there is no file to read, the timeout parameter will be used to determine whether the timeout occurs. You can set readfs to NULL to indicate that you do not care about any file read changes. writefds is similar to readfs, except that writefs is used for monitoring
Whether these files can be written. exceptfds is used to monitor exceptions to these files.

For example, if we want to read data from a device file, we can define an fd_set variable, which is passed to the parameter readfds. When we define a FD_ The set variable can be operated using the following macros:

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 convert FD_ All bits of the set variable are cleared, fd_set is used to set FD_ A position 1 of the set variable, that is, to fd_set adds a file descriptor, and the parameter FD is the file descriptor to be added. FD_CLR is used to convert FD_ A bit of the set variable is cleared, that is, a file descriptor is removed from FD_ The parameter FD is the file descriptor to be deleted.
FD_ISSET is used to test whether a file belongs to a set. The parameter fd is the file descriptor to be judged. Timeout: timeout,

When we call the select function and wait for some file descriptors, we can set the timeout
The interval is represented by structure timeval, and the structure definition is as follows:

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

When timeout is NULL, it means waiting indefinitely.
Return value: 0 indicates that the timeout occurred, but there is no file descriptor to operate- 1. An error occurred; Other values, the number of file descriptors that can be operated on.

An example of reading non blocking access to a device driver file using the select function is as follows:

1 void main(void)
2 {
3 int ret, fd; /* File descriptor to monitor */
4 fd_set readfds; /* Read operation file descriptor set */
5 struct timeval timeout; /* Timeout structure */
6
7 fd = open("dev_xxx", O_RDWR | O_NONBLOCK); /* Non blocking access */
8
9 FD_ZERO(&readfds); /* Clear readfds */
10 FD_SET(fd, &readfds); /* Add fd to readfds */
11
12 /* Construction timeout */
13 timeout.tv_sec = 0;
14 timeout.tv_usec = 500000; /* 500ms */
15
16 ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
17 switch (ret) {
18 case 0: /* overtime */
19 printf("timeout!\r\n");
20 break;
21 case -1: /* error */
22 printf("error!\r\n");
23 break;
24 default: /* Can read data */
25 if(FD_ISSET(fd, &readfds)) { /* Determine whether it is an fd file descriptor */
26 /* Use the read function to read data */
27 }
28 break;
29 }
30 }

2.poll

In a single thread, the select function can monitor the maximum number of file descriptors, usually 1024. You can modify the kernel to increase the number of file descriptors monitored, but this will reduce efficiency! You can use the poll function at this time. The poll function is essentially not much different from select, but the poll function has no maximum file descriptor limit. The prototype of the poll function in Linux applications is as follows:

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

Function parameters and return values have the following meanings:

fds: the set of file descriptors to be monitored and the events to be monitored are an array. The array elements are of structure pollfd type,

pollfd structure is as follows:

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

fd is the file descriptor to be monitored. If fd is invalid, the events monitoring event is invalid, and the events are invalid
Return 0. events is the event to be monitored,

The types of events that can be monitored are as follows:

POLLIN has data to read.
POLLPRI has urgent data to read.
POLLOUT can write data.
POLLERR an error occurred with the specified file descriptor.
POLLHUP specified file descriptor is pending.
POLLNVAL invalid request.
POLLRDNORM is equivalent to POLLIN
Events are return parameters, that is, return events, which are set by the Linux kernel.
nfds: the number of file descriptors to be monitored by the poll function.
Timeout: timeout, in ms.
Return value: returns the number of pollfd structures that are not 0 in the events field, that is, the number of file descriptors with events or errors; 0, timeout- 1. An error occurs and errno is set as the error type.

An example of reading non blocking access to a device driver file using the poll function is as follows:

1 void main(void)
2 {
3 int ret;
4 int fd; /* File descriptor to monitor */
5 struct pollfd fds;
6
7 fd = open(filename, O_RDWR | O_NONBLOCK); /* Non blocking access */
8
9 /* Structural body */
10 fds.fd = fd;
11 fds.events = POLLIN; /* Monitor whether the data can be read */
12
13 ret = poll(&fds, 1, 500); /* Whether the polling file is operable, timeout 500ms */
14 if (ret) { /* Data valid */
15 ......
16 /* Read data */
17 ......
18 } else if (ret == 0) { /* overtime */
19 ......
20 } else if (ret < 0) { /* error */
21 ......
22 }
23 }

3.ePoll

The traditional selcet and poll functions are inefficient with the increase of the number of fd monitored. Moreover, the poll function must traverse all descriptors to check the ready descriptors every time, which is a waste of time. Therefore, epoll came into being. Epoll is prepared to deal with large concurrency. Generally, epoll function is often used in network programming. The application needs to use epoll first_ The create function creates an epoll handle, epoll_ The prototype of the create function is as follows:

int epoll_create(int size)

Function parameters and return values have the following meanings:

size: from Linux 2 6.8 at the beginning, this parameter is meaningless. Just fill in a value greater than 0.
Return value: epoll handle. If it is - 1, it indicates creation failure.

Epoll handle is created successfully. Epoll will be used later_ CTL function adds the file descriptor to be monitored and the monitored events to it, epoll_ The prototype of CTL function 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: handle of epoll to operate on, that is, use epoll_ The epoll handle created by the create function.
op: indicates the operation to be performed on epfd(epoll handle), which can be set to:
EPOLL_CTL_ADD adds the descriptor represented by the file parameter fd to epfd.
EPOLL_CTL_MOD modifies the event event event of parameter fd.
EPOLL_CTL_DEL deletes the fd descriptor from epfd.
fd: file descriptor to monitor.
Event: event type to monitor, epoll_event struct type pointer,

epoll_ The event structure type is as follows

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

Structure epoll_ The events member variable of event represents the events to be monitored. The optional events are as follows:

Epolin has data to read.
EPOLLOUT can write data.
EPOLLPRI has urgent data to read.
An error occurred in the file descriptor specified by EPOLLERR.
The file descriptor specified by EPOLLHUP is suspended.
EPOLLET sets epoll as edge trigger, and the default trigger mode is horizontal trigger.
EPOLLONESHOT is a one-time monitoring. When a fd needs to be monitored again after the monitoring is completed, fd needs to be added to epoll again.
The above events can be "or", that is, you can set and monitor multiple events.
Return value: 0, successful- 1. Failed, and set the value of errno to the corresponding error code.

After everything is set up, the application can pass epoll_wait function to wait for an event to occur, similar to the select function.
epoll_ The prototype of the wait function 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: epoll to wait for.
Events: point to epoll_ The array of event structure. When an event occurs, the Linux kernel will fill in events, and the caller can judge which events have occurred according to events.
maxevents: the size of events array must be greater than 0.
Timeout: timeout, in ms.
Return value: 0, timeout- 1. Error; Other values, number of file descriptors ready.

epoll is more used in large-scale concurrent servers, because select and poll are not suitable in this situation. selcet and poll are suitable when there are few file descriptors (fd). In this chapter, we use sellect and poll.

4. poll operation function driven by Linux

When the application calls the select or poll function to make non blocking access to the driver, the driver file_ The poll function in the operations operation set is executed. Therefore, the driver writer needs to provide the corresponding poll function. The prototype of the poll function is as follows:

unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait)

Function parameters and return values have the following meanings:

filp: the device file (file descriptor) to open.
wait: struct poll_table_struct type pointer, passed in by the application. This parameter is generally passed to
poll_wait function.
Return value: returns the device or resource status to the application. The returned resource status is as follows:
POLLIN has data to read.
POLLPRI has urgent data to read.
POLLOUT can write data.
POLLERR an error occurred with the specified file descriptor.
POLLHUP specified file descriptor is pending.
POLLNVAL invalid request.
POLLRDNORM is equivalent to POLLIN, and ordinary data is readable

We need to call poll_ in the poll function of the driver. Wait function, poll_ The wait function does not cause blocking, just adds the application to the poll_ In table, poll_ The prototype of wait function is as follows:

void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)

Parameter wait_address is the address to add to the poll_ Wait queue header in table,
Parameter p is poll_table is file_ wait parameter of poll function in operations

2, Blocking IO experiment

In the Linux interrupt experiment in the previous chapter, we continuously read the key status through the read function directly in the application program, and print the key value when the key is valid. One disadvantage of this method is that the test application imx6uirqApp has a high CPU utilization. You can load the driver module imx6uirq in the previous chapter in the development board Ko, and then open imx6uirqApp in the background running mode. The command is as follows:

./imx6uirqApp /dev/imx6uirq &

Test whether the driver works normally. If the driver works normally, enter the "top" command to check imx6uirqApp the CPU utilization of the application. The results are as follows:

The CPU utilization of imx6uirqApp is as high as 99.6%, which is only
Is an application that reads key values. Such a high CPU utilization is obviously a problem! The reason is that we directly read the key value through the read function in the while loop, so imx6uirqApp will run and read the key value all the time, and the CPU utilization will be very high.

The best way is that when no effective key event occurs, the imx6uirqApp application should be in sleep. When a key event occurs, the imx6uirqApp application will run and print the key value, which will reduce the CPU utilization. In this section, we use blocking IO to achieve this function.

1. Hardware schematic analysis

2. Preparation of experimental program

1. Driver programming

Create a new folder named "14_blockio", and then_ Create a vscode project in the blockio folder, and the workspace is named "blockio". The imx6uirq in the "13_irq" experiment C copy to 14_ In the blockio folder and rename it blockio c. Next, we will modify blockio C this file, add the blocking related code in it, and complete the blockio The content of C is as follows (because it is modified based on the imx6uirq.c file of the experiment in the previous chapter,

#include <linux/types.h>

#include <linux/kernel.h>

#include <linux/delay.h>

#include <linux/ide.h>

#include <linux/init.h>

#include <linux/module.h>

#include <linux/errno.h>

#include <linux/gpio.h>

#include <linux/cdev.h>

#include <linux/device.h>

#include <linux/of.h>

#include <linux/of_address.h>

#include <linux/of_gpio.h>

#include <linux/semaphore.h>

#include <linux/timer.h>

#include <linux/of_irq.h>

#include <linux/irq.h>

#include <asm/mach/map.h>

#include <asm/uaccess.h>

#include <asm/io.h>

/***************************************************************

Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.

file name 		:  block.c

author 	  	:  Zuo Zhongkai

edition 	   	:  V1.0

describe 	   	:  Blocking IO access

other 	   	:  nothing

Forum 	   	:  www.openedv.com

journal 	   	:  First edition v1 0 created by Zuo Zhongkai on July 26, 2019

***************************************************************/

#define IMX6UIRQ_CNT 		 one 			/*  Number of equipment numbers 	*/

#define IMX6UIRQ_NAME 		 "blockio" 	/*  name 		*/

#define KEY0VALUE 			 0X01 		/*  KEY0 key value 	*/

#define INVAKEY 				 0XFF 		/*  Invalid key value*/

#define KEY_NUM 				 one 			/*  Number of keys 	*/



/* Interrupt IO description structure */

struct irq_keydesc {

	int gpio;								/* gpio */

	int irqnum;								/* interrupt number     */

	unsigned char value;					/* Key value corresponding to key */

	char name[10];							/* name */

	irqreturn_t (*handler)(int, void *);	/* Interrupt service function */

};



/* imx6uirq Equipment structure */

struct imx6uirq_dev{

	dev_t devid;			/* Equipment number 	 */	

	struct cdev cdev;		/* cdev 	*/                 

	struct class *class;	/* class 		*/

	struct device *device;	/* equipment 	 */

	int major;				/* Main equipment No	  */

	int minor;				/* Secondary equipment No   */

	struct device_node	*nd; /* Device node */	

	atomic_t keyvalue;		/* Valid key values */

	atomic_t releasekey;	/* A key that marks whether a single completion is completed, including press and release */

	struct timer_list timer;/* Define a timer*/

	struct irq_keydesc irqkeydesc[KEY_NUM];	/* Key init array */

	unsigned char curkeynum;				/* Current init key number */



	wait_queue_head_t r_wait;	/* Read wait queue header */

};



struct imx6uirq_dev imx6uirq;	/* irq equipment */



/* @description		: Interrupt the service function and start the timer		

 *				  	  The timer is used for key debounce.

 * @param - irq 	: interrupt number 

 * @param - dev_id	: Equipment structure.

 * @return 			: Interrupt execution result

 */

static irqreturn_t key0_handler(int irq, void *dev_id)

{

	struct imx6uirq_dev *dev = (struct imx6uirq_dev*)dev_id;



	dev->curkeynum = 0;

	dev->timer.data = (volatile long)dev_id;

	mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));	/* 10ms timing */

	return IRQ_RETVAL(IRQ_HANDLED);

}



/* @description	: Timer service function, used to press the key to eliminate jitter. After the timer arrives

 *				  Read the key value again. If the key is still pressed, it means that the key is valid.

 * @param - arg	: Equipment structure variable

 * @return 		: nothing

 */

void timer_function(unsigned long arg)

{

	unsigned char value;

	unsigned char num;

	struct irq_keydesc *keydesc;

	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;



	num = dev->curkeynum;

	keydesc = &dev->irqkeydesc[num];



	value = gpio_get_value(keydesc->gpio); 	/* Read IO value */

	if(value == 0){ 						/* Press the key */

		atomic_set(&dev->keyvalue, keydesc->value);

	}

	else{ 									/* Key release */

		atomic_set(&dev->keyvalue, 0x80 | keydesc->value);

		atomic_set(&dev->releasekey, 1);	/* Mark and release the key to complete a complete key pressing process */

	}               



	/* Wake up process */

	if(atomic_read(&dev->releasekey)) {	/* Complete a key press process */

		/* wake_up(&dev->r_wait); */

		wake_up_interruptible(&dev->r_wait);

	}

}



/*

 * @description	: Key IO initialization

 * @param 		: nothing

 * @return 		: nothing

 */

static int keyio_init(void)

{

	unsigned char i = 0;

	char name[10];

	int ret = 0;

	

	imx6uirq.nd = of_find_node_by_path("/key");

	if (imx6uirq.nd== NULL){

		printk("key node not find!\r\n");

		return -EINVAL;

	} 



	/* Extract GPIO */

	for (i = 0; i < KEY_NUM; i++) {

		imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio", i);

		if (imx6uirq.irqkeydesc[i].gpio < 0) {

			printk("can't get key%d\r\n", i);

		}

	}

	

	/* Initialize the IO used by the key and set it to interrupt mode */

	for (i = 0; i < KEY_NUM; i++) {

		memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(name));	/* Buffer reset */

		sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i);		/* Combination name */

		gpio_request(imx6uirq.irqkeydesc[i].gpio, name);

		gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);	

		imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);

#if 0

		imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);

#endif

	}



	/* Application interruption */

	imx6uirq.irqkeydesc[0].handler = key0_handler;

	imx6uirq.irqkeydesc[0].value = KEY0VALUE;

	

	for (i = 0; i < KEY_NUM; i++) {

		ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler, 

		                 IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc[i].name, &imx6uirq);

		if(ret < 0){

			printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum);

			return -EFAULT;

		}

	}



	/* Create timer */

     init_timer(&imx6uirq.timer);

     imx6uirq.timer.function = timer_function;



	/* Initialize wait queue header */

	init_waitqueue_head(&imx6uirq.r_wait);

	return 0;

}



/*

 * @description		: open device

 * @param - inode 	: inode passed to driver

 * @param - filp 	: The device file has a file structure called private_ Member variable of data

 * 					  Generally, private is used when open ing_ Data points to the device structure.

 * @return 			: 0 success; Other failures

 */

static int imx6uirq_open(struct inode *inode, struct file *filp)

{

	filp->private_data = &imx6uirq;	/* Set private data */

	return 0;

}



 /*

  * @description     : Read data from device 

  * @param - filp    : Device file to open (file descriptor)

  * @param - buf     : Data buffer returned to user space

  * @param - cnt     : Length of data to read

  * @param - offt    : Offset relative to the first address of the file

  * @return          : The number of bytes read. If it is negative, it indicates that the read failed

  */

static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)

{

	int ret = 0;

	unsigned char keyvalue = 0;

	unsigned char releasekey = 0;

	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;



#if 0

	/* Join the waiting queue and wait to be awakened, that is, a key is pressed */

 	ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey)); 

	if (ret) {

		goto wait_error;

	} 

#endif



	DECLARE_WAITQUEUE(wait, current);	/* Define a waiting queue */

	if(atomic_read(&dev->releasekey) == 0) {	/* No key pressed */

		add_wait_queue(&dev->r_wait, &wait);	/* Add wait queue to wait queue header */

		__set_current_state(TASK_INTERRUPTIBLE);/* Set task status */

		schedule();							/* Make a task switch */

		if(signal_pending(current))	{			/* Judge whether it is wake-up caused by signal */

			ret = -ERESTARTSYS;

			goto wait_error;

		}

		__set_current_state(TASK_RUNNING);      /* Set the current task to run */

	    remove_wait_queue(&dev->r_wait, &wait);    /* Delete the corresponding queue item from the waiting queue header */

	}



	keyvalue = atomic_read(&dev->keyvalue);

	releasekey = atomic_read(&dev->releasekey);



	if (releasekey) { /* A key is pressed */	

		if (keyvalue & 0x80) {

			keyvalue &= ~0x80;

			ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));

		} else {

			goto data_error;

		}

		atomic_set(&dev->releasekey, 0);/* Press the flag to clear */

	} else {

		goto data_error;

	}

	return 0;



wait_error:

	set_current_state(TASK_RUNNING);		/* Set the task to running status */

	remove_wait_queue(&dev->r_wait, &wait);	/* Remove waiting queue */

	return ret;



data_error:

	return -EINVAL;

}



/* Device operation function */

static struct file_operations imx6uirq_fops = {

	.owner = THIS_MODULE,

	.open = imx6uirq_open,

	.read = imx6uirq_read,

};



/*

 * @description	: Drive entry function

 * @param 		: nothing

 * @return 		: nothing

 */

static int __init imx6uirq_init(void)

{

	/* 1,Build equipment number */

	if (imx6uirq.major) {

		imx6uirq.devid = MKDEV(imx6uirq.major, 0);

		register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);

	} else {

		alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);

		imx6uirq.major = MAJOR(imx6uirq.devid);

		imx6uirq.minor = MINOR(imx6uirq.devid);

	}



	/* 2,Register character device */

	cdev_init(&imx6uirq.cdev, &imx6uirq_fops);

	cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);



	/* 3,Create class */

	imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);

	if (IS_ERR(imx6uirq.class)) {	

		return PTR_ERR(imx6uirq.class);

	}



	/* 4,Create device */

	imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);

	if (IS_ERR(imx6uirq.device)) {

		return PTR_ERR(imx6uirq.device);

	}

		

	/* 5,Start button */

	atomic_set(&imx6uirq.keyvalue, INVAKEY);

	atomic_set(&imx6uirq.releasekey, 0);

	keyio_init();

	return 0;

}



/*

 * @description	: Drive exit function

 * @param 		: nothing

 * @return 		: nothing

 */

static void __exit imx6uirq_exit(void)

{

	unsigned i = 0;

	/* Delete timer */

	del_timer_sync(&imx6uirq.timer);	/* Delete timer */

		

	/* Release interrupt */	

	for (i = 0; i < KEY_NUM; i++) {

		free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);

	}

	cdev_del(&imx6uirq.cdev);

	unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);

	device_destroy(imx6uirq.class, imx6uirq.devid);

	class_destroy(imx6uirq.class);

}

	

module_init(imx6uirq_init);

module_exit(imx6uirq_exit);

MODULE_LICENSE("GPL");

In line 32, change the device file name to "blockio". When the driver is loaded successfully, a file named "/ dev/blockio" will appear in the root file system.
In line 61, add a waiting queue header r to the device structure_ Wait, because the wait queue is required to handle blocked IO in the Linux driver.
In lines 107-110, the timer interrupts the execution of the processing function, indicating that a key is pressed. First, judge whether it is a valid key in line 107. If so, pass wake_up or wake_up_ The interruptible function wakes up the waiting queue r_wait.
Line 168, call init_ waitqueue_ The head function initializes the waiting queue header r_wait.
Lines 200 ~ 206: wait events are used to handle the blocking access of read, such as wait_ event_ The interruptible function waits for the releasekey to be valid, that is, a key is pressed. If the key is not pressed, the process will go to sleep because of the wait_ event_ The interruptible function, so the process entering the sleep state can be interrupted by the signal.
Lines 208 to 218, first use declare_ The waitqueue macro defines a waiting queue and uses add if no key is pressed_ wait_ The queue function adds the waiting queue of the current task to the waiting queue header R_ Waiting. Then we call it. set_ current_ The state function sets the status of the current process to TASK_INTERRUPTIBLE, that is, it can be trusted
No. interrupt. Next, call the schedule function to switch tasks, and the current process will enter the sleep state. If a key is pressed, the process entering the sleep state will wake up, and then run from the sleep point.
Here, i.e. starting from line 213, first run through signal_ The pending function determines whether the process is awakened by a signal. If it is awakened by a signal, it directly returns the error code - erestatsys. If not awakened by the signal (i.e. awakened by the key)
Then call on line 217__ set_ current_ The state function sets the task status to TASK_RUNNING,
Remove is then called at line 218_ wait_ The queue function deletes a process from the waiting queue.

Using wait queue to realize blocking access, we should pay attention to two points:

① , add a task or process to the waiting queue header,
② Wake up the waiting queue at the appropriate point, which is usually in the interrupt processing function.

2. Write test APP

The test APP in this section directly uses section 51.3 Imx6uirqapp written in Section 3 c. Add imx6uirqapp C copy it to the experiment folder of this section and rename it blockioapp c. There is no need to modify anything.

3. Operation test

depmod //This command needs to be run when the driver is loaded for the first time
modprobe blockio.ko  //Load driver

After the driver is loaded successfully, open the blockioApp test APP with the following command and run it in background mode:

./blockioApp /dev/blockio &

Press the KEY0 key on the development board, and the result is as shown in the figure:

Enter the "top" command to check the CPU utilization of blockioAPP,

When we added blocking access to the key driver, the CPU utilization of blockioApp application decreased from 99.6% to 0.0%. Note that the 0.0% here does not mean that blockioApp does not use CPU, but because the utilization rate is too small, the CPU utilization rate may be 0.00001%, but the figure can only show one digit after the decimal point, so it is displayed as 0.0%.

We can use the "kill" command to close the application running in the background. For example, we can close the application running in the background blockioApp. First, output "Ctrl+C" to close the top command interface and enter the command line mode. Then use the "ps" command to check the PID of blockioApp, as shown in the figure:

The PID of blockioApp application is 81. Use "kill -9 PID", i.e
You can "kill" the process with the specified PID. For example, we now want to "kill" the blockioApp application with PID 81, but use the following command

kill -9 81

3, Non blocking IO experiment

1. Hardware schematic analysis

2. Preparation of experimental program

1. Driver programming

Create a new folder named "15_noblockio", and then_ Create a vscode project in the noblockio folder, and the workspace is named "noblockio". Add "14_blockio" to blockio in the experiment C copy to 15_ In the noblockio folder and rename it noblockio c. Next, we will modify noblockio C this file, add non blocking related code in it, and complete the future noblockio C the contents are as follows:

#include <linux/types.h>

#include <linux/kernel.h>

#include <linux/delay.h>

#include <linux/ide.h>

#include <linux/init.h>

#include <linux/module.h>

#include <linux/errno.h>

#include <linux/gpio.h>

#include <linux/cdev.h>

#include <linux/device.h>

#include <linux/of.h>

#include <linux/of_address.h>

#include <linux/of_gpio.h>

#include <linux/semaphore.h>

#include <linux/timer.h>

#include <linux/of_irq.h>

#include <linux/irq.h>

#include <linux/wait.h>

#include <linux/poll.h>

#include <asm/mach/map.h>

#include <asm/uaccess.h>

#include <asm/io.h>

/***************************************************************

Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.

file name 		:  noblock.c

author 	  	:  Zuo Zhongkai

edition 	   	:  V1.0

describe 	   	:  Non blocking IO access

other 	   	:  nothing

Forum 	   	:  www.openedv.com

journal 	   	:  First edition v1 0 created by Zuo Zhongkai on July 26, 2019

***************************************************************/

#define IMX6UIRQ_CNT 		 one 			/*  Number of equipment numbers 	*/

#define IMX6UIRQ_NAME 		 "noblockio" 	/*  name 		*/

#define KEY0VALUE 			 0X01 		/*  KEY0 key value 	*/

#define INVAKEY 				 0XFF 		/*  Invalid key value*/

#define KEY_NUM 				 one 			/*  Number of keys 	*/



/* Interrupt IO description structure */

struct irq_keydesc {

	int gpio;								/* gpio */

	int irqnum;								/* interrupt number     */

	unsigned char value;					/* Key value corresponding to key */

	char name[10];							/* name */

	irqreturn_t (*handler)(int, void *);	/* Interrupt service function */

};



/* imx6uirq Equipment structure */

struct imx6uirq_dev{

	dev_t devid;			/* Equipment number 	 */	

	struct cdev cdev;		/* cdev 	*/                 

	struct class *class;	/* class 		*/

	struct device *device;	/* equipment 	 */

	int major;				/* Main equipment No	  */

	int minor;				/* Secondary equipment No   */

	struct device_node	*nd; /* Device node */	

	atomic_t keyvalue;		/* Valid key values */

	atomic_t releasekey;	/* A key that marks whether a single completion is completed, including press and release */

	struct timer_list timer;/* Define a timer*/

	struct irq_keydesc irqkeydesc[KEY_NUM];	/* Key init array */

	unsigned char curkeynum;				/* Current init key number */



	wait_queue_head_t r_wait;	/* Read wait queue header */

};



struct imx6uirq_dev imx6uirq;	/* irq equipment */



/* @description		: Interrupt the service function and start the timer		

 *				  	  The timer is used for key debounce.

 * @param - irq 	: interrupt number 

 * @param - dev_id	: Equipment structure.

 * @return 			: Interrupt execution result

 */

static irqreturn_t key0_handler(int irq, void *dev_id)

{

	struct imx6uirq_dev *dev = (struct imx6uirq_dev*)dev_id;



	dev->curkeynum = 0;

	dev->timer.data = (volatile long)dev_id;

	mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));	/* 10ms timing */

	return IRQ_RETVAL(IRQ_HANDLED);

}



/* @description	: Timer service function, used to press the key to eliminate jitter. After the timer arrives

 *				  Read the key value again. If the key is still pressed, it means that the key is valid.

 * @param - arg	: Equipment structure variable

 * @return 		: nothing

 */

void timer_function(unsigned long arg)

{

	unsigned char value;

	unsigned char num;

	struct irq_keydesc *keydesc;

	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;



	num = dev->curkeynum;

	keydesc = &dev->irqkeydesc[num];



	value = gpio_get_value(keydesc->gpio); 	/* Read IO value */

	if(value == 0){ 						/* Press the key */

		atomic_set(&dev->keyvalue, keydesc->value);

	}

	else{ 									/* Key release */

		atomic_set(&dev->keyvalue, 0x80 | keydesc->value);

		atomic_set(&dev->releasekey, 1);	/* Mark and release the key to complete a complete key pressing process */

	}               



	/* Wake up process */

	if(atomic_read(&dev->releasekey)) {	/* Complete a key press process */

		/* wake_up(&dev->r_wait); */

		wake_up_interruptible(&dev->r_wait);

	}

}



/*

 * @description	: Key IO initialization

 * @param 		: nothing

 * @return 		: nothing

 */

static int keyio_init(void)

{

	unsigned char i = 0;

	char name[10];

	int ret = 0;

	

	imx6uirq.nd = of_find_node_by_path("/key");

	if (imx6uirq.nd== NULL){

		printk("key node not find!\r\n");

		return -EINVAL;

	} 



	/* Extract GPIO */

	for (i = 0; i < KEY_NUM; i++) {

		imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio", i);

		if (imx6uirq.irqkeydesc[i].gpio < 0) {

			printk("can't get key%d\r\n", i);

		}

	}

	

	/* Initialize the IO used by the key and set it to interrupt mode */

	for (i = 0; i < KEY_NUM; i++) {

		memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(name));	/* Buffer reset */

		sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i);		/* Combination name */

		gpio_request(imx6uirq.irqkeydesc[i].gpio, name);

		gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);	

		imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);

#if 0

		imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);

#endif

	}



	/* Application interruption */

	imx6uirq.irqkeydesc[0].handler = key0_handler;

	imx6uirq.irqkeydesc[0].value = KEY0VALUE;

	

	for (i = 0; i < KEY_NUM; i++) {

		ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler, 

		                 IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc[i].name, &imx6uirq);

		if(ret < 0){

			printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum);

			return -EFAULT;

		}

	}



	/* Create timer */

    init_timer(&imx6uirq.timer);

    imx6uirq.timer.function = timer_function;



	/* Initialize wait queue header */

	init_waitqueue_head(&imx6uirq.r_wait);

	return 0;

}



/*

 * @description		: open device

 * @param - inode 	: inode passed to driver

 * @param - filp 	: The device file has a file structure called private_ Member variable of data

 * 					  Generally, private is used when open ing_ Data points to the device structure.

 * @return 			: 0 success; Other failures

 */

static int imx6uirq_open(struct inode *inode, struct file *filp)

{

	filp->private_data = &imx6uirq;	/* Set private data */

	return 0;

}



 /*

  * @description     : Read data from device 

  * @param - filp    : Device file to open (file descriptor)

  * @param - buf     : Data buffer returned to user space

  * @param - cnt     : Length of data to read

  * @param - offt    : Offset relative to the first address of the file

  * @return          : The number of bytes read. If it is negative, it indicates that the read failed

  */

static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)

{

	int ret = 0;

	unsigned char keyvalue = 0;

	unsigned char releasekey = 0;

	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;



	if (filp->f_flags & O_NONBLOCK)	{ /* Non blocking access */

		if(atomic_read(&dev->releasekey) == 0)	/* If no key is pressed, return to - EAGAIN */

			return -EAGAIN;

	} else {							/* Blocking access */

		/* Join the waiting queue and wait to be awakened, that is, a key is pressed */

 		ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey)); 

		if (ret) {

			goto wait_error;

		}

	}



	keyvalue = atomic_read(&dev->keyvalue);

	releasekey = atomic_read(&dev->releasekey);



	if (releasekey) { /* A key is pressed */	

		if (keyvalue & 0x80) {

			keyvalue &= ~0x80;

			ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));

		} else {

			goto data_error;

		}

		atomic_set(&dev->releasekey, 0);/* Press the flag to clear */

	} else {

		goto data_error;

	}

	return 0;



wait_error:

	return ret;

data_error:

	return -EINVAL;

}



 /*

  * @description     : poll Function to handle non blocking access

  * @param - filp    : Device file to open (file descriptor)

  * @param - wait    : Wait list (poll_table)

  * @return          : Device or resource status,

  */

unsigned int imx6uirq_poll(struct file *filp, struct poll_table_struct *wait)

{

	unsigned int mask = 0;

	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;



	poll_wait(filp, &dev->r_wait, wait);	/* Add wait queue header to poll_ In table */

	

	if(atomic_read(&dev->releasekey)) {		/* Press the key */

		mask = POLLIN | POLLRDNORM;			/* Return to PLLIN */

	}

	return mask;

}



/* Device operation function */

static struct file_operations imx6uirq_fops = {

	.owner = THIS_MODULE,

	.open = imx6uirq_open,

	.read = imx6uirq_read,

	.poll = imx6uirq_poll,

};



/*

 * @description	: Drive entry function

 * @param 		: nothing

 * @return 		: nothing

 */

static int __init imx6uirq_init(void)

{

	/* 1,Build equipment number */

	if (imx6uirq.major) {

		imx6uirq.devid = MKDEV(imx6uirq.major, 0);

		register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);

	} else {

		alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);

		imx6uirq.major = MAJOR(imx6uirq.devid);

		imx6uirq.minor = MINOR(imx6uirq.devid);

	}



	/* 2,Register character device */

	cdev_init(&imx6uirq.cdev, &imx6uirq_fops);

	cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);



	/* 3,Create class */

	imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);

	if (IS_ERR(imx6uirq.class)) {	

		return PTR_ERR(imx6uirq.class);

	}



	/* 4,Create device */

	imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);

	if (IS_ERR(imx6uirq.device)) {

		return PTR_ERR(imx6uirq.device);

	}

		

	/* 5,Start button */

	atomic_set(&imx6uirq.keyvalue, INVAKEY);

	atomic_set(&imx6uirq.releasekey, 0);

	keyio_init();

	return 0;

}



/*

 * @description	: Drive exit function

 * @param 		: nothing

 * @return 		: nothing

 */

static void __exit imx6uirq_exit(void)

{

	unsigned i = 0;

	/* Delete timer */

	del_timer_sync(&imx6uirq.timer);	/* Delete timer */

		

	/* Release interrupt */	

	for (i = 0; i < KEY_NUM; i++) {

		free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);

	}

	cdev_del(&imx6uirq.cdev);

	unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);

	device_destroy(imx6uirq.class, imx6uirq.devid);

	class_destroy(imx6uirq.class);

}	

	

module_init(imx6uirq_init);

module_exit(imx6uirq_exit);

MODULE_LICENSE("GPL");

In line 32, change the name of the device file to "noblockio". When the driver is loaded successfully, a file named "/ dev/noblockio" will appear in the root file system.
In lines 202-204, judge whether it is a non blocking read access. If so, judge whether the key is valid, that is, judge whether the key is pressed. If not, return to - EAGAIN.
Lines 241-252, imx6uirq_ The poll function is file_operations drives the poll function in the operation set. When the application calls the select or poll function imx6uirq_ The poll function executes.
Line 246 calls poll_ The wait function adds the wait queue header to the poll_ In table,
Lines 248 ~ 250 judge whether the key is valid. If the key is valid, the POLLIN event will be returned to the application, indicating that there is data that can be read.
Line 259, set file_ The poll member variable of operations is imx6uirq_poll.

2. Write test APP

Create a new one named noblockioapp C test the APP file, and then enter the following contents in it

#include "stdio.h"

#include "unistd.h"

#include "sys/types.h"

#include "sys/stat.h"

#include "fcntl.h"

#include "stdlib.h"

#include "string.h"

#include "poll.h"

#include "sys/select.h"

#include "sys/time.h"

#include "linux/ioctl.h"

/***************************************************************

Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.

file name 		:  noblockApp.c

author 	  	:  Zuo Zhongkai

edition 	   	:  V1.0

describe 	   	:  Non blocking access test APP

other 	   	:  nothing

usage method 	: ./ blockApp /dev/blockio open test App

Forum 	   	:  www.openedv.com

journal 	   	:  First edition v1 0 created by Zuo Zhongkai on September 8, 2019

***************************************************************/



/*

 * @description		: main main program

 * @param - argc 	: argv Number of array elements

 * @param - argv 	: Specific parameters

 * @return 			: 0 success; Other failures

 */

int main(int argc, char *argv[])

{

	int fd;

	int ret = 0;

	char *filename;

	struct pollfd fds;

	fd_set readfds;

	struct timeval timeout;

	unsigned char data;



	if (argc != 2) {

		printf("Error Usage!\r\n");

		return -1;

	}



	filename = argv[1];

	fd = open(filename, O_RDWR | O_NONBLOCK);	/* Non blocking access */

	if (fd < 0) {

		printf("Can't open file %s\r\n", filename);

		return -1;

	}



#if 0

	/* Structural body */

	fds.fd = fd;

	fds.events = POLLIN;

		

	while (1) {

		ret = poll(&fds, 1, 500);

		if (ret) {	/* Data valid */

			ret = read(fd, &data, sizeof(data));

			if(ret < 0) {

				/* Read error */

			} else {

				if(data)

					printf("key value = %d \r\n", data);

			} 	

		} else if (ret == 0) { 	/* overtime */

			/* User defined timeout processing */

		} else if (ret < 0) {	/* error */

			/* User defined error handling */

		}

	}

#endif



	while (1) {	

		FD_ZERO(&readfds);

		FD_SET(fd, &readfds);

		/* Construction timeout */

		timeout.tv_sec = 0;

		timeout.tv_usec = 500000; /* 500ms */

		ret = select(fd + 1, &readfds, NULL, NULL, &timeout);

		switch (ret) {

			case 0: 	/* overtime */

				/* User defined timeout processing */

				break;

			case -1:	/* error */

				/* User defined error handling */

				break;

			default:  /* Can read data */

				if(FD_ISSET(fd, &readfds)) {

					ret = read(fd, &data, sizeof(data));

					if (ret < 0) {

						/* Read error */

					} else {

						if (data)

							printf("key value=%d\r\n", data);

					}

				}

				break;

		}	

	}



	close(fd);

	return ret;

}

In lines 52-73, this code uses the poll function to achieve non blocking access. In the while loop, use the poll function to continuously poll to check whether the driver has data to read. If it can read, call the read function to read the key data.
Lines 75 to 101, this code uses the select function to achieve non blocking access.

3. Operation test

depmod //This command needs to be run when the driver is loaded for the first time
modprobe noblockio.ko //Load driver

After the driver is loaded successfully, use the following command to open the noblockioApp test APP and run it in background mode:

./noblockioApp /dev/noblockio &

Press the KEY0 key on the development board, and the result is as shown in the figure:

Enter the "top" command to check the CPU utilization of the noblockioAPP APP,

After non blocking read processing, the CPU utilization of noblockioApp is also as low as 0.0%,

Topics: Linux Embedded system