linux内核分析要点.docx

上传人:b****8 文档编号:9429977 上传时间:2023-02-04 格式:DOCX 页数:32 大小:149.50KB
下载 相关 举报
linux内核分析要点.docx_第1页
第1页 / 共32页
linux内核分析要点.docx_第2页
第2页 / 共32页
linux内核分析要点.docx_第3页
第3页 / 共32页
linux内核分析要点.docx_第4页
第4页 / 共32页
linux内核分析要点.docx_第5页
第5页 / 共32页
点击查看更多>>
下载资源
资源描述

linux内核分析要点.docx

《linux内核分析要点.docx》由会员分享,可在线阅读,更多相关《linux内核分析要点.docx(32页珍藏版)》请在冰豆网上搜索。

linux内核分析要点.docx

linux内核分析要点

linux内核分析--中断的分类

什么是中断

Linux内核需要对连接到计算机上的所有硬件设备进行管理,毫无疑问这是它的份内事。

如果要管理这些设备,首先得和它们互相通信才行,一般有两种方案可实现这种功能:

轮询(polling)让内核定期对设备的状态进行查询,然后做出相应的处理;中断(interrupt)让硬件在需要的时候向内核发出信号(变内核主动为硬件主动)。

第一种方案会让内核做不少的无用功,因为轮询总会周期性的重复执行,大量地耗用CPU时间,因此效率及其低下,所以一般都是采用第二种方案。

对于中断的理解我们先看一个生活中常见的例子:

QQ。

第一种情况:

你正在工作,然后你的好友突然给你发送了一个窗口抖动,打断你正在进行的工作。

第二种情况:

当然你有时候也会每隔5分钟就去检查一下QQ看有没有好友找你,虽然这很浪费你的时间。

在这里,一次窗口抖动就可以被相当于硬件的中断,而你就相当于CPU,你的工作就是CPU这在执行的进程。

而定时查询就被相当于CPU的轮询。

在这里可以看到:

同样作为CPU和硬件沟通的方式,中断是硬件主动的方式,较轮询(CPU主动)更有效些,因为我们都不可能一直无聊到每隔几分钟就去查一遍好友列表。

CPU有大量的工作需要处理,更不会做这些大量无用功。

当然这只是一般情况下。

好了,这里又有了一个问题,每个硬件设备都中断,那么如何区分不同硬件呢?

不同设备同时中断如何知道哪个中断是来自硬盘、哪个来自网卡呢?

这个很容易,不是每个QQ号码都不相同吗?

同样的,系统上的每个硬件设备都会被分配一个IRQ号,通过这个唯一的IRQ号就能区别张三和李四了。

从物理学的角度看,中断是一种电信号,由硬件设备产生,并直接送入中断控制器(如8259A)的输入引脚上,然后再由中断控制器向处理器发送相应的信号。

处理器一经检测到该信号,便中断自己当前正在处理的工作,转而去处理中断。

此后,处理器会通知OS已经产生中断。

这样,OS就可以对这个中断进行适当的处理。

不同的设备对应的中断不同,而每个中断都通过一个唯一的数字标识,这些值通常被称为中断请求线。

APICvs8259A

X86计算机的CPU为中断只提供了两条外接引脚:

NMI和INTR。

其中NMI是不可屏蔽中断,它通常用于电源掉电和物理存储器奇偶校验;INTR是可屏蔽中断,可以通过设置中断屏蔽位来进行中断屏蔽,它主要用于接受外部硬件的中断信号,这些信号由中断控制器传递给CPU。

常见的中断控制器有两种:

1.可编程中断控制器8259A

传统的PIC(ProgrammableInterruptController)是由两片8259A风格的外部芯片以“级联”的方式连接在一起。

每个芯片可处理多达8个不同的IRQ。

因为从PIC的INT输出线连接到主PIC的IRQ2引脚,所以可用IRQ线的个数达到15个,如图1所示。

图1:

8259A级联原理图

2.高级可编程中断控制器(APIC)

