I.MX6ULL Linux 驱动开发篇---Linux非阻塞IO实验-- Ubuntu20.04

🎬 渡水无言个人主页渡水无言

专栏传送门 : 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》

专栏传送门 : 《freertos专栏》 《STM32 HAL库专栏》《linux裸机开发专栏

专栏传送门《产品测评专栏

⭐️流水不争先,争的是滔滔不绝

📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生

| 省级优秀毕业生获得者 | csdn新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生

在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连

目录

前言

一、前置准备

二、硬件原理图分析

三、实验程序编写

3.1、驱动程序编写(noblockio.c)

[3.2、测试 APP 代码](#3.2、测试 APP 代码)

四、运行测试

[4.1、编译驱动程序和测试 APP](#4.1、编译驱动程序和测试 APP)

5.2、运行测试

总结


前言

在Linux驱动开发中,IO访问分为阻塞和非阻塞两种方式。上一篇博客我们实现了阻塞IO(应用程序等待设备就绪,期间会休眠,不占用CPU),本篇将基于I.MX6ULL开发板,在阻塞IO基础上改造,实现非阻塞IO,重点讲解非阻塞IO的驱动实现、poll/select函数的使用,以及完整的实验验证流程。


一、前置准备

硬件:I.MX6ULL开发板(KEY0按键)

内核:Linux 4.1.15(适配I.MX6ULL)

工具:VSCode、交叉编译器(arm-linux-gnueabihf-gcc)

前置知识:掌握阻塞IO原理、按键中断驱动、file_operations结构体

二、硬件原理图分析

按键 KEY0 的原理图如下:

图中可以看出,按键 KEY0 是连接到 I.MX6U 的 UART1_CTS 这个 IO 上的,KEY0接了一个 10K 的上拉电阻,因此 KEY0 没有按下的时候 UART1_CTS 应该是高电平,当 KEY0按下以后 UART1_CTS 就是低电平。

三、实验程序编写

通过按键中断触发IO就绪,驱动层支持非阻塞读取,应用层分别用poll和select两种方式实现非阻塞访问,对比阻塞IO,理解非阻塞IO"轮询但低CPU占用"的核心优势。

实验目录结构:

复制代码
15_noblockio/          # 实验根目录
├── noblockio.c        # 非阻塞IO驱动程序
├── noblockioApp.c     # 非阻塞IO测试APP
└── Makefile           # 编译脚本

3.1、驱动程序编写(noblockio.c)

驱动核心修改点:

在read函数中增加非阻塞判断(通过f_flags & O_NONBLOCK),无数据时返回-EAGAIN

实现poll函数,将等待队列添加到poll_table,检测设备就绪状态

完善file_operations结构体,添加poll成员映射

cpp 复制代码
1 #include <linux/types.h>
2 #include <linux/kernel.h>
3 #include <linux/delay.h>
4 #include <linux/ide.h>
5 #include <linux/init.h>
6 #include <linux/module.h>
7 #include <linux/errno.h>
8 #include <linux/gpio.h>
9 #include <linux/cdev.h>
10 #include <linux/device.h>
11 #include <linux/of.h>
12 #include <linux/of_address.h>
13 #include <linux/of_gpio.h>
14 #include <linux/semaphore.h>
15 #include <linux/timer.h>
16 #include <linux/of_irq.h>
17 #include <linux/irq.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 文件名		: noblock.c
26 作者	  	: duan
27 版本	   	: V1.0
28 描述	   	: 非阻塞IO访问
29 其他	   	: 无
30 ***************************************************************/
31 #define IMX6UIRQ_CNT		1			/* 设备号个数 	*/
32 #define IMX6UIRQ_NAME		"noblockio"	/* 名字 		*/
33 #define KEY0VALUE			0X01		/* KEY0按键值 	*/
34 #define INVAKEY				0XFF		/* 无效的按键值 */
35 #define KEY_NUM				1			/* 按键数量 	*/
36 
37 /* 中断IO描述结构体 */
38 struct irq_keydesc {
39 	int gpio;								/* gpio */
40 	int irqnum;								/* 中断号     */
41 	unsigned char value;					/* 按键对应的键值 */
42 	char name[10];							/* 名字 */
43 	irqreturn_t (*handler)(int, void *);	/* 中断服务函数 */
44 };
45 
46 /* imx6uirq设备结构体 */
47 struct imx6uirq_dev{
48 	dev_t devid;			/* 设备号 	 */	
49 	struct cdev cdev;		/* cdev 	*/                 
50 	struct class *class;	/* 类 		*/
51 	struct device *device;	/* 设备 	 */
52 	int major;				/* 主设备号	  */
53 	int minor;				/* 次设备号   */
54 	struct device_node	*nd; /* 设备节点 */	
55 	atomic_t keyvalue;		/* 有效的按键键值 */
56 	atomic_t releasekey;	/* 标记是否完成一次完成的按键,包括按下和释放 */
57 	struct timer_list timer;/* 定义一个定时器*/
58 	struct irq_keydesc irqkeydesc[KEY_NUM];	/* 按键init述数组 */
59 	unsigned char curkeynum;				/* 当前init按键号 */
60 
61 	wait_queue_head_t r_wait;	/* 读等待队列头 */
62 };
63 
64 struct imx6uirq_dev imx6uirq;	/* irq设备 */
65 
66 /* @description		: 中断服务函数,开启定时器		
67  *				  	  定时器用于按键消抖。
68  * @param - irq 	: 中断号 
69  * @param - dev_id	: 设备结构。
70  * @return 			: 中断执行结果
71  */
72 static irqreturn_t key0_handler(int irq, void *dev_id)
73 {
74 	struct imx6uirq_dev *dev = (struct imx6uirq_dev*)dev_id;
75 
76 	dev->curkeynum = 0;
77 	dev->timer.data = (volatile long)dev_id;
78 	mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));	/* 10ms定时 */
79 	return IRQ_RETVAL(IRQ_HANDLED);
80 }
81 
82 /* @description	: 定时器服务函数,用于按键消抖,定时器到了以后
83  *				  再次读取按键值,如果按键还是处于按下状态就表示按键有效。
84  * @param - arg	: 设备结构变量
85  * @return 		: 无
86  */
87 void timer_function(unsigned long arg)
88 {
89 	unsigned char value;
90 	unsigned char num;
91 	struct irq_keydesc *keydesc;
92 	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
93 
94 	num = dev->curkeynum;
95 	keydesc = &dev->irqkeydesc[num];
96 
97 	value = gpio_get_value(keydesc->gpio); 	/* 读取IO值 */
98 	if(value == 0){ 						/* 按下按键 */
99 		atomic_set(&dev->keyvalue, keydesc->value);
100 	}
101 	else{ 									/* 按键松开 */
102 		atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
103 		atomic_set(&dev->releasekey, 1);	/* 标记松开按键,即完成一次完整的按键过程 */
104 	}               
105 
106 	/* 唤醒进程 */
107 	if(atomic_read(&dev->releasekey)) {	/* 完成一次按键过程 */
108 		/* wake_up(&dev->r_wait); */
109 		wake_up_interruptible(&dev->r_wait);
110 	}
111 }
112 
113 /*
114  * @description	: 按键IO初始化
115  * @param 		: 无
116  * @return 		: 无
117  */
118 static int keyio_init(void)
119 {
120 	unsigned char i = 0;
121 	char name[10];
122 	int ret = 0;
123 	
124 	imx6uirq.nd = of_find_node_by_path("/key");
125 	if (imx6uirq.nd== NULL){
126 		printk("key node not find!\r\n");
127 		return -EINVAL;
128 	} 
129 
130 	/* 提取GPIO */
131 	for (i = 0; i < KEY_NUM; i++) {
132 		imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio", i);
133 		if (imx6uirq.irqkeydesc[i].gpio < 0) {
134 			printk("can't get key%d\r\n", i);
135 		}
136 	}
137 	
138 	/* 初始化key所使用的IO,并且设置成中断模式 */
139 	for (i = 0; i < KEY_NUM; i++) {
140 		memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(name));	/* 缓冲区清零 */
141 		sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i);		/* 组合名字 */
142 		gpio_request(imx6uirq.irqkeydesc[i].gpio, name);
143 		gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);	
144 		imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);
145 #if 0
146 		imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
147 #endif
148 	}
149 
150 	/* 申请中断 */
151 	imx6uirq.irqkeydesc[0].handler = key0_handler;
152 	imx6uirq.irqkeydesc[0].value = KEY0VALUE;
153 	
154 	for (i = 0; i < KEY_NUM; i++) {
155 		ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler, 
156 		                 IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc[i].name, &imx6uirq);
157 		if(ret < 0){
158 			printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum);
159 			return -EFAULT;
160 		}
161 	}
162 
163 	/* 创建定时器 */
164     init_timer(&imx6uirq.timer);
165     imx6uirq.timer.function = timer_function;
166 
167 	/* 初始化等待队列头 */
168 	init_waitqueue_head(&imx6uirq.r_wait);
169 	return 0;
170 }
171 
172 /*
173  * @description		: 打开设备
174  * @param - inode 	: 传递给驱动的inode
175  * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
176  * 					  一般在open的时候将private_data指向设备结构体。
177  * @return 			: 0 成功;其他 失败
178  */
179 static int imx6uirq_open(struct inode *inode, struct file *filp)
180 {
181 	filp->private_data = &imx6uirq;	/* 设置私有数据 */
182 	return 0;
183 }
184 
185  /*
186  * @description     : 从设备读取数据 
187  * @param - filp    : 要打开的设备文件(文件描述符)
188  * @param - buf     : 返回给用户空间的数据缓冲区
189  * @param - cnt     : 要读取的数据长度
190  * @param - offt    : 相对于文件首地址的偏移
191  * @return          : 读取的字节数,如果为负值,表示读取失败
192  */
193 static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
194 {
195 	int ret = 0;
196 	unsigned char keyvalue = 0;
197 	unsigned char releasekey = 0;
198 	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
199 
200 	if (filp->f_flags & O_NONBLOCK)	{ /* 非阻塞访问 */
201 		if(atomic_read(&dev->releasekey) == 0)	/* 没有按键按下,返回-EAGAIN */
202 			return -EAGAIN;
203 	} else {							/* 阻塞访问 */
204 		/* 加入等待队列,等待被唤醒,也就是有按键按下 */
205 		ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey)); 
206 		if (ret) {
207 			goto wait_error;
208 		}
209 	}
210 
211 	keyvalue = atomic_read(&dev->keyvalue);
212 	releasekey = atomic_read(&dev->releasekey);
213 
214 	if (releasekey) { /* 有按键按下 */	
215 		if (keyvalue & 0x80) {
216 			keyvalue &= ~0x80;
217 			ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
218 		} else {
219 			goto data_error;
220 		}
221 		atomic_set(&dev->releasekey, 0);/* 按下标志清零 */
222 	} else {
223 		goto data_error;
224 	}
225 	return 0;
226 
227 wait_error:
228 	return ret;
229 data_error:
230 	return -EINVAL;
231 }
232 
233  /*
234  * @description     : poll函数,用于处理非阻塞访问
235  * @param - filp    : 要打开的设备文件(文件描述符)
236  * @param - wait    : 等待列表(poll_table)
237  * @return          : 设备或者资源状态,
238  */
239 unsigned int imx6uirq_poll(struct file *filp, struct poll_table_struct *wait)
240 {
241 	unsigned int mask = 0;
242 	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
243 
244 	poll_wait(filp, &dev->r_wait, wait);	/* 将等待队列头添加到poll_table中 */
245 	
246 	if(atomic_read(&dev->releasekey)) {		/* 按键按下 */
247 		mask = POLLIN | POLLRDNORM;			/* 返回PLLIN */
248 	}
249 	return mask;
250 }
251 
252 /* 设备操作函数 */
253 static struct file_operations imx6uirq_fops = {
254 	.owner = THIS_MODULE,
255 	.open = imx6uirq_open,
256 	.read = imx6uirq_read,
257 	.poll = imx6uirq_poll,
258 };
259 
260 /*
261  * @description	: 驱动入口函数
262  * @param 		: 无
263  * @return 		: 无
264  */
265 static int __init imx6uirq_init(void)
266 {
267 	/* 1、构建设备号 */
268 	if (imx6uirq.major) {
269 		imx6uirq.devid = MKDEV(imx6uirq.major, 0);
270 		register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
271 	} else {
272 		alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
273 		imx6uirq.major = MAJOR(imx6uirq.devid);
274 		imx6uirq.minor = MINOR(imx6uirq.devid);
275 	}
276 
277 	/* 2、注册字符设备 */
278 	cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
279 	cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
280 
281 	/* 3、创建类 */
282 	imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
283 	if (IS_ERR(imx6uirq.class)) {	
284 		return PTR_ERR(imx6uirq.class);
285 	}
286 
287 	/* 4、创建设备 */
288 	imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
289 	if (IS_ERR(imx6uirq.device)) {
290 		return PTR_ERR(imx6uirq.device);
291 	}
292 		
293 	/* 5、始化按键 */
294 	atomic_set(&imx6uirq.keyvalue, INVAKEY);
295 	atomic_set(&imx6uirq.releasekey, 0);
296 	keyio_init();
297 	return 0;
298 }
299 
300 /*
301  * @description	: 驱动出口函数
302  * @param 		: 无
303  * @return 		: 无
304  */
305 static void __exit imx6uirq_exit(void)
306 {
307 	unsigned i = 0;
308 	/* 删除定时器 */
309 	del_timer_sync(&imx6uirq.timer);	/* 删除定时器 */
310 		
311 	/* 释放中断 */	
312 	for (i = 0; i < KEY_NUM; i++) {
313 		free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);
314 		gpio_free(imx6uirq.irqkeydesc[i].gpio);
315 	}
316 	cdev_del(&imx6uirq.cdev);
317 	unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
318 	device_destroy(imx6uirq.class, imx6uirq.devid);
319 	class_destroy(imx6uirq.class);
320 }	
321 	
322 module_init(imx6uirq_init);
323 module_exit(imx6uirq_exit);
324 MODULE_LICENSE("duan");

