嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)驱动程序基石

文章目录

    • 1.1 休眠与唤醒
      • 1.1.1 适用场景
      • 1.1.2 内核函数
        • 1.1.2.1 休眠函数
        • 1.1.2.2 唤醒函数
      • 19.1.3 驱动框架
      • 1.1.4 编程
        • 1.1.4.1 驱动程序关键代码
        • 1.1.4.2 应用程序
      • 1.1.5 上机实验
      • 1.1.6 使用环形缓冲区改进驱动程序
    • 1.2 POLL机制
      • 1.2.1 适用场景
      • 1.2.2 使用流程
      • 1.2.3 驱动编程
      • 1.2.4 应用编程
      • 1.2.5 现场编程
      • 1.2.6 上机实验
      • 1.2.7 POLL机制的内核代码详解
      • 1.2.7.1 sys_poll函数
        • 1.2.7.2 do_sys_poll函数
        • 1.2.7.3 do_poll函数
    • 1.3 异步通知
      • 1.3.1 适用场景
      • 1.3.2 使用流程
      • 1.3.3 驱动编程
      • 1.3.4 应用编程
      • 1.3.5 现场编程
      • 1.3.6 上机编程
      • 1.3.7 异步通知机制内核代码详解
    • 1.4 阻塞与非阻塞
      • 1.4.1 应用编程
      • 1.4.2 驱动编程
      • 1.4.3 驱动开发原则
    • 1.5 定时器
      • 1.5.1 内核函数
      • 1.5.2 定时器时间单位
      • 1.5.3 使用定时器处理按键抖动
      • 1.5.4 现场编程、上机
      • 1.5.5 深入研究:定时器的内部机制
      • 1.5.6 深入研究:找到系统滴答
    • 1.6 中断下半部tasklet
      • 1.6.1 内核函数
        • 1.6.1.1 定义tasklet
        • 1.6.1.2 使能/禁止tasklet
        • 1.6.1.3 调度tasklet
        • 1.6.1.4 kill tasklet
      • 19.6.2 tasklet使用方法
      • 19.6.3 tasklet内部机制
    • 1.7 工作队列
      • 1.7.1 内核函数
        • 1.7.1.1 定义work
        • 1.7.1.2 使用work:schedule_work
        • 1.7.1.3 其他函数![在这里插入图片描述](https://img-blog.csdnimg.cn/2734a5e0a7db44fe992e6f77eeb604b2.png” />
      • 1.7.2 编程、上机
      • 1.7.3 内部机制
        • 1.7.3.1 Linux 2.x的工作队列创建过程
        • 1.7.3.2 Linux 4.x的工作队列创建过程
    • 1.8 中断的线程化处理
      • 1.8.1 内核机制
        • 1.8.1.1 调用request_threaded_irq后内核的数据结构
        • 1.8.1.2 request_threaded_irq
        • 1.8.1.3 中断的执行过程
      • 1.8.2 编程、上机
    • 1.9 mmap
      • 1.9.1 内存映射现象与数据结构
      • 1.9.2 ARM架构内存映射简介
        • 1.9.2.1 一级页表映射过程
        • 1.9.2.2 二级页表映射过程
      • 1.9.3 怎么给APP新建一块内存映射
        • 1.9.3.1 mmap调用过程
        • 1.9.3.2 cache和buffer
        • 1.9.3.3 驱动程序要做的事
      • 1.9.4 编程
        • 1.9.4.1 APP编程
        • 1.9.4.2 驱动编程
        • 1.9.4.3 上机测试

1.1 休眠与唤醒

1.1.1 适用场景

在前面引入中断时,我们曾经举过一个例子:

嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)驱动程序基石
驱动中没有数据时,APP1在内核态执行到drv_read时会休眠。所谓休眠就是把自己的状态改为非RUNNING,这样内核的调度器就不会让它运行。当按下按键,驱动程序中的中断服务程序被调用,它会记录数据,并唤醒APP1。所以唤醒就是把程序的状态改为RUNNING,这样内核的调度器有合适的时间就会让它运行。当APP1再次运行时,就会继续执行drv_read中剩下的代码,把数据复制回用户空间,返回用户空间。APP1的执行过程如下图的红色实线所示,它被分成了2段:
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)驱动程序基石
比较重要的参数就是:
① wq:waitqueue,等待队列
休眠时除了把程序状态改为非RUNNING之外,还要把进程/进程放入wq中,以后中断服务程序要从wq中把它取出来唤醒。
没有wq的话,茫茫人海中,中断服务程序去哪里找到你br> ② condition
这可以是一个变量,也可以是任何表达式。表示“一直等待,直到condition为真”。

1.1.2.2 唤醒函数

参考内核源码:includelinuxwait.h。

嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)驱动程序基石
要休眠的线程,放在wq队列里,中断处理函数从wq队列里把它取出来唤醒。
所以,我们要做这几件事:
① 初始化wq队列
② 在驱动的read函数中,调用wait_event_interruptible:
它本身会判断event是否为FALSE,如果为FASLE表示无数据,则休眠。
当从wait_event_interruptible返回后,把数据复制回用户空间。
③ 在中断服务程序里:
设置event为TRUE,并调用wake_up_interruptible唤醒线程。

1.1.4 编程

使用GIT命令载后,源码位于这个目录下:

03_read_key_irq_circle_buffer使用了环型缓冲区,可以避免按键丢失。

1.1.4.1 驱动程序关键代码

02_read_key_irqgpio_key_drv.c中,要先定义“wait queue”:

在驱动的读函数里调用wait_event_interruptible:

