进程间通信IPC(二)信

目录

 

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捕捉信 案例

信 捕捉特性

信 屏蔽字中的信 会阻塞,阻塞会导致该信 一直处于未决状态,直到解除阻塞才会处理该信 ,处理信 可以用默认处理动作,也可以用自定义的动作函数,当使用自定义动作处理函数的时候,就称为信 的捕捉。注意屏蔽并不是忽略,忽略只是解除屏蔽之后一种可能的处理动作。

  1. 捕捉信 时候,如果自定义的动作处理函数处理时间很长,在函数执行期间不使用PCB中的信 屏蔽字,而是使用信 捕捉函数sigaction中的sa_mask来指定动作处理函数执行期间的屏蔽字。
  2. x信 的捕捉函数执行期间,x信 自动被屏蔽,也就是说,如果x信 的捕捉函数正在执行,又来了一个x信 ,则该信 阻塞,当当前捕捉函数执行完毕会再执行一次捕捉。
  3. 阻塞的常规信 不支持排队,产生多次信 只记录一次。但是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进行处理,非常感谢!

上一篇 2021年4月22日
下一篇 2021年4月22日

相关推荐