天津做网站一般多少钱,网站做统计,seo培训多少钱,江苏搜索引擎优化i.MX8MM处理器采用了先进的14LPCFinFET工艺#xff0c;提供更快的速度和更高的电源效率;四核Cortex-A53#xff0c;单核Cortex-M4#xff0c;多达五个内核 #xff0c;主频高达1.8GHz#xff0c;2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…i.MX8MM处理器采用了先进的14LPCFinFET工艺提供更快的速度和更高的电源效率;四核Cortex-A53单核Cortex-M4多达五个内核 主频高达1.8GHz2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT、4G模块、CAN、RS485等接口一应俱全。H264、VP8视频硬编码H.264、H.265、VP8、VP9视频硬解码并提供相关历程,支持8路PDM接口、5路SAI接口、2路Speaker。系统支持Android9.0(支持获取root限)Linux4.14.78Qt5.10.1、Yocto、Ubuntu20、Debian9系统。适用于智能充电桩物联网工业控制医疗智能交通等可用于任何通用工业和物联网应用、 【公众号】迅为电子
【粉丝群】258811263 第五十九章 等待队列
本章导读 阻塞和非阻塞 IO 是 Linux 驱动开发里面很常见的两种设备访问模式在编写驱动的时候一定要考虑到阻塞和非阻塞。本章我们就来学习一下阻塞和非阻塞 IO以及如何在驱动程序中处理阻塞与非阻塞
59.1章节讲解了阻塞和非阻塞IO的概念
59.2章节编写了驱动程序在iTOP-IMX8MM开发板上为例实现了非阻塞的按键驱动
59.3章节编写应用测试程序
59.4章节运行测试发现CPU占用率很高
59.5章节在59.2章节的基础上编写驱动程序用等待队列阻塞当按键按下的时候再去读取value的信息这样做是比较专业的而且可以极大的减少cpu的占用率。
本章内容对应视频讲解链接在线观看
等待队列 → https://www.bilibili.com/video/BV1Vy4y1B7ta?p38
程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\016-等待队列”路径下。
59.1 阻塞和非阻塞IO
59.1.1 阻塞与非阻塞简介
阻塞操作是指在执行设备操作时若不能获得资源则挂起进程直到满足可操作的条件后再进行操作。
被挂起的进程进入睡眠状态被从调度器的运行队列移走直到等待的条件被满足。而非阻塞操作的进程
在不能进行设备操作时并不挂起它要么放弃要么不停地查询直至可以进行操作为止。
在阻塞访问时不能获取资源的进程将进入休眠它将 CPU 资源“礼让”给其他进程。因为阻塞的进程会进入休眠状态所以必须确保有一个地方能够唤醒休眠的进程否则进程就真的“寿终正寝”了。唤醒进程的地方最大可能发生在中断里面因为在硬件资源获得的同时往往伴随着一个中断。而非阻塞的进程则不断尝试直到可以进行 I/O。阻塞访问如图所示 若用户以非阻塞的方式访问设备文件则当设备资源不可获取时设备驱动的 xxx_read 、 xxx_write 等操作应立即返回read 、write 等系统调用也随即被返回应用程序收到-EAGAIN 返回值。 应用程序可以使用如下所示示例代码来实现阻塞访问 int fd; int data 0; fd open(/dev/xxx_dev, O_RDWR); /* 阻塞方式打开 */ ret read(fd, data, sizeof(data)); /* 读取数据 */ 可以看出对于设备驱动文件的默认读取方式就是阻塞式的所以我们前面所有的例程测试 APP 都是采
用阻塞 IO。
如果应用程序要采用非阻塞的方式来访问驱动设备文件可以使用如下所示代码 int fd; int data 0; fd open(/dev/xxx_dev, O_RDWR | O_NONBLOCK); /* 非阻塞方式打开 */ ret read(fd, data, sizeof(data)); /* 读取数据 */ 使用 open 函数打开“/dev/xxx_dev”设备文件的时候添加了参数“O_NONBLOCK”表示以非阻塞方式打开设备这样从设备中读取数据的时候就是非阻塞方式的了。 使用 open 函数打开“/dev/xxx_dev”设备文件的时候添加了参数“O_NONBLOCK”表示以非阻塞方式打开设备这样从设备中读取数据的时候就是非阻塞方式的了。
59.1.2 等待队列
当我们进程去访问设备的时候经常需要等待有特定事件发生以后再继续往下运行这个时候就需要在驱动里面实现当条件不满足的时候进行休眠当条件满足的时候在由内核唤醒进程。在 Linux 驱动程序中可以使用等待队列Wait Queue来实现阻塞进程的唤醒。等待队列很早就作为一个基本的功能单位出现在 Linux 内核里了它以队列为基础数据结构与进程调度机制紧密结合可以用来同步对系统资源的访问。队列是一种特殊的线性表特殊之处在于它只允许在表的前端front进行删除操作而在表的后端rear进行插入操作和栈一样队列是一种操作受限制的线性表。进行插入操作的端称为队尾进行删除操作的端称为队头。即满足先进先出的形式 FIFO。
举个例子比如说我现在去食堂打饭阿姨和我说现在没有饭你需要等一会等我做好了我再叫你那么我当前不能获得资源我被阻塞在这儿了那么等待队列就是让我们阻塞在这儿然后等特定的事件发生以后再继续运行。那么等待队列阻塞在这儿的这件事情就相当于阿姨和我们说现在没有饭你需要等一会。为什么我们要先讲完中断以后再讲等待队列呢举个例子来说比如说阿姨和你说现在没饭你需要在旁边等一会等我做好了我再叫你如果说阿姨做完了不叫你你又睡着了那么你今天是不是吃不上饭了所以说在我们阻塞访问的时候不能获得资源的进程将进入休眠状态他将cpu的资源全部让给别的进程必须保证有一个地方可以唤醒休眠进程否则的话将会长睡不醒。进程唤醒最大可能的地方发生在中断里面伴随着一个中断的发生我们可以唤醒该进程对应的事件是阿姨说饭好了小王你过来打吧。所以说我们学习等待队列在中断之后这样用等待队列可以极大的降低cpu的占用率。
Linux 内核的等待队列是以双循环链表为基础数据结构与进程调度机制紧密结合能够用于实现核心
的异步事件通知机制。它有两种数据结构等待队列头wait_queue_head_t和等待队列项wait_queue_t。
等待队列头和等待队列项中都包含一个 list_head 类型的域作为”连接件”。它通过一个双链表和把等待 task
的头和等待的进程列表链接起来。
59.1.3 等待队列头
等待队列头就是一个等待队列的头部 每个访问设备的进程都是一个队列项 当设备不可用的时候就要将这些进程对应的等待队列项添加到等待队列里面。
等待队列头使用结构体wait_queue_head_t来表示这个结构体定义在文件include/linux/wait里面结构体内容如下 struct __wait_queue_head { spinlock_t lock; //自旋锁 struct list_head task_list; //链表头 }; typedef struct __wait_queue_head wait_queue_head_t; 类型名是wait_queue_head_t只需要记住这个即可。
定义一个等待队列头 wait_queue_head_t test_wq; //定义一个等待队列的头 定义等待队列头以后需要初始化可以使用init_waitqueue_head函数初始化等待队列头 函数原型如下 函数 void init_waitqueue_head(wait_queue_head_t *q) q wait_queue_head_t 指针 功能 动态初始化等待队列头结构
也可以使用宏 DECLARE_WAIT_QUEUE_HEAD 来一次性完成等待队列头的定义和初始化。 DECLARE_WAIT_QUEUE_HEAD (wait_queue_head_t *q); 59.1.4 等待队列项
等待队列头就是一个等待队列的头部每个访问设备的进程都是一个队列项当设备不可用的时候就
要将这些进程对应的等待队列项添加到等待队列里面。结构体 wait_queue_t 表示等待队列项结构体内容如下 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; 使用宏 DECLARE_WAITQUEUE 定义并初始化一个等待队列项宏的内容如下 DECLARE_WAITQUEUE(name, tsk) name 就是等待队列项的名字tsk 表示这个等待队列项属于哪个任务(进程)一般设置为 current 在
Linux 内核中 current 相当于一个全局变量表示当前进程。因此 DECLARE_WAITQUEUE 就是给当前正在运行的进程创建并初始化了一个等待队列项。
59.1.5 添加/删除队列
当设备不可访问的时候就需要将进程对应的等待队列项添加到前面创建的等待队列头中只有添加到
等待队列头中以后进程才能进入休眠态。当设备可以访问以后再将进程对应的等待队列项从等待队列头中
移除即可等待队列项添加队列函数如下所示 函数 void add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait) q 等待队列项要加入的等待队列头。 wait 要加入的等待队列项 返回值 无 功能 从等待队列头中添加队列
等待队列项移除队列函数如下 函数 void remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait) q 要删除的等待队列项所处的等待队列头 wait 要删除的等待队列项 返回值 无
59.1.6 等待唤醒
当设备可以使用的时候就要唤醒进入休眠态的进程唤醒可以使用如下两个函数 void wake_up(wait_queue_head_t *q) //功能唤醒所有休眠进程 void wake_up_interruptible(wait_queue_head_t *q)//功能唤醒可中断的休眠进程 参数 q 就是要唤醒地等待队列头这两个函数会将这个等待队列头中的所有进程都唤醒。
wake_up 函数可以唤醒处于 TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE状态的进程而wake_up_interruptible 函数只能唤醒处于 TASK_INTERRUPTIBLE 状态的进程。 59.1.7 等待事件
除了主动唤醒以外也可以设置等待队列等待某个事件当这个事件满足以后就自动唤醒等待队列中
的进程相关函数 #define wait_event(wq, condition) do { if (condition) break; __wait_event(wq, condition); } while (0) wait_event(queue,condition);等待以 queue 为等待队列头等待队列被唤醒condition 必须满足否则阻塞 wait_event_interruptible(queue,condition);可被信号打断 wait_event_timeout(queue,condition,timeout);阻塞等待的超时时间时间到了不论 condition 是否满足都要返回 wait_event_interruptible_timeout(queue,condition,timeout) wait_event()宏
功能不可中断的阻塞等待让调用进程进入不可中断的睡眠状态在等待队列里面睡眠直到condition变成真被内核唤醒。
wait_event_interruptible() 函数
功能可中断的阻塞等待让调用进程进入可中断的睡眠状态直到condition变成真被内核唤醒或被信号打断唤醒。
wait_event_timeout() 宏
也与 wait_event()类似.不过如果所给的睡眠时间为负数则立即返回.如果在睡眠期间被唤醒,且 condition为真则返回剩余的睡眠时间,否则继续睡眠直到到达或超过给定的睡眠时间,然后返回 0.
wait_event_interruptible_timeout() 宏
与 wait_event_timeout()类似,不过如果在睡眠期间被信号打断则返回 ERESTARTSYS 错误码.
wait_event_interruptible_exclusive() 宏
同样和 wait_event_interruptible()一样,不过该睡眠的进程是一个互斥进程
注意调用的时要确认condition 值是真还是假如果调用condition为真则不会休眠。
59.2 编写驱动程序
程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\016-等待队列\001”路径下。
我们以IMX8MM开发板为例在Ubuntu的/home/topeet/imx8mm/16/001目录下新建driver.c,编写驱动代码如下所示;
/** Author: topeet* Description: 等待队列实验*/
#include linux/init.h
#include linux/module.h
#include linux/platform_device.h
#include linux/of.h
#include linux/of_address.h
#include linux/miscdevice.h
#include linux/uaccess.h
#include linux/fs.h
#include linux/gpio.h
#include linux/of_gpio.h
#include linux/interrupt.h
#include linux/of_irq.h
#include linux/wait.h
#include linux/sched.h
#include linux/io.h//定义结构体表示我们的节点
struct device_node *test_device_node;
struct property *test_node_property;
//要申请的中断号
int irq;
//GPIO 编号
int gpio_nu;
//用来模拟管脚的状态
int value 0;/*** description: 中断处理函数test_key* param {int} irq 要申请的中断号* param {void} *args * return {*}IRQ_HANDLED*/
irqreturn_t test_key(int irq, void *args)
{value !value;return IRQ_RETVAL(IRQ_HANDLED);
}
int misc_open(struct inode *node, struct file *file)
{printk(hello misc_open \n);return 0;
}int misc_release(struct inode *node, struct file *file)
{printk(hello misc_release bye bye\n);return 0;
}static ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{if (copy_to_user(ubuf, value, sizeof(value)) ! 0){printk(copy_to_user error\n);return -1;}return 0;
}
//文件操作集
struct file_operations misc_fops {.owner THIS_MODULE,.open misc_open,.release misc_release,.read misc_read};struct miscdevice misc_dev {.minor MISC_DYNAMIC_MINOR,.name test_wq,.fops misc_fops,
};
/*** brief led_probe : 与设备信息层设备树匹配成功后自动执行此函数* param inode : 文件索引* param file : 文件* return 成功返回 0
*/
int led_probe(struct platform_device *pdev)
{int ret 0;printk(led_probe\n);//of_find_node_by_path函数通过路径查找节点/test_key是设备树下的节点路径test_device_node of_find_node_by_path(/test);if (test_device_node NULL){printk(of_find_node_by_path is error\n);return -1;}//of_get_named_gpio函数获取 GPIO 编号gpio_nu of_get_named_gpio(test_device_node, gpios, 0);if (gpio_nu 0){printk(of_get_named_gpio is error\n);return -1;}//设置GPIO为输入模式gpio_direction_input(gpio_nu);//获取GPIO对应的中断号irq irq_of_parse_and_map(test_device_node, 0);printk(irq is %d \n, irq);/*申请中断irq:中断号名字 test_key中断处理函数IRQF_TRIGGER_RISING中断标志意为上升沿触发test_key中断的名字*/ret request_irq(irq, test_key, IRQF_TRIGGER_RISING, test_key, NULL);if (ret 0){printk(request_irq \n);return -1;}//注册杂项设备ret misc_register(misc_dev);if (ret 0){printk(misc_register is error\n);return -1;}printk(misc_register is successd \n);return 0;
}int led_remove(struct platform_device *pdev)
{printk(led_remove \n);return 0;
}
const struct platform_device_id led_idtable {.name led_test,
};
const struct of_device_id of_match_table_test[] {{.compatible keys},{},
};
struct platform_driver led_driver {//3. 在led_driver结构体中完成了led_probe和led_remove.probe led_probe,.remove led_remove,.driver {.owner THIS_MODULE,.name led_test,.of_match_table of_match_table_test},//4 .id_table的优先级要比driver.name的优先级要高优先与.id_table进行匹配.id_table led_idtable};static int led_driver_init(void)
{//1.我们看驱动文件要从init函数开始看int ret 0;//2. 在init函数里面注册了platform_driverret platform_driver_register(led_driver);if (ret 0){printk(platform_driver_register error \n);return ret;}printk(platform_driver_register ok \n);return 0;
}static void led_driver_exit(void)
{printk(gooodbye! \n);free_irq(irq, NULL);misc_deregister(misc_dev);platform_driver_unregister(led_driver);
}
module_init(led_driver_init);
module_exit(led_driver_exit);
MODULE_LICENSE(GPL);
59.3 编写应用程序
在Ubuntu的/home/topeet/imx8mm/16/001目录下我们编写应用程序app.c如下图所示
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.h
int main(int argc,char *argv[])
{int fd;int value;//打开设备节点fd open(/dev/test_wq,O_RDWR);if(fd 0){//打开设备节点失败perror(open error \n); return fd;}while(1){read(fd,value,sizeof(value)); printf(value is %d \n,value); }close(fd);return 0;
}
编译应用程序,如下图所示 59.4 运行测试
我们将刚刚编写的驱动代码编译为驱动模块如下图所示 我们进入共享目录并且加载驱动模块如下图所示 运行应用程序串口调试信息会不停的打印value值是0如下图所示 我们按底板上的音量按键时value值取反变为1如下图所示 我们重新编译将应用程序app后台运行然后输入top查看内存占用率如下图所示 59.5 优化方案
在59.4章节中如上图所示app的cpu占用率高达99%这样肯定是不行的别的程序是不能运行的所以说我们要用等待队列阻塞当按键按下的时候再去读取value的信息这样做是比较专业的而且可以极大的减少cpu的占用率。我们在part1代码的基础上进行修改代码如下所示
程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\016-等待队列\002”路径下。
/** Author: topeet* Description: 等待队列实验*/
#include linux/init.h
#include linux/module.h
#include linux/platform_device.h
#include linux/of.h
#include linux/of_address.h
#include linux/miscdevice.h
#include linux/uaccess.h
#include linux/fs.h
#include linux/gpio.h
#include linux/of_gpio.h
#include linux/interrupt.h
#include linux/of_irq.h
#include linux/wait.h
#include linux/sched.h
#include linux/io.h//定义结构体表示我们的节点
struct device_node *test_device_node;
struct property *test_node_property;
//要申请的中断号
int irq;
//GPIO 编号
int gpio_nu;
//用来模拟管脚的状态
int value 0;
//等待队列头的定义和初始化。
DECLARE_WAIT_QUEUE_HEAD(key_wq);
//定义等待队列标志位
int wq_flags 0;/*** description: 中断处理函数test_key* param {int} irq 要申请的中断号* param {void} *args * return {*}IRQ_HANDLED*/
irqreturn_t test_key(int irq, void *args)
{//将等待队列置1然后唤醒等待队列wq_flags 1;wake_up(key_wq);//将value取反value !value;return IRQ_HANDLED;
}int misc_open(struct inode *node, struct file *file)
{printk(hello misc_open \n);return 0;
}
int misc_release(struct inode *node, struct file *file)
{printk(hello misc_release bye bye\n);return 0;
}
ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{//阻塞可被wake_up唤醒wait_event_interruptible(key_wq, wq_flags);wq_flags 0;if (copy_to_user(ubuf, value, sizeof(value)) ! 0){printk(copy_to_user error\n);return -1;}return 0;
}
//文件操作集
struct file_operations misc_fops {.owner THIS_MODULE,.open misc_open,.release misc_release,.read misc_read};
struct miscdevice misc_dev {.minor MISC_DYNAMIC_MINOR,.name test_wq,.fops misc_fops,
};/***************************************************************************************** brief led_probe : 与设备信息层设备树匹配成功后自动执行此函数* param inode : 文件索引* param file : 文件* return 成功返回 0 ****************************************************************************************/
int led_probe(struct platform_device *pdev)
{int ret 0;// 打印匹配成功进入probe函数printk(led_probe\n);test_device_node of_find_node_by_path(/test);if (test_device_node NULL){//查找节点失败则打印信息printk(of_find_node_by_path is error \n);return -1;}gpio_nu of_get_named_gpio(test_device_node, gpios, 0);if (gpio_nu 0){printk(of_get_namd_gpio is error \n);return -1;}//设置GPIO为输入模式gpio_direction_input(gpio_nu);//获取GPIO对应的中断号irq gpio_to_irq(gpio_nu);// irq irq_of_parse_and_map(test_device_node,0);printk(irq is %d \n, irq);/*申请中断irq:中断号名字 test_key中断处理函数IRQF_TRIGGER_RISING中断标志意为上升沿触发test_key中断的名字*/ret request_irq(irq, test_key, IRQF_TRIGGER_RISING, test_key, NULL);if (ret 0){printk(request_irq is error \n);return -1;}//注册杂项设备retmisc_register(misc_dev);if(ret0){printk(misc_register is error \n);return -1;}printk(misc_register is success \n);return 0;
}int led_remove(struct platform_device *pdev)
{printk(led_remove\n);return 0;
}
const struct platform_device_id led_idtable {.name keys,
};
const struct of_device_id of_match_table_test[] {{.compatible keys},{},
};
struct platform_driver led_driver {//3. 在led_driver结构体中完成了led_probe和led_remove.probe led_probe,.remove led_remove,.driver {.owner THIS_MODULE,.name led_test,.of_match_table of_match_table_test},//4 .id_table的优先级要比driver.name的优先级要高优先与.id_table进行匹配.id_table led_idtable};/*** description: 模块初始化函数* param {*}* return {*}*/
static int led_driver_init(void)
{//1.我们看驱动文件要从init函数开始看int ret 0;//2.在init函数里面注册了platform_driverret platform_driver_register(led_driver);if (ret 0){printk(platform_driver_register error \n);}printk(platform_driver_register ok \n);return 0;
}/*** description: 模块卸载函数* param {*}* return {*}*/
static void led_driver_exit(void)
{free_irq(irq, NULL);platform_driver_unregister(led_driver);printk(gooodbye! \n);
}
module_init(led_driver_init);
module_exit(led_driver_exit);MODULE_LICENSE(GPL);
我们还是像part1实验一样将驱动编译为驱动模块应用程序还是使用part1编译好的app我们加载驱动模块如下图所示 如上图所示运行了应用程序以后我们触摸以下屏幕按一次按键打印一次value的值。