8259A只适合单CPU的情况,为了充分挖掘SMP体系结构的并行性,能够把中断传递给系统中的每个CPU至关重要。

基于此理由,Intel引入了一种名为I/O高级可编程控制器的新组件,来替代老式的8259A可编程中断控制器。

该组件包含两大组成部分:

一是“本地APIC”,主要负责传递中断信号到指定的处理器;举例来说,一台具有三个处理器的机器,则它必须相对的要有三个本地APIC。

另外一个重要的部分是I/OAPIC,主要是收集来自I/O装置的Interrupt信号且在当那些装置需要中断时发送信号到本地APIC,系统中最多可拥有8个I/OAPIC。

每个本地APIC都有32位的寄存器,一个内部时钟,一个本地定时设备以及为本地中断保留的两条额外的IRQ线LINT0和LINT1。

所有本地APIC都连接到I/OAPIC,形成一个多级APIC系统,如图2所示。

图2:

多级I/OAPIC系统

目前大部分单处理器系统都包含一个I/OAPIC芯片,可以通过以下两种方式来对这种芯片进行配置:

1)作为一种标准的8259A工作方式。

本地APIC被禁止,外部I/OAPIC连接到CPU,两条LINT0和LINT1分别连接到INTR和NMI引脚。

2)作为一种标准外部I/OAPIC。

本地APIC被激活,且所有的外部中断都通过I/OAPIC接收。

辨别一个系统是否正在使用I/OAPIC,可以在命令行输入如下命令:

#cat/proc/interrupts

CPU0

0:

90504IO-APIC-edgetimer

1:

131IO-APIC-edgei8042

8:

4IO-APIC-edgertc

9:

0IO-APIC-levelacpi

12:

111IO-APIC-edgei8042

14:

1862IO-APIC-edgeide0

15:

28IO-APIC-edgeide1

177:

9IO-APIC-leveleth0

185:

0IO-APIC-levelvia82cxxx

...

如果输出结果中列出了IO-APIC,说明您的系统正在使用APIC。

如果看到XT-PIC,意味着您的系统正在使用8259A芯片。

在CPU中集成了APIC,在SMP上,主板上有一个(至少一个,有的主板有多个IO-APIC,用来更好的分发中断信号)全局的APIC,它负责从外设接收中断信号,再分发到CPU上,这个全局的APIC被称作IO-APIC。

还有一部分是“本地APIC”,主要负责传递中断信号到指定的处理器;举例来说,一台具有三个处理器的机器,则它必须相对的要有三个本地APIC。

中断分类

中断,广义的来说通常被定义为一个事件,该事件触发改变处理器执行指令的顺序。

狭义地来说,针对80x86体系,中断被分为中断和异常,又叫同步中断和异步中断。

注意广义的中断和狭义的中断千万不要混淆,以后我的博文中所有所谓的“中断”二字,就是指狭义的中断,即Linux处理80x86异步中断的细节。

我们首先必须好好理清一下80x86体系中,

中断可分为同步(synchronous)中断和异步(asynchronous)中断:

1.同步中断是当指令执行时由CPU控制单元产生,之所以称为同步,是因为只有在一条指令执行完毕后CPU才会发出中断,而不是发生在代码指令执行期间,比如系统调用。

2.异步中断是指由其他硬件设备依照CPU时钟信号随机产生,即意味着中断能够在指令之间发生,例如键盘中断。

(PS:

总结为一句话:

中断时由硬件产生的异步中断,而异常则是处理器产生的同步中断)

根据Intel官方资料,同步中断称为异常(exception),异步中断被称为中断(interrupt)。

中断可分为可屏蔽中断(Maskableinterrupt)和非屏蔽中断(Nomaskableinterrupt)。

异常可分为故障(fault)、陷阱(trap)、终止(abort)三类。

从广义上讲,中断可分为四类:

中断、故障、陷阱、终止。

这些类别之间的异同点请参看表1。

表1:

中断类别及其行为

