2009.6.18更新:参考http://linux.derkeiler.com/Mailing-Lists/Kernel/2004-03/4562.html
,查证LXR,重新诠释PREEMPT_ACTIVE标志。
中断信号分类
中断信号是一个统称,统称那些改变CPU指令执行序列的事件。但它又分为两种:
一种是同步的,没那么突然,因为它只在一个指令的执行终止之后才发生,书中依从Intel的惯例,称为异常(Exception)。一般是编程错误(一般的处理是发信号)或者内核必须处理的异常情况(内核会采取恢复异常所需的一些步骤);
一种是异步的,突然一些,因为它是由间隔定时器和I/O设备产生的,只遵循CPU时钟信号,所以可能在任何时候产生,书中也依从Intel的惯例,称为中断(Interrupt)。
内核控制路径
内核在允许中断信号到来之前,必须先准备好对它们的处理,也就是适当地初始化中断描述符表(Interrupt Descriptor Table, IDT)。
中断信号一来,CPU控制单元就自动把当前的程序计数器(eip、cs)和eflags保存到内核stack,然后把事先与发生的中断信号类型关联好的处理程序的地址(保存在IDT中)放进程序计数器。这时,内核控制路径(kernel control path)横空出世。
什么是内核控制路径?它是不是一个进程?不是。内核进程?也不是。它虽然也需要切换上下文,需要保存那些它可能使用的寄存器的并在返回时恢复,但这是一个非常轻的上下文切换。它诞生的时候并没有发生进程切换,处理中断的主语仍然是中断发生时正在执行的那个进程。那个进程就像突然被内核抓进了一间小屋做事,或者突然潜入了水(内核)里不见踪影,但它仍然在使用分配给它的那段时间片。
有趣的是,如果一个进程还在处理一个异常的时候,分配给它的时间片到期了,会发生什么事情呢?这取决于有没有启用内核抢占(Kernel Preemption),如果没有启用,进程就继续处理异常,如果启用了,进程可能会立即被抢占,异常的处理也就暂停了,直到schedule()再度选择原先那个进程(注意:内核处理中断的时候,必然会禁用内核抢占,所以这里才说是异常)。
中断信号处理的约束
中断信号处理需要满足下面三个严格的约束:
1)中断处理要尽可能块地完成、返回。因此只执行关键而紧急的部分,尽可能把更多的后续处理过程仅仅标志一下,放到之后再去执行。
2)一个中断还在处理的时候,另外一个中断可能又来了,这个时候最好能先放下手中的处理,先去处理新的中断,然后在回头来接着处理这个中断,这称之为中断和异常处理程序的嵌套执行(nested execution),或者说是内核控制路径的嵌套执行。要实现这一点,有一点必须满足,那就是中断处理程序运行期间不能阻塞,不能发生进程切换。
如果对异常的种类做一番思考,就会发现,异常最多嵌套两层,一个由系统调用产生,一个由系统调用执行过程中的缺页产生(这时必然挂起当前进程,发生进程切换)。与之相反,在复杂的情况下,中断产生的嵌套则可能任意多。
3)内核中存在一些临界区,在这些临界区,中断必须被禁止。中断处理程序要尽可能地减少进入临界区的次数和时间,为了内核的响应性能,中断应该在大部分时间都是启用的。
异常的种类
异常有很多种,其中比较有趣的有:
编号
|
异常
|
异常处理程序
|
信号
|
有趣之处
|
1
|
Debug
|
debug( )
|
SIGTRAP
|
用于调试
|
3
|
Breakpoint
|
int3( )
|
SIGTRAP
|
7
|
Device not available
|
device_not_available( )
|
None
|
用于在需要的时候才加载FPU
、MMX
、XMM
(
当cr0
的TS
标志被设置)
|
14
|
Page Fault
|
page_fault( )
|
SIGSEGV
|
如果是正常缺页,内核会挂起当前进程,然后将该页读入RAM
;如果是页错误,就发出信号。
|
4
|
Overflow
|
overflow( )
|
SIGSEGV
|
调试时非常常见的一个信号SIGSEQV
,Segment Violation
,呵呵,关注一下都是什么异常导致的。
|
5
|
Bounds check
|
bounds( )
|
SIGSEGV
|
10
|
Invalid TSS
|
invalid_TSS( )
|
SIGSEGV
|
13
|
General protection
|
general_protection( )
|
SIGSEGV
|
中断描述符
Intel 80x86 CPU认得三种中断描述符,Linux为了检验权限,将其细分为:
Interrupt Gate, DPL = 0的中断门,set_intr_gate(n,addr),所有中断
System Interrupt Gate,DPL = 3的中断门,set_system_intr_gate(n,addr),int3异常
System Gate,DPL = 3的陷阱门,set_system_gate(n,addr),into、bound、int $0x80异常
Trap Gate, DPL = 0的陷阱门,set_trap_gate(n,addr),大部分异常
Task Gate, DPL = 0的任务门,set_task_gate(n,gdt),double fault异常
异常处理的标准结构
-
用汇编把大多数寄存器的值保存到kernel stack;
-
-
通过ret_from_exception(
)
函数退出处理程序.
I/O中断处理的标准结构
-
将IRQ值和寄存器值保存到kernel stack;
-
给服务这条IRQ线的PIC发送应答,从而允许它继续发出中断;
-
-
通过跳转到ret_from_intr( )
的地址结束中断处理。
IRQ(Interrupt ReQuest)线(IRQ向量)的分配
IRQ共享:几个设备共享一个IRQ,中断来时,每个设备的中断服务例程(Interrupt Service Routine,ISR)都执行,检查一下是否与己有关;
IRQ动态分配:IRQ可以在使用一个设备的时候才与一个设备关联,这样同一个IRQ就可以被不同的设备在不同时间使用。
中断向量中,0-19用于异常和非屏蔽中断,20-31被Intel保留了,32-238这个范围内都可以分配给物理IRQ,但128(0x80)被分配给用于系统调用的可编程异常。
延后的工作谁来做?
首先是两种非紧迫的、可中断的内核函数——可延迟函数(deferrable functions
),然后是通过工作队列(work queues )来执行的函数。
软中断(softirq)是可重入函数而且必须明确地使用自旋锁保护其数据结构;tasklet在软中断基础上实现,但由于内核保证不会在两个CPU上同时运行相同类型的tasklet,所以它不必是可重入的。
六种软中断
Softirq
Index (priority)
Description
HI_SOFTIRQ
|
0
|
Handles high priority tasklets
|
TIMER_SOFTIRQ
|
1
|
Tasklets related to timer interrupts
|
NET_TX_SOFTIRQ
|
2
|
Transmits packets to network cards
|
NET_RX_SOFTIRQ
|
3
|
Receives packets from network cards
|
SCSI_SOFTIRQ
|
4
|
Post-interrupt processing of SCSI commands
|
TASKLET_SOFTIRQ
|
5
|
Handles regular tasklets
|
内核会在一些检查点(适宜的时候,其中有时钟中断)检查挂起的软中断,用__do_softirq()执行它们。__do_softirq()会循环若干次,以保证处理掉一些在处理过程中新出现的软中断,但如果还有更多新挂起的软中断,__do_softirq()就不管了,而是调用wakeup_softirq()唤醒每CPU内核进程ksoftirqd/n(这样就可以被调度,而不会一直占着CPU),来处理剩下的软中断。
这种做法是为了解决一个矛盾:与网络相关的软中断是高流量的,也是对实时性有一定要求的。但是如果do_softirq()为了实时性一直处理它们,就会一直不返回,结果用户程序就僵在那里了;如果do_softirq()处理完一些软中断就返回,不论这中间机器有无空闲,直到下一个时钟中断才又处理其余的,网络处理需要的许多实时性就得不到保证。现在的做法,唤醒内核进程,让它在后台调度,由于内核进程优先级很低,用户程序就有机会运行,不会僵死;但如果机器空闲下来,挂起的软中断很快就能被执行。
tasklet则多用于在I/O驱动程序的开发中实现可延迟函数。
但是,可延迟函数有一个限制,它是运行在中断上下文的,它执行时不可能有任何正在运行的进程,它也不能调用任何可阻塞(从而会休眠)的函数。这就是工作队列的意义所在。工作队列把需要执行的内核函数交给一些内核进程来执行。
处于效率的考虑,内核预定义了叫做events的工作队列,内核开发者可以用schedule_work族函数随意呼唤它们。
内核抢占(Kernel Preemption)
本章在很多地方都涉及到了内核抢占,我觉得还是将内核抢占在本章的笔记记完,不必像原书那样等到内核同步一章了。
在非抢占内核的情形,一个执行在内核态的进程是不可能被另外的进程取代的(进程切换);而在抢占内核的情形,是有可能的:但只有当内核正在执行异常处理程序(尤其是系统调用),而且内核抢占没有被显式禁用的时候,才可能抢占内核。
一个例子:当A在处理异常的时候,一个中断的处理程序唤醒了优先级更高的B,在抢占内核的情形,就会发生强制性进程切换。这样做的目的是减少dispatch latency,即从进程(结束阻塞)变为可执行状态到它实际开始运行的时间间隔,降低了它被另外一个运行在内核态的进程延迟的风险。
进程描述符中的thread_info字段中有一个32位的preempt_counter字段,0-7位为抢占计数器,用于记录显式禁用内核抢占的次数;8-15位为软中断计数器,记录可延迟函数被禁用的次数;16-27为硬中断计数器,表示中断处理程序的嵌套数(irq_enter()递增它,irq_exit()递减它);28位为PREEMPT_ACTIVE标志。只要内核检测到preempt_counter整体不为0,就不会进行内核抢占,这个简单的探测一下子保证了对众多不能抢占的情况的检测。
说明:
1)为了避免在可延迟函数访问的数据结构上发生的竞争条件,最简单直接的方法是禁用中断,但禁用中断有时太夸张了,所以有了禁用可延迟函数这回事。
2) PREEMPT_ACTIVE标志的本意是说明正在抢占,设置了之后preempt_counter就不再为0,从而执行抢占相关工作的代码不会被抢占。
它可被非常tricky地这样使用:
preempt_schedule()是内核抢占时进程调度的入口,其中调用了schedule()。它在调用schedule()前设置PREEMPT_ACTIVE标志,调用后清除这个标志。而schedule()会检查这个标志,对于不是TASK_RUNNING(state != 0)的进程,如果设置了PREEMPT_ACTIVE标志,就不会调用deactivate_task(),而deactivate_task()的工作是把进程从runqueue移除。
你可能会疑惑,为什么要预防已经不在RUNNING状态的进程从runqueue中移除?设想一下,一个进程刚把自己标志为TASK_INTERRUPTIBL,就被preempt了,它还没来得及把自己放进wait_queue中...这个时候当然要让它回头接着运行,直到把自己放进wait_queue然后自愿进程切换,那时才可以把它从runqueue中移除。
在面对内核的时候,思维不能僵化在操作系统提供给用户的进程切换的抽象中,而要想象一个永不停歇运行着的、虽然有意识地跳来跳去的指令流的。所以,没有标志为RUNNING不意味就不会还剩下一些(比如处理状态转换的)代码需要执行哦。
通过这个标志,保证了被抢占的进程将可以被正确地重新调度和运行。
在中断、异常、系统调用返回过程中也会设置PREEMPT_ACTIVE标志。
分享到:
相关推荐
Linux 2.6内核的中断机制是操作系统核心的重要组成部分,它负责处理来自硬件的各种事件,如键盘输入、网络数据包接收或定时器中断。在实时性方面,Linux内核的默认实现并不理想,尤其对于需要快速响应的嵌入式系统而...
在深入探讨Linux 2.6内核源代码中的存储技术原理之前,首先需要理解操作系统内核是如何管理和控制存储资源的。Linux内核是操作系统的核心,它负责管理硬件资源,包括内存和磁盘存储,以提供高效、可靠的系统服务。...
《存储技术原理分析_基于Linux 2.6内核源代码》是一本深入探讨存储技术的书籍,尤其侧重于从Linux 2.6内核源代码的角度进行解析。该书对于理解存储系统的底层运作机制、优化存储性能以及解决相关问题具有重要的指导...
LINUX 2.6内核标准教程(华清远见,河秦)(高清PDF共218M)10/10
"Linux2.6内核驱动移植" Linux2.6内核驱动移植是指将驱动程序移植到Linux2.6内核中,需要遵守新的编程规范和接口标准。以下是Linux2.6内核驱动移植中的重要知识点: 1. 新的入口函数:在Linux2.6内核中,驱动程序...
### Linux2.6 内核的 Initrd 机制解析 #### 深入理解Initrd技术 Initrd,全称Init RAM Disk,是Linux启动过程中一个关键的技术环节,尤其是在Linux2.6内核中,其机制与早期版本如2.4内核有了显著的变化。本文旨在...
然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...
"Linux 2.6内核测试及其到ARM嵌入式平台的移植" 本文将深入探讨Linux 2.6内核对嵌入式应用的影响,及其到ARM嵌入式平台的移植。Linux 2.6内核相对以前的Linux内核在可配置性和实时性方面有了很大的改进,特别是在...
LINUX 2.6内核标准教程(华清远见,河秦)(高清PDF共218M)9/10
存储技术原理分析_基于Linux 2.6内核源代码
### 存储技术原理分析:基于Linux_2.6内核源代码 #### 一、存储技术概述 存储技术是计算机系统中一个重要的组成部分,它主要用于数据的持久化保存。随着信息技术的发展,存储技术也在不断地演进和发展。本文将重点...
### Linux2.6内核IPSec支持机构的研究与分析 #### 摘要与背景 在互联网技术的持续演进中,确保数据传输的安全性变得日益关键。IPSec(Internet Protocol Security)作为一种重要的安全协议,它在IP层上提供了一...
《Linux 2.6内核标准教程》是一本专为Linux内核爱好者、驱动开发者和系统工程师设计的深度解析书籍。它旨在帮助读者理解和掌握Linux内核的核心工作原理,通过详细解析内核的关键组件,引领读者进入Linux内核的世界。...
### Linux 2.6内核设备模型分析 #### 引言 Linux 2.6内核设备模型是针对系统拓扑结构复杂化和对智能电源管理、热插拔及即插即用(PnP)需求增加的情况下进行的重大改进。与前代2.4内核相比,2.6内核引入了一套更为...
然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...
Linux 2.6 内核编译过程 Linux 2.6 内核编译过程是一个复杂的过程,需要了解 Linux 内核相关概念和编译过程中的各个步骤。在本文档中,我们将详细介绍 Linux 2.6 内核编译过程,并提供相关的知识点。 基础知识 在...
Linux2.6内核总结,供大家学习参考。。
Linux 2.6 内核在实时性方面存在一些不足之处,如调度器的时间复杂度高、内核不可抢占等问题,这些问题限制了 Linux 2.6 内核在嵌入式实时领域的应用。 知识点 3: O(1) 调度器 Linux 2.6 内核中实现了 O(1) 调度器...
然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...