信号的概念与分类
问题:按下 Ctrl + C 后,命令行中的前台进程会被中止。为什么???
什么是信号?
信号是一种 “软件中断”, 用来处理异步事件
- 内核发送信号到某个进程,通知进程事件的发生
- 时间可能来自硬件,可能来自用户输入,可能来自除零错误
信号是一种类型的进程间通讯方式(一个进程向另一个进程发送信号)
- A 进程发生事件 T, 向 B 进程发送信号, B 进程执行动作响应事件
- 进程可以对接收到的不同信号进行不同动作响应(信号 → 处理)
信号的分类
硬件异常
- 内核检测到硬件错误,发送相应信号给相关进程
终端信号
- 在终端输入 “特殊字符” 等价于向前台进程组发送相应信号
软件信号
- 在软件层面(进程代码中)触发的信号(发送给自身或其它进程)
硬件异常信号
信号 | 值 | 说明 |
---|---|---|
SIGBUS | 7 | 总线错误,进程发生了内存访问错误(如,访问的内存地址未对齐) |
SIGFPE | 8 | 算数错误,FPE标识浮点异常 |
SIGILL | 9 | 指令错误,进程尝试执行非法指令 |
SIGSEGV | 11 | 段错误,进程访问了非法内存区域(如,进程写了受保护的内存) |
终端相关信号
SIGINT (Ctrl + C)
- 程序中止信号,用于通知前台进程组终止进程
SIGOUIT (Ctrl + )
- 与 SIGINT 类似,进程收到该信号退出时可产生 coredump 文件
SIGTSTP (Ctrl + Z)
- 进程收到该信号后停止运行(状态发生转换),后续可恢复运行状态
- 停止进程的运行,进程收到该信号后可以选择处理或忽略
信号发送与处理
软件相关信号
- 子进程退出:父进程收到 SIGCHLD 信号
- 父进程退出:子进程可能收到信号(什么信号?)
- 定时器到期:
alarm(). ualarm(). timer_create(), ...
- 主动发送信号:
kill(), raise() ...
- 。。。
内核与信号
紫色剑头:
进程通过系统调用申请内核发送信号 (比如 timer)
蓝色箭头:
内核监测到硬件错误,给进程 B 发送信号
黄色箭头:
进程间通讯,进程 A 发送信号给进程 B
信号的默认处理
默认处理方式 | 说明 | 示例 |
---|---|---|
ignore | 进程丢弃信号不会产生任何影响 | SIGCHLD, SIGURG(TCP带外数据) |
terminate | 中止进程 | SIGKILL, SIGHUP |
coredump | SIGQUIT, SIGILL | |
stop / continue | 停止进程执行 / 恢复进程执行 | SIGSTOP, SIGCONT |
知识加油站 : System V vs BSD (仅操作风格上存在不同)
System V
- 也被称为 AT&T System V, 是 Unix 操作系统众多版本中的一支
BSD
- 加州大学伯克利分校开创,Unix 衍生系统,代表由此派生出的各种套件集合
Linux 之所以被称为类 Unix 操作系统 (Unix Like), 部分原因就是 Linux 的操作风格介于上述二者纸间,且不同厂商为了照顾不同的用户,其发行版的操作风格存在差异。
Sytem V | BSD | |
Root脚本位置 | /etc/init.d | /etc/rc.d |
默认Shell | Bshell | Cshell |
文件系统数据 | /etc/mnttab | /etc/mtab |
内核位置 | /UNIX | /vmUnix |
打印机设备 | lp | rlp |
字符串函数 | memcopy | bcopy |
终端初始化设置文件 | /etc/initab | /etc/ttys |
终端控制 | termio | termios |
自定义信号处理
#include <sys/types.h>
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum. sighandler_t handler);
→ 默认风格sighandler_t sysv_signal(int signum. sighandler_t handler);
→ Sytem V 风格sighandler_t bsd_signal(int signum. sighandler_t handler);
→ BSD 风格
信号处理示例
#include <signal.h>
void signal_handler(int sig)
{
printf("sig = %d\n", sig);
}
int main(int argc, char *argv)
{
int i =0;
signal(SIGINT, signal_handler);
// sysv_signal(SIGINT, signal_handler);
// bsd_signal(SIGINT, signal_handler);
while (1) {
sleep(1);
}
return 0;
}
自定义信号发送
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
- pid > 0,指定信号发送给对应进程
- pid == 0,指定信号发送给当前进程组中的每一个进程
- pid < -1, 如 -1000, 那么发送指定信号给 PGID 为 1000 的进程组
int raise(int sig); // 发送指定信号给当前进程(自己发自己),信号处理完毕才返回
标准信号是 Unix 系统的信号,编号范围从 1 - 31。
实时信息是 Linux 独有的信号,编号范围从 31 到 64。
信号发送示例
int main(int argc, char *argv[])
{
int pid = atoi(argv[1]);
int sig = atoi(argv[2]);
printf("send sig(%d) to process(%d)\n", sig, pid);
kill(pid, sig); // 给指定进程发执行信号
raice(SIGINT); // 终止退出
while (1) {
printf("while ... \n");
sleep(1);
}
}
信号发送与处理实验
信号的接收
//#define _GNU_SOURCE
//#define _XOPEN_SOURCE 600
//#define _POSIX_C_SOURCE 200800L
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
void signal_handler(int sig)
{
printf("handler : sig = %d\n", sig);
}
int main(void)
{
signal(SIGINT, signal_handler);
// sysv_signal(SIGINT, signal_handler);
// bsd_signal(SIGINT, signal_handler);
while (1) {
sleep(1);
}
return 0;
}
输出:
^Chandler : sig = 2 // ctrl + c
^Chandler : sig = 2 // ctrl + c
^Chandler : sig = 2 // ctrl + c
^Chandler : sig = 2 // ctrl + c
^Chandler : sig = 2 // ctrl + c
^\Quit (core dumped) // ctrl + \
信号的发送
app1.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
void signal_handler(int sig)
{
printf("handler : sig = %d\n", sig);
}
int main(void)
{
signal(40, signal_handler);
while (1) {
sleep(1);
}
return 0;
}
app2.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
int main(int argc, char *argv[])
{
int pid = atoi(argv[1]);
int sig = atoi(argv[2]);
printf("send sig(%d) to process (%d)...\n", sig, pid);
kill(pid, sig);
raise(SIGINT);
while (1) {
printf("while...\n");
sleep(1);
}
return 0;
}
输出:
wu_tiansong@ubuntu-server:~/test$ ./app1 & // 信号接收
[1] 3871089
wu_tiansong@ubuntu-server:~/test$ ./app2 3871089 40 // 信号发送
send sig(40) to process (3871089)...
handler : sig = 40
思考
三种自定义信号处理函数有什么区别?
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。