异步通知机制详解
异步通知
生活例子:买奶茶
使用流程
驱动程序怎么通知 APP: 发信号,这只有 3 个字,却可以引发很多问题:
- 谁发:驱动程序发。
- 发什么:信号。
- 发什么信号:SIGIO
- 怎么发:内核里提供有函数
- 发给谁:APP,APP 要把自己告诉驱动
- APP 收到后做什么:执行信号处理函数
- 信号处理函数和信号,之间怎么挂钩: APP 注册信号处理函数
Linux 系统中也有很多信号,在 Linux 内核源文件 include\uapi\asmgeneric\signal.h
中,有很多信号的宏定义
#define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3
#define SIGILL 4
#define SIGTRAP 5
#define SIGABRT 6
#define SIGIOT 6
#define SIGBUS 7
#define SIGFPE 8
#define SIGKILL 9
#define SIGUSR1 10
#define SIGSEGV 11
#define SIGUSR2 12
#define SIGPIPE 13
#define SIGALRM 14
#define SIGTERM 15
#define SIGSTKFLT 16
#define SIGCHLD 17
#define SIGCONT 18
#define SIGSTOP 19
#define SIGTSTP 20
#define SIGTTIN 21
#define SIGTTOU 22
#define SIGURG 23
#define SIGXCPU 24
#define SIGXFSZ 25
#define SIGVTALRM 26
#define SIGPROF 27
#define SIGWINCH 28
#define SIGIO 29
#define SIGPOLL SIGIO
/*
#define SIGLOST 29
*/
#define SIGPWR 30
#define SIGSYS 31
#define SIGUNUSED 31
/* These should not be considered constants from userland. */
#define SIGRTMIN 32
#ifndef SIGRTMAX
#define SIGRTMAX _NSIG
#endif
就 APP 而言,你想处理 SIGIO 信息,那么需要提供信号处理函数,并且要跟 SIGIO 挂钩。这可以通过一个 signal 函数来“给某个信号注册处理函数”,用法如下:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
APP 还要做什么事?想想这几个问题:
APP 要打开驱动程序的设备节点。
APP 要把自己的进程 ID 告诉驱动程序。
应该可以把 APP 的意愿告诉驱动。
驱动程序要做什么?发信号。
综上所述,使用异步通知,也就是使用信号的流程如下图所示:
应用编程
重点从2开始
② 注册信号处理函数:APP 给 SIGIO 这个信号注册信号处理函数 func,以后 APP 收到 SIGIO信号时,这个函数会被自动调用;
③ 把APP的PID(进程ID)告诉驱动程序,这个调用不涉及驱动程序,在内核的文件系统层次记录 PID;
④ 读取驱动程序文件 Flag;
⑤ 设置Flag里面的FASYNC位为1:当FASYNC位发生变化时,会导致驱动程序的fasync被调用;
⑥⑦ 调用faync_helper,它会根据FAYSNC的值决定是否设置button_async->fa_file=驱动文件 filp:驱动文件 filp 结构体里面含有之前设置的 PID;
⑧ APP 可以做其他事;
⑨⑩按下按键,发生中断,驱动程序的中断服务程序被调用,里面调用kill_fasync 发信号;
⑪⑫⑬ APP 收到信号后,它的信号处理函数被自动调用,可以在里面调用 read 函数读取按键。
总结
应用层需要做的:
- 写信号处理函数
static void sig_func(int sig)
{
in val;
read(fd, &val, 4);
printf("get button: 0x%x\n", val);
}
- 注册信号处理函数
// 3注册信号处理函数
signal(SIGIO, sig_func);
- 打开文件
fd = open(argv[1], O_RDWR);
- 把APP的pid告诉驱动程序;
fcntl(fd ,F_SETOWN, getpid());
- 读取驱动程序flag并设置FASYNC位为1
flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC);
驱动编程
驱动层需要做的:
- 提供对应的fasync函数,并调用fasync_helper函数;
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;
}
else
{
return -EIO;
}
}
// 定义自己的file_operations结构体
static struct file_operations gpio_key_drv = {
.owner = THIS_MODULE,
.read = gpio_key_drv_read,
.poll = gpio_key_drv_poll,
.fasync = gpio_key_drv_fasync,
};
fasync_helper 函数会分配、构造一个fasync_struct结构体button_async:
驱动文件的 flag 被设置为 FAYNC 时:
button_async->fa_file = filp; // filp 表示驱动程序文件,里面含有之前设置的 PID
驱动文件被设置为非 FASYNC 时:
button_async->fa_file = NULL;
以后想发送信号时,使用 button_async 作为参数就可以,它里面“可能”含有 PID。
什么时候发信号呢?在本例中,在 GPIO 中断服务程序中发信号。
- 中断处理函数中使用kill fasync发送信号。
kill_fasync(&button_fasync, SIGIO, POLL_IN);
编译
测试
把编译出来的设备树文件拷贝到/boot目录下
reboot重启
安装驱动,强制安装
insmod -f gpio_key_drv.ko
执行测试程序
./button_test /dev/100ask_gpio_key
作者:执念、坚持