目录
1 信 的概念
特点:
信 机制:
信 特质:
信 产生方式:
信 状态:
信 的默认处理方式:
信 的四要素:
常见信 :
2 阻塞信 集与未决信 集
3 信 的产生
按键产生
硬件异常产生
命令与系统调用产生
kill函数
raise函数
abort函数
软件条件产生/时钟产生信
alarm函数
4 信 集的函数
信 集处理函数
阻塞信 集函数
未决信 集函数
5 打印未决信 集
6 信 捕捉
sigaction函数
sigaction捕捉信 案例
信 捕捉特性
7 内核实现捕捉过程
8 用SIGCHLD信 回收子进程
第一版代码:
第二版代码:
第三版代码:
1 信 的概念
也是一种进程间通信方式
特点:
简单,携带信息量小,满足某个特定条件才发送
信 机制:
A进程给B进程发送信 ,B收到信 之前执行自己的代码,收到信 后,不管执行到程序的什么位置,都要暂停执行去处理信 ,处理完毕再继续执行。处理信 可以是忽略信 ,或者捕获信 待会处理,或者马上去处理。信 是软件层面实现的中断,早期被称为“软中断”。需要注意的是,虽然说是由进程A发送信 给进程B,但所有信 ,都是由内核发送,内核处理。
信 特质:
由于信 通过软件方法实现,其实现手段导致信 有很强的延时性,但对于用户来说,这个延迟时间非常短,不易察觉。
信 产生方式:
按键产生:crtl C, crtl z, crtl
系统调用产生:kill raise abort
软件条件产生:定时器alarm
硬件异常产生:非法访问内存(段错误)、除0、内存对齐出错(总线错误),SIGPIPE(管道通信时候读端全部关闭时候产生的信 )
命令产生:kill命令等
信 状态:
产生
递达 信 到达并且处理完
未决 信 被阻塞了
注意递达与未决的异同,不管是递达还是未决,此时进程都已经接收到了信 ,如果进程把该信 处理了,那么该信 的状态就变成了递达,如果虽然接收到信 但是还没处理,那么就是未决。未决并不是没有接收到信 的意思,因为信 是由内核产生的,因此可以认为信 产生之后每个进程都能接收到信 。
信 的默认处理方式:
执行默认动作
捕获 学习信 主要目的就是为了捕获信 ,比如像段错误之类的硬件异常信 会导致程序异常终止,学习捕获是为了处理异常让程序继续执行
信 的四要素:
编
事件
名称
默认处理动作 忽略 终止 终止+core 暂停 继续
常见信 :
从左到右四列分别对应了四要素中的名字,编 ,默认动作,事件
常见信 31个,32到64是嵌入式时候用的实时信
2 阻塞信 集与未决信 集
Linux内核的简称控制块PCB是一个结构体,task_struct结构体,除了包含进程id,状态,工作目录,用户id,组id,文件描述符表,还包含了信 相关的信息,即每个进程各自的阻塞信 集与未决信 集。
阻塞信 集与未决信 集本质就是两个位图,但不是直接的位图,而是用sigset_t结构体来定义出来的位图。
阻塞信 集:将某一位设置为1即将这个信 加入了阻塞信 集,当进程接收到这个信 后不会去处理它,直到解除对该信 的屏蔽才会处理。
未决信 集:
3 信 的产生
按键产生
硬件异常产生
命令与系统调用产生
kill函数
是系统API产生信
函数原型:
int kill(pid_t pid , int sig);
如果pid > 0, 发送sig编 信 给pid进程
如果pid = 0, 发送给pid进程组内所有进程
如果pid = -1, 发送给所有有权限发送的进程(不包括init进程)
如果pid
如果sig = 0,不发送信 ,但是可以用来检查pid进程(组)是否存在或者当前进程有没有权限给pid进程发送信 。
例子:父进程生成五个子进程,然后让2 进程杀死父进程
raise函数
给自己发信
#include
int raise(int, sig);
杀死自己例子:
abort函数
也是自己给自己发信 ,跟raise不同,人raise至少还能选择一下发哪一个信 ,abort是默认直接给自己发SIGABRT这个信 。
软件条件产生/时钟产生信
alarm函数
也是一个系统api
在seconds秒后给自己发送一个信
该信 是SIGALRM,默认动作是终止进程
返回值:上一次设定的闹钟还有多长时间触发,比如上一次我们设置了一个20秒的闹钟,然后到第7秒的时候我们等不下去了要重新设置一个,那么重新设置这次的返回值就是14,因为上一次设置的闹钟还有14秒就要出发了,当然,第一次设置的闹钟返回值一定是0。
如果传入参数为0,代表取消所有已经设置的闹钟
例子:6秒后杀死自己
setitimer函数
可以周期性发送信
函数原型:
#include
// int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval *new_value,
struct itimerval *old_value);
which有三种选择,不同选择对应不同信 :
ITIMER_REAL 自然计时法 SIGALRM
ITIMER_VIRTUAL 进程执行时间 SIGVTALRM
ITIMER_PROF 进程执行时间+调度时间 SIGPROF
new_value:
是itimerval结构体,第一个成员it_interval设置发送信 的周期,it_value设置第一次发送信 的延时。
old_value:
一般不用,直接设置为NULL,是上一次调用setitimer时候的new_value值
itimerval 结构体定义:
struct itimerval {
struct timeval it_interval; /* Interval for periodic timer */
struct timeval it_value; /* Time until next expiration */
};
timeval结构体定义:
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
其中tv_sec是秒数,tv_usec是微秒数
返回值:成功返回0,失败返回-1
例子:
先看一下用于捕获的signal函数
#include
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
第一个参数signum是要捕获的信 编
第二个参数是一个函数指针,用来捕获以后进行处理,该函数返回值是void,参数是int
4 信 集的函数
首先阻塞信 集和未决信 集都可以理解为位向量,但是我们不能简单直接得通过操作某一位来改变,而应该使用特定的系统调用。内核通过读取未决信 集来判断信 是否应被处理,信 屏蔽字mask(即阻塞信 集)可以影响未决信 集。
可以在应用程序中自定义set来改变mask,达到屏蔽指定信 的目的。
信 集处理函数
清空信 集(全变成0)
int sigempty(sigset_t *set);
填充信 集(全变成1)
int sigfillset(sigset_t *set);
添加某个信 到信 集
int sigaddset(sigset_t *set, int signum);
signum为信 编
从集合中删除某个信
int sigdelset(sigset_t *set, int signum);
以上四个函数成功返回0,失败返回1
判断是否是集合里的成员
int sigismember(const sigset_t *set, int signum);
是集合中成员返回1,不是返回0,函数出错返回-1
阻塞信 集函数
设置阻塞或者解除阻塞信 集
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how:
SIG_BLOCK 设置阻塞,应该是把set对应的位设置阻塞
SIG_UNBLOCK解除阻塞
SIG_SETMASK 把set设置为新的阻塞信 集
set:
传入的信 集
oldset:
旧的信 集,是一个传出参数,用来获取改变之前的阻塞信 集
未决信 集函数
获取未决信 集
int sigpending(sigset_t *set);
set:
传出参数,用于获取当前的未决信 集
5 打印未决信 集
6 信 捕捉
能防止进程意外死掉
在第3节信 的产生中已经使用了一个signal函数用来信 捕捉,不过由于signal这个单词常常有特定的含义,因此一般不用这个函数,而是用另外一个,sigaction
sigaction函数
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
signum: 要捕捉的信 编 (或者宏)
act: 传入的动作,不过这里不是一个直接的函数指针了,而是一个sigaction结构,结构的成员中有函数指针。结构定义在下边。
oldact: 传出参数,传出原动作
返回值:成功返回0失败-1
struct sigaction{
void (*sa_handler)(int); // 与signal函数一样的捕捉函数
void(*sa_sigaction)(int, siginfo_t *, void *); // 一般不用,有点麻烦
sigset_t sa_mask; // 执行捕捉函数期间,临时屏蔽信 集
int sa_flags; // 一般填0,这个时候用第一个函数指针,SA_SIGINFO用第二个指针
void (*sa_restorer)(void); // 无效参数
};
sigaction捕捉信 案例
信 捕捉特性
信 屏蔽字中的信 会阻塞,阻塞会导致该信 一直处于未决状态,直到解除阻塞才会处理该信 ,处理信 可以用默认处理动作,也可以用自定义的动作函数,当使用自定义动作处理函数的时候,就称为信 的捕捉。注意屏蔽并不是忽略,忽略只是解除屏蔽之后一种可能的处理动作。
- 捕捉信 时候,如果自定义的动作处理函数处理时间很长,在函数执行期间不使用PCB中的信 屏蔽字,而是使用信 捕捉函数sigaction中的sa_mask来指定动作处理函数执行期间的屏蔽字。
- x信 的捕捉函数执行期间,x信 自动被屏蔽,也就是说,如果x信 的捕捉函数正在执行,又来了一个x信 ,则该信 阻塞,当当前捕捉函数执行完毕会再执行一次捕捉。
- 阻塞的常规信 不支持排队,产生多次信 只记录一次。但是32个实时信 支持排队。
捕捉函数处理期间屏蔽两种信 ,一个是捕捉的信 本身,一个是sa_mask指定的信 。
7 内核实现捕捉过程
主函数执行过程因为中断、异常或系统调用进入内核
内核先处理当前进程中可以递送的信 (在PCB中)
如果是默认处理动作,直接在内核中完成
如果用户定义了捕捉函数,那么由内核态回到用户态,执行自定义动作函数,处理完成返回内核
内核再返回主函数中上次中断的位置继续向下执行
8 用SIGCHLD信 回收子进程
子进程暂停或者终止的时候会发送这个信
默认处理动作是忽略
我们可以通过捕捉SIGCHLD信 来回收子进程,就不用wait在那里等待回收了
案例:用SIGCHLD回收多个子进程
第一版代码:
因为子进程分别sleep了i秒,所以能依次得到回收,如果子进程都没有sleep呢能发生在捕获函数执行期间很多个子进程同时终止,当捕捉第一个SIGCHLD信 时候,捕捉函数执行期间其它SIGCHLD信 是自动屏蔽的,而且阻塞信 没有排队机制。
如下图,会发生回收不全的问题,当然,至少能回收到两个子进程。也就是说,如果只有两个子进程,是一定能被回收的,因此即使在一个被处理的时候一个被屏蔽,当第一个处理完成第二个就会解除屏蔽随后被处理。(这里没有考虑一种极端的情况,即父进程捕捉信 之前两个进程都结束了,也就是说sigaction函数还没执行两个子进程就终止了,此时可能会用自动处理动作,把信 忽略掉,这样就会都变成僵尸进程)
第二版代码:
解决上一版代码中的问题,如果在一个捕捉函数执行期间又有其它子进程终止
可以在自定义动作函数中设置只要收到一个信 ,就一直不退出动作函数,而是一个调用waitpid回收子进程,直到没有子进程可回收,这样在动作函数执行期间终止的子进程都能被回收到。
第三版代码:
如果sigaction捕捉函数执行之前,子进程就已经全部终止,则sigaction还没有捕捉到信 ,信 就被默认处理动作给忽略了,因此造成僵尸进程的存在。
解决方法,其实只要捕捉函数能捕捉到一个信 就足够了,这样在catch_sig函数中就能回收所有的子进程。
所以只需要在子进程死之前把信 给屏蔽掉,然后等执行到捕捉函数之后再解除屏蔽来处理,这样就不会被忽略导致捕捉函数没有捕捉到。
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!