Linux_进程间通信_信号

一、信号

​ 信号是 Linux 进程间通信的最古老的方式之一,是事件发生时对进程的通知机制,有时也称之为软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式。

​ 信号由内核发送给进程,可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。

​ 发往进程的诸多信号,通常都是源于内核。引发内核为进程产生信号的各类事件如下:

​ ①对于前台进程,用户可以通过输入特殊的终端字符来给它发送信号。比如输入Ctrl+C 通常会给进程发送一个中断信号。

​ ②硬件发生异常,即硬件检测到一个错误条件并通知内核,随即再由内核发送相应信号给相关进程。比如执行一条异常的机器语言指令,诸如被 0 除,或者引用了无法访问的内存区域。

​ ③系统状态变化,比如 alarm 定时器到期将引起 SIGALRM 信号,进程执行的 CPU 时间超限,或者该进程的某个子进程退出。

​ ④运行 kill 命令或调用 kill 函数。

使用信号的两个主要目的是:

​ 让进程知道已经发生了一个特定的事情。

​ 强迫进程执行它自己代码中的信号处理程序。

信号的特点:

​ 简单

​ 不能携带大量信息

​ 满足某个特定条件才发送

​ 优先级比较高

系统定义的信号如下:

​ 前 31 个信号为常规信号,其余为实时信号。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

**查看系统定义的信号列表:**kill –l

**查看信号的详细信息:**man 7 signal

信号的 5 种默认处理动作:

​ Term 终止进程

​ Ign 当前进程忽略掉这个信号

​ Core 终止进程,并生成一个Core文件(保存进程异常退出的一些错误信息)

​ Stop 暂停当前进程

​ Cont 继续执行当前被暂停的进程

信号的几种状态:

​ 产生、未决:信号处理函数还没有执行、递达

SIGKILL 和 SIGSTOP 信号不能被捕捉、阻塞或者忽略,只能执行默认动作。

​ Ctrl + c 产生SIGINT

​ Ctrl + \ 产生SIGQUIT

​ Ctrl + z 产生SIGSTOP

二、信号相关函数

​ #include <sys/types.h>

​ #include <signal.h>

kill:

int kill(pid_t pid, int sig);

​ - 功能:给任何的进程或者进程组pid, 发送任何的信号 sig

​ - 参数:

​ - pid:需要发送给的进程的id

​ > 0 : 将信号发送给指定的进程

​ = 0 : 将信号发送给当前的进程组

​ = -1 : 将信号发送给每一个有权限接收这个信号的进程

​ < -1 : 这个pid=某个进程组的ID取反 (-12345)

​ - sig : 需要发送的信号的编号或者宏值,0表示不发送任何信号

​ kill(getppid(), 9);

​ kill(getpid(), 9);

raise:

​ int raise(int sig);

​ - 功能:给当前进程发送信号

​ - 参数:

​ - sig : 要发送的信号

​ - 返回值:

​ - 成功 0

​ - 失败 非0

abort:

​ void abort(void);

​ - 功能: 发送SIGABRT信号给当前的进程,杀死当前进程

​ kill(getpid(), SIGABRT);

alarm:

​ #include <unistd.h>

​ unsigned int alarm(unsigned int seconds);

​ - 功能:设置定时器(闹钟)。函数调用,开始倒计时,当倒计时为0的时候,函数会给当前的进程发送一个信号:SIGALARM

​ - 参数:

​ seconds: 倒计时的时长,单位:秒。如果参数为0,定时器无效(不进行倒计时,不发信号)。

​ 取消一个定时器,通过alarm(0)。

​ - 返回值:

​ - 之前没有定时器,返回0

​ - 之前有定时器,返回之前的定时器剩余的时间

​ SIGALARM :默认终止当前的进程,每一个进程都有且只有唯一的一个定时器。

​ alarm(10); -> 返回0

​ 过了1秒

​ alarm(5); -> 返回9

定时函数settimer:

​ #include <sys/time.h>

​ int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);