修改设备文件名字为"noblockio",当驱动程序加载成功以后就会在根文件系统中出现一个名为"/dev/noblockio"的文件。

第200~202行,判断是否为非阻塞式读取访问,如果是的话就判断按键是否有效,也就是

判断一下有没有按键按下,如果没有的话就返回-EAGAIN。

第239~250行,imx6uirg_poll 函数就是file_operations 驱动操作集中的poll 函数,当应用程序调用select或者poll函数的时候imx6uirg poll函数就会执行。

第244行调用poll wait 函数将等待队列头添加到poll_table中

第246~248行判断按键是否有效,如果按键有效的话就向应用程序返回POLLIN这个事件,表示有数据可以读取。

第257行,设置file_operations 的poll成员变量为imx6uirg_poll。

3.2、测试 APP 代码

应用层实现两种非阻塞访问方式:poll函数和select函数,可根据需求注释/启用对应代码,核心逻辑是"轮询检测设备就绪,有数据则读取,无数据则超时返回,不阻塞主线程"。

新建名为 noblockioApp.c 测试 APP 文件,然后在其中输入如下所示内容:

cpp 复制代码
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"      // poll函数头文件
9 #include "sys/select.h"// select函数头文件
10 #include "sys/time.h"
11 
12 /***************************************************************
13 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
14 文件名 : noblockioApp.c
17 描述 : 非阻塞IO测试APP(支持poll/select两种方式)
18 使用方法 :./noblockioApp /dev/noblockio & (后台运行)
20 ***************************************************************/
21 
22 int main(int argc, char *argv[])
23 {
24     int fd;
25     int ret = 0;
26     char *filename;
27     struct pollfd fds;      // poll函数所需结构体
28     fd_set readfds;         // select函数所需文件描述符集
29     struct timeval timeout; // select超时时间
30     unsigned char data;     // 存储读取的按键值
31 
32     /* 检查参数是否正确 */
33     if (argc != 2) {
34         printf("Error Usage!\r\n");
35         printf("Usage: %s /dev/noblockio\r\n", argv[0]);
36         return -1;
37     }
38 
39     filename = argv[1];
40     /* 以非阻塞方式打开设备(O_NONBLOCK标志) */
41     fd = open(filename, O_RDWR | O_NONBLOCK);
42     if (fd < 0) {
43         printf("Can't open file %s\r\n", filename);
44         return -1;
45     }
46 
47     /* 方式1:使用poll函数实现非阻塞访问(注释则启用select方式) */
48 #if 1
49     fds.fd = fd;                // 要检测的文件描述符
50     fds.events = POLLIN;        // 检测"有数据可读取"事件
51 
52     while (1) {
53         /* poll函数:参数(文件描述符集,数量,超时时间ms) */
54         ret = poll(&fds, 1, 500);  // 超时时间500ms,超时返回0
55         switch (ret) {
56             case 0:  // 超时(无按键按下)
57                 // 可添加自定义超时处理逻辑(如打印提示)
58                 // printf("poll timeout...\r\n");
59                 break;
60             case -1: // 错误
61                 printf("poll error!\r\n");
62                 break;
63             default: // 有数据可读取(按键按下)
64                 if (fds.revents & POLLIN) {  // 确认是POLLIN事件
65                     ret = read(fd, &data, sizeof(data));
66                     if (ret < 0) {
67                         printf("read error!\r\n");
68                     } else {
69                         if (data) {  // 读取到有效按键值
70                             printf("key value = %d\r\n", data);
71                         }
72                     }
73                 }
74                 break;
75         }
76     }
77 #endif
78 
79     /* 方式2:使用select函数实现非阻塞访问(启用需注释上面的poll代码) */
80 #if 0
81     while (1) {
82         FD_ZERO(&readfds);              // 清空文件描述符集
83         FD_SET(fd, &readfds);           // 将fd添加到读描述符集
84 
85         /* 设置超时时间:500ms(tv_sec=0,tv_usec=500000) */
86         timeout.tv_sec = 0;
87         timeout.tv_usec = 500000;
88 
89         /* select函数:参数(最大fd+1,读集,写集,异常集,超时时间) */
90         ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
91         switch (ret) {
92             case 0:  // 超时
93                 // printf("select timeout...\r\n");
94                 break;
95             case -1: // 错误
96                 printf("select error!\r\n");
97                 break;
98             default: // 有数据可读取
99                 if (FD_ISSET(fd, &readfds)) {  // 确认fd有数据
100                    ret = read(fd, &data, sizeof(data));
101                    if (ret < 0) {
102                        printf("read error!\r\n");
103                    } else {
104                        if (data) {
105                            printf("key value=%d\r\n", data);
106                        }
107                    }
108                }
109                break;
110        }
111    }
112 #endif
113 
114    /* 关闭文件(实际不会执行,因为while循环是死循环) */
115    close(fd);
116    return ret;
117 }

