信号和 中断是很类似的,只不过是一个是硬件中断,另外一个是软中断。中断是系统对于异步事件的响应。
简单理解就是:中断源 发出 中断信号 在 中断向量表 中执行 中断处理程序 之前保存 现场信息
异步事件的响应:进程执行代码的过程中可以随时被打断,然后去执行异常处理程序。
中断源发出中断信号,CPU判断中断是否屏蔽屏蔽、保护现场 ,cpu执行中断处理程序, cpu恢复现场,继续原来的任务。
中断向量表保存了中断处理程序的入口地址。
中断个数固定,操作系统启动时初始化中断向量表。
中断有优先级,中断可以屏蔽。
中断分类
硬件中断(外部中断)
外部中断是指由外部设备通过硬件请求的方式产生的中断,也称为硬件中断, 例如DMA中断
软件中断(内部中断)
内部中断是由CPU运行程序错误或执行内部程序调用引起的一种中断,也称为软件中断。
x86平台INT指令 ARM软中断指令SWI
信号是UNIX系统响应某些状况而产生的事件,进程在接收到信号时会采取相应的行动。
信号是因为某些错误条件而产生的,比如内存段冲突、浮点处理器错误或者非法指令等
信号是在软件层次上对中断的一种模拟,所以通常把它称为是软中断
信号和中断的区别
信号与中断的相似点:
(1)采用了相同的异步通信方式;
(2)当检测出有信号或中断请求时,都暂停正在执行的程序而转去执行相应的处理程序;
(3)都在处理完毕后返回到原来的断点;
(4)对信号或中断都可进行屏蔽。
信号与中断的区别:
(1)中断有优先级,而信号没有优先级,所有的信号都是平等的;
(2)信号处理程序是在用户态下运行的,而中断处理程序是在核心态下运行;
(3)中断响应是及时的,而信号响应通常都有较大的时间延迟。
进程对信号的三种相应
忽略信号
不采取任何操作、有两个信号不能被忽略:SIGKILL(9号信号)和SIGSTOP。
进程不能忽略SIGKILL、SIGSTOP这两个信号。如果应用程序可以忽略这2个信号,系统管理无法杀死、暂停进程,无法对系统进行管理。。SIGKILL(9号信号)和SIGSTOP信号是不能被捕获的。
捕获并处理信号
内核中断正在执行的代码,转去执行先前注册过的处理程序。
执行默认操作
默认操作通常是终止进程,这取决于被发送的信号。
signal信号安装函数 就是对PCB中的信号状态进行重置
typedef void (*__sighandler_t) (int);
#define SIG_ERR ((__sighandler_t) -1)
#define SIG_DFL ((__sighandler_t) 0)
#define SIG_IGN ((__sighandler_t) 1)
__sighandler_t signal(int signum, __sighandler_t handler);
signal是一个带signum和handler两个参数的函数,准备捕捉或屏蔽的信号由参数signum给出,接收到指定信号时将要调用的函数由handler给出
handler这个函数必须有一个int类型的参数(即接收到的信号代码),它本身的类型是void
handler也可以是下面两个特殊值:
SIG_IGN 屏蔽该信号
SIG_DFL 恢复默认行为
其实就是上面定义的两个宏而已,也就是0 和 1.
不可靠信号和可靠信号
linux信号机制基本上是从unix系统中继承过来的。早期unix系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,它的主要问题是:
进程每次处理信号后,就将对信号的响应设置为默认动作。在某些情况下,将导致对信号的错误处理;因此,用户如果不希望这样的操作,那么就要在信号处理函数结尾再一次调用signal(),重新安装该信号。
早期unix下的不可靠信号主要指的是进程可能对信号做出错误的反应以及信号可能丢失。
linux支持不可靠信号,但是对不可靠信号机制做了改进:在调用完信号处理函数后,不必重新调用该信号的安装函数(信号安装函数是在可靠机制上的实现)。因此,linux下的不可靠信号问题主要指的是信号可能丢失。
unix信号机制不可靠地方,1)处理完信号以后,需要重新再注册信号;2)信号可能丢失。linux下已经对1做了优化。Linux下主要是指信号丢失,比如 发送了3个信号,可能只能接受一个信号。
可靠信号,这些信号支持排队,不会丢失。同 时,信号的发送和安装也出现了新版本:信号发送函数sigqueue()及信号安装函数sigaction()。
不可靠信号不支持排队,可靠信号支持排队。
信号注册函数 和 信号发送函数
signal sigaciton 信号安装函数 都是调用内核服务do_signal函数,应用系统无法直接调用的函数
Int kill(pid_t pid, int siq)
Int kill(pid_t pid, int siq)
参数组合情况解释:
kill(pid_t pid, int siq)
pid>0 将信号sig发给pid进程
pid=0 将信号sig发给同组进程
pid=-1 将信号sig发送给所有进程,调用者进程有权限发送的每一个进程(除了1号进程之外,还有它自身)
pid<-1 将信号sig发送给进程组是pid(绝对值)的每一个进程
如果在fork之前安装信号,则子进程可以继承信号。
sleep函数几点说明
1)sleep函数作用,让进程睡眠。
2)能被信号打断,然后处理信号函数以后,就不再睡眠了。直接向下执行代码
3)sleep函数的返回值,是剩余的秒数
因此 如果想要休眠足够的秒数,需要使用do while循环来进行。
raise 给自己发送信号。raise(sig)等价于kill(getpid(), sig);
killpg 给进程组发送信号。killpg(pgrp, sig)等价于kill(-pgrp, sig);
sigqueue 给进程发送信号,支持排队,可以附带信息。
因此可以给进程发送信号的函数有,kill, killgp, raise, sigqueue。
pause()函数
将进程置为可中断睡眠状态。然后它调用内核函数schedule(),使linux进程调度器找到另一个进程来运行。pause使调用者进程挂起,直到一个信号被捕获。
alarm函数,设置一个闹钟延迟发送信号。告诉linux内核n秒中以后,发送SIGALRM信号;
可重入函数和不可重入函数
为了增强程序的稳定性,在信号处理函数中应使用可重入函数。
所谓可重入函数是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错。因为进程在收到信号后,就将跳转到信号处理函数去接着执行。如果信号处理函数中使用了不可重入函数,那么信号处理函数可能会修改原来进程中不应该被修改的数据,这样进程从信号处理函数中返回接着执行时,可能会出现不可预料的后果。不可再入函数在信号处理函数中被视为不安全函数。满足下列条件的函数多数是不可再入的:(1)使用静态的数据结构,如getlogin(),gmtime(),getgrgid(),getgrnam(),getpwuid()以及getpwnam()等等;(2)函数实现时,调用了malloc()或者free()函数;(3)实现时使用了标准I/O函数的。使用man 7 signal 查找可重入函数和不可重入函数。
信号的阻塞和未达
信号在内核中的表示
执行信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞(Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。注意,阻塞和忽略是不同,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。递达的信号被忽略了,同样是不能执行回调函数的。
PCB进程控制块中函数有信号屏蔽状态字(block)信号未决状态字(pending)还有是否忽略标志;信号屏蔽状态字(block),1代表阻塞、0代表不阻塞;信号未决状态字(pending)的1代表未决,0代表信号可以抵达了;向进程发送SIGINT,内核首先判断信号屏蔽状态字是否阻塞,信号未决状态字(pending相应位制成1;若阻塞解除,信号未决状态字(pending)相应位制成0;表示信号可以抵达了。block状态字、pending状态字 64bit;block状态字用户可以读写,pending状态字用户只能读;这是信号设计机制。是通过block间接的控制pending状态字,使它们保持一直状态。
信号集操作函数
#include <signal.h>
int sigemptyset(sigset_t *set); 把信号集情况64bit/8=8个字节的buffer
int sigfillset(sigset_t *set); 把信号集情况1
int sigaddset(sigset_t *set, int signo); 根据signo,把信号集中的对应为置成1
int sigdelset(sigset_t *set, int signo); 根据signo,把信号集中的对应为置成0
int sigismember(const sigset_t *set, int signo);//判断signo是否在信号集中
上面的仅仅是操作了信号集合,还没有将相应的信号集放置到内核中去,需要使用下面这个函数来操作对应的信号屏蔽字。
读取或更改进程的信号屏蔽状态字(block)
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
功能:读取或更改进程的信号屏蔽字。
返回值:若成功则为0,若出错则为-1
如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,
通过sigprocmask可以将对应信号字类型中的数据写到进程的block状态字中。
sigpending用于获取信号未决状态字(pending)信息
函数原型是这样的。int sigpending(sigset_t *set);
到此为止,已经完成了信号的设置工作,剩下的 就是信号的发送 和信号的处理工作。
、
原文地址:https://www.cnblogs.com/randyniu/p/9124982.html