4.内核同步 2009-12-24 23:24 250人阅读 评论(0) 收藏
上述,在中断处理程序中,不可以发生进程切换,但在异常处理程序中,当前进程可能被另一进程取代,并按这种取代可否发生将内核分为可抢占式和不可抢占式。如进程A在异常处理程序中来了中断,在中断处理程序中唤醒了一个高优先级的进程B,如果内核是抢占式,那么会发生强制进程切换,用B切换A。异常处理则暂停(上一章述,它的一些信息存在内核异常栈中,该栈在进程的thread_info中)。再如,一个执行异常处理程序的进程用完了它的时间片,在抢占式内核情况下,进程会被取代,而非抢占式内核则会运行到异常处理结束。要区分的是内核的抢占与进程的抢占:Linux进程总是抢占的,而抢占式内核是由Love编写的,从linux2.6开始支持,Love写过一本《Linux内核设计与实现》。内核抢占的相关数据结构是thread_info结构中的preempt_count字段,它大于0时,禁用内核抢占,此字段由三个字段组成,分别是三个计数器:中断、可延迟函数、显式使用的抢占许可。所以,如上章所述,在硬中断或软中断中,preempt_count字段总大于0,即总是禁用内核抢占。综上,只有在显式抢占计数为0且在异常处理程序中时,内核才可以被抢占。但在异常准备返回时也要关中断,防止在做准备工作时被抢占。中断程序返回时,要检查CPU的thread_info中的flags字段是否为TIF_NEED_RESCHED,如果是,则会检查preempt_count与中断开关,确定是否要调用调度程序。内核抢占开销很大,在编译内核时可以设置相应的选项来开或关内核抢占。
现在,有中断中断服务程序、中断异常服务程序、内核抢占、内核线程,如果再加上多处理器,使得内核同步以保护相关的临界区域变得非常重要和复杂。以下是常用的同步原语,多处理器先忽略:
1. 原子操作:顾名思义,不多说了。它的实现是由inc,等芯片级不可中断汇编语句。在多处理器环境中要加lock前缀,锁定内存总线。Linux专门提供了C函数与宏来实现。
2. 优化屏障、内存屏障:用编译器优化代码时,不要指望指令会严格按出现在源代码中的顺序来执行,同时,现代CPU会并行执行若干指令,并可重新安排内存访问。所以处理器同步要避免指令重排序。优化屏障使编译器不用寄存器中的值来优化asm指令前的代码。类似C的volatile功能;内存屏障确保原语之前的操作已经完成,原语之后的未开始。Linux提供的原语兼有两个屏障的功能。
3. 自旋锁:自旋,即忙等。为何要忙等?不能像信号量一样把自己阻塞起来?因为当在内核控制路径中的进程,有时(应该说大多数时)不可抢占,所以不能阻塞。它的概念上的功能同信号量。当然,自旋锁锁区禁止内核抢占,否则乱套了。
4. 禁止中断:最容易想到的同步方案,简单快捷,问题是对系统并发性影响很大。
5. 其它:读写自旋锁,实现读写同步的自旋锁,同读写信号量类似;顺序锁类似读写自旋锁,只是算法不同,它允许写者提前,而读者在读前与读后得分别读取一个计数值,而写会改变这个计数值。如果读前后的计数值不一致,说明读的过程中出现了写,这时要重读。
回头再看自旋锁,如果单CPU时自旋,那就系统冻结了,显然不行。它是给多CPU专用的。单CPU中,异常处理可被抢占,即进程可阻塞,所以用信号量,其它内核控制路径(中断,软中断)都不可抢占,所以只能通过关中断实现同步。这也再次说明了可延迟函数的必要性。否则单个中断太长,对并发度影响很大。
版权声明:本文为博主原创文章,未经博主允许不得转载。