1. 概念
事件发生时对进程通知,有时也成为软件中断。是一种异步通信的方式
信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件
发往进程的诸多信号,通常都是源于内核。
特点:简单、不能携带大量信息、满足某个特定条件才会发送、优先级比较高
2. 信号种类
键盘发送信号:
- Ctrl-C发送INT信号(SIGINT);默认情况下,这会导致进程终止。
- Ctrl-Z发送TSTP信号(SIGTSTP);默认情况下,这会导致进程挂起。
- Ctrl-\发送QUIT信号(SIGQUIT);默认情况下,这会导致进程终止并且将内存中
查看所有信号:
kill -l
- 信号2:SIGINT信号:当用户按下了`<Ctrl+C>`组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号,终止进程(强行中断)
- 信号3:SIGQUIT信号:用户按下`<Ctrl+\>`组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出些信号,终止前台进程并生成 core 文件(退出)
- 信号9:SIGKILL信号:无条件终止进程。该信号不能被忽略,处理和阻塞,终止进程,可以杀死任何进程
- 信号11:SIGSEGV信号:指示进程进行了无效内存访问(段错误) ,终止进程并产生core文件
- 信号13:SIGPIPE信号:管道破裂Broken pipe,例如一直向一个没有读端的管道写数据,终止进程
- 信号17:SIGCHLD信号: 子进程结束时,父进程会收到这个信号,默认忽略这个信号
- 信号19:SIGSTOP信号:键盘Ctrl-Z,停止(暂停/挂起)进程的执行。信号不能被忽略,处理和阻塞,为终止进程。可以发送SIGCONT信号使其继续运行。
3. alarm函数
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
作用:设置定时器(闹钟)。当函数调用的时候,定时器从seconds秒开始倒计时,到0时,函数给当前进程发送一个SIGALARM信号,结束当前进程。
参数:如果seconds为0,不启动/停止定时器。
返回值:- 之前没有定时器,返回0;- 之前有定时器,返回之前的定时器剩余的时间
特点:
- 每个进程只有唯一的定时器,多次调用只是改变唯一的定时器的值。
- 定时器是在内核运行,与进程的状态无关(自然定时法),无论进程处于什么状态,定时器都会计时。
- 不能周期性的定时。
- 非阻塞
5. 信号集
多个信号可使用一个称之为信号集的数据结构来表示,其系统数据类型为 sigset_t。
在 进程的PCB 中有两个非常重要的信号集。一个称之为 “阻塞信号集” ,另一个称之为“未决信号集” 。每个信号集有64位,每一位代表一种信号。
未决和阻塞:
- 信号的 “未决” 是一种状态,指的是从信号的产生到信号被处理前的这一段时间。
- 信号的 “阻塞” 是一个开关动作,指的是阻止(不让)信号被处理,但不是阻止信号产生。
信号的阻塞就是让系统暂时保留信号,留待以后发送。由于另外有办法让系统忽略信号,所以一般情况下信号的阻塞只是暂时的,只是为了防止信号打断敏感的操作。
操作步骤:
- 用户自定义阻塞信号集-->写入阻塞信号集
- 信号产生-->未决信号集对应位置1-->内核自动与阻塞信号集对比-->如果两个都为1,说明信号直到阻塞完毕才会被处理;如果阻塞信号集对应为0,则直接处理。
用户不能修改未决信号集(只能读取),但是可以修改&读取阻塞信号集。
5.1 未决信号集
由于不能修改,我们只能读取
int sigpending(sigset_t *set);
- 功能:获取内核中的未决信号集
- 参数:set,传出参数,保存的是内核中的未决信号集中的信息
5.2 阻塞信号集
5.2.1 对自定义阻塞信号集函数
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 : 失败
5.2.2 修改阻塞信号集的函数
当我们用上面的函数自定义完成自己的阻塞信号集之后,我们需要把它写入PCB的阻塞信号集。
我们可以添加/覆盖/解除阻塞:
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
6. 信号捕捉sigaction
和signal函数的区别?signal是ANSI标准。推荐使用sigaction。
6.1 捕捉函数
#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);
};
例子:
struct sigaction act;
act.sa_flags = 0; //使用第一种信号处理函数
act.sa_handler = myalarm; //信号处理函数
sigemptyset(&act.sa_mask); // 清空临时阻塞信号集
// 注册信号捕捉
sigaction(SIGALRM, &act, NULL);
6.2 注意的问题
1)在回调函数处理信号时,该信号会被屏蔽。
也就是说,如果回调函数在处理alarm信号,又来了一个alarm信号,回调函数不会立即处理新来的alarm信号。
2)对于前31个信号,不会有排队处理。
比如产生alarm信号,未决指令集的对应位会被置1,如果阻塞信号集的对应位也被置1,阻塞处理alarm信号(信号不会被处理);
如果阻塞的过程中又产生了alarm信号,该信号会直接被丢弃(因为未决指令集的一个位置只能记录一个信号)
3)用户区和内核区的变化: