韦东山嵌入式Liunx入门驱动开发五

文章目录

      • 一、驱动程序基石
        • 1-1 休眠与唤醒
        • 1-2 POLL机制
        • 1-3 异步通知
          • (1) 异步通知程序解析
          • (2) 异步通知机制内核代码详解
        • 1-4 阻塞与非阻塞
        • 1-5 定时器
          • (1) 内核函数
          • (2) 定时器时间单位
        • 1-6 中断下半部 tasklet

本人学习完韦老师的视频,因此来复习巩固,写以笔记记之。
韦老师的课比较难,第一遍不知道在说什么,但是坚持看完一遍,再来复习,基本上就水到渠成了。
看完视频复习的同学观看最佳!
基于 IMX6ULL-PRO
参考视频 Linux快速入门到精通视频
参考资料:01_嵌入式Linux应用开发完全手册V5.1_IMX6ULL_Pro开发板.pdf

一、驱动程序基石

1-1 休眠与唤醒

当应用程序必须等待某个事件发生,比如必须等待按键被按下时, 可以使用休眠-唤醒机制
① APP调用read等函数试图读取数据,比如读取按键;
② APP进入内核态,也就是调用驱动中的对应函数,发现有数据则复制到用户空间并马上返回;
③ 如果APP在内核态,也就是在驱动程序中发现没有数据,则APP休眠;
④ 当有数据时,比如当按下按键时,驱动程序的中断服务程序被调用,它会记录数据、唤醒APP
⑤ APP继续运行它的内核态代码,也就是驱动程序中的函数,复制数据到用户空间并马上返回。
在这里插入图片描述
驱动框架
在这里插入图片描述
休眠,直到condition 为真;休眠期间是可被打断的,可以被信号打断

wait_event_interruptible(wq, condition)

唤醒wq队列中状态为“ TASK_INTERRUPTIBLE ”的线程,只唤醒其中的一个线程

wake_up_interruptible(wq)

要休眠的线程,放在wq 队列里,中断处理函数从wq队列里把它取出来唤
醒。
① 初始化wq队列
② 在驱动的read函数中,调用 wait_event_interruptible。它本身会判断
event是否为 FALSE ,如果为FASLE表示无数据,则休眠。
当从wait_event_interruptible 返回后,把数据复制回用户空间。
③ 在中断服务程序里:设置event 为TRUE,并调用wake_up_interruptible 唤醒线程。

1-2 POLL机制

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

在这里插入图片描述
drv_poll函数需要做的事
① 把当前线程挂入队列wq:poll_wait
②返回设备状态:drv_poll 要返回自己的当前状态:P OLLIN | POLLRDNORM) 或 POLLOUT | POLLWRNORM) 。

static unsigned int gpio_key_drv_poll(struct file *fp, poll_table * wait)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);poll_wait(fp, &gpio_key_wait, wait);return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;}

button_test.c

int main(int argc, char **argv)
{int fd;int val;struct pollfd fds[1];int timeout_ms = 5000;int ret;/* 1. 判断参数 */if (argc != 2) {printf("Usage: %s <dev>\n", argv[0]);return -1;}/* 2. 打开文件 */fd = open(argv[1], O_RDWR);if (fd == -1){printf("can not open file %s\n", argv[1]);return -1;}fds[0].fd = fd;fds[0].events = POLLIN;while (1){/* 3. 读文件 */ret = poll(fds, 1, timeout_ms);if((ret == 1) && (fds[0].revents & POLLIN)){read(fd, &val, 4);printf("get button : 0x%x\n", val);}else{printf("timeout\n");}}close(fd);return 0;
}
1-3 异步通知
(1) 异步通知程序解析

异步通知流程如下:
在这里插入图片描述

① APP给SIGIO这个信号注册信号处理函数func,以后APP收到SIGIO信号时,这个函数会被自动调用。
② 把APP的PID(进程 ID)告诉驱动程序,这个调用不涉及驱动程序,在内核的文件系统层次记录PID。

filp->f_owner.pid = get_pid(pid);

③ 读取驱动程序文件Flag
④ 设置Flag里面的FASYNC位为1;当 FASYNC位发生变化时,会导致驱动程序的fasync被调用
⑤ 调用faync_helper ,它会根据 FAYSNC的值决定是否设置
button_async -->fa_file=filp(内含PID);open文件时,会在内核文件系统中有一个struct file *filp结构体,filp->f_owner.pid里面含有之前设置的PID 。
⑥ APP做其他事;当按下按键,发生中断,驱动程序的中断服务程序被调用,里面调用kill_fasync 发信号
⑦ APP收到信号后,它的信号处理函数被自动调用,可以在里面调用
read函数读取按键。

驱动程序中提供对应的drv_fasync函数,并在FAYNC变化时,调用fasync_helper函数,使得button_fasync->fa_file = filp或者NULL

