一、信号的基本概念

为了更加清晰的了解信号,那么就拿我们最熟悉的场景切入:

1>用户输入命令,在shell下启动一个前台进程。

2>用户按下ctrl-c,这个键盘输入就是一个硬件中断。

3>如果Cpu当前正在执行这个代码,则该进程的用户空间代码暂停执行,CPU从用户态切换到内核态处理硬件中断。

4>终端驱动将Ctrl-C解释成一个SIGINT信号,,记在该进程的PCB中(也可以说发送一个SIGINT信号给该进程)。

5>当某个时刻要从内核态返回到该进程的用户态之时,首先处理PCB中记录的信号,发现有一个SIGINT信号带处理,而这个信号的默认动作是终制进程,所以直接终止进程而不在返回用户空间代码执行。

注:

    1>Ctrl-C产生的信号只能发给前台进程,一个命令加个&可以放到后台运行,这样shellbubi等待进程结束就可以 接受新的命令,启动新的进程。

    2>shell可以同时运行一个前台进程与任意多个后台进程,只是前台进程才能接到像Ctrl-C这样控制键产生的信号/

    3>前台进程在运行过程中用户随时可能按下Ctrl-C而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到SIGINT信号而终止,所以信号相对于进程的控制来说是异步的。

二、信号的查看

用Kill-l 命令可以查看系统定义的信号列表:

        每个信号都有一个编号和一个宏定义名称,这个可以在signal.h中查找

三、产生信号的方式

1>用户在终端按下某些键时,终端驱动程序会发送给前台进程《例如:Ctrl-C   ;  Ctrl-Z   ;Ctrl-\》

2>硬件异常产生信号,这些条件有硬件检测并通知内核,然后内核向当前进程发送适当的信号,例如当前进程执行了除0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。

3>一个进程调用KILL(2)函数可以发送信号给另一个进程。

4>软件条件产生。

总而言之:信号的产生可以分为以下三种:(1.通过终端产生信号;2.调用系统函数向进程发信号;3.由软件条件产生。)

四、信号处理常见方式

1>忽略此信号。

2>执行此信号的默认处理动作。

3>提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉一个信号。

五、常见信号

1>sigset_t(信号集)

        sigset_t类型对于每种信号用一个bit表示”有效“或”无效“,至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不关心的,使用者只能调用以下函数来操作sigset_t变量,而不应该对它的内部数据做任何解释,比如用printf直接打印sigset_t变量是没有意义的。

#include<signal.h>

int sigemptyset(sigset_t * set);

int sigfillset(sigset_t * set);

int sigaddset(sigset_t * set,int signo);

int sigdelset(sigset_t* set,int signo);

int sigismember(const sigset_t * set,int signo);

2>sigpending

 用法:

#include<signal.h>

sigpending(sigset_t *set)

读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1.

3>pause

#include<signal.h>

int pause(void);

pause函数使调用进程挂起直到有信号递达。

4>sigaction

#include<signal.h>

int sigaction(int signo,const struct sigaction *act,struct sigaction *oact);

注:相当于signal;

5>sigchld

        子进程在终止时会给父进程发SIGCHLGD信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理函数,这样父进程就只需专心处理自己的工作,不必关系子进程了,子进程结束时通知父进程,父进程在信号处理函数中调用wait清理子进程即可。

六、代码的实现

例1:

信号的知识点总结

检测结果:

信号的知识点总结

例2:

信号的知识点总结

检测结果:

信号的知识点总结

例3:

信号的知识点总结

检测结果:

信号的知识点总结

例4:

信号的知识点总结

检测结果:

信号的知识点总结

相关文章: