1.异步通知概述#
前面讲到APP 读取按键方式里面包含4种方式:1.查询方式,2.休眠唤醒,3,poll机制的休眠唤醒,4.异步通知
字符设备驱动-3-GPIO驱动KEY示例 | Hexo (fuzidage.github.io)
什么是异步通知?
你去买奶茶:
◼ 你在旁边等着,眼睛盯着店员,生怕别人插队,他一做好你就知道:你是主动等待他做好,这叫 “同步”。
◼ 你付钱后就去玩手机了,店员做好后他会打电话告诉你:你是被动获得结果,这叫“异步”。
同理,还是以gpio_key
的例子,app想要判断按键是否有按下,无需去查询或者休眠,只需要注册SIGIO信号
给driver, 当按键按下,driver中会自己主动通知app,给app发送SIGIO信号
,app上层收到信号SIGIO
会执行事先注册的信号处理函数
。
异步通知使用信号来实现。在 Linux 内核源文件 include\uapi\asmgeneric\signal.h
中:
1.1 sigaction函数#
1 | 第一个参数为信号的值, 可以是除SIGKILL及SIGSTOP外的任何一个特定有效的信号。 |
1 | int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact)); |
1 | void _SAMPLE_PLAT_SYS_HandleSig(int nSignal, siginfo_t *si, void *arg) { |
1.2 signal函数#
1.2.1 示例1#
按下“Ctrl+C”
将向其发出SIGINT
信号, 正在运行kill的进程将向其发出SIGTERM
信号, 以下代码的进程可捕获这两个信号并输出信号值:
点击查看代码
1 | void sigterm_handler(int signo){ |
可以看到对应SIGINT
输入kill [pid]
可以看到对应SIGTERM
信号
1.2.2 示例2#
通过signal(SIGIO, input_handler);
对标准输入文件描述符STDIN_FILENO
启动信号机制.用户输入后, 应用程序将接收到SIGIO
信号, 其处理函数input_handler()
将被调用.
点击查看代码
1 | void input_handler(int num) |
1.1.3 示例3#
手动对app进程发送SIGIO
信号
点击查看代码
1 |
|
发送SIGIO
信号给进程,也就是kill -29 [pid]
2 异步通知的实现原理流程#
驱动程序怎么通知 APP:发信号,这只有 3 个字,却可以引发很多问题:
1 | 1. 谁发:驱动程序发 |
Linux系统中也有很多信号,内核源文件include\uapi\asmgeneric\signal.h
中,有很多信号的宏定义:
就APP而言,你想处理 SIGIO
信息,那么需要提供信号处理函数,并且要跟SIGIO
挂钩。这可以通过一个 signal 函数来 “给某个信号注册处理函数”,用法如下:
APP 还要做什么事?想想这几个问题:
1 | a) 内核里有那么多驱动,你想让哪一个驱动给你发 SIGIO 信号?APP 要打开驱动程序的设备节点。 |
驱动程序要做什么?发信号。
1 | a) APP 设置进程 ID 时,驱动程序要记录下进程 ID; |
2.1. 异步通知的信号流程#
① APP open,在drv中调用drv_open
,进行注册按键中断服务函数gpio_key_irq
;
② APP 给SIGIO
信号注册信号处理函数func
, 以后 APP 收到 SIGIO
信号时,这个函数会被自动调用;
③ APP 调用fcntl
, 把PID(进程 ID)告诉驱动程序,这个调用不涉及驱动程序,只是在内核的文件系统层次sys_cntl
,记录 PID在filp
中(sys_call
会建立filp
结构);
④ APP 调用fcntl
, 读取驱动程序文件 Flag;
⑤ 设置 Flag 里面的FASYNC
位为 1:当 FASYNC
位发生变化时,会导致驱动程序的fasync
被调用;
⑥⑦ 调 用 faync_helper
, 它 会 根 据FAYSNC
的值决定是否设置button_async->fa_file = 驱动文件filp
:
驱动文件filp
结构体里面含有之前设置的 PID。
⑧ APP 可以做其他事;
⑨⑩ 按下按键,发生中断,驱动程序的中断服务程序被调用,里面调用kill_fasync
发信号;
⑪⑫⑬ APP 收到信号后,它的信号处理函数被自动调用,可以在里面调用read 函数读取按键。
3.驱动代码编写#
先修改Linux-4.9.88/arch/arm/boot/dts/100ask_imx6ull-14x14.dts
建立dts节点,“100ask,gpio_key”
和platform drvier
中保持一致。这里将goio-keys
disabled
掉是为了drv去匹配新添加的这个节点。
使用异步通知时,驱动程序的核心有2个:
① 提供对应的drv_fasync
函数;
② 并在合适的时机发信号。
3.1 开启async#
drv_fasync
函数很简单,调用 fasync_helper
函数就可以,如下:fasync_helper
函 数 会 分 配 、 构 造 一 个 fasync_struct
结构体button_async
:
⚫ 驱动文件的 flag 被设置为 FAYNC 时
:button_async->fa_file = filp; // filp 表示驱动程序文件,里面含有之前设置的 PID
⚫ 驱动文件被设置为非FASYNC
时:button_async->fa_file = NULL;
以后想发送信号时,使用 button_async
作为参数就可以,它里面 “可能” 含有 PID。
3.2 发SIGIO信号#
什么时候发信号呢?在本例中,在 GPIO 中断服务程序中发信号。
怎么发信号呢?代码如下:
1 | kill_fasync(&button_async, SIGIO, POLL_IN); |
第 1 个参数:button_async->fa_file
非空时,可以从中得到 PID,表示发给哪一个 APP;
第 2 个参数表示发什么信号:SIGIO
;
第 3 个参数表示为什么发信号:POLL_IN
,有数据可以读了。(APP 用不到这个参数)
点击查看完整驱动代码
1 |
|
4.应用编写#
应用程序要做的事情有这几件:
编写信号处理函数:
1
2
3
4
5static void sig_func(int sig) {
int val;
read(fd, &val, 4);
printf("get button : 0x%x\n", val);
}注册信号处理函数:
1
signal(SIGIO, sig_func);
打开驱动:
1
fd = open(argv[1], O_RDWR);//./aout /dev/100ask_gpio_key
把进程 ID 告诉驱动:
1
fcntl(fd, F_SETOWN, getpid());
使能驱动的 FASYNC 功能:
1
2flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC);
点击查看代码
1 |
|