​ - 功能:设置定时器(闹钟)。可以替代alarm函数。精度微妙us,可以实现周期性定时

​ - 参数:

​ - which : 定时器以什么时间计时

​ ITIMER_REAL: 真实时间,时间到达,发送 SIGALRM 常用

​ ITIMER_VIRTUAL: 用户时间,时间到达,发送 SIGVTALRM

​ ITIMER_PROF: 以该进程在用户态和内核态下所消耗的时间来计算,时间到达,发送 SIGPROF

​ - new_value: 设置定时器的属性

​ struct itimerval { // 定时器的结构体

​ struct timeval it_interval; // 每个阶段的时间,间隔时间

​ struct timeval it_value; // 延迟多长时间执行定时器

​ };

​ struct timeval { // 时间的结构体

​ time_t tv_sec; // 秒数

​ suseconds_t tv_usec; // 微秒

​ };

​ - old_value :记录上一次的定时的时间参数,一般不使用,指定NULL

- 返回值:

​ 成功 0

​ 失败 -1 并设置错误号

三、信号捕捉函数

signal函数:

​ #include <signal.h>

​ typedef void (*sighandler_t)(int);

​ sighandler_t signal(int signum, sighandler_t handler);

​ - 功能:设置某个信号的捕捉行为

​ - 参数:

​ - signum: 要捕捉的信号

​ - handler: 捕捉到信号要如何处理

​ - SIG_IGN : 忽略信号

​ - SIG_DFL : 使用信号默认的行为

​ - 回调函数 : 这个函数是内核调用,程序员只负责写,捕捉到信号后如何去处理信号。

​ - 返回值:

​ 成功,返回上一次注册的信号处理函数的地址。第一次调用返回NULL

​ 失败,返回SIG_ERR,设置错误号

​ SIGKILL SIGSTOP不能被捕捉,不能被忽略。

sigaction函数:

​ #include <signal.h>

​ int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

​ - 功能:检查或者改变信号的处理。信号捕捉

​ - 参数:

​ - signum : 需要捕捉的信号的编号或者宏值(信号的名称)

​ - act :捕捉到信号之后的处理动作

​ - oldact : 上一次对信号捕捉相关的设置,一般不使用,传递NULL

​ - 返回值:

​ 成功 0

​ 失败 -1

struct sigaction {
// 函数指针,指向的函数就是信号捕捉到之后的处理函数
void   (*sa_handler)(int);
// 不常用
void   (*sa_sigaction)(int, siginfo_t *, void *);
// 临时阻塞信号集,在信号捕捉函数执行过程中,临时阻塞某些信号。
sigset_t  sa_mask;
// 使用哪一个信号处理对捕捉到的信号进行处理
// 这个值可以是0,表示使用sa_handler,也可以是SA_SIGINFO表示使用sa_sigaction
int    sa_flags;
// 被废弃掉了
void   (*sa_restorer)(void);
};

内核实现信号捕捉的过程:

在这里插入图片描述

四、信号集

​ 许多信号相关的系统调用都需要能表示一组不同的信号,多个信号可使用一个称之为信号集的数据结构来表示,其系统数据类型为 sigset_t。

​ 在 PCB 中有两个非常重要的信号集。一个称之为 “阻塞信号集” ,另一个称之为“未决信号集” 。这两个信号集都是内核使用位图机制来实现的。但操作系统不允许我们直接对这两个信号集进行位操作,而需自定义另外一个集合,借助信号集操作函数来对 PCB 中的这两个信号集进行修改。

​ 信号的 “未决” 是一种状态,指的是从信号的产生到信号被处理前的这一段时间。

​ 信号的 “阻塞” 是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。

​ 信号的阻塞就是让系统暂时保留信号留待以后发送。由于另外有办法让系统忽略信号,所以一般情况下信号的阻塞只是暂时的,只是为了防止信号打断敏感的操作。

阻塞信号集和未决信号集:

1.信号产生但是没有被处理 (未决)

