《Linux/Unix系统编程》第六章学习笔记-20191304
信号和信号处理
广义的“进程”是一系列的活动,可以是每天完成日常事务的人,可以是系统的进程,还可以是执行指令的CPU。而“中断”是发送给“进程”的事件,将“进程”从正常活动转移到其他活动,完成“中断”后,“进程”恢复正常活动。
- 人员中断:一个人正在工作时,办公楼着火了,他马上离开。大楼着火就是“人员中断”,它将工作中的人转向“应对和处理中断”,中断结束后,此人还可以继续之前的活动。
- 进程中断:进程执行时,可能来自硬件的中断,比如终端、Ctrl+C,也可能来自其他进程的中断,比如kill(pid,SIG#)、death_of_child,也可能因为自己除以0、无效地址而中断。Unix/Linux中的进程中断成为信号
- 硬件中断:是发送给处理器或者CPU的信号,可能来自I/O设备,定时器,来自其他CPU,或者自己造成的中断。
进程遇到异常时,会陷入操作系统内核,将陷阱原因转换为信号编号发送给自己。若在用户模式下发生异常,默认操作是终止。不过可以通过信号捕捉器,允许在用户模式下处理信号。
信号和中断的区别:
信号与中断的相似点:
- 采用了相同的异步通信方式;
- 当检测出有信号或中断请求时,都暂停正在执行的程序而转去执行相应的处理程序;
- 都在处理完毕后返回到原来的断点;
- 对信号或中断都可进行屏蔽。
信号与中断的区别:
- 中断有优先级,而信号没有优先级,所有的信号都是平等的;
- 信号处理程序是在用户态下运行的,而中断处理程序是在核心态下运行;
- 中断响应是及时的,而信号响应通常都有较大的时间延迟。
信号示例及其类型
-
1.Ctrl+C
2.nohup
3.kill pid
1) SIGHUP (挂起) 当运行进程的用户注销时通知该进程,使进程终止
2) SIGINT (中断) 当用户按下时,通知前台进程组终止进程
3) SIGQUIT (退出) 用户按下或时通知进程,使进程终止
4) SIGILL (非法指令) 执行了非法指令,如可执行文件本身出现错误、试图执行数据段、堆栈溢出
5) SIGTRAP 由断点指令或其它trap指令产生. 由debugger使用
6) SIGABRT (异常中止) 调用abort函数生成的信号
7) SIGBUS 非法地址, 包括内存地址对齐(alignment)出错. eg: 访问一个四个字长的整数, 但其地址不是4的倍数.
8) SIGFPE (算术异常) 发生致命算术运算错误,包括浮点运算错误、溢出及除数为0.
9) SIGKILL (确认杀死) 当用户通过kill -9命令向进程发送信号时,可靠的终止进程
10) SIGUSR1 用户使用
11) SIGSEGV (段越界) 当进程尝试访问不属于自己的内存空间导致内存错误时,终止进程
12) SIGUSR2 用户使用
13) SIGPIPE 写至无读进程的管道, 或者Socket通信SOCT_STREAM的读进程已经终止,而再写入。
14) SIGALRM (超时) alarm函数使用该信号,时钟定时器超时响应
15) SIGTERM (软中断) 使用不带参数的kill命令时终止进程
17) SIGCHLD (子进程结束) 当子进程终止时通知父进程
18) SIGCONT (暂停进程继续) 让一个停止(stopped)的进程继续执行. 本信号不能被阻塞.
19) SIGSTOP (停止) 作业控制信号,暂停停止(stopped)进程的执行. 本信号不能被阻塞, 处理或忽略.
20) SIGTSTP (暂停/停止) 交互式停止信号, Ctrl-Z 发出这个信号
21) SIGTTIN 当后台作业要从用户终端读数据时, 终端驱动程序产生SIGTTIN信号
22) SIGTTOU 当后台作业要往用户终端写数据时, 终端驱动程序产生SIGTTOU信号
23) SIGURG 有"紧急"数据或网络上带外数据到达socket时产生.
24) SIGXCPU 超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变。
25) SIGXFSZ 当进程企图扩大文件以至于超过文件大小资源限制。
26) SIGVTALRM 虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.
27) SIGPROF (梗概时间超时) setitimer(2)函数设置的梗概统计间隔计时器(profiling interval timer)
28) SIGWINCH 窗口大小改变时发出.
29) SIGIO(异步I/O) 文件描述符准备就绪, 可以开始进行输入/输出操作.
30) SIGPWR 电源失效/重启动
31) SIGSYS 非法的系统调用。
信号处理
- 忽略信号
不采取任何操作、有两个信号不能被忽略:SIGKILL(9号信号)和SIGSTOP。
(注意:为什么进程不能忽略SIGKILL、SIGSTOP信号。(如果应用程序可以忽略这2个信号,系统管理无法杀死、暂停进程,无法对系统进行管理。)。SIGKILL(9号信号)和SIGSTOP信号是不能被捕获的。) - 捕获并处理信号
内核中断正在执行的代码,转去执行先前注册过的处理程序。 - 执行默认操作
默认操作通常是终止进程,这取决于被发送的信号。
sigaction函数
功能:sigaction函数用于改变进程接收到特定信号后的行为。
原型:
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
参数:
该函数的第一个参数为信号的值,可以为除SIGKILL及SIGSTOP外的任何一个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)
第二个参数是指向结构sigaction的一个实例的指针,在结构sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理
第三个参数oldact指向的对象用来保存原来对相应信号的处理,可指定oldact为NULL。
sigaction结构体:
struct sigaction {
void (*sa_handler)(int); //信号处理程序不接受额外数据
void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序能接受额外数据,和sigqueue配合使用
sigset_t sa_mask; //
int sa_flags; //影响信号的行为SA_SIGINFO表示能接受数据
void (*sa_restorer)(void); //废弃
};
实践内容
#include
#include
#include
#include
#include
#include
jmp_buf env;
int count = 0;
void handler(int sig, siginfo_t *siginfo, void *context)
{
printf("handler: sig=%d from PID=%d UID=%d count=%d\n",
sig, siginfo->si_pid, siginfo->si_uid, ++count);
if (count >= 4) // let it occur up to 4 times
longjmp(env, 1234);
}
int BAD()
{
int *ip = 0;
printf("in BAD(): try to dereference NULL pointer\n");
*ip = 123; // dereference a NULL pointer
printf("should not see this line\n");
}
int main(int argc, char *argv[])
{
int r;
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_sigaction = &handler;
act.sa_flags = SA_SIGINFO;
sigaction(SIGSEGV, &act, NULL);
if ((r = setjmp(env)) == 0)
BAD();
else
printf("proc %d survived SEGMENTATION FAULT: r=%d\n", getpid(), r);
printf("proc %d looping\n", getpid());
while (1)
;
}