第41行:以O_NONBLOCK标志打开设备,告知驱动"应用层采用非阻塞访问"。

第49~76行:poll方式实现,通过poll函数轮询设备,超时时间500ms,有数据则读取按键值,无数据则超时继续轮询。

第81~111行:select方式实现,通过select函数检测读描述符集,超时时间500ms,逻辑与poll一致,只是API不同。

两种方式二选一,推荐使用poll(代码更简洁),可根据项目需求选择。

四、运行测试

4.1、编译驱动程序和测试 APP

编写 Makefile 文件,本次实验的 Makefile 文件和中断实验之前的实验基本一样,只是将 obj-m 变量的值改为noblockio.o,Makefile 内容如下所示:

cpp 复制代码
KERNELDIR := /home/duan/linux/linux-imx-rel_imx_4.1.15_2.1.1_ga_alientek_v2.2
CURRENT_PATH := $(shell pwd)

obj-m := noblockio.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

第 4 行,设置 obj-m 变量的值为noblockio.o。

输入如下命令编译出驱动模块文件:

cpp 复制代码
make -j32

编译成功以后就会生成一个名为"noblockio.ko"的驱动模块文件。

编译测试 APP

输入如下命令编译测试noblockioApp.c这个测试程序:

cpp 复制代码
arm-linux-gnueabihf-gcc  noblockioApp.c -o   noblockioApp

