信号是一种软中断,是进程之间进行异步通知的一种方式。
在Linux下,信号拥有对应的编号。其中:1号~31号是普通信号,34~64是实时信号。
kill命令用来向进程发送信号。
kill -l:查看当前系统内定义了哪些信号(编号+名称)。kill -编号/名称 + 进程id:向对应发送信号。
常见信号如:
2号SIGINT:即interrupt,中断信号,相当于进程执行时键入ctrl c,该信号强制前台进程退出,即直接杀死前台进程。19号SIGSTOP:暂停信号,相当于进程执行时键入ctrl z,该信号会将进程暂时挂起并放入后台,可以通过18号SIGCONT重新唤醒并运行于后台。9号SIGKILL:立即结束正在执行的进程(前台、后台都适用)。注:对于相当一部分信号而言,进程收到该信号的默认处理动作都是终止当前进程。
ctrl c强制当前进程退出。kill命令。alarm函数可以设定一定时间后让内核发送SIGALRM信号使得该进程退出。因为这些错误一般都和硬件联系在一起,比如除零错误对应CPU的运算器,系统可以通过当前CPU执行的进程来决定向谁发送对应的出错信号。
普通信号不是立即处理的,因此需要先被保存在进程控制块
task_struct中的信号位图中,以标志收到的信号编号。
block位图(信号屏蔽字):第几个比特位为1则说明几号信号被阻塞,因此block位图可以理解为一种状态掩码。如果进程收到被阻塞的信号,那么该信号就会处于未决状态,暂时不会被处理,直到它被解除阻塞,即从block位图中移除。pending位图(未决信号集):第几个比特位为1则说明收到几号信号,该信号是未决状态。信号产生时,内核将pending对应位置修改为1,当信号递达后该位置恢复为0。handler数组:用信号编号作为索引,对应位置存储不同信号的处理方法。
block和pending都是位图,它们在Linux中被封装为sigset_t类型,即信号集。
int sigemptyset(sigset_t *set)将信号集全部置0
int sigfillset(sigset_t *set)将信号集全部置1
int sigaddset (sigset_t *set, int signum)将指定信号所在位置为1
int sigdelset(sigset_t *set, int signum)将指定信号所在位置为0
int sigismember(const sigset_t *set, int signum)判断指定信号所在位是否为1
int sigprocmask(int how, const sigset_t *set, sigset_t *oset)如果set不为空,则将当前进程的block位图设置为set,如果oset不为空,则将更改前的阻塞信号集作为输出型参数返回为用户。
其中,how有三种参数:
SIG_BLOCK:将set指定的信号添加到当前进程的block中
SIG_SETMASK:将当前进程的block替换为set
SIG_UNBLOCK:将set指定的信号从当前进程的block中删除,即解除阻塞
int sigpending(sigset_t* set)set作为输出型参数,获取当前进程的pending信号集
忽略
执行该信号的默认处理动作
提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉(Catch)一个信号。
进程收到信号时,其实并不是立即处理的,而是在从内核态切换到用户态的之前检查task_struct的pending信号集是否标记接收到信号,如果接收到并且没有被阻塞,则执行该信号对应的处理操作。
处理分为两种:
忽略或执行默认处理函数。此时直接在内核态完成对应的系统函数调用,然后返回用户态即可。
用户自定义处理函数。此时需要先从内核态返回用户态(因为OS不信任用户提供的代码,所以不在最高权限的内核态执行,以避免系统安全问题),执行完用户代码提供的处理函数后,再次返回内核态,调用sys_sigreturn()函数,之后再回到用户态,继续向下执行。

信号的产生可以在进程的任何时候,而进程处理信号不一定是实时的,因此信号和进程是"异步"的。
1、sighandler_t signal(int signum, sighandler_t handler)
定义进程接收到signum号信号的处理函数handler,同时返回该信号原先的处理函数。
注:当handler为SIG_IGN时,表示忽略signum信号。
2、int sigaction(int signum, const struct sigaction *act, struct sigaction *oact)
该函数也可用于自定义信号处理的方式,其主要参数包括要自定义的信号signum,以及处理方式结构体act:

我们一般将act.sa_flags初始化为0,act.sa_handler初始化为自定义处理函数,act.sa_mask是一个信号集,表示在signum信号被处理时,act.sa_mask中标注的信号要被屏蔽,其余指针成员可以初始化为NULL。
oact是输出型参数,可以将修改前的默认处理方式保存下来,方便以后恢复。
子进程退出时,会向父进程发送SIGCHLD信号。
父进程可以选择等待子进程,也可以选择不等待。如果父进程并不关心子进程的退出状态,那么可以选择将SIGCHLD的处理函数设置为SIG_IGN,即忽略。此时子进程在退出后就会立刻被系统回收,不会造成内存泄漏(Linux下有效,其余类Unix平台不一定)。
Linux规定:对于9号信号SIGKILL和19号信号SIGSTOP,我们无法捕捉或阻塞它们,因而也就无法自定义它的行为或忽略它。
SIGKILL与SIGTERM的区别SIGKILL(9号)无法被捕捉,因此收到该信号的进程会直接退出。
SIGTERM(15号)可以被捕捉,因此进程可以通过自定义处理函数来让进程在退出前完成资源的释放等善后操作。
正因SIGTERM的处理更加"人性化",因此它也是kill命令默认使用的信号。
普通信号通过pending未决信号集来标识是否接收,因此当多份相同的信号同时发送时,未决信号集对应位置只能被标注一次,从而导致信号丢失。
而task_struct中维护了实时信号的执行队列,因此每一个实时信号都会加入到队列中,它们都会被执行。
一个函数被多个执行流进入的情况叫做函数的重入。
例如:在主函数执行func函数时收到信号signum,而signum的自定义处理函数也要调用func函数,此时func就是被重入了。
如果一个函数符合以下条件之一则是不可重入的:
malloc或free,因为malloc也是用全局链表来管理堆的。注:函数的重入是针对单线程提出的,它并不依赖与多线程的概念。