首页 > 代码库 > 信号中断与异步信号中断安全编程
信号中断与异步信号中断安全编程
1、什么是中断?
1.1、什么是中断
外围设备的速度远低于CPU的速度,所以为提高CPU计算效率,现代计算机变内核主动为硬件主动,只在硬件需要的时候才发送信号,通知内核来处理数据。这样外围设备与内核的协作方式即为中断机制。而设备发送的信号即为中断,其本质为一种特殊的电信号。
硬中断处理流程:
1、各外围设备与中断管理器各输入引脚相连;
2、中断管理器与CPU之间只存在一条中断管线;
3、设备发送一个中断到中断管理器;
4、中断管理器发送对应电信号给CPU。
5、CPU中断当前工作,开始处理中断,并通知操作系统。
6、操作系统调用中断处理程序。
中断的特点:
1、不同的设备对应不同的中断,并被用数字标识;
2、对应的设备需要对应的中断处理程序;
3、中断值即中断请求线(IRQ),被关联到不同的数值量,如IRQ 0,中断亦可动态分配。
4、设备中断信号可能在任意时刻到来,不与CPU时钟同步,即异步硬件中断。
糕富帅CPU来到女儿国,女儿国的妹子们(中断集合)精心打扮,总在认为打扮完美的时刻向糕富帅抛媚眼露大腿扮性感,引起糕富帅的注意。糕富帅玩弄妹子的手段高超,经验丰富,与最靓的妹纸牵手,喜欢为不同的妹子编号并制定不同的攻略策略,总是上半场激烈,下半场缠绵,中场偷腥不断,并在腻味之后回归原始的浪荡生活。妹纸们总是很傻很天真,屡败屡战,不停地打扮自己,完美自己,期待着与糕富帅的重新开始。
1.2、什么是异常
如果程序出现编码错误、CPU本身故障和内存缺页等反常情况时,CPU将异常反馈给内核,调用异常处理程序进行处理。异常的产生必须与cpu时钟同步,又叫同步中断。
异常分为:
1、故障,执行处理函数后,恢复现场继续执行;
2、陷阱,用于调试,陷阱命令执行完毕后,内核将控制权返回给用户程序;
3、异常中止,用于报告严重错误,如硬件故障和系统表数值异常等,其异常处理函数将立即中止程序的执行;
4、编程异常,用于执行系统调用或向调试程序通报特定事件,即软中断,被当做陷阱处理。
1.3、中断处理程序
内核在响应一个中断时,将调用对应的特定函数,这个函数就是中断处理程序 。这个函数是按照特定类型声明的C函数,运行与中断上下文中。中断随时可能到来,为保证尽快恢复被中断代码的执行,中断处理程序必须保证快速执行。但中断处理程序可能要完成大量非瞬时工作(比如将网络数据拷贝到内存,并将预处理的数据转交给特定的协议栈),所以中断处理任务被两部分,上半部--中断处理程序实时响应中断并执行,下半部--完成可被退出处理的工作,在合适的时机到来后,下半部将开中断执行。
中断程序是硬件驱动程序的重要组成部分。硬件的驱动程序在初始化加载时,必须为其中断线注册对应的中断处理程序。同一中断线可能被对个设备共享,此时必须使用设备结构来区分。共享中断线的各设备,注册中断程序时必须使用IRQF_SHARED标志位。中断程序可以注销,若某中断线上所有诊断处理程序均已注销,则此条中断线被禁用。
1.4、中断上下文
内核在执行中断处理程序时,即处于中断上下文中。中断上下文有严格的时间限制。中断上下文时执行的代码,必须迅速并简洁,不适用循环处理繁杂任务,且不能睡眠或调用可能睡眠的函数(否则无法重新调度),以迅速恢复中断现场,防止打断其他代码(甚至其他中断线的中断程序)的执行。所以中断程序的上半部必须力求简约。
2.6版本之前的中断程序共享被中断进程的内核栈(无进程调度时,将使用空任务进程idle),2.6版本的linux为每个CPU均分配了一个中断栈。内核将中断处理程序的入口点预定义在内存中,中断到来,内核跳到中断入口点,保存该中断号和当前寄存器的值,接着执行do_IRQ()禁止该中断线并屏蔽该CPU对任意中断的响应(防止中断的嵌套和重入问题),接着开始调用对应的中断处理C代码。
1.6、中断下半部与软中断
中断下半部设计方案:
1、BH静态全局链表:即bottom half,在全局范围内同步,共32个bottom halves组成,用一个32位数标识那个BH可执行。简单方便、死板、性能不够;
2、任务队列:即task queue,一组待处理函数组成的链表队列,驱动程序将下半部注册到队列中,内核依照队列循序处理。性能有提升,但不能完全替换BH,无法处理高性能子系统;
3、软中断、tasklet和工作队列:2.3开发版本开始。软中断是静态定义的32个下半部接口(结构体数组),可在所有处理器上同时运行;tasklet基于软中断实现,可动态创建和加载,不同类型的tasklet可同时在不同cpu上运行,但同类型tasklet只能同时运行一个,由于其灵活性,tasklet是最常见的下半部实现;工作队列将下半部任务排队,在进程的上下文中执行,替代任务队列(2.5)。2.5版本后,BH被彻底废弃。
4、内核定时器:将工作延期到某个确定时间段去执行,适用于有时间要求的下半部。
2、信号是对中断的模拟
信号是进程处理异步事件的方法,信号是进程与内核、进程与进程通信的一种方式,是进程接受外部控制的一种方式,比如:接受内核执行除零计算返回的SIGSEGV信号,父进程接受子进程的SIGCHLD,接受键盘驱动产生的SIGINIT信号等。进程对信号的默认处理方式:终止进程、终止进程+生成core文件、忽略和暂停进程等。软件编程人员可以在程序中设置信号处理函数,以响应外部信号。
信号即软件中断,它与软中断不同,信号是软件程序的中断,软中断是内核的中断,但它们有许多的共同点,都将中断程序的运行并执行指定的函数,尤其是在使用中必须注意的同步和互斥问题,必须谨慎的使用临界区代码。为防止临界区代码对共享资源的并发访问,必须保证临界区代码的原子性。信号中断实际的中断点是在某个系统调用后,而不是用户函数。
当进程收到信号时,内核信号预处理函数do_singal()会将信号的处理句柄(对应信号处理函数)插入到用户进程的程序堆栈中,在当前系统调用结束并返回后,信号处理函数才会被pop并执行,最终返回执行用户程序。
对于慢速系统调用,慢速系统调用将进程运行状态置为TASK_INTERRUPTIBLE,可能在其为完全执行完毕前,信号的到来将导致其提前返回(内核进程调度函数将进程运行状态更新为TASK_RUNNING,唤醒进程,直接将进程入可调度队列并执行)。4.2BSD为部分慢速系统调用设计了自动重启动功能,详细内容可参照APUE。
各类信号设置函数,支持的系统请参看APUE2.p304
1、signal
2、sigset
3、sigvec
4、sigaction(推荐)
旧signal提供的是不可靠的信号机制,sigaction提供的是可靠的信号机制,但当前大多数系统的signal均以sigaction实现,所以signal也是可靠的信号处理了。但依然建议只使用sigaction,除非系统不支持。
这些安装信号处理程序的函数全部支持:保持已安装的信号处理程序,并提供阻塞信号的功能。
2.1、不可靠信号和可靠信号
早期UNIX版本的信号,可能会丢失,对信号的控制能力也非常弱,不能阻塞信号。比如:早期接收信号并处理时,该信号的处理复位为默认,而从捕获信号到执行信号处理函数之间会有一个时间窗,此时如果信号再次到来时,进程可能被终止。有时我们只想暂时阻塞信号,但是不要忽略信号,在进程准备好是再去获取并处理,但是早期的UNIX并不具备该功能。此时,信号处理是不可靠的。
在后期的4.2BSD和SVR3均提供了改进的信号处理机制,POSIX.1最终选择了BSD的可靠信号方案作为标准化的基础。
注:不要有这样的误解:由sigqueue()发送、sigaction安装的信号就是可靠的。事实上,可靠信号(实时信号)是指后来添加的新信号(信号值位于SIGRTMIN及SIGRTMAX之间);不可靠信号(非实时信号)是信号值小于SIGRTMIN的信号。信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。signal()及sigaction()都不能把SIGRTMIN以前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),而且对SIGRTMIN以后的信号都支持排队。这两个函数的最大区别在于,经过sigaction安装的信号都能传递信息给信号处理函数(对所有信号这一点都成立),而经过signal安装的信号却不能向信号处理函数传递信息。对于信号发送函数来说也是一样的。
2.2、简单的信号处理程序
信号机制具有以下三方面的功能:
(1 )发送信号。发送信号的主要函数有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort();
(2 )预置对信号的处理方式。接收信号的程序用 signal( ) 来实现对处理方式的预置;
(3 )收受信号的进程按事先的规定完成对相应事件的处理。
如下是一简单的信号处理程序,sigaction设置信号SIGINT的处理方式,用户通过按Crtl+C生成SIGINT信号给当前进程,程序收到信号后foo函数打印信息。
#include<stdio.h> #include<unistd.h> #include<signal.h> void foo(int signum) { printf("Hello signal(%d), i catch you!\n", signum); } int main() { struct sigaction act, oact; act.sa_handler = foo; sigemptyset(&act.sa_mask); act.sa_flags = 0; if(sigaction(SIGINT, &act, &oact) < 0) { return (-1); } while(1) { sleep(1); } return 0; }
2.3、信号发送函数
1、int kill(pid_t pid, int signo)
2、int raise(int signo)
3、int sigqueque(pid_t pid, int signo, const union sigval val)
4、unsigned int alarm(unsigned int seconds)
5、int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue))
6、void abort(void)
2.4、信号处理预置函数
2.5、信号处理函数编程
2.6、信号与中断的异同
信号与中断的相似点:
(1 )采用了相同的异步通信方式;
(2 )当检测出有信号或中断请求时,都暂停正在执行的程序而转去执行相应的处理程序;
(3 )都在处理完毕后返回到原来的断点;
(4 )对信号或中断都可进行屏蔽。
信号与中断的区别:
(1 )中断有优先级,而信号没有优先级,所有的信号都是平等的;
(2 )信号处理程序是在用户态下运行的,而中断处理程序是在核心态下运行;
(3 )中断响应是及时的,而信号响应通常都有较大的时间延迟。
3、异步信号安全高级编程
3.1、异步信号安全、可重入与线程安全
3.2、化异步为同步
3.3、信号安全的系统调用
4、用信号安全的系统调用实现非安全usleep功能
5、定时器
6、信号安全编程总结
7、信号、线程、进程、操作系统
8、参考资料
参考网上资源:
关于软中断:http://www.ibm.com/developerworks/cn/linux/kernel/interrupt/index.html
信号入门与信号编程:http://www.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html 对信号相关的基础知识介绍的很全面
http://www.ibm.com/developerworks/cn/linux/l-ipc/part2/index2.html 对信号编程做了较详细说明
http://www.man7.org/linux/man-pages/man7/signal.7.html signal的man信息
http://linux.chinaunix.net/techdoc/system/2006/06/04/933746.shtml 对信号的内核处理以及信号编程做了比较详细的描述
可重入、异步信号安全和线程安全:http://www.ibm.com/developerworks/cn/linux/l-cn-signalsec/ 提供了异步信号转化为同步信号的方法
http://www.ibm.com/developerworks/cn/linux/l-reent.html 使用可重入函数进行更安全的信号处理
http://blog.sina.com.cn/s/blog_8fa7dd4101015hi5.html 可重入、线程安全、异步信号安全小结
参考书目:
W. Richard Stevens, Advanced Programming in the UNIX Environment(2 Edition) 对信号编程有介绍
赵炯,Linux内核完全剖析——基于0.12内核 详细分析了0.12内核下的信号和中断处理源码
Robert Love, Linux Kernel Development(3 Edition) 有对Linux的中断机制的阐述