编译成功以后就会生成noblockioApp这个应用程序。

5.2、运行测试

将上一小节编译出来的noblockio.ko 和noblockioApp这两个文件拷贝到 rootfs/lib/modules/4.1.15 目录中。

cpp 复制代码
sudo cp noblockio.ko /home/duan/linux/nfs/rootfs/lib/modules/4.1.15/ -f
cpp 复制代码
sudo cp  noblockioApp /home/duan/linux/nfs/rootfs/lib/modules/4.1.15/ -f

进入到目录 lib/modules/4.1.15 中,输入如下命令加载noblockio.ko 驱动模块:

cpp 复制代码
depmod //第一次加载驱动的时候需要运行此命令
modprobe noblockio.ko  //加载驱动

驱动加载成功以后使用如下命令打开noblockioApp这个测试 APP,并且以后台模式运行

cpp 复制代码
./noblockioApp /dev/noblockio &

按下开发板上的 KEY0 键,终端就会输出按键值,如下图所示:

当按下 KEY0 按键以后noblockioApp 这个测试 APP 就会打印出按键值。输入"top"命令,

查看 noblockioApp这个应用 APP 的 CPU 使用率,如下图所示:


从上图 可以看出,采用非阻塞方式读处理以后, noblockioApp 的 CPU 占用率也低至 0.0%,和之前的 blockioApp 一样,这里的 0.0% 并不是说 noblockioApp 这个应用程序。
不使用 CPU 了,只是因为使用率太小了,而图中只能显示出小数点后一位,因此就显示成了 0.0%。
如果要"杀掉"处于后台运行模式的 noblockioApp 这个应用程序,可以上一期博客 的方法。


