嵌入式Linux应用开发-基础知识-第十九章驱动程序基石②
- 第十九章 驱动程序基石②
- 19.3 异步通知
- 19.3.1 适用场景
- 19.3.2 使用流程
- 19.3.3 驱动编程
- 19.3.4 应用编程
- 19.3.5 现场编程
- 19.3.6 上机编程
- 19.3.7 异步通知机制内核代码详解
 
- 19.4 阻塞与非阻塞
- 19.4.1 应用编程
- 19.4.2 驱动编程
- 19.4.3 驱动开发原则
 
 
第十九章 驱动程序基石②

19.3 异步通知
使用 GIT命令载后,本节源码位于这个目录下:
01_all_series_quickstart\ 
05_嵌入式 Linux驱动开发基础知识\source\ 
06_gpio_irq\ 
    05_read_key_irq_poll_fasync 
19.3.1 适用场景
在前面引入中断时,我们曾经举过一个例子:
 
妈妈怎么知道卧室里小孩醒了?
 ① 时不时进房间看一下:查询方式
 简单,但是累
 ② 进去房间陪小孩一起睡觉,小孩醒了会吵醒她:休眠-唤醒
 不累,但是妈妈干不了活了
 ③ 妈妈要干很多活,但是可以陪小孩睡一会,定个闹钟:poll方式 要浪费点时间,但是可以继续干活。
 妈妈要么是被小孩吵醒,要么是被闹钟吵醒。
 ④ 妈妈在客厅干活,小孩醒了他会自己走出房门告诉妈妈:异步通知 妈妈、小孩互不耽误
 使用休眠-唤醒、POLL机制时,都需要休眠等待某个事件发生时,它们的差别在于后者可以指定休眠的时长。
 在现实生活中:妈妈可以不陪小孩睡觉,小孩醒了之后可以主动通知妈妈。
 如果 APP不想休眠怎么办?也有类似的方法:驱动程序有数据时主动通知APP,APP收到信号后执行信息处理函数。
 什么叫“异步通知”?
 你去买奶茶:
 你在旁边等着,眼睛盯着店员,生怕别人插队,他一做好你就知道:你是主动等待他做好,这叫“同步”。 你付钱后就去玩手机了,店员做好后他会打电话告诉你:你是被动获得结果,这叫“异步”。
19.3.2 使用流程
驱动程序怎么通知 APP:发信号,这只有 3个字,却可以引发很多问题:
 ① 谁发:驱动程序发
 ② 发什么:信号
 ③ 发什么信号:SIGIO
 ④ 怎么发:内核里提供有函数
 ⑤ 发给谁:APP,APP要把自己告诉驱动
 ⑥ APP收到后做什么:执行信号处理函数
 ⑦ 信号处理函数和信号,之间怎么挂钩:APP注册信号处理函数
 小孩通知妈妈的事情有很多:饿了、渴了、想找人玩。
 Linux系统中也有很多信号,在 Linux内核源文件 include\uapi\asm-generic\signal.h中,有很多信号的宏定义:
 
就 APP而言,你想处理 SIGIO信息,那么需要提供信号处理函数,并且要跟 SIGIO挂钩。这可以通过一个 signal函数来“给某个信号注册处理函数”,用法如下:
 
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。
 完美!
 APP收到信号后,是怎么执行信号处理函数的?
 这个,很难,有兴趣的话就看本节最后的文档。初学者没必要看。
 综上所述,使用异步通知,也就是使用信号的流程如下图所示:
 
重点从②开始:
 ② 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函数读取按键。
19.3.3 驱动编程
使用异步通知时,驱动程序的核心有 2:
① 提供对应的 drv_fasync函数; ② 并在合适的时机发信号。
 drv_fasync函数很简单,调用 fasync_helper函数就可以,如下:
static struct fasync_struct *button_async; 
static int drv_fasync (int fd, struct file *filp, int on) 
{ 
 return fasync_helper (fd, filp, on, &button_async); 
} 
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 (&button_async, SIGIO, POLL_IN); 
第 1个参数:button_async->fa_file非空时,可以从中得到 PID,表示发给哪一个 APP; 第 2个参数表示发什么信号:SIGIO;
 第 3个参数表示为什么发信号:POLL_IN,有数据可以读了。(APP用不到这个参数)
19.3.4 应用编程
应用程序要做的事情有这几件:
 ① 编写信号处理函数:
static void sig_func(int sig) 
{ 
 int val; 
 read(fd, &val, 4); 
 printf("get button : 0x%x\n", val); 
 } 
② 注册信号处理函数:
signal(SIGIO, sig_func); 
③ 打开驱动:
fd = open(argv[1], O_RDWR); 
④ 把进程 ID告诉驱动:
fcntl(fd, F_SETOWN, getpid()); 
⑤ 使能驱动的 FASYNC功能:
flags = fcntl(fd, F_GETFL); 
fcntl(fd, F_SETFL, flags | FASYNC); 
19.3.5 现场编程
19.3.6 上机编程
19.3.7 异步通知机制内核代码详解
还没写
19.4 阻塞与非阻塞
所谓阻塞,就是等待某件事情发生。比如调用 read读取按键时,如果没有按键数据则 read函数不会返回,它会让线程休眠等待。
 使用 poll时,如果传入的超时时间不为 0,这种访问方法也是阻塞的。
使用 poll时,可以设置超时时间为 0,这样即使没有数据它也会立刻返回,这就是非阻塞方式。能不能让 read函数既能工作于阻塞方式,也可以工作于非阻塞方式?可以!
 APP调用 open函数时,传入 O_NONBLOCK,就表示要使用非阻塞方式;默认是阻塞方式。
 注意:对于普通文件、块设备文件,O_NONBLOCK不起作用。
 注意:对于字符设备文件,O_NONBLOCK起作用的前提是驱动程序针对 O_NONBLOCK做了处理。
 只能在 open时表明 O_NONBLOCK吗?在 open之后,也可以通过 fcntl修改为阻塞或非阻塞。
 使用 GIT命令载后,本节源码位于这个目录下:
01_all_series_quickstart\ 
05_嵌入式 Linux驱动开发基础知识\source\ 
06_gpio_irq\ 
    06_read_key_irq_poll_fasync_block 
19.4.1 应用编程
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);  /* 阻塞方式 */ 
19.4.2 驱动编程
以 drv_read为例:
static ssize_t drv_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos) 
{  
if (queue_empty(&as->queue) && fp->f_flags & O_NONBLOCK) 
  return -EAGAIN; 
 
wait_event_interruptible(apm_waitqueue, !queue_empty(&as->queue)); 
    …… 
} 
从驱动代码也可以看出来,当 APP打开某个驱动时,在内核中会有一个 struct file结构体对应这个驱动,这个结构体中有 f_flags,就是打开文件时的标记位;可以设置 f_flasgs的 O_NONBLOCK位,表示非阻塞;也可以清除这个位表示阻塞。
 驱动程序要根据这个标记位决定事件未就绪时是休眠和还是立刻返回。
19.4.3 驱动开发原则
驱动程序程序“只提供功能,不提供策略”。就是说驱动程序可以提供休眠唤醒、查询等等各种方式,,驱动程序只提供这些能力,怎么用由 APP决定。



















