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.
Blocking and non blocking IO
Introduction to blocking and non blocking
The "IO" here is not what we call "GPIO" (that is, pin) when we learn STM32 or other MCU. 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. Blocking IO is shown in figure 52.1.1.1:
In figure 52.1.1.1, 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. Non blocking IO is shown in figure 52.1.2:
As can be seen from figure 52.1.1.2, 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 fails. 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*/
As can be seen from the example code 52.1.1.1, the default reading method for the device driver file is blocking, so 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*/
In line 4, 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.
Waiting queue
1. Wait 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, the process must be awakened. 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, and its contents are 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 queue item
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 item to / from wait 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 can access it, you can remove the waiting queue item corresponding to the process from the waiting queue header. The API functions added to the waiting queue item 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. Waiting to 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. Wait event
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 the waiting event are shown in table 52.1.2.1:
function | describe |
---|---|
wait_event(wq, condition) | Wait for the waiting queue with wq as the head of the waiting queue to wake up, provided that the condition condition must be met (true), otherwise it will be blocked all the time. This function sets the process to task_ Uniterruptible status |
wait_event_timeout(wq, condition, timeout) | Function and wait_event is similar, but this function can add timeout in jiffies. This function has a return value. If it returns 0, it indicates that the timeout has expired and the condition is false. If it is 1, it means that the condition is true, that is, the condition is satisfied. |
wait_event_interruptible(wq, condition) | And wait_ The event function is similar, but it sets the process to TASK_INTERRUPTIBLE means that it can be interrupted by a signal. |
wait_event_interruptible_timeout(wq, condition, timeout) | And wait_ event_ Similar to the timeout function, this function also sets the process to TASK_INTERRUPTIBLE, can be interrupted by signal. |
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 can be read. As long as there is a file in these sets 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 to monitor 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 description of the file to be deleted
Symbol. 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 to wait for some file descriptors, we can set the timeout. The timeout is represented by the structure timeval. 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 function
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 type pollfd. The 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 events returns 0. Events is the event to be monitored. The types of events that can be monitored are as follows:
POLLIN There is data to read. POLLPRI There is urgent data to read. POLLOUT You can write data. POLLERR An error occurred with the specified file descriptor. POLLHUP The specified file descriptor is pending. POLLNVAL Invalid request. POLLRDNORM 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 function
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: since Linux 2.6.8, this parameter has no meaning. 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 towards epfd Add file parameters fd Represents the descriptor of the. EPOLL_CTL_MOD modify parameters fd of event event. EPOLL_CTL_DEL from epfd Delete in fd Descriptor.
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:
EPOLLIN There is data to read. EPOLLOUT You can write data. EPOLLPRI There is urgent data to read. EPOLLERR An error occurred with the specified file descriptor. EPOLLHUP The specified file descriptor is pending. EPOLLET set up epoll It is edge trigger, and the default trigger mode is horizontal trigger. EPOLLONESHOT It is a one-time monitoring. After the monitoring is completed, it is necessary to monitor a certain area again fd,Then you need to fd Re add to epoll Inside.
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.
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 There is data to read. POLLPRI There is urgent data to read. POLLOUT You can write data. POLLERR An error occurred with the specified file descriptor. POLLHUP The specified file descriptor is pending. POLLNVAL Invalid request. POLLRDNORM Equivalent to POLLIN,Common data 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_ The waiting queue header in table. The parameter p is poll_table is file_ Wait parameter of poll function in operations.
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.ko in the previous chapter in the development board, and then open the test software imx6uirqApp in the background running mode. The commands are 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 shown in figure 52.2.1:
As can be seen from figure 52.2.1, the CPU utilization rate of imx6uirqApp application is as high as 99.6%, which is only an application for reading key values. Such a high CPU utilization rate is obviously problematic! 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 when no valid key press event occurs,
The imx6uirqApp application should be dormant. 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.
Hardware schematic analysis
Refer to section 15.2 for the schematic diagram of experimental hardware in this chapter.
Experimental programming
1. Driver programming
The routine path corresponding to this experiment is: development board CD - > 2, Linux driver routine - > 14_ blockio.
This chapter is based on the "13_irq" experiment in the previous chapter, mainly adding blocking access related code. Create a new folder named "14_blockio", and then_ Create a vscode project in the blockio folder, and the workspace is named "blockio". Copy imx6uirq.c in the "13_irq" experiment to 14_ In the blockio folder and rename it blockio.c. Next, we will modify the blockio.c file and add the blocking related code to complete the future work
The content of blockio.c is as follows (because it is modified based on the imx6uirq.c file of the experiment in the previous chapter, in order to reduce the length, the following code is omitted):
1 #include <linux/types.h> 2 #include <linux/kernel.h> ...... 18 #include <asm/mach/map.h> 19 #include <asm/uaccess.h> 20 #include <asm/io.h> 21 /*************************************************************** 22 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 23 File name: block.c 24 Author: Zuo Zhongkai 25 Version: V1.0 26 Description: blocking IO access 27 Others: None 28 Forum: www.openedv.com 29 Log: first version v1.0 created by Zuo Zhongkai on July 26, 2019 30 ***************************************************************/ 31 #define IMX6UIRQ_CNT 1 / * number of equipment numbers*/ 32 #define IMX6UIRQ_NAME "blockio" / * name*/ 33 #define KEY0VALUE 0X01 /* KEY0 key value*/ 34 #Define invalid 0xff / * invalid key value*/ 35 #define KEY_NUM 1 / * number of keys*/ 36 37 /* Interrupt IO description structure*/ 38 struct irq_keydesc { 39 int gpio; /* gpio */ 40 int irqnum; /* interrupt number*/ 41 unsigned char value; /* Key value corresponding to key*/ 42 char name[10]; /* name*/ 43 irqreturn_t (*handler)(int, void *); /* Interrupt service function*/ 44 }; 45 46 /* imx6uirq Equipment structure*/ 47 struct imx6uirq_dev{ 48 dev_t devid; /* Equipment number*/ 49 struct cdev cdev; /* cdev */ 50 struct class *class; /* class*/ 51 struct device *device; /* equipment*/ 52 int major; /* Main equipment No*/ 53 int minor; /* Secondary equipment No*/ 54 struct device_node *nd; /* Device node*/ 55 atomic_t keyvalue; /* Valid key values*/ 56 atomic_t releasekey; /* Key to mark whether to complete one-time completion*/ 57 struct timer_list timer; /* Define a timer*/
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 interrupted by a signal. Next, call the schedule function to switch tasks, and the current process will enter the sleep state. If yes, press
Press the key, then 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 it is not awakened by the signal (i.e. awakened by the key), it is called in line 217__ set_ current_ The state function sets the task status to TASK_RUNNING, and then
Line 218 calls remove_ 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 for the experiment in this section directly uses imx6uirqApp.c written in section 51.3.3, copies imx6uirqApp.c to the experiment folder in this section, and renames it as blockioApp.c without modifying anything.
Run test
1. Compile driver and test APP
① , compile driver
Write the Makefile file. The Makefile file of the experiment in this chapter is basically the same as the experiment in Chapter 40, except that the value of obj-m variable is changed to blockio.o. the contents of the Makefile are as follows:
1 KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek ...... 4 obj-m := blockio.o ...... 11 clean: 12 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
In line 4, set the value of obj-m variable to blockio.o.
Input the following command to compile the driver module file:
make -j32
After the compilation is successful, a driver module file named "blockio.ko" will be generated.
② Compiling and testing APP
Compile the test program noblockioApp.c by entering the following command:
arm-linux-gnueabihf-gcc blockioApp.c -o blockioApp
After successful compilation, the blcokioApp application will be generated.
2. Run test
Compile the previous section into blockio.ko and blockioApp files and copy them to rootfs/lib/modules/4.1.15 directory, restart the development board, and enter the directory lib/modules/4.1.15, and input the following command to load the blockio.ko driver module:
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 KEY0 on the development board, and the result is shown in figure 52.2.3.1:
When the KEY0 key is pressed, the blockioapp test APP will print the key value. Enter the "top" command to view the CPU utilization of blockioapp, as shown in figure 52.2.3.2:
As can be seen from figure 52.2.3.2, after we added blocking access to the key driver, the CPU utilization of blockioApp application decreased from 99.6% in figure 52.2.1 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 figure 52.2.3.2 can only display one decimal place, 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 figure 52.2.3.3:
As can be seen from figure 52.2.3.3, the PID of the blockioApp application is 149. Use "kill -9 PID" to "kill" the process with the specified PID. For example, we want to "kill" the blockioApp application with PID 149, but use the following command:
kill -9 149
After inputting the above command, the terminal displays as shown in figure 52.2.3.4:
It can be seen from figure 52.2.3.4 that the application of ". / blockioApp /dev/blockio" has been "killed". Enter the "ps" command here to view the running process of the current system, and you will find that blockioApp has disappeared. This is how to use the kill command to "kill" the specified process.
Non blocking IO experiment
Hardware schematic analysis
Refer to section 15.2 for the schematic diagram of experimental hardware in this chapter.
Experimental programming
1. Driver programming
The routine path corresponding to this experiment is: development board CD - > 2, Linux driver routine - > 15_ noblockio.
We completed the experiment in this chapter on the basis of the "14_blockio" experiment in section 52.2. In the experiment in the previous section, we have added blocking IO code to the driver. In this section, we continue to improve the driver and add non blocking IO driver code. Create a new folder named "15_noblockio", and then_ Create a vscode project in the noblockio folder, and the workspace is named "noblockio". Copy blockio.c in the "14_blockio" experiment to 15_ In the noblockio folder and rename it noblockio.c. Next, we will modify the file noblockio.c and add non blocking related code to complete
The content of noblockio.c in the future is as follows (because it is modified based on the blockio.c file in the experiment in the previous section, the following code is omitted in order to reduce the length):
1 #include <linux/types.h> 2 #include <linux/kernel.h> ...... 18 #include <linux/wait.h> 19 #include <linux/poll.h> 20 #include <asm/mach/map.h> 21 #include <asm/uaccess.h> 22 #include <asm/io.h> 23 /*************************************************************** 24 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 25 File name: noblock.c 26 Author: Zuo Zhongkai 27 Version: V1.0 28 Description: non blocking IO access 29 Others: None 30 Forum: www.openedv.com 31 Log: first version v1.0 created by Zuo Zhongkai on July 26, 2019 32 ***************************************************************/ 31 #define IMX6UIRQ_CNT 1 / * number of equipment numbers*/ 32 #define IMX6UIRQ_NAME "noblockio" / * name*/ ...... 187 /* 188 * @description : Read data from device 189 * @param - filp : Device file to open (file descriptor) 190 * @param - buf : Data buffer returned to user space 191 * @param - cnt : Length of data to read 192 * @param - offt : Offset relative to the first address of the file 193 * @return : The number of bytes read. If it is negative, it indicates that the read failed 194 */ 195 static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
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 to 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_table, page
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 test APP file named noblockioApp.c, and then enter the following contents in it:
1 #include "stdio.h" 2 #include "unistd.h" 3 #include "sys/types.h" 4 #include "sys/stat.h" 5 #include "fcntl.h" 6 #include "stdlib.h" 7 #include "string.h" 8 #include "poll.h" 9 #include "sys/select.h" 10 #include "sys/time.h" 11 #include "linux/ioctl.h" 12 /*************************************************************** 13 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 14 File name: noblockApp.c 15 Author: Zuo Zhongkai 16 Version: V1.0 17 Description: non blocking access test APP 18 Others: None 19 Usage:. / blockApp /dev/blockio open the test App 20 Forum: www.openedv.com 21 Log: first version v1.0 created by Zuo Zhongkai on September 8, 2019 22 ***************************************************************/ 23 24 /* 25 * @description : main main program 26 * @param - argc : argv Number of array elements 27 * @param - argv : Specific parameters 28 * @return : 0 success; Other failures 29 */ 30 int main(int argc, char *argv[]) 31 { 32 int fd; 33 int ret = 0;
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.
Run test
1. Compile driver and test APP
① , compile driver
Write the Makefile file. The Makefile file of the experiment in this chapter is basically the same as the experiment in Chapter 40, except that the value of obj-m variable is changed to noblockio.o. the contents of the Makefile are as follows:
1 KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek ...... 4 obj-m := noblockio.o ...... 11 clean: 12 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
In line 4, set the value of obj-m variable to noblockio.o.
Input the following command to compile the driver module file:
make -j32
After the compilation is successful, a driver module file named "noblockio.ko" will be generated.
② Compiling and testing APP
Compile the test program noblockioApp.c by entering the following command:
arm-linux-gnueabihf-gcc noblockioApp.c -o noblockioApp
After the compilation is successful, the application of noblcokioApp will be generated.
2. Run test
Copy the two files noblockio.ko and noblockioApp compiled in the previous section to
In the rootfs/lib/modules/4.1.15 directory, restart the development board, enter the directory lib/modules/4.1.15, and enter the following command
Load blockio.ko driver module:
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 KEY0 on the development board, and the result is shown in figure 52.3.3.1:
When the KEY0 key is pressed, the noblockioapp test APP will print out the key value. Enter the "top" command to check the CPU utilization of noblockioapp, as shown in figure 52.3.3.2:
As can be seen from figure 52.3.3.2, after non blocking read processing, the CPU utilization of noblockioApp is also as low as 0.0%. Like blockioApp in figure 52.2.3.2, the 0.0% here does not mean that noblockioApp does not use CPU, but because the utilization is too small, and only one decimal place can be displayed in the figure, so it is displayed as 0.0%.
If you want to "kill" the application noblockioApp in background running mode, you can refer to the method explained in section 52.2.3.