在设备驱动中使用异步通知可以使得对设备的访问可进行时,由驱动主动通知应用程序进行访问。这样,使用无阻塞 I/O 的应用程序无需轮询设备是否可访问,而阻塞访问也可以被类似“中断”的异步通知所取代。
1. 信号:使用信号进行进程间通信(IPC)是 UNIX 中的一种传统机制,Linux 也支持这种机制。在Linux 中,异步通知使用信号来实现,Linux 中可用的信号如下:
信号值含义SIGHUP1挂起SIGINT(ctrl+c)2终端中断SIGQUIT3终端退出SIGILL4无效命令SIGTRAP5跟踪陷阱SIGIOT6IOT 陷阱... SIGKILL9强行终止(不能被捕获或忽略)SIGTERM15终止SIGCHLD17子进程已经停止或退出SIGSTOP19停止执行(不能被捕获或忽略)SIGIO29I/O
除了 SIGSTOP 和 SIGKILL 两个信号外,进程能够忽略或捕获其他的全部信号。一个信号被捕获的意思是当一个信号到达时有相应的代码处理它。如果一个信号没有被这个进程所捕获,内核将采用默认行为处理。
2. 信号的接收:用户程序中,定义信号的处理函数用如下函数:
[cpp] view plain copy
print?<img id="aimg_AB4va" class="zoom" width="12" height="12" file="https://code.csdn.net/assets/CODE_ico.png" border="0" alt="" /><img id="aimg_dsy8Z" class="zoom" width="12" height="12" file="https://code.csdn.net/assets/ico_fork.svg" border="0" alt="" />
void (*signal(int signum, void (*handler)(int)))(int);
第一个参数,指定信号的值,就是上边 SIGCHLD 之类的宏的值
第二个参数,指定针对前边信号值的处理函数
SIG_IGN,忽略信号
SIG_DFL,系统默认的方式处理
用户自定义函数,信号捕捉后函数被执行
返回值:
成功,函数的返回值
失败,SIG_ERR
一个用 ctrl+c 触发到自己的函数中的例子:
[cpp] view plain copy
print?<img id="aimg_k6bug" class="zoom" width="12" height="12" file="https://code.csdn.net/assets/CODE_ico.png" border="0" alt="" /><img id="aimg_xc2kH" class="zoom" width="12" height="12" file="https://code.csdn.net/assets/ico_fork.svg" border="0" alt="" />
void sigterm_handler(int signo) { printf(&quot;Have caught sig N.O. %d\n&quot;, signo); exit(0); } int main(void) { signal(SIGINT, sigterm_handler); while(1); return 0; }
一个用实现输入字符,打印字符异步的例子:
[cpp] view plain copy
print?<img id="aimg_x9N9Y" class="zoom" width="12" height="12" file="https://code.csdn.net/assets/CODE_ico.png" border="0" alt="" /><img id="aimg_b256d" class="zoom" width="12" height="12" file="https://code.csdn.net/assets/ico_fork.svg" border="0" alt="" />
#include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <fcntl.h> #include <signal.h> #include <unistd.h> #define MAX_LEN 100 void input_handler(int num) { char data[MAX_LEN]; int len; /* 读取并输出 STDIN_FILENO 上的输入 */ len = read(STDIN_FILENO, &data, MAX_LEN); data[len] = 0; printf(&quot;input available:%s\n&quot;, data); } main() { int oflags; /* 启动信号驱动机制 */ signal(SIGIO, input_handler); fcntl(STDIN_FILENO, F_SETOWN, getpid()); oflags = fcntl(STDIN_FILENO, F_GETFL); fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC); /* 最后进入一个死循环,仅为保持进程不终止,如果程序中 没有这个死循会立即执行完毕 */ while (1); }
程序解释:
主函数中:
①为 SIGIO 信号安装 input_handler()作为处理函数
②fcntl(STDIN_FILENO, F_SETOWN, getpid()); 设置本进程为STDIN_FILENO 文件的拥有者(owner)
③对设备设置 FASYNC 标志
运行结果:
> ./signal_test
I am Chinese.
input available: I am Chinese.
I love Linux driver.
input available: I love Linux driver.
需要注意的是:当用户输入完字符串的时候,才会触发 SIGIO 的信号
用户空间是实现异步通知的 3 项工作:
通过 F_SETOWN IO 控制命令设置设备文件的拥有者为本进程,这样从设备驱动发出的信号才能被本进程接收到。 通过 F_SETFL IO 控制命令设置设备文件支持 FASYNC,即异步通知模式。 通过 signal()函数连接信号和信号处理函数。
3. 信号的释放:在设备驱动和应用程序的异步通知交互中,需要应用程序捕捉信号,驱动程序释放信号。
所要设计的 3 项工作:
支持 F_SETOWN 命令,能在这个控制命令处理中设置 filp->f_owner 为对应进程 ID。不过此项工作已由内核完成,设备驱动无需处理。 支持 F_SETFL 命令的处理,每当FASYNC 标志改变时,驱动程序中的fasync()函数将得以执行。因此,驱动中应该实现 fasync()函数。 在设备资源可获得时,调用 kill_fasync()函数激发相应的信号。 |