类别

原因

异步/同步

返回行为

中断

来自I/O设备的信号

异步

总是返回到下一条指令

陷阱

有意的异常

同步

总是返回到下一条指令

故障

潜在可恢复的错误

同步

返回到当前指令

终止

不可恢复的错误

同步

不会返回

X86体系结构的每个中断都被赋予一个唯一的编号或者向量(8位无符号整数)。

非屏蔽中断和异常向量是固定的,而可屏蔽中断向量可以通过对中断控制器的编程来改变。

中断:

1.可屏蔽中断:

当中断被屏蔽,则CPU控制单元就忽略它。

这里提一下,所有的IRQ中断都是可屏蔽中断。

2.非可屏蔽中断:

总由CPU辨认并处理。

所以,其为非常紧急的硬件故障。

向量中断——由硬件提供中断服务程序入口地址,当中断为向量中断时,直接跳转到预先提供的中断服务程序执行,这种处理方式响应速度快

非向量中断——由软件件提供中断服务程序入口地址。

当中断为非向量中断时,无论是什么外部中断源发出的中断,cpu将跳到指定的一段程序执行(称为中断解析程序),在解析程序里,通过判断相应的中断状态寄存器找到对应的中断源,跳转到相应的中断执行程序。

有点类似软件中断的处理方式,但是软中断(SWI)与非向量中断不同,它的入口是0x0000,0008。

进入软中断后,系统变为管理模式。

而非向量中断入口是0x0000,0018。

它引导系统进入fiq/irq模式。

这种处理方式简单,但是要通过软件查询来判断具体的中断服务程序,所有延迟时间较长.

向量中断模式用于RESET、NMI、异常处理。

当向量中断产生时,控制器直接将PC赋值,如跳到0x0000000d处,而在0x0000000d地址处通常放置ISR服务程序地址LDRPC,=ISR_HANDLER。

非向量中断模式,有一个寄存器标识位,跳转到统一的函数地址,此函数通过判别寄存器标识位和优先级关系进行中断处理。

向量中断模式是当CPU读取位于0x18处的IRQ中断指令的时候,系统自动读取对应于该中断源确定地址上的指令取代0x18处的指令,通过跳转指令系统就直接跳转到对应地址函数中,节省了中断处理时间提高了中断处理速度。

例如ADC中断的向量地址为0xC0,则在0xC0处放如下代码:

ldrPC,=HandlerADC当ADC中断产生的时候系统会自动跳转到HandlerADC函数中处理中断。

非向量中断模式处理方式是一种传统的中断处理方法,当系统产生中断的时候,系统将INTPND寄存器中对应标志位置位,然后跳转到位于0x18处的统一中断函数中;该函数通过读取INTPND寄存器中对应标志位来判断中断源,并根据优先级关系再跳到对应中断源的处理代码中处理中断。

Linux2.6中断处理原理简介

中断描述符表(InterruptDescriptorTable,IDT)是一个系统表,它与每一个中断或异常向量相联系,每一个向量在表中存放的是相应的中断或异常处理程序的入口地址。

内核在允许中断发生前,也就是在系统初始化时,必须把IDT表的初始化地址装载到idtr寄存器中,初始化表中的每一项。

当处于实模式下时,IDT被初始化并由BIOS程序所使用。

然而,一旦Linux开始接管,IDT就被移到ARM的另一个区域,并进行第二次初始化,因为Linux不使用任何BIOS程序,而使用自己专门的中断服务程序(例程)(interruptserviceroutine,ISR)。

中断和异常处理程序很像常规的C函数

有三个主要的数据结构包含了与IRQ相关的所有信息:

hw_interrupt_type、irq_desc_t和irqaction,图3解释了它们之间是如何关联的。

图3:

IRQ结构之间的关系

在X86系统中,对于8259A和I/OAPIC这两种不同类型的中断控制器,hw_interrupt_type结构体被赋予不同的值,