总结

本次实验完成了Linux非阻塞IO的驱动开发和测试。

适用场景:非阻塞IO适合需要同时处理多个设备、不允许长时间阻塞的场景(如串口通信、按键检测等)。

相关推荐
ulias2129 小时前
Linux系统中的权限问题
linux·运维·服务器
mzhan01711 小时前
Linux: lock: preempt_count 是一个线程级别的变量
linux·lock
Dream of maid12 小时前
Linux(下)
linux·运维·服务器
齐鲁大虾12 小时前
统信系统UOS常用命令集
linux·运维·服务器
ZzzZZzzzZZZzzzz…12 小时前
Nginx 平滑升级:从 1.26.3 到 1.28.0,用户无感知
linux·运维·nginx·平滑升级·nginx1.26.3·nginx1.28.0
FreakStudio12 小时前
MicroPython LVGL基础知识和概念:底层渲染与性能优化
python·单片机·嵌入式·电子diy
一叶知秋yyds13 小时前
Ubuntu 虚拟机安装 OpenClaw 完整流程
linux·运维·ubuntu·openclaw
楠奕16 小时前
CentOS7安装GoldenDB单机搭建及常见报错解决方案
linux·运维·服务器
剑锋所指,所向披靡!16 小时前
Linux常用指令(2)
linux·运维·服务器