第六章学习笔记

第六章 信号和信号处理

一、摘要

本章讲述了信号和信号处理;介绍了信号和中断的统一处理,有助于从正确的角度看待信号;将信号视为进程中断,将进程从正常执行转移到信号处理;解释了信号的来源,包括来自硬件、异常和其他进程的信号;然后举例说明了信号在Unix/Linux中的常见用法;详细解释了Unix/Linux中的信号处理,包括信号类型、信号向量位、信号掩码位、进程PROC结构体中的信号处理程序以及信号处理步骤;用示例展示了如何安装信号捕捉器来处理程序异常,如用户模式下的段错误;还讨论了将信号用作进程间通信(IPC)机制的适用性。读者可借助该编程项目,使用信号和管道来实现用于进程交换信息的进程间通信机制。

二、教材内容整理

6.1信号和中断
“中断”是从1/0设备或协处理器发送到CPU的外部请求,它将CPU从正常执行转移到中断处理。与发送给CPU的中断请求一样,“信号”是发送给进程的请求,将进程从正常执行转移到中断处理。
(1)首先,我们将进程的概念概括为:一个“进程”(引号中)就是一系列活动。广义的 “进程”包括
・从事日常事务的人。
・在用户模式或内核模式下运行的Unix/Linux进程。
・执行机器指令的CPU。
(2)“中断”是发送给“进程”的事件,它将“进程”从正常活动转移到其他活动,称 为“中断处理”。“进程”可在完成“中断”处理后恢复正常活动。
(3)“中断” 一词可应用于任何“进程”,并不仅限于计算机中的CPU。
来自硬件的中断、来自其他人的中断、自己造成的中断等
6.2Unix/Linux信号示例
(1)按"Ctrl+C”组合键通常会导致当前运行的进程终止。原因如下。“Ctrl+C”组合键会生成一个键盘硬件中断。键盘中断处理程序将"Ctrl+C”组合键转换为SIGINT (2)信号,发送给终端上的所有进程,并唤醒等待键盘输入的进程。在内核模式下,每个进程都要检查和处理未完成的信号。进程对大多数信号的默认操作是调用内核的kexit(exitValue)函数来终止。在Linux中,exitValue的低位字节是导致进程终止的信号编号。
(2)用户可使用nohup a.out &命令在后台运行一个程序。即使在用户退出后,进程仍将继续运行。nohup命令会使sh像往常一样复刻子进程来执行程序,但是子进程会忽略S1GHUP(1)信号。当用户退出时,sh会向与终端有关的所有进程发送一个S1GHUP信号。后台进程在接收到这一信号后,会忽略它并继续运行。为防止后台进程使用终端进行I/O, 后台进程通常会断开与终端的连接(通过将其文件描述符0、1、2重定向到/dev/null),使 其完全不受任何面向终端信号的影响。
(3)也许几天后,用户再次登录时会发现(通过ps-u LTD)后台进程仍在运行。用户可以使用sh命令
6.3Unix/Linux中的信号处理
6.3.1 信号类型
Unix/Linux支持31种不同的信号,每种信号在signal.h文件中都有定义。
6.3.2 信号的来源
来自硬件中断的信号:在进程执行过程中,一些硬件中断被转换为信号发送给进程。 硬件信号示例是
中断键(Ctrl+C),它产生一个SIGINT ( 2 )信号。
间隔定时器,当它的时间到期时,会生成一个SIGALRM ( 14 ) .SIGVTALRM ( 26 ) 或 SIGPROF ( 27 )信号。
其他硬件错误,如总线错误、IO陷阱等。
来自异常的信号:当用户模式下的进程遇到异常时,会陷入内核模式,生成一个信 号,并发送给自己。常见的陷阱信号有SIGFPE ( 8 ),表示浮点异常(除以0),最常 见也是最可怕的是SIGSEGV (11),表示段错误,等等。
来自其他进程的信号:进程可使用kill(pid, sig)系统调用向pid标识的目标进程发送 信号。读者可以尝试以下实验。在Linux下,运行简单的C程序
6.3.3 进程PROC结构体中的信号
每个进程PROC都有一个32位向量,用来记录发送给进程的信号。在位向量中,每一 位(0位除外)代表一个信号编号。此外,它还有一个信号MASK位向量,用来屏蔽相应的 信号。可使用一系列系统调用,如sigmasks sigsetmask, siggetmask. sigblock等设置、清
除和检查MASK位向量。待处理信号只在未被屏蔽的情况下才有效。这样可以让进程延迟 处理被屏蔽的信号,类似于CPU屏蔽某些中断。