在中断初始化阶段,调用hw_interrupt_type类型的变量初始化irq_desc_t结构中的handle成员。

在早期的系统中使用级联的8259A,所以将用i8259A_irq_type来进行初始化,而对于SMP系统来说,要么以ioapic_edge_type,或以ioapic_level_type来初始化handle变量。

对于每一个外设,要么以静态(声明为static类型的全局变量)或动态(调用request_irq函数)的方式向Linux内核注册中断处理程序。

不管以何种方式注册,都会声明或分配一块irqaction结构(其中handler指向中断服务程序),然后调用setup_irq()函数,将irq_desc_t和irqaction联系起来。

当中断发生时,通过中断描述符表IDT获取中断服务程序入口地址,对于32≤i≤255(i≠128)之间的中断向量,将会执行push$i-256,jmpcommon_interrupt指令。

随之将调用do_IRQ()函数,以中断向量为irq_desc[]结构的下标,获取action的指针,然后调用handler所指向的中断服务程序。

从以上描述,我们不难看出整个中断的流程,如图4所示:

图4:

X86中断流

中断线程化

中断线程化是实现Linux实时性的一个重要步骤,在Linux标准内核中,中断时最高优先级的执行单元,不管内核代码当时处理什么,只要有中断事件,系统将立即响应改事件并执行相应的中断处理代码,除非当时中断禁用。

因此,如果系统有严重的网络或I/O负载,中断将非常频繁,实时任务将很难有机会运行,也就是说毫无实时性可言。

应用背景

Linux实时性的要求,中断线程化之后,中断将作为内核线程运行而且赋予不同的实时优先级,实时任务可以有比中断线程更高的优先级,这样,实时任务就可以作为最高优先级的执行单元来运行,即使在严重负载下仍有实时性保证。

中断线程化的另一个重要原因是spinlock被mutex取代。

中断处理代码中大量地使用了spinlock,当spinlock被mutex取代之后,中断处理代码就有可能因为得不到锁而需要被挂到等待队列上,但是只有可调度的进程才可以这么做,如果中断处理代码仍然使用原来的spinlock,则spinlock取代mutex的努力将大打折扣,因此为了满足这一要求,中断必须被线程化,包括IRQ和softirq。

实现原理

现在的线程化是作为内核R-TPatch实现的,并不是主流,是一个实时补丁。

我们简单看一下其整个实现流程。

[4][5]

中断初始化:

对于向量表的初始化在系统引导时就已经开始了。

通过调用setupidt将向量表中的每一项都初始化为默认的中断服务例程ignoreint。

这个默认的例程只是打印中断向量号,因此需要进一步初始化;在调用startkernel()函数进行内核初始化时,将调用initIRQ()函数对中断进行第二次初始化,将IRQi对应的IDT表项设成interrupti,这部分已经运行于保护模式下。

在startkernel结尾处调用我们的线程初始化函数inithardirqs,该函数为每个IRQ创建一个内核线程。

最高实时优先级为50,依次类推直到25,因此任何IRQ线程的最低实时优先级为25。

中断处理过程:

如果某个中断号状态位中的IRQNODELAY被置位,那么该中断不能被线程化。

在中断处理阶段,两者之间的异同点主要体现在:

两者相同的部分是当发生中断时,CPU将调用doIRQ()函数来处理相应的中断,doIRQ()在做了必要的相关处理之后调用_doIRQ()。

两者最大的不同点体现在doIRQ()函数中,在该函数中,将判断该中断是否已经被线程化(如果中断描述符的状态字段不包含IRQNODELAY标志,则说明该中断被线程化了),对于没有线程化的中断,将直接调用handleIRQ_event()函数来处理。

对于已经线程化的情况,调用wakeupprocess()函数唤醒中断处理线程,并开始运行,内核线程将调用dohardirq()来处理相应的中断,该函数将判断是否有中断需要被处理,如果有就调用handleIRQevent()来处理。

handleIRQ_event()将直接调用相应的中断处理函数来完成中断处理。