struct fasync_struct *button_fasync;
static int gpio_key_drv_fasync(int fd, struct file *file, int on)
{if (fasync_helper(fd, file, on, &button_fasync) >= 0)return 0;elsereturn -EIO;
}

在GPIO中断服务程序中,若button_fasync->fa_file非空,则获得PID,并发信号给上层应用

kill_fasync(&button_fasync, SIGIO, POLL_IN);

上层应用程序
在这里插入图片描述

(2) 异步通知机制内核代码详解

上层应用执行fcntl函数,内核会调用fs/fcntl.c 的如下函数。
在这里插入图片描述
进入do_fcntl函数,flag标志对应函数的cmd
在这里插入图片描述

以下分别对三种flag进行代码演示
① 当flag是F_SETOWN时,内核do_fcntl中调用f_setown函数,最终将pid给filp

fcntl(fd, F_SETOWN, getpid());

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
② 当flag是F_GETFL时,获取文件的状态标志

flags = fcntl(fd, F_GETFL);

在这里插入图片描述
③ 当flag是F_SETFL时,设置文件支持异步通知功能

fcntl(fd, F_SETFL, flags | FASYNC);

在这里插入图片描述
在这里插入图片描述
启动了FASYNC 功能的话,驱动程序的 button_fasync 就被设置了,它指向的 fasync_struct 结构体里含有 filp里含有PID

static int gpio_key_drv_fasync(int fd, struct file *file, int on)
{if (fasync_helper(fd, file, on, &button_fasync) >= 0)return 0;elsereturn -EIO;
}

在这里插入图片描述
从button_fasync 指针中,取出 fasync_struct 结构体,从这个结构体的 fa_file 中得到接收方的PID ,然后使用 send_sigio函数发送信号。根据 PID找到进程在内核的 task_struct结构体, 修改里面的某些成员表示收到了信号。

kill_fasync(&button_fasync, SIGIO, POLL_IN);

在这里插入图片描述

1-4 阻塞与非阻塞

所谓阻塞,就是等待某件事情发生。比如调用read读取按键时,如果没有按键数据则read函数不会返回,它会让线程休眠等待。
使用poll时,传入超时时间不为0(阻塞);设置超时时间为0,没有数据立即返回(非阻塞)
如何设置阻塞与非阻塞呢?
① open时

int fd = open(/dev/xxx”, O_RDWR | O_NONBLOCK); 	/* 非阻塞方式*/
int fd = open(/dev/xxx”, O_RDWR ); 	/* 阻塞方式*/

② open后

int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_NONBLOCK); 	/* 非阻塞方式*/
fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); 	/* 阻塞方式*/

驱动程序中,当APP打开某个驱动时,在内核中会有一个struct file 结构体的f _flags对应打开文件时的标记位;可以设置f _flasgs 的O_NONBLOCK 位,表示非阻塞;也可以清除这个位表示阻塞。

/* 实现对应的open/read/write等函数,填入file_operations结构体*/                
static ssize_t gpio_key_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);int err;int key;if(is_key_buf_empty() && (file->f_flags & O_NONBLOCK))return -EAGAIN;else{wait_event_interruptible(gpio_key_wait, !is_key_buf_empty());key = get_key();err = copy_to_user(buf, &key, 4);return 4;}
}
1-5 定时器
(1) 内核函数

所谓定时器,就是闹钟,时间到后你就要做某些事。有2个要素:时间、做事;换成程序员的话就是:超时时间、函数。
内核源码:include\linux\timer.h

1、设置定时器,主要是初始化timer_list结构体,设置其中的函数、参数。

setup_timer(timer, fn, data);

2、向内核添加定时器。 timer–>expires 表示超时时间。
当超时时间到达,内核就会调用这个函数:timer->function(timer -->data) 。

void add_timer(struct timer_list *timer)

在这里插入图片描述
3、修改定时器的超时时间

int mod_timer(struct timer_list *timer, unsigned long expires):

4、删除定时器

int del_timer(struct timer_list *timer)
(2) 定时器时间单位

这表示内核每秒中会发生100次系统滴答中断 (tick),这是Linux系统的心跳。每发生一次tick中断,全局变量jiffies累加1。即:每个滴答是10ms。

CONFIG_HZ=100

按键触发中断,进入中断处理函数,若不断发生机械振动,会不断进入中断处理函数更新定时器超时时间,时间到后进入定时器处理函数,打印GPIO端口信息
probe函数设置定时器

static int gpio_key_probe(struct platform_device *pdev)
{/* 设置定时器*/setup_timer(&gpio_keys_100ask[i].key_timer, key_timer_expire, &gpio_keys_100ask[i]);/*设置超时时间*/gpio_keys_100ask[i].key_timer.expires = ~0;add_timer(&gpio_keys_100ask[i].key_timer);
}