6.3.4 信号处理函数
每个进程PROC都有一个信号处理数组int sig[32]o sig[32]数组的每个条目都指定了如何处理相应的信号,其中0表示DEFault (默认),1表示IGNore (忽略),其他非零值表示 用户模式下预先安装的信号捕捉(处理)函数。图6.1给岀了信号位向量、屏蔽位向量和信号处理函数。

信号处理步骤

当某进程处于内核模式时,会检查信号并处理未完成的信号。如果某信号有用户安装的捕捉函数,该进程会先清除信号,获取捕捉函数地址,对于大多数陷阱信号,则将已安装的捕捉函数重置为DEFault。然后,它会在用户模式下返回,以执行捅捉函数,以这种方式篡改返回路径。当捕捉函数结束时,它会返回到最初的中断点,即它最后进入内核模式的地方。因此,该进程会先迁回执行捕捉函数,然后再恢复正常执行。
重置用户安装的信号捕捉函数:用户安装的陷阱相关信号捕捉函数用于处理用户代码中的陷阱错误。由于捕捉函数也在用户模式下执行,因此可能会再次出现同样的错误。如果是这样,该进程最终会陷入无限循环,一直在用户模式和内核模式之间跳跃。为了防止这种情况,Unix内核通常会在允许进程执行捕捉函数之前先将处理函数重置为DEFault。这意味着用户安装的捕捉函数只对首次出现的信号有效。若要捕捉再次出现的同一信号,则必须重新安装捕捉函数。但是,用户安装的信号捕捉函数的处理方法并不都一样,在不同 Unix版本中会有所不同。例如,在 BSD Unix中,信号处理函数不会被重置,但是该信号在执行信号捕捉函数时会被阻塞。感兴趣的读者可参考关于Lioux信号和 sigaction函数的手册页,以了解更多详细信息。
信号和唤醒:在Unix/Linux内核中有两种SLEEP进程;深度休眠进程和浅度休眠进程。前一种进程不可中断,而后一种进程可由信号中断。如果某进程处于不可中断的SLEEP状态,到达的信号(必须来自硬件中断或其他进程)不会唤醒进程。如果它处于可中断的SLEEP状态,到达的信号将会唤醒它。例如,当某进程等待终端输入时,它会以低优先级休眠,这种休眠是可中断的,SIGINT这类信号即可唤醒它。

信号与异常

作为进程异常的统一处理方法:当进程遇到异常时,它会陷入内核模式,将陷阱原因 转换为信号编号,并将信号发送给自己。如果在内核模式下发生异常,内核只打印一 条PANIC错误消息,然后就停止了。如果在用户模式下发生异常,则进程通常会终 止,并以内存转储进行调试。
让进程通过预先安装的信号捕捉函数处理用户模式下的程序错误。这类似于MVS [IBM MVS]中的 ESPIE 宏。
在特殊情况下,它会让某个进程通过信号杀死另一个进程。注意,这里所说的杀死并 不是直接杀死某个进程.而只是向目标进程发出“死亡”请求 为什么我们不直接杀 死某个进程呢?(提示:瑞士银行有大量无人认领的匿名账户。)

三、实践环节代码与截图

代码:
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)
    ;

}
运行截图:

posted @ 2022-10-30 19:52  西宁西  阅读(54)  评论(0)    收藏  举报