线程化之后,我们还需要思考的问题

一旦中断线程化之后,中断将会运行在进程上下文中,而之前中断上下文所有的约束在这里将不复存在。

我们也可以动态地对线程化的中断设置优先等级。

那么,对于这种中断世界“革命性”的改变,我们又该有哪些思考:

还需要下半部吗?

中断线程化后,中断可以运行与进程上下文,没有了中断上下文的束缚。

那么我们还需要下半部吗,因为在下半部能做的事情,现在都可以通过线程化中断实现了。

作为我个人来看,并不认为这时候就可以草率的淘汰下半部。

首先,中断线程化还需完善,还需标准化。

现在只是作为一个补丁提供在内核中,还需考验。

其次,中断线程化并不是万能的,我们很多事情是中断线程化不能实现的,后面会谈到。

再者,下半部作为辅助中断处理程序来完成推后执行的工作,在一些场合下仍然具有不可替代的作用。

和CPU亲和力相结合

同中断亲和力一样,CPU也有亲和力,所谓CPU亲和力就是通过设置CPU亲和力(CPUaffinity),将一个或多个进程绑定到一个或多个处理器上运行。

一旦中断线程化后,中断就可以用进程(或线程)的观点来看待,这就为我们把中断线程化和CPU亲和力相结合提供可能。

对于线程化的中断,我们可以通过设置CPU亲和力来限制线程迁移和优化处理器Cache使用率。

当然可能的应用还有很多,这也是研究的方向。

线程化不一定永远好

并不是所有的中断都可以被线程化,比如时钟中断,主要用来维护系统时间以及定时器等,其中定时器是操作系统的脉搏,一旦被线程化,就有可能被挂起,这样后果将不堪设想,所以不应当被线程化。

类似的还有串行端口等。

所以即使现在我们能够线程化我们的中断并不意味着我们都应该线程化我们的中断。

linux内核分析--浅析中断处理

linux内核的中断处理,里面由始至终都贯穿着"重要的事马上做,不重要的事推后做"的异步处理思想.于是整理一下~

第一阶段--获取中断号

每个CPU都有响应中断的能力,每个CPU响应中断时都走相同的流程.这个流程就是内核提供的中断服务程序.

在进入中断服务程序时,CPU已经自动禁止了本CPU上的中断响应,因为CPU不能假定中断服务程序是可重入的.

中断处理程序的第一步要做两件事情:

1.将中断号压入栈中;(不同中断号的中断对应不同的中断服务程序入口)

2.将当前寄存器信息压入栈中;(以便中断退出时恢复)

显然,这两步都是不可重入的(如果在保存寄存器值时被中断了,那么另外的操作很可能就把寄存器给改写了,现场将无法恢复),所以前面说到的CPU进入中断服务程序时要自动禁止中断.

栈上的信息被作为函数参数,调用do_IRQ函数.

第二阶段--中断串行化

进入do_IRQ函数,第一步进行中断的串行化处理,将多个CPU同时产生的某一中断进行串行化.其方法是如果当前中断处于"执行"状态(表明另一个CPU正在处理相同的中断),则重新设置它的"触发"标记,然后立即返回.正在处理同一中断的那个CPU完成一次处理后,会再次检查"触发"标记,如果设置,则再次触发处理过程.

于是,中断的处理是一个循环过程,每次循环调用handle_IRQ_event来处理中断.

第三阶段--关中断条件下的中断处理

进入handle_IRQ_event函数,调用对应的内核或内核模块通过request_irq函数注册的中断处理函数.

注册的中断处理函数有个中断开关属性,一般情况下,中断处理函数总是在关中断的情况下进行的.而调用request_irq注册中断处理函数时也可以设置该中断处理函数在开中断的情况下进行,这种情况比较少见,因为这要求中断处理代码必须是可重入的.(另外,这里如果开中断,正在处理的这个中断一般也是会被阻塞的.因为正在处理某个中断的时候,硬件中断控制器上的这个中断并未被ack,硬件不会发起下一次相同的中断.)

