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 |
|