- 信号是 UNIX 和 Linux 系统响应某些条件而产生的一个事件。接收到该信号的进程会相应的采取一些行动。我们用术语 生成 表示一个信号的产生,使用术语 捕获 表示接收到一个信号。
- 信号是由于某些错误条件而生成的,如 内存段冲突 / 浮点处理器错误 或 非法指令等。它们由 shell 和终端处理器生成来引起中断,它们还可以作为在 进程间传递消息 或 修改行为 的一种方式,明确地由一个进程发送给另一个进程。
目录
- 信号列表
- 处理信号(使用 signal 函数)
- 发送信号(使用键盘组合键 / 使用 kill 命令 / 使用 kill 函数)
- 处理信号(使用 sigaction 函数)(优先使用该函数)
1. 信号列表
信号名称 | 说明 |
---|---|
SIGABORT | * 进程异常终止 |
SIGALRM | 超时警告 |
SIGFPE | * 浮点运行异常 |
SIGHUP | 连接挂断 |
SIGILL | * 非法指令 |
SIGINT | 终端中断 |
SIGKILL | 终止进程(此信号不能被捕获或忽略) |
SIGPIPE | 向无读进程的管道写数据 |
SIGQUIT | 终端退出 |
SIGSEGV | * 无效内存段访问 |
SIGTERM | 终止 |
SIGUSR1 | 用户定义信号 1 |
SIGUSR2 | 用户定义信号 2 |
SIGCHLD | 子进程已经停止或退出 |
SIGCONT | 继续执行暂停进程 |
SIGSTOP | 停止执行(此信号不能被捕获或忽略) |
SIGTSTP | 终端挂起 |
SIGTTIN | 后台进程尝试读操作 |
SIGTTOU | 后台进程尝试写操作 |
(‘ * ’ 表示:系统对信号的响应视具体实现而定)
2. 处理信号(使用 signal 函数)
- signal 函数
#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);
- 特殊的信号处理函数
名称 | 说明 |
---|---|
SIG_IGN | 忽略信号 |
SIG_DEL | 恢复默认行为 |
- 实验代码如下(程序响应 SIGINT 终端中断信号,然后恢复到该信号的默认行为):
/* test2.c */
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
void handleSig(int);
void main(){
printf("running...\n");
signal(SIGINT, handleSig);
// 主进程循环,等待信号
while(1){
sleep(2);
printf("alive.\n");
}
}
void handleSig(int sig){
printf("got signal: %d\n", sig);
signal(SIGINT, SIG_DFL);
}
运行程序,执行结果如下:
ubuntu@cuname:~/dev/beginning-linux-programming/test$ gcc -o test2 test2.c
ubuntu@cuname:~/dev/beginning-linux-programming/test$ ./test2
running...
alive.
alive.
^Cgot signal: 2 // 第一次 响应 SIGINT 终端中断信号:打印字符串
alive.
alive.
^C // 第二次 响应,程序默认终止
ubuntu@cuname:~/dev/beginning-linux-programming/test$
3. 发送信号(使用键盘组合键 / 使用 kill 命令 / 使用 kill 函数)
- 使用 kill 命令发送信号(格式为:‘kill 信号代码或信号名称 目标进程PID’)
ubuntu@cuname:~/dev$ kill -sigint 38897
- kill 函数(这个函数和同名的 kill 命令完成相同的功能)
- 想要发送一个信号,发送进程必须拥有相应的权限。这通常意味着两个进程必须拥有相同的用户 ID (即你只能发送信号给属于自己的进程,但超级用户可以发送信号给任何进程)
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
- alarm 函数(闹钟功能)
- alarm 函数用来在 seconds 秒之后安排发送一个 SIGALRM 信号。但由于处理的延时和时间调度的不确定性,实际闹钟时间将比预先安排的要稍微拖后一点儿。
- 把参数 seconds 设置为 0 将取消所有已设置的闹钟请求。
- 如果在接收到 SIGALRM 信号之前再次调用 alarm 函数,则闹钟重新开始计时。
- 每个进程只能有一个闹钟时间。
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
使用 alarm 函数,实现进程自我唤醒,代码如下:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void handleSig(int);
void main(){
printf("main running...\n");
// 响应/处理 SIGALRM 闹钟信号
signal(SIGALRM, handleSig);
// 闹钟倒计时 5 秒
alarm(5);
// 暂停执行,等待被 SIGALRM 闹钟信号唤醒
pause();
printf("done.\n");
}
void handleSig(int sig){
printf("wake up.\n");
}
执行结果如下(达到预期目的):
ubuntu@cuname:~/dev/beginning-linux-programming/test$ gcc -o test2 test2.c
ubuntu@cuname:~/dev/beginning-linux-programming/test$ ./test2
main running...
wake up. // 被 SIGALRM 闹钟信号唤醒
done.
4. 处理信号(使用 sigaction 函数)
- signal 函数在传统的 UNIX 编程中很常见。但是 X/Open 和 UNIX 规范推荐了一个更新和更健壮的信号编程接口:sigaction
- sigaction 函数
#include <signal.h>
int sigaction(int sig, const struct sigaction *act, struct sigaction *oact);
sigaction 结构定义在文件 signal.h 中,它的作用是定义在接收到参数 sig 指定的信号后应该采取的行动。该结构至少应该包括以下几个成员:
void (*) (int) sa_handler
sigset_t sa_mask
int sa_flags