【Linux】信号的产生、保存与处理
信号
什么是信号
信号是Linux提供的一种向指定进程发送特定事件的方式。
Linux中规定了64种信号,其中1到31号为不可靠信号(可能丢失),32到64号为可靠信号(不可能丢失)。
注:绝大多数的默认操作都是终止进程。
信号取值 | 名称 | 作用 | 默认操作 |
---|---|---|---|
1 | SIGHUP | 挂起 | |
2 | SIGINT | 中断 | |
3 | SIGQUIT | 退出 | |
4 | SIGILL | 非法指令 | |
5 | SIGTRAP | 断点或陷阱指令 | |
6 | SIGABRT | abort发出的信号 | |
7 | SIGBUS | 非法内存访问 | |
8 | SIGFPE | 浮点异常 | |
9 | SIGKILL | kill信号 | 不能被忽略、处理和阻塞 |
10 | SIGUSR1 | 用户信号1 | |
11 | SIGSEGV | 无效内存访问 | |
12 | SIGUSR2 | 用户信号2 | |
13 | SIGPIPE | 管道破损,没有读端的管道写数据 | |
14 | SIGALRM | alarm发出的信号 | |
15 | SIGTERM | 终止信号 | |
16 | SIGSTKFLT | 栈溢出 | |
17 | SIGCHLD | 子进程退出 | 默认忽略 |
18 | SIGCONT | 进程继续 | |
19 | SIGSTOP | 进程停止 | 不能被忽略、处理和阻塞 |
20 | SIGTSTP | 进程停止 | |
21 | SIGTTIN | 进程停止,后台进程从终端读数据时 | |
22 | SIGTTOU | 进程停止,后台进程向终端写数据时 | |
23 | SIGURG | IO有紧急数据到达当前进程 | 默认忽略 |
24 | SIGXCPU | 进程的CPU时间片到期 | |
25 | SIGXFSZ | 文件大小的超出上限 | |
26 | SIGVTALRM | 虚拟时钟超时 | |
27 | SIGPROF | profile时钟超时 | |
28 | SIGWINCH | 窗口大小改变 | 默认忽略 |
29 | SIGIO | IO相关 | |
30 | SIGPWR | 关机 | 默认忽略 |
31 | SIGSYS | 系统调用异常 |
信号通常经历以下三个阶段,产生、保存、递达(处理)。处于保存时期的信号处于一个叫未决的状态。可以简单理解为信号到了,但未处理的状态。
举个例子,你的朋友让你给他买瓶汽水,但由于你手头上有更加重要的事,所以你默默地记下给他买瓶汽水这个信号等到你忙完后才去执行。此时你对信号的处理是阻塞,信号在被递达前都处于未决状态。当然,除了阻塞你也可以选择忽略,忽略则是信号到了但是你直接无视。
信号的产生
信号的产生是异步的,他能够使一个正在执行的进程被异步打断,转而去处理一个突发事件。
信号的产生大致有以下五种原因,但从始至终信号都是有OS来发送的。
- 通过kill命令向指定进程发信号。例:kill -2 8888 (2是信号取值,代表2号信号。8888是一个进程的pid)
- 键盘也可以产生信号(Ctrl+c 就相当于向当前进行发送2号信号)
- 系统调用,例如 int kill(pid_t pid,int sig)函数。
- 软件条件,例如管道读端关闭,写端一直写,那么OS就会向他发送13号信号SIGPIPE关闭写端。(这就是读端关闭写端就不写了的原因)
- 异常,出现异常进程停止运行也是OS对其发送了信号。
信号的保存与处理
每个进程都有一个对应的task_struct,而在task_struct中有一个管理信号的结构。这个结构主要分为三部分。pending、block是两张位图,pengding的每个位置都代表对应的信号,0或1代表该信号是否未决(到达但未处理)。clock的每个位置同样代表对应的信号,0或1代表该信号是否被阻塞。而handle是一个函数指针数组,他的每个位置都对应信号的处理方法,不修改则为默认,可以通过系统调用进行自定义。
当信号到来时,OS就会遍历pending位图,为0则查看下一个比特位,为1则查看block位图的对应位置,此时block的该比特位为1则不做处理(被阻塞),为0则将对应位置pending置0再执行对应位置的handle操作。
信号的捕捉
信号的捕捉流程
内核态对比用户态最明显的区别的内核态拥有更大的权力。所以进行自定义的信号处理时,要从内核态回到用户态,这是对操作系统的保护,防止用户定义的函数在内核态进行非法操作。
signal函数
作用:捕捉一个指定信号,设定该信号的操作方法。
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
- 第一个参数signum代表信号的取值。
- 第二个参数有三种,第一种是返回值为void,参数为int的自定义函数指针;第二种是 SIG_IGN 代表忽略一个信号;SIG_DFL代表默认处理。
#include <iostream>
#include <unistd.h>
#include <signal.h>
void sigcb(int sig)
{
std::cout << "get a sig : " << sig << std::endl;
}
int main()
{
signal(2, sigcb);
while (true)
{
std::cout << "process is running , pid : " << getpid() << std::endl;
sleep(1);
}
return 0;
}
向进程3348发送2号信号,进程收到信号后执行了sigcb函数。
sigaction函数
作用:捕捉一个指定信号,设定该信号的操作方法。
- #include <signal.h> int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
- 第一个参数signum代表信号的取值。
- 第二个参数设定新的结构体(结构体中包含handle函数指针,与signal中的一致)
- 第三个参数输出原来的结构体
#include <iostream>
#include <unistd.h>
#include <signal.h>
void sigcb(int sig)
{
std::cout << "get a sig : " << sig << std::endl;
}
/*struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
*/
int main()
{
struct sigaction act;
act.sa_handler = sigcb;
sigaction(2, &act, nullptr);
while (true)
{
std::cout << "process is running , pid : " << getpid() << std::endl;
sleep(1);
}
return 0;
}
sigprocmask函数
作用: sigprocmask
函数用于检查或修改当前进程的信号屏蔽字(signal mask)
- #include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
- 第一个参数,操作标志,决定如何修改信号屏蔽字。有三个选项:
SIG_BLOCK
:把set
指向的信号集中的信号添加到当前信号屏蔽字中。SIG_UNBLOCK
:从当前信号屏蔽字中移除set
指向的信号集中的信号。SIG_SETMASK
:用set
指向的信号集替换当前信号屏蔽字。const sigset_t *set
:指向要修改的新信号集的指针。sigset_t *oldset
:如果不为NULL
,则存储之前的信号屏蔽字。
#include <iostream>
#include <unistd.h>
#include <signal.h>
void PrintfPending(sigset_t &pending)
{
std::cout << "cur process pid : " << getpid() << "\n";
std::cout << "pending signo :";
for (int signo = 31; signo >= 1; signo--)
{
if (sigismember(&pending, signo))
{
std::cout << 1;
}
else
{
std::cout << 0;
}
}
std::cout << "\n";
}
int main()
{
signal(2, handler);
sigset_t block_set, old_set;
sigemptyset(&block_set);
sigemptyset(&old_set);
sigaddset(&block_set, 2);
sigprocmask(SIG_BLOCK, &block_set, &old_set);
while (true)
{
sigset_t pending;
sigpending(&pending);
PrintfPending(pending);
sleep(2);
}
return 0;
}