异步机制
Linux 内核中使用到异步机制的地方:
信号,这是一种进程间通信的异步机制 【通信】
epoll,这是一种高效I/O的异步通信机制 【I/O】
信号的本质:
软中断信号(signal,又简称为信号),用来通知进程发生了异步事件。
在软件层次上,信号是对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。
信号是进程通信机制中唯一的异步通信机制。(一个进程不必通过任何操作来等待信号的到达,进程也不知道信号到底什么时候到达)
进程收到信号后,有3种处理方法:
1. 类似中断的处理程序。对于需要处理的信号,进程可以指定处理函数,由该函数去处理。
2. 忽略来的信号
3. 对所来信号保留系统的默认值(对大部分信号的缺省操作是让进程终止)
信号的种类:
1. 可靠信号与不可靠信号
2. 实时信号与非实时信号
不可靠信号:信号值小于SIGRTMIN的信号都是不可靠信号,不可靠 ---- 这些信号有可能丢失
可靠信号:由于上面的信号不可靠,所以对其进行改进和扩充,引入可靠信号,将信号值位于SIGRTMIN和SIGRTMAX之间信号定义为可靠信号(这些信号,支持排队,不会丢失)
早期Unix系统只定义了32种信号(这32种信号都有确定的用途和含义,并且每种信号都有各自默认的缺省操作,比如:按键盘的ctrl+c,会产生SIGINT信号,默认的缺省操作就是将 进程终止),这32种信号是非实时信号(非实时信号都不支持排队,都是不可靠信号),后32种信号是实时信号(实时信号都支持排队,都是可靠信号)
信号的生命周期:
对于一个完整的信号生命周期(从信号发送到相应的处理函数执行完毕)来说,可以分为三个重要的阶段,这三个阶段由四个重要事件来刻画:信号诞生、信号在进程中注册完毕、信号在进程中注销完毕、信号处理函数执行完毕。相邻两个事件的时间间隔构成信号生命周期的一个阶段。
信号诞生:
信号的诞生指的是触发信号的事件发生(如检测到硬件异常,定时器超时以及调用信号发送函数kill()或sigqueue())
信号在进程中注册完毕:
信号在进程中注册指的是将信号值加入到进程的未决信号集中(sigpending结构的第二个成员sigset_t signal),并且信号所携带的信息被保留到未决信号信息链的某个sigqueue结构中。只要信号在进程的未决信号集中,表明进程已经知道这些信号的存在,但还没来得及处理,或者该信号被进程阻塞。
进程的task_struct结构:
struct sigpending
{
struct sigqueue* head, **tail;
sigset_t signal;
};
struct sigqueue
{
struct sigqueue* next;
siginfo_t info;
};
结构sigpending中的第一个,第二个成员分别指向 未决信号信息链 的首尾,信息链中的sigqueue结构刻画一个特定信号所携带的信息,并指向下一个sigqueue结构。
注意问题:
当一个实时信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,因此,实时信号不会丢失(实时信号又叫可靠信号)。这意味着同一个实时信号可以在同一个进程的未决信号信息链中占有多个sigqueue结构(进程每收到一个实时信号,都会为它分配一个结构来登记该信号信息,并把该结构添加到未决信号链尾,即所有诞生的实时信号都会在目标进程中注册)
当一个非实时信号发送给一个进程时,如果该信号已经在进程中注册,则该信号将被丢弃,因此会造成信号丢失(非实时信号又叫不可靠信号)。这意味着同一个非实时信号在进程的未决信号链中,至多占有一个sigqueue结构。
信号在进程中的注销:
在目标进程执行过程中,会检测是否有信号等待处理(每次从系统空间返回到用户空间时都做这样的检查)。如果存在未决信号等待处理且该信号没有被进程阻塞,则在运行相应的信号处理函数前,进程会把信号在未决信号链中占有的结构卸掉。是否将信号从进程未决信号集中删除对于实时与非实时信号是不同的。对于非实时信号来说,由于在未决信号信息链中最多只占用一个sigqueue结构,因此该结构被释放后,应该把信号在进程未决信号集中删除(信号注销完毕);而对于实时信号来说,可能在未决信号信息链中占用多个sigqueue结构,因此应该针对占用sigqueue结构的数目区别对待:如果只占用一个sigqueue结构(进程只收到该信号一次),则应该把信号在进程的未决信号集中删除(信号注销完毕)。否则,不应该在进程的未决信号集中删除该信号(信号注销完毕)。
注意:
信号注册与否,与发送信号的函数(如kill()或sigqueue()等)以及信号安装函数(signal()及sigaction())无关,只与信号值有关(信号值小于SIGRTMIN的信号最多只注册一次,信号值在SIGRTMIN及SIGRTMAX之间的信号,只要被进程接收到就被注册)。
在信号被注销到相应的信号处理函数执行完毕这段时间内,如果进程又收到同一信号多次,则对实时信号来说,每一次都会在进程中注册;而对于非实时信号来说,无论收到多少次信号,都会视为只收到一个信号,只在进程中注册一次。
linux下的信号应用并没有想象的那么恐怖,我们所要做的最多只有三件事情:
1、安装信号(推荐使用sigaction());
2、实现三参数信号处理函数,handler(intsignal,struct siginfo *info, void *);
3、发送信号,推荐使用sigqueue()。
实际上,对有些信号来说,只要安装信号就足够了(信号处理方式采用缺省或忽略)。其他可能要做的无非是与信号集相关的几种操作。
#include <stdio.h> #include <stdlib.h> #include <signal.h> /* /usr/include/bits/sigaction.h */ #include <sys/types.h> #include <unistd.h> void handle_signal(int signum, siginfo_t* info, void* myact) { printf("\n"); printf("receive signal %d\n", signum); sleep(5); exit(0); } int main(int argc, char* argv[]) { struct sigaction act; if (argc < 2) { printf("please input the signal value.\n"); printf("example: ./a.out 2\n"); return 0; } int sig = atoi(argv[1]); sigemptyset(&act.sa_mask); act.sa_flags = SA_SIGINFO; // Invoke signal-catching function handle_signal. act.sa_sigaction = &handle_signal; // Specified signal-catching function handle_signal. /* * Register the signal to this process. * * return value: * 0 success * -1 fail **/ if (sigaction(sig, &act, NULL) < 0) { printf("install signal error.\n"); } while (1) { sleep(2); printf("wait for the signal.\n"); } return 0; }