一、知识总结
信号和中断
中断是从I/O设备或协处理器发送到CPU的外部请求,将CPU从正常执行转移到中断处理。信号是发送给进程的请求,将进程从正常执行转移到中断处理
(1)人员中断
人员的每个动作函数都是通过本能或经验实现的,每个中断都分配由一个唯一的ID识别号和一个预先安装的动作函数,人在收到中断请求时“执行”动作函数。
- 来自硬件的中断
- 来自其他人的中断
- 自己造成的中断
- 不可屏蔽(NMI)
- 可屏蔽
(2)进程中断
每个进程中断都被转换为一个唯一ID号,发送给进程。Unix/Linux中的进程中断成为信号,编号为1—31.进程也可屏蔽某些类型的信号以推迟处理,必要时可能修改信号动作函数。
- 来自硬件的中断:定时器、I/O设备等。
- 来自其他处理器的中断:FFP、DMA、多处理器系统中的其他CPU。
- 自己造成的中断:除以0、保护错误、INT指令。
(4)进程的陷阱错误
进程可能会自己造成中断,是由被CPU识别为异常的错误引起的(除以0、无效地址、非法指令、越权……),它会陷入操作系统内核,将陷阱原因转换为信号编号发送给自己。
Unix/Linux信号示例
- Ctrl+C组合键:转换为SIGINT(2)信号,发送给终端,终止当前运行的进程。进程对大多数信号的默认操作是调用内核的kexit(exitValue)函数来终止。
- 用户可使用nohup a.out命令在后台运行程序,nohup会使sh复刻子进程来执行程序,但子进程会忽略SIGNUP(1)信号。
- 通过ps -u LTD发现后台进程仍在运行,可使用kill pid (or kill -s 9 pid)杀死进程。执行杀死的进程向pid标识的目标进程发送一个SIGTERM(15)信号,请求它死亡。
- 如果进程忽略该信号,可能拒绝死亡,可以使用kill -s 9 pid必能杀死。
Unix/Linux中的信号处理
1.信号类型
Unix/Linux支持31种不同的信号,每种信号在signal.h文件中都有定义,每种信号都有一个符号名。
2.信号来源
- 来自硬件中断的信号
中断健(Ctrl+C)产生一个SIGINT(2)信号
间隔定时器:SIGALRM(14)、SIGVTALRM(26)或SIGPROF(27)
其他硬件错误:总线错误、I/O陷阱等 - 来自异常的信号
SIGFPE(8):浮点异常(除以0)
SIGSEGV(11):段错误 - 来自其他进程的信号
可使用kill(pid,sig)系统调用向pid标识的目标进程发送信号。
二、 苏格拉底大挑战
三、 代码实践
#include <stdio.h>
#include <signal.h>
void WrkProcess(int nsig)
{
printf("WrkProcess .I get signal.%d threadid:%d/n",nsig,pthread_self());
int i=0;
while(i<5){
printf("%d/n",i);
sleep(1);
i++;
}
}
int main()
{
struct sigaction act,oldact;
act.sa_handler = WrkProcess;
act.sa_flags = SA_NODEFER | SA_RESETHAND;
sigaction(SIGINT,&act,&oldact);
printf("main threadid:%d/n",pthread_self());
while(1)sleep(5);
return 0;
}
四、 问题与解决思路
之前的signal(SIGALARM,function)函数设置的处理函数和本次不一样,那么本次signal函数怎样在不覆盖前次信号处理函数的基础上继续工作呢?
经查找资料,找到了这个。signal的返回值,要么是SIG_ERR,要么是前次信号处理的函数指针,同样在注册函数时,保存上次函数,在函数处理结束前回复原来的值。SIGALARM的默认动作是终止进程。设置SIGALARM信号是为了给调用者一个闹钟,应用的场所一般是在低速设备阻塞时,例如read/write,设置alarm函数可以防止系统长期阻塞,但这里有一个问题就是,在alarm(nsec)和read()之间有一个竞争关系,如果在read()之前调用了alarm()函数,那么read()还是会长期阻塞。解决这个问题可以通过setjmp和longjmp函数来解决,因为setjmp设置的时候返回0,longjmp可以设置不同的返回值,可以将read函数包裹在setjmp = 0里面,这样如果处理函数里面的longjmp跳转了也不会出现上面的问题了