中断处理函数修改定时器超时时间

static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{struct gpio_key *gpio_key = dev_id;printk("gpio_key_isr %d irq happened\n", gpio_key->gpio);mod_timer(&gpio_key->key_timer, jiffies + HZ/50); //20ms  HZ = 1sreturn IRQ_HANDLED;
}

定时器处理函数中打印GPIO信息和唤醒线程

struct timer_list key_timer;
/*定时器处理函数*/
static void key_timer_expire(unsigned long data)
{/*data ==> gpio*/struct gpio_key *gpio_key = data;int val;int key;val = gpiod_get_value(gpio_key->gpiod);printk("key_timer_expire %d %d\n", gpio_key->gpio, val);key = (gpio_key->gpio << 8) | val;put_key(key);wake_up_interruptible(&gpio_key_wait); 			/*唤醒线程*/kill_fasync(&button_fasync, SIGIO, POLL_IN);	/*发信号*/}
1-6 中断下半部 tasklet

在上半部处理紧急的事情时,在处理过程中,中断是被禁止的;在下半部处理耗时的事情时,在处理过程中,中断是使能的。
内核源码:include\linux\interrupt.h

struct tasklet_struct
{struct tasklet_struct *next;unsigned long state;atomic_t count;void (*func)(unsigned long);unsigned long data;
};

state用于表示 tasklet 的状态,一共有2位。
bit0 表示 TASKLET_STATE_SCHED
等于1时,表示已经执行了 tasklet_schedule把tasklet放入队列;
bit1 表示 TASKLET_STATE_RUN
等于1时,表示正在运行 tasklet 中的func函数;函数执行完后内核会把该位清0。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/717698.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《幻兽帕鲁》游戏对服务器性能的具体要求是什么?

《幻兽帕鲁》游戏对服务器性能的具体要求是什么&#xff1f; CPU&#xff1a;官方最低要求为i5-3570K&#xff0c;但在多人游玩时可能会有明显卡顿。此外&#xff0c;还有建议选择4核或更高性能的处理器&#xff0c;以确保游戏运行流畅。 内存&#xff1a;对于不同人数的联机&…

苹果ios群控软件开发常用源代码分享!

在移动软件开发领域&#xff0c;苹果设备由于其封闭性和安全性受到了广大开发者的青睐&#xff0c;然而&#xff0c;这也为开发者带来了一些挑战&#xff0c;特别是在进行群控软件开发时。 群控软件是指可以同时控制多台设备的软件&#xff0c;这在自动化测试、批量操作等场景…

数据要素:数字化转型中的新“金矿”及其发展潜力

作为一名在数字化转型项目中摸爬滚打的实践者&#xff0c;我们见证了数据从简单的信息处理工具逐渐演变为驱动经济社会发展的关键要素。近日&#xff0c;多部门联合发布的《“数据要素”三年行动计划&#xff08;2024—2026年&#xff09;》更是将数据要素的重要性提升到了新的…

C++ //练习 10.15 编写一个lambda,捕获它所在函数的int,并接受一个int参数。lambda应该返回捕获的int和int参数的和。

C Primer&#xff08;第5版&#xff09; 练习 10.15 练习 10.15 编写一个lambda&#xff0c;捕获它所在函数的int&#xff0c;并接受一个int参数。lambda应该返回捕获的int和int参数的和。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;v…

十六、异常和File

异常和File 一、异常1.1异常的分类1.2 异常的作用1.3 异常的处理方式1.3.1 JVM默认的处理方式1.3.2 自己处理&#xff08;捕获异常&#xff09;1.3.3 自己处理&#xff08;灵魂四问&#xff09; 1.4 异常中的常见方法1.5 抛出异常综合练习&#xff08;键盘录入数据&#xff09;…

基于springboot+vue的社区养老服务平台

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

黑马点评-商户查询业务

缓存原理 本文的业务就是redis的经典应用&#xff0c;标准的操作方式就是查询数据库之前先查询缓存&#xff0c;如果缓存数据存在&#xff0c;则直接从缓存中返回&#xff0c;如果缓存数据不存在&#xff0c;再查询数据库&#xff0c;然后将数据存入redis。 缓存更新策略 根据…

Spring重点记录

文章目录 1.Spring的组成2.Spring优点3.IOC理论推导4.IOC本质5.IOC实现&#xff1a;xml或者注解或者自动装配&#xff08;零配置&#xff09;。6.hellospring6.1beans.xml的结构为&#xff1a;6.2.Spring容器6.3对象的创建和控制反转 7.IOC创建对象方式7.1以有参构造的方式创建…

【OneAPI】猫狗类别检测API

