Linux编程--信号基础

一、概念和概述

1. 信号是事件发生时对进程的通知机制。有时也称为软件中断

2. 引发内核为进程产生信号的各类事件如下:

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

  • 用户键入了能够产生信号的终端特殊字符。其中包括中断字符(通常是Control-C)、暂停字符(通常是 Control-Z)。

  • 发生了软件事件。例如,针对文件描述符的输出变为有效,调整了终端窗口大小,定时器到期,进程执行的 CPU 时间超限,或者该进程的某个子进程退出。

3. 信号分为两大类:

  • 第一组用于内核向进程通知事件;

  • 第二组信号由实时信号构成

4. 通知内核应当去调用某一处理器程序的行为,通常称之为安装或者建立信号处理器程序。调

用信号处理器程序以响应传递来的信号,则称之为信号已处理(handled),或者已捕获(caught)。

5. 无法将信号处置设置为终止进程或者转储核心(除非这是对信号的默认处置)。

效果最为近似的是为信号安装一个处理器程序,并于其中调用 exit()或者 abort()。abort()函数

为进程产生一个 SIGABRT 信号,该信号将引发进程转储核心文件并终止。

6. Linux 特有的/proc/PID/status 文件包含有各种位掩码字段,通过检查这些掩码可以确定

进程对信号的处理。位掩码以十六进制数形式显示,最低有效位代表信号 1,相临的左边一

位代表信号 2,以此类推。使用 ps(1)命令的各种选项也能获得相同信息。

二、改变信号处置:signal()

  1. UNIX 系统提供了两种方法来改变信号处置:signal()和 sigaction()。

2. signal()的的行为在不同 UNIX 实现间存在差异,这也意味着对可移植性有所追求的程序绝不能使用此调用来建立信号处理器函数。故此,sigaction()是建立信号处理器的首选API。

Linux编程--信号基础_第1张图片

添加图片注释,不超过 140 字(可选)

3. 在为 signal()指定 handler 参数时,可以以如下值来代替函数地址:

  • SIG_DFL :将信号处置重置为默认值。这适用于将之前signal()调用所改变的信号处置还原。

  • SIG_IGN :忽略该信号。如果信号专为此进程而生,那么内核会默默将其丢弃。进程甚至从未知道曾经产生了该信号。

三、发送信号:kill()

  1. 与 shell的 kill 命令相类似,一个进程能够使用kill()系统调用向另一进程发送信号。

添加图片注释,不超过 140 字(可选)

  • 如果 pid > 0,那么会发送信号给由 pid 指定的进程。

  • 如果 pid = 0,那么会发送信号给与调用进程同组的每个进程,包括调用进程自身。

  • 如果 pid < −1,那么会向组 ID 等于该 pid 绝对值的进程组内所有下属进程发送信号。向一个进程组的所有进程发送信号在 shell 作业控制中有特殊用途。

  • 如果 pid ==−1,那么信号的发送范围是:调用进程有权将信号发往的每个目标进程,除去 init(进程 ID 为 1)和调用进程自身。如果特权级进程发起这一调用,那么会发送信号给系统中的所有进程,上述两个进程除外。显而易见,有时也将这种信号发送方式称之为广播信号。

2进程要发送信号给另一进程,还需要适当的权限,其权限规则如下。

  • 特权级(CAP_KILL)进程可以向任何进程发送信号。

  • root 用户和组运行的 init 进程(进程号为 1),是一种特例,仅能接收已安装了处理器函数的信号。这可以防止系统管理员意外杀死 init 进程—这一系统运作的基石。

  • SIGCONT 信号需要特殊处理。无论对用户 ID 的检查结果如何,非特权进程可以向同一会话中的任何其他进程发送这一信号。利用这一规则,运行作业控制的 shell 可以重启已停止的作业(进程组),即使作业进程已经修改了它们的用户 ID。

四、检查进程的存在

1. wait()系统调用,这些调用仅用于监控调用者的子进程。

2. 信号量和排他文件锁:如果进程持续持有某一信号量或文件锁,并且一直处于被监控状态,那么如能获取到信号量或锁时,即表明该进程已经终止

3. 诸如管道和 FIFO 之类的 IPC 通道:可对监控目标进程进行设置,令其在自身生命周期内持有对通道进行写操作的打开文件描述符。同时,令监控进程持有针对通道进行读操作的打开文件描述符,且当通道写入端关闭时(遭遇文件结束符),即可获知监控目标进程已经终止

4. /proc/PID 接口:例如,如果进程 ID 为 12345 的进程存在,那么目录/proc/12345 将存在,可以发起诸如 stat()之类的调用来进行检查。

五、发送信号的其他方式

  1. 进程需要向自身发送信号。raise()函数就执行了这一任务。

#include  
int raise(int sig);

2. 当进程使用 raise()(或者 kill())向自身发送信号时,信号将立即传递。