第49行并不一定会进入休眠,它会先判断g_key是否为TRUE。
执行到第50行时,表示要么有了数据(g_key为TRUE),要么有信号等待处理(本节课程不涉及信号)。

假设g_key等于0,那么APP会执行到上述代码第49行时进入休眠状态。它被谁唤醒控制的中断服务程序:

上述代码中,第72行确定按键值g_key,g_key也就变为TRUE了。
然后在第73行唤醒gpio_key_wait中的第1个线程。
注意这2个函数,一个没有使用“&”,另一个使用了“&”:

1.1.4.2 应用程序

应用程序并不复杂,调用open、read即可,代码在button_test.c中:

在33行~38行的循环中,APP基本上都是休眠状态。你可以执行top命令查看CPU占用率。

1.1.5 上机实验

跟上一节视频类似,需要先修改设备树,请使用上一节视频的设备树文件。
然后安装驱动程序,运行测试程序。

1.1.6 使用环形缓冲区改进驱动程序

使用GIT命令载后,源码位于这个目录下:

使用环形缓冲区,可以在一定程序上避免按键数据丢失,关键代码如下:

嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)驱动程序基石
妈妈怎么知道卧室里小孩醒了br> ① 时不时进房间看一下:查询方式
简单,但是累
② 进去房间陪小孩一起睡觉,小孩醒了会吵醒她:休眠-唤醒
不累,但是妈妈干不了活了
③ 妈妈要干很多活,但是可以陪小孩睡一会,定个闹钟:poll方式
要浪费点时间,但是可以继续干活。
妈妈要么是被小孩吵醒,要么是被闹钟吵醒。
④ 妈妈在客厅干活,小孩醒了他会自己走出房门告诉妈妈:异步通知
妈妈、小孩互不耽误

使用休眠-唤醒的方式等待某个事件发生时,有一个缺点:等待的时间可能很久。我们可以加上一个超时时间,这时就可以使用poll机制。
① APP不知道驱动程序中是否有数据,可以先调用poll函数查询一下,poll函数可以传入超时时间;
② APP进入内核态,调用到驱动程序的poll函数,如果有数据的话立刻返回;
③ 如果发现没有数据时就休眠一段时间;
④ 当有数据时,比如当按下按键时,驱动程序的中断服务程序被调用,它会记录数据、唤醒APP;
⑤ 当超时时间到了之后,内核也会唤醒APP;
⑥ APP根据poll函数的返回值就可以知道是否有数据,如果有数据就调用read得到数据

1.2.2 使用流程

妈妈进入房间时,会先看小孩醒没醒,闹钟响之后走出房间之前又会再看小孩醒没醒。
注意:看了2次小孩!
POLL机制也是类似的,流程如下:

嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)驱动程序基石
函数执行流程如上图①~⑧所示,重点从③开始看。假设一开始无按键数据:
③ APP调用poll之后,进入内核态;
④ 导致驱动程序的drv_poll被调用:
注意,drv_poll要把自己这个线程挂入等待队列wq中;假设不放入队列里,那以后发生中断时,中断服务程序去哪里找到你嘛br> drv_poll还会判断一下:有没有数据啊回这个状态。
⑤ 假设当前没有数据,则休眠一会;
⑥ 在休眠过程中,按下了按键,发生了中断:
在中断服务程序里记录了按键值,并且从wq中把线程唤醒了。
⑦ 线程从休眠中被唤醒,继续执行for循环,再次调用drv_poll:
drv_poll返回数据状态
⑧ 哦,你有数据,那从内核态返回到应用态吧
⑨ APP调用read函数读数据
如果一直没有数据,调用流程也是类似的,重点从③开始看,如下:
③ APP调用poll之后,进入内核态;
④ 导致驱动程序的drv_poll被调用:
注意,drv_poll要把自己这个线程挂入等待队列wq中;假设不放入队列里,那以后发生中断时,中断服务程序去哪里找到你嘛br> drv_poll还会判断一下:有没有数据啊回这个状态。
⑤ 假设当前没有数据,则休眠一会;
⑥ 在休眠过程中,一直没有按下了按键,超时时间到:内核把这个线程唤醒;
⑦ 线程从休眠中被唤醒,继续执行for循环,再次调用drv_poll:
drv_poll返回数据状态
⑧ 哦,你还是没有数据,但是超时时间到了,那从内核态返回到应用态吧
⑨ APP不能调用read函数读数据

注意几点:
① drv_poll要把线程挂入队列wq,但是并不是在drv_poll中进入休眠,而是在调用drv_poll之后休眠
② drv_poll要返回数据状态
③ APP调用一次poll,有可能会导致drv_poll被调用2次
④ 线程被唤醒的原因有2:中断发生了去队列wq中把它唤醒,超时时间到了内核把它唤醒
⑤ APP要判断poll返回的原因:有数据,还是超时。有数据时再去调用read函数。

1.2.3 驱动编程

使用poll机制时,驱动程序的核心就是提供对应的drv_poll函数。
在drv_poll函数中要做2件事:
① 把当前线程挂入队列wq:poll_wait
APP调用一次poll,可能导致drv_poll被调用2次,但是我们并不需要把当前线程挂入队列2次。
可以使用内核的函数poll_wait把线程挂入队列,如果线程已经在队列里了,它就不会再次挂入。
② 返回设备状态:
APP调用poll函数时,有可能是查询“有没有数据可以读”:POLLIN,也有可能是查询“你有没有空间给我写数据”:POLLOUT。
所以drv_poll要返回自己的当前状态:(POLLIN | POLLRDNORM) 或 (POLLOUT | POLLWRNORM)。来源:da..

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2022年2月13日
下一篇 2022年2月13日

相关推荐