异步通知机制

异步通知

生活例子:买奶茶

  • 在旁边等着,眼睛盯着店员,生怕别人插队,他一做好你就知道:你是主动等待他做好,这叫“同步”。
  • 付钱后就去玩手机了,店员做好后他会打电话告诉你:你是被动获得结果,这叫“异步”。

使用流程

驱动程序怎么通知 APP: 发信号,这只有 3 个字,却可以引发很多问题:

  1. 谁发:驱动程序发。
  2. 发什么:信号。
  3. 发什么信号:SIGIO
  4. 怎么发:内核里提供有函数
  5. 发给谁:APP,APP 要把自己告诉驱动
  6. APP 收到后做什么:执行信号处理函数
  7. 信号处理函数和信号,之间怎么挂钩: 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 还要做什么事?想想这几个问题:

  • 内核里有那么多驱动,你想让哪一个驱动给你发 SIGIO 信号?
    APP 要打开驱动程序的设备节点。
  • 驱动程序怎么知道要发信号给你而不是别人?
    APP 要把自己的进程 ID 告诉驱动程序。
  • APP 有时候想收到信号,有时候又不想收到信号:
    应该可以把 APP 的意愿告诉驱动。

驱动程序要做什么?发信号。

  • APP 设置进程 ID 时,驱动程序要记录下进程 ID;
  • APP 还要使能驱动程序的异步通知功能,驱动中有对应的函数:
  • APP 打开驱动程序时,内核会创建对应的 file 结构体, file 中有 f_flags;f_flags 中有一个 FASYNC 位,它被设置为 1 时表示使能异步通知功能。当 f_flags 中的 FASYNC 位发生变化时,驱动程序的 fasync 函数被调用。
  • 发生中断时,有数据时,驱动程序调用内核辅助函数发信号。这个辅助函数名为 kill_fasync。

综上所述,使用异步通知,也就是使用信号的流程如下图所示:
在这里插入图片描述

应用编程

重点从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 函数读取按键。

总结

应用层需要做的:

  1. 写信号处理函数
static void sig_func(int sig)
{
	in val;
	read(fd, &val, 4);
	printf("get button: 0x%x\n", val);
}
  1. 注册信号处理函数
// 3注册信号处理函数
signal(SIGIO, sig_func);
  1. 打开文件
fd = open(argv[1], O_RDWR);
  1. 把APP的pid告诉驱动程序;
fcntl(fd ,F_SETOWN, getpid());
  1. 读取驱动程序flag并设置FASYNC位为1
flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC);

驱动编程

驱动层需要做的:

  1. 提供对应的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 中断服务程序中发信号。

  1. 中断处理函数中使用kill fasync发送信号。

kill_fasync(&button_fasync, SIGIO, POLL_IN);

  • 第 1 个参数: button_async->fa_file 非空时,可以从中得到 PID,表示发给哪一个 APP;
  • 第 2 个参数表示发什么信号: SIGIO;
  • 第 3 个参数表示为什么发信号: POLL_IN,有数据可以读了。 (APP 用不到这个参数)

编译

在这里插入图片描述

测试

把编译出来的设备树文件拷贝到/boot目录下
在这里插入图片描述
reboot重启
安装驱动,强制安装

insmod -f gpio_key_drv.ko

在这里插入图片描述
执行测试程序

./button_test /dev/100ask_gpio_key

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值