OneAPI新接口发布&#xff1a;猫狗类别检测 45种狗狗类别和15种猫猫类别检测。 API地址&#xff1a;POST https://oneapi.coderbox.cn/openapi/api/detect/dogcat 请求参数&#xff08;body&#xff09; 参数名类型必填含义说明imageUrlstring是图片地址网络图片地址&#…

Vue路由(黑马程序员)

路由介绍 将资代码/vue-project(路由)/vue-project/src/views/tlias/DeptView.vue拷贝到我们当前EmpView.vue同级&#xff0c;其结构如下&#xff1a; 此时我们希望&#xff0c;实现点击侧边栏的部门管理&#xff0c;显示部门管理的信息&#xff0c;点击员工管理&#xff0c;显…

【周总结平淡但不平凡的周末】

上周总结 根据系统生产环境的日志文件&#xff0c;写了个脚本统计最近使用我们系统的用户的手机型号以及系统&#xff0c;帮助聚焦主要测试的机型&#xff0c;以及系统类型 依然是根据时区不同对项目进行改造&#xff0c;还有一个开发好的接口需要下周联调 2024/3/3 晴…

QT Mingw32/64编译ffmpeg源码生成32/64bit库以及测试

文章目录 前言下载msys2ysamFFmpeg 搭建编译环境安装msys2安装QT Mingw编译器到msys环境中安装ysam测试 编译FFmpeg测试 前言 FFmpeg不像VLC有支持QT的库文件&#xff0c;它仅提供源码&#xff0c;需要使用者自行编译成对应的库&#xff0c;当使用QTFFmpeg实现播放视频以及视频…

连接 mongodb集群的集中方式

mongodb 连接到复制集 mongodb://node1,node2,node3.../database?[options]mongodb 连接到分片集 mongodb://mongos1,mongos2,mongos3.../database?[options]使用 mongosrv 通过域名解析得到所有的 mongos 或 节点的地址, 而不是把这些写在连接字符串中. mongodbsrv://se…

经典的算法面试题(1)

题目&#xff1a; 给定一个整数数组 nums&#xff0c;编写一个算法将所有的0移到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 示例: 输入: [0,1,0,3,12] 输出: [1,3,12,0,0] 注意&#xff1a;必须在原数组上操作&#xff0c;不能拷贝额外的数组。尽量减少操作次数。 这…

数据处理——一维数组转列向量(分割时间序列为数据块时的问题)

记录在处理数据时被磕绊了一下的一个处理细节。 1.想要达到的要求 在某次滑动窗口取样时间序列数据时&#xff0c;我得到如下一个以一维数组为元素的列表&#xff1a; 对于如上输出列表中的每个一维数组&#xff0c;我希望将其转换为下图中的形式&#xff0c;简单说就是希望他…

【详识JAVA语言】面向对象程序三大特性之三:多态

多态 多态的概念 多态的概念&#xff1a;通俗来说&#xff0c;就是多种形态&#xff0c;具体点就是去完成某个行为&#xff0c;当不同的对象去完成时会产生出不同的状态。 多态实现条件 在java中要实现多态&#xff0c;必须要满足如下几个条件&#xff0c;缺一不可&#xf…

循环队列与循环双端队列

文章目录 前言循环队列循环双端队列 前言 1、学习循环队列和循环双端队列能加深我们对队列的理解&#xff0c;提高我们的编程能力。 2、本文循环队列使用的是数组&#xff0c;循环双端队列用的是双向链表 3、题目连接&#xff1a;设计循环队列 &#xff0c;设计循环双端队列。 …

【机器学习】有监督学习算法之:支持向量机

支持向量机 1、引言2、决策树2.1 定义2.2 原理2.3 实现方式2.4 算法公式2.5 代码示例 3、总结 1、引言 小屌丝&#xff1a;鱼哥&#xff0c;泡澡啊。 小鱼&#xff1a;不去 小屌丝&#xff1a;… 此话当真&#xff1f; 小鱼&#xff1a;此话不假 小屌丝&#xff1a;到底去还是…

什么是支持向量机(Support vector machine)和其原理

作为机器学习的基础算法&#xff0c;SVM被反复提及&#xff0c;西瓜书、wiki都能查到详细介绍&#xff0c;但是总是觉得还差那么点&#xff0c;于是决定自己总结一下。 一、什么是SVM&#xff1f; 1、解决什么问题&#xff1f; SVM&#xff0c;最原始的版本是用于最简单的线…

总结 HashTable, HashMap, ConcurrentHashMap 之间的区别

1.多线程环境使用哈希表 HashMap 不行,线程不安全 更靠谱的,Hashtable,在关键方法上加了synchronized 后来标准库又引入了一个更好的解决方案;ConcurrentHashMap 2.HashMap 首先HashMap本身线程不安全其次HashMap的key值可以为空&#xff08;当key为空时&#xff0c;哈希会…