今天吧,来具体了解一下 Linux 的一个 signal 机制。

问题引入

起因在于我向老师抛出一个问题,在 子进程调用 execve 并重定向标准输出为管道时,父进程从管道读取数据时,采用 read 函数去读取,但是会发现一个问题。就是读和写不太能同步,即使程序退出,管道中还残留数据,如果管道中的数据还有,那么我读入不完整,如果没,则继续调用 read 会挂起父进程无法退出,一直想不到有效的办法解决。

请教老师之后,老师推荐我学学 signal 机制,并给我看了一个例程,例程大致简化一下是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include<stdio.h>
#include<unistd.h>
int k;
void func(int sig){
k=0;
}
int main(){
pid_t pid=fork();
if(pid<0){
write(2,"fork error!\n",12);
exit(-1);
}
if(pid){
sleep(5);
kill(pid,12);

}
else{
k=1;
signal(12,func);
while(k){
printf("Child process here\n");
sleep(1);
}
printf("child process exit\n");
}
}

signal机制

Linux 为进程提供了很多的通信机制,除了熟知的管道以外,还有 signal。比如我们平时希望快速结束一个程序,我们会通过 Ctrl + C 的方式去快速结束,可是发现有时候能成功结束,有时候不能,有时候输出一个 Abort 再结束。

其实我们在 Ctrl + C 的时候会发送一个 SIGINT 的 signal,有时候程序内置了对 SIGINT 的处理,比如输出一个 ABORT 再退出,比如我们结束不掉就是程序内部忽略此信号。

signal简介

这里给出一些常见信号的信号表

信号 取值 默认动作 含义(发出信号的原因)
SIGHUP 1 Term 终端的挂断或进程死亡
SIGINT 2 Term 来自键盘的中断信号
SIGQUIT 3 Core 来自键盘的离开信号
SIGILL 4 Core 非法指令
SIGABRT 6 Core 来自abort的异常信号
SIGFPE 8 Core 浮点例外
SIGKILL 9 Term 杀死
SIGSEGV 11 Core 段非法错误(内存引用无效)
SIGPIPE 13 Term 管道损坏:向一个没有读进程的管道写数据
SIGALRM 14 Term 来自alarm的计时器到时信号
SIGTERM 15 Term 终止
SIGUSR1 30,10,16 Term 用户自定义信号1
SIGUSR2 31,12,17 Term 用户自定义信号2
SIGCHLD 20,17,18 Ign 子进程停止或终止
SIGCONT 19,18,25 Cont 如果停止,继续执行
SIGSTOP 17,19,23 Stop 非来自终端的停止信号
SIGTSTP 18,20,24 Stop 来自终端的停止信号
SIGTTIN 21,21,26 Stop 后台进程读终端
SIGTTOU 22,22,27 Stop 后台进程写终端
SIGBUS 10,7,10 Core 总线错误(内存访问错误)
SIGPOLL Term Pollable事件发生(Sys V),与SIGIO同义
SIGPROF 27,27,29 Term 统计分布图用计时器到时
SIGSYS 12,-,12 Core 非法系统调用(SVr4)
SIGTRAP 5 Core 跟踪/断点自陷
SIGURG 16,23,21 Ign socket紧急信号
SIGVTALRM 26,26,28 Term 虚拟计时器到时
SIGXCPU 24,24,30 Core 超过CPU时限
SIGXFSZ 25,25,31 Core 超过文件长度限制
SIGIOT 6 Core IOT自陷,与SIGABRT同义
SIGEMT 7,-,7 Term
SIGSTKFLT -,16,- Term 协处理器堆栈错误(不使用)
SIGIO 23,29,22 Term 描述符上可以进行I/O操作
SIGCLD -,-,18 Ign 与SIGCHLD同义
SIGPWR 29,30,19 Term 电力故障
SIGINFO 29,-,- 与SIGPWR同义
SIGLOST -,-,- Term 文件锁丢失
SIGWINCH 28,28,20 Ign 窗口大小改变
SIGUNUSED -,31,- Term 未使用信号

在表中,Term 表示 Terminate终止程序,Core 表示核心转储,Ign 表示忽略,Stop 表示挂起。

在 Linux 的命令当中,我们使用 kill 命令可以向一个程序发出信号,一般我们终止程序发起 9 也就是 SIGKILL 信号,因为该信号不能被捕获,阻塞或者是程序自行忽略。

在 Linux C 当中,可以使用 kill(pid,signal) 向某个进程发起一个 signal

signal函数

signal 函数用于自定义信号处理函数,希望程序在接收到某个信号时作出某些处理。

比如我分别实现上面三个 Ctrl+C 的情况。

输出 Abort 退出

忽略Ctrl+C

当然什么都不注册默认是退出的。

然后这个参数应该是可以让我们知道获取的是什么类型的中断,不至于让我们每一个信号都要写一个处理函数,我们可以写上一整个 signal 的处理方式,用 switch 分发,然后循环给所有信号注册该信号处理函数即可。

学了新的知识点还挺开心,就是这个通信问题解决之后,感觉初始的问题还是没有解决,希望往后能找到更好的方案达到预期目标。