3. killpg()函数向某一进程组的所有成员发送一个信号。

#include  
int killpg(int pgrp, int sig);

killpg()调用相当于对 kill()的如下调用:kill(-pgrp,sig)

六、显示信号描述

  1. 每个信号都有一串与之相关的可打印说明。这些描述位于数组 sys_siglist 中。例如,可以用 sys_siglist[SIGPIPE]来获取对 SIGPIPE 信号(管道断开)的描述。然而,较之于直接引用sys_siglist 数组,还是推荐使用 strsignal()函数。

#include  
char *strsignal(int sig); 
extern const char * const sys_siglist[];

2. psignal()函数(在标准错误设备上)所示为 msg 参数所给定的字符串,后面跟有一个冒号,随后是对应于 sig 的信号描述。和 strsignal()一样,psignal()函数也对本地设置敏感。

#include  
void psignal(int sig, const char *s);

七、信号集

1. 多个信号可使用一个称之为信号集的数据结构来表示,其系统数据类型为 sigset_t.。

2. sigemptyset()函数初始化一个未包含任何成员的信号集。sigfillset()函数则初始化一个信号

集,使其包含所有信号(包括所有实时信号)。

#include  
int sigemptyset(sigset_t *set); 
int sigfillset(sigset_t *set);

3. 信号集初始化后,可以分别使用 sigaddset()和 sigdelset()函数向一个集合中添加或者移除单个信号。

#include  
int sigaddset(sigset_t *set, int signum); 
int sigdelset(sigset_t *set, int signum);

4. sigismember()函数用来测试信号 sig 是否是信号集 set 的成员。

#include  
int sigaddset(sigset_t *set, int signum); 
int sigdelset(sigset_t *set, int signum);

5. 如果 sig 是 set 的一个成员,那么 sigismember()函数将返回 1(true),否则返回 0(false)。

Linux编程--信号基础_第2张图片

八、信号掩码

  1. 内核会为每个进程维护一个信号掩码,即一组信号,并将阻塞其针对该进程的传递。如果将遭阻塞的信号发送给某进程,那么对该信号的传递将延后,直至从进程信号掩码中移除该信号,从而解除阻塞为止。

  2. 使用 sigprocmask()系统调用,随时可以显式向信号掩码中添加或移除信号

#include  
/* 
 *how 参数指定了 sigprocmask()函数想给信号掩码带来的变化 
 */ 
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • how的取值:

  • SIG_BLOCK:将 set指向信号集内的指定信号添加到信号掩码中。换言之,将信号掩码设置为其当前值和 set 的并集。

  • SIG_UNBLOCK:将 set指向信号集中的信号从信号掩码中移除。即使要解除阻塞的信号当前并未处于阻塞状态,也不会返回错误。

  • SIG_SETMASK: set 指向的信号集赋给信号掩码。

  • 如果想获取信号掩码而又对其不作改动,那么可将 set 参数指定为空,这时将忽略 how 参数。

九、处于等待状态的信号

  1. 如果某进程接受了一个该进程正在阻塞的信号,那么会将该信号填加到进程的等待信号集中。当(且如果)之后解除了对该信号的锁定时,会随之将信号传递给此进程。

    #include  
    int sigpending(sigset_t *set);

2. 如果修改了对等待信号的处置,那么当后来解除对信号的锁定时,将根据新的处置来处理信号

十、不对信号进行排队处理

  1. 等待信号集只是一个掩码,仅表明一个信号是否发生,而未表明其发生的次数。换言之,如果同一信号在阻塞状态下产生多次,那么会将该信号记录在等待信号集中,并在稍后仅传递一次。

十一、改变信号处置:sigaction()

Linux编程--信号基础_第3张图片

  1. sa_flags包含的位如下:

  • SA_NOCLDSTOP:若 sig 为 SIGCHLD 信号,则当因接受一信号而停止或恢复某一子进程时,将不会产生此信号。

  • SA_NOCKDWAIT: sig 为 SIGCHLD 信号,则当子进程终止时不会将其转化为僵尸进程。

  • SA_NODEFER:捕获该信号时,不会在执行处理器程序时将该信号自动添加到进程掩码中。

  • SA_ONSTACK:针对此信号调用处理器函数时,使用了由 sigaltstack()安装的备选栈。

  • SA_RESETHAND:当捕获该信号时,会在调用处理器函数之前将信号处置重置为默认值(即SIG_DFL)(默认情况下,信号处理器函数保持建立状态,直至进一步调用 sigaction()将其显式解除。)

  • SA_RESTART:自动重启由信号处理器程序中断的系统调用。

  • SA_SIGINFO:调用信号处理器程序时携带了额外参数。

2. 等待信号:pause()

  • 调用 pause()将暂停进程的执行,直至信号处理器函数中断该调用为止.

#include  
int pause(void);
 
  

你可能感兴趣的:(linux,linux)