中断处理函数的过程可能会很长,如果整个过程都在关中断的情况下进行,那么后续的中断将被阻塞很长的时间.

于是,有了soft_irq.把不可重入的一部分在中断处理程序中(关中断)去完成,然后调用raise_softirq设置一个软中断,中断处理程序结束.后面的工作将放在soft_irq里面去做.

第四阶段--开中断条件下的软中断

上一阶段循环调用完当前所有被触发的中断处理函数后,do_softirq函数被调用,开始处理软件中断.

在软中断机制中,为每个CPU维护了一个若干位的掩码集,每位掩码代表一个中断号.在上一阶段的中断处理函数中,调用raise_softirq设置了对应的软中断,到了这里,软中断对应的处理函数就会被调用(处理函数由open_softirq函数来注册).

可以看出,软中断与中断的模型很类似,每个CPU有一组中断号,中断有其对应的优先级,每个CPU处理属于自己的中断.最大的不同是开中断与关中断.

于是,一个中断处理过程被分成了两部分,第一部分在中断处理函数里面关中断的进行,第二部分在软中断处理函数里面开中断的进行.

由于这一步是在开中断条件下进行的,这里还可能发生新的中断(中断嵌套),然后新中断对应的中断处理又将开始一个新的第一阶段~第三阶段。

在新的这个第三阶段中,可能又会触发新的软中断。

但是这个新的中断处理过程并不会进入第四阶段,而是当它发现自己是嵌套的中断时,完成第三阶段之后就会退出了。

也就是说,只有第一层中断处理过程会进入第四阶段,嵌套发生的中断处理过程只执行到第三阶段。

然而嵌套发生的中断处理过程也可能会触发软中断,所以第一层中断处理过程在第四阶段需要是一个循环的过程,需要循环处理嵌套发生的所有软中断。

为什么要这样做呢?

因为这样可以按软中断触发的顺序来执行这些软中断,否则后来的软中断可能就会先执行完成了。

极端情况下,嵌套发生的软中断可能非常多,全部处理完可能需要很长的时间,于是内核会在处理完一定数量的软中断后,将剩下未处理的软中断推给一个叫ksoftirqd的内核线程来处理,然后结束本次中断处理过程。

第五阶段--开中断条件下的tasklet

实际上,软中断很少直接被使用.而第二部分开中断情况下的进行的处理过程一般是由tasklet机制来完成的.

tasklet是由软中断引出的,内核定义了两个软中断掩码HI_SOFTIRQ和TASKLET_SOFTIRQ(两者优先级不同),这两个掩码对应的软中断处理函数作为入口,进入tasklet处理过程.

于是,在第三阶段的中断处理函数中,完成关中断的部分后,然后调用tasklet_schedule/tasklet_hi_schedule标记一个tasklet,然后中断处理程序结束.后面的工作由HI_SOFTIRQ/TASKLET_SOFTIRQ对应的软中断处理程序去处理被标记的tasklet(每个tasklet在其初始化时都设置了处理函数).

看上去,tasklet只不过是在softirq的基础上多了一层调用,其作用是什么呢?

前面说过,softirq是与CPU相对应的,每个CPU处理自己的softirq.这些softirq的处理函数需要设计为可重入的,因为它们可能在多个CPU上同时运行.而tasklet则是在多个CPU间被串行化执行的,其处理函数不必考虑可重入的事情.

然而,softirq毕竟还是要比tasklet少绕点弯路,所以少数实时性要求相对较高的处理过程还是在精心设计之后,直接使用softirq了.比如:

时钟中断处理过程,网络发送/接收处理过程.

结尾阶段

CPU接收到中断以后,以历以上五个阶段,中断处理完成.最后需要恢复第一阶段中被保存在栈上的寄存器信息.中断处理结束.

关于调度

上面的流程中,还隐含了一个问题,整个处理过程是持续占有CPU的

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 人文社科 > 设计艺术

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1