- 在内核中将所有的没有被处理的信号存储在一个集合中 (未决信号集)

- SIGINT信号状态被存储在第二个标志位上

​ - 这个标志位的值为0, 说明信号不是未决状态

​ - 这个标志位的值为1, 说明信号处于未决状态

2.这个未决状态的信号,需要被处理,处理之前需要和另一个信号集(阻塞信号集),进行比较

- 阻塞信号集默认不阻塞任何的信号

- 如果想要阻塞某些信号需要用户调用系统的API

3.在处理的时候和阻塞信号集中的标志位进行查询,看是不是对该信号设置阻塞了

- 如果没有阻塞,这个信号就被处理

- 如果阻塞了,这个信号就继续处于未决状态,直到阻塞解除,这个信号就被处理

在这里插入图片描述

未决信号集只能被读取,不能被设置,由内核自动设置

五、信号集函数:

int sigemptyset(sigset_t *set);

​ - 功能:清空信号集中的数据,将信号集中的所有的标志位置为0

​ - 参数:set,传出参数,需要操作的信号集

​ - 返回值:成功返回0, 失败返回-1

int sigfillset(sigset_t *set);

​ - 功能:将信号集中的所有的标志位置为1

​ - 参数:set,传出参数,需要操作的信号集

​ - 返回值:成功返回0, 失败返回-1

int sigaddset(sigset_t *set, int signum);

​ - 功能:设置信号集中的某一个信号对应的标志位为1,表示阻塞这个信号

​ - 参数:

​ - set:传出参数,需要操作的信号集

​ - signum:需要设置阻塞的那个信号

​ - 返回值:成功返回0, 失败返回-1

int sigdelset(sigset_t *set, int signum);

​ - 功能:设置信号集中的某一个信号对应的标志位为0,表示不阻塞这个信号

​ - 参数:

​ - set:传出参数,需要操作的信号集

​ - signum:需要设置不阻塞的那个信号

​ - 返回值:成功返回0, 失败返回-1

int sigismember(const sigset_t *set, int signum);

​ - 功能:判断某个信号是否阻塞

​ - 参数:

​ - set:需要操作的信号集

​ - signum:需要判断的那个信号

​ - 返回值:

​ 1 : signum被阻塞

​ 0 : signum不阻塞

​ -1 : 失败

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

​ - 功能:将自定义信号集中的数据设置到内核中(设置阻塞,解除阻塞,替换)

​ - 参数:

​ - how : 如何对内核阻塞信号集进行处理

​ SIG_BLOCK: 将用户设置的阻塞信号集添加到内核中,内核中原来的数据不变

​ 假设内核中默认的阻塞信号集是mask, mask | set

​ SIG_UNBLOCK: 根据用户设置的数据,对内核中的数据进行解除阻塞

​ mask &= ~set

​ SIG_SETMASK:覆盖内核中原来的值

​ - set :已经初始化好的用户自定义的信号集

​ - oldset : 保存设置之前的内核中的阻塞信号集的状态,可以是 NULL

​ - 返回值:

​ 成功:0

​ 失败:-1

​ 设置错误号:EFAULT、EINVAL

int sigpending(sigset_t *set);

​ - 功能:获取内核中的未决信号集

​ - 参数:set,传出参数,保存的是内核中的未决信号集中的信息。
假设内核中默认的阻塞信号集是mask, mask | set

​ SIG_UNBLOCK: 根据用户设置的数据,对内核中的数据进行解除阻塞

​ mask &= ~set

​ SIG_SETMASK:覆盖内核中原来的值

​ - set :已经初始化好的用户自定义的信号集

​ - oldset : 保存设置之前的内核中的阻塞信号集的状态,可以是 NULL

​ - 返回值:

​ 成功:0

​ 失败:-1

​ 设置错误号:EFAULT、EINVAL

int sigpending(sigset_t *set);

​ - 功能:获取内核中的未决信号集

​ - 参数:set,传出参数,保存的是内核中的未决信号集中的信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值