`

深入理解Linux内核--中断和异常(阅读笔记)(原创)

阅读更多

深入理解Linux内核--中断和异常(阅读笔记)(原创)


 

由 王宇 原创并发布

 

 

第四章中断和异常

中断通常被定义为一个事件,该事件改变处理器执行的指令。这样的事件与CPU芯片内外部硬件电路产生的电信号相对应。

中断通常分为同步中断异步中断

同步中断是当指令执行时由CPU控制单元 产生的,之所以称为同步,是因为只有在一条指令终止执行后CPU才会发出中断。

异步中断是由其他硬件设备 依照CPU时钟信号随机产生的

在intel微处理器手册中,把同步和异步终端分别称为异常和中断 。当然有时我们也用术语“中断信号 “指着两种类型

中断是由间隔定时器和I/O设备产生的 ,例如,用户的一次按键会引起一个中断

另一方面,异常是由程序的错误产生的,或者是由内核必须处理的异常条件产生的 。第一种情况下,内核通过发送一个每个Unix程序员都熟悉的信号来处理异常。第二种情况下,内核执行恢复异常需要的所有步骤,例如:缺页,或对内核服务的一个请求

I/O设备产生IRQ

1、中断信号的作用

中断信号提供了一种特殊的方式,使处理器转而去运行正常控制流之外的代码。当一个中断信号到达时,CPU必须停止它当前正在做的事情,并且切换到一个新的活动。 为了做到这一点,就要在内核态堆栈保存程序计数器的当前值,并把与中断类型相关的一个地址放入程序计数器。

中断处理与进程切换有一个明显的差异:由中断或异常处理程序执行的代码不是一个进程。更确切地说,它是一个内核控制路径,代表中断发生时正在运行的进程。

中断处理是由内核执行的最敏感的任务之一,因为它必须满足下列约束

[1]当内核正打算去完成一些别的事情时,中断随时会到来。因此,内核和目标就是让中断尽可能快地处理完,尽其所能把更多的处理向后推迟 。因此,内核响应中断后需要进行的操作分为两部分:关键而紧急的部分,内核立即执行;其余推迟的部分内核随后执行。

[2]因为中断随时会到来,所以内核可能正在处理其中的一个中断时,另一个中断又发生了。应该尽可能多地允许这种情况发生,因此这能维持更多的I/O设备处于忙状态。因此,中断处理程序必须编写成是相应的内核控制路径能以嵌套的方式执行。 当最后一个内核控制路径终止时,内核必须能恢复被中断进程的执行,或者,如果中断信号已导致了重新调度,内核能切换到另外的进程。

[3]尽管内核在处理前一个中断时可以接受一个新的中断,但在内核代码中还是存在一些临界区 ,在临界区中,中断必须被禁止。必须尽可能地限制这样的临界区,因为根据以前的要求,内核,尤其是中断处理程序,应该在大部分时间内以开中断的方式运行

2、中断和异常

Intel文档把中断和异常分为以下几类:

中断:

可屏蔽中断

I/O设备发出的所有中断请求都产生可屏蔽中断。可屏蔽中断可以处于两种状态:屏蔽的或非屏蔽的;一个屏蔽的中断只要还是屏蔽的,控制单元就忽略它。

非屏蔽中断

只有几个危急事件(如硬件故障)才引起非屏蔽中断。非屏蔽中断总是由CPU辨认。

异常

故障 :通常可以纠正

陷阱 :在陷阱指令执行后立即报告;主要用于是为了调试程序。在这种情况下,中断信号的作用是通知调试程序一条特殊指令已被执行。

异常中止 :发生一个严重的错误

编程异常 :在编程者发出请求时发生

每个中断和异常是由0-255之间的一个数来标识。因为一些未知的原因,Intel把这个8位的无符号整数叫做一个向量。非屏蔽中断的向量和异常的向量是固定的,而可屏蔽中断的向量可以通过对中断控制器的编程来改变。

[1]IRQ和中断

每个能够发出中断请求的硬件设备控制器都有一条名为IRQ的输出线。所有现有的IRQ线都与一个名为可编程中断控制器的硬件电路的输入引脚相连 ,可编程中断控制器执行下列动作:

(1)监视IRQ线,检查产生的信号

(2)如果一个引发信号出现在IRQ线上:

a.把接收到的引发信号转换成对应的向量

b.把这个向量存放在中断控制器的一个I/O端口,从而允许CPU通过数据总线读此向量

c.把引发信号发送到处理器的INTR引脚,即产生一个中断

d.等待,直到CPU通过把这个中断信号写进可编程中断控制器的一个I/O端口来确认它;当这种情况发生时,清INTR线。

(3)返回到第1步

IRQ线是从0开始顺序编号的,因此,第一条IRQ线通常表示成IRQ0.与IRQn并联的Intel的缺省向量是n+32.

可以有选择地禁止每条IRQ线。因此,可以对PIC编程从而禁止IRQ,也就是说,可以告诉PIC停止对给定的IRQ线发布中断,或者激活它们。禁止的中断时丢失不了的,它们一旦被激活,PIC就又把它们发送到CPU。这个特点被大多数中断处理程序使用,因为这允许中断处理程序逐次地处理同一类型的IRQ

有选择地激活/禁止IRQ线不同于可屏蔽中断的全局屏蔽/非屏蔽

[2]高级可编程中断控制器

为了充分发挥SMP体系结构的并行性,能够把中断传递给系统中的每个CPU至关重要。基于此理由,Intel从PentiumIII开始引入了一种名为I/O高级可编程控制器(I/O Advanced Programmable InterruptController APIC)



图4-1

[3]异常

80x86微处理器发布了大约20种不同的异常。内核必须为每种异常提供一个专门的异常处理程序。更多信息可以在Intel的技术文档中找到

表4-1由异常处理程序发送的信号

[4]中断描述符表

中断描述符(Interrupt Descriptor Table IDT)是一个系统表,它与每一个中断或异常向量相关联,每一个向量在表中有相应的中断或异常处理程序的入口地址。 内核在允许中断发生前,必须适当地初始化IDT



图4-2

任务门 (task gate)

当中断信号发生时,必须取代当前进程的那个进程的TSS选择符存放在任务门中。

中断门 (interupt gate)

包含段选择符和中断或异常处理程序的段内偏移量。当控制权转移到一个适当的段时,处理器清IF标志,从而关闭将来会发生的可屏蔽中断

陷阱门 (Trap gate)

与中断门相似,只是控制权传递到一个适当的段时处理器不修改IF标志

Linux利用中断门处理中断,利用陷阱门处理异常

[5]中断和异常的硬件处理

操作:参考P145-146

3、中断和异常处理程序的嵌套执行

每个中断或异常都会引起一个内核控制路径,或者说代表当前进程在内核态执行单独的指令序列。 例如:当I/O设备发出一个中断时,相应的内核控制路径的第一部分指令就是那些把寄存器的内容保存在内核堆栈的指令,而最后一部分指令就是恢复寄存器内容并让CPU返回到用户态的那些指令

内核控制路径可以任意嵌套 :一个中断处理程序可以被另一个中断处理程序“中断”,因此引起内核控制路径的嵌套执行,如图4-3所示。其结果是,对中断进行处理的内核控制路径,其最后一部分指令并不总能使当前进程返回到用户态:如果嵌套深度大于1,这些指令将执行上次被打断的内核控制路径,此时的CPU依然运行在内核态

允许内核控制路径嵌套执行必须付出代价,那就是中断处理程序必须永不阻塞,换句话说,中断处理程序运行期间不能发生进程切换。 事实上,嵌套的内核控制路径恢复执行时,需要的所有数据都存放在内核态堆栈中,这个栈毫无疑义的属于当前进程。

事实上,异常要么是由编程错误引起,要么是有调试程序触发。然而,“Page Fault(缺页)”异常发生在内核态。

与异常形成对照的是,尽管处理中断的内核控制路径代表当前进程运行,但由I/O设备产生的中断并不引用当前进程的专有数据结构。事实上,当一个给定的中断发生时,要预测那个进程将会运行是不可能的。

一个中断处理程序即可以抢占其他的中断处理程序,也可以抢占异常处理程序。相反,异常处理程序从不抢占中断处理程序。 在内核态能触发的唯一异常就是刚刚描述的缺页异常。但是,中断处理程序从不执行可以导致缺页得操作。

基于以下两个主要原因,Linux交错执行内核控制路径

为了调高可编程中断控制器和设备控制器的吞吐量。 假定设备控制器在一条IRQ线上产生了一个信号,PIC把这个信号转换成一个外部中断,然后PIC和设备控制器保持阻塞,一直到PIC从CPU处接收到一条应答信息 。由于内核控制路径的交错执行,内核即使正在处理前一个中断,也能发送应答。

为了实现一种没有优先级的中断模型 。因为每个中断处理程序都可以被另一个中断处理程序延缓,因此,在硬件设备之间没必要建立预定义优先级。这就简化了内核代码,提高了内核的可移植性

4、初始化中断描述符表(IDT)

[1]中断门、陷阱门、及系统门

Intel提供了三种类型的中断描述符,Linux使用与Intel稍有不同的细目分类和术语,把它们分类:

中断门

用户态的进程不能访问的一个Intel中断门。所有的Linux中断处理程序都通过中断门激活,并全部限制在内核态

系统门

用户态的进程可以访问的一个Intel陷阱门。通过系统门来激活三个Linux异常处理程序,它们的向量是4,5及128,因此,在用户态下,可以发布intobound及int$0x85三条汇编语言指令

系统中断门

能够被用户态进程访问的Intel中断门。与向量3相关的异常处理程序是由系统中断门激活的,因此,在用户态可以使用汇编语言指令int3

陷阱门

用户态的进程不能访问的一个Intel陷阱门。大部分Linux异常处理程序都通过陷阱门来激活。

任务门

不能被用户态进程访问的Intel任务门。Linux对“Double fault”异常的处理程序是由任务门激活的

[2]IDT的初步初始化

当计算机还运行在实模式时,IDT被初始化并由BIOS例程使用。然而,一旦Linux接管,IDT就被移动到RAM的另一个区域,并进行第二次初始化,因此Linux没有利用任何BIOS例程

IDT存放在idt_table表中,有256个表项

5、异常处理

CPU产生的大部分异常都由Linux解释为出错条件。当其中一个异常发生时,内核就向引起异常的进程发送一个信号向它通知一个反常条件。

但是,在两种情况下,Linux利用CPU异常更有效地管理硬件资源:

“Devicenotavaileble”异常与cr0寄存器的TS标志一起用来把新值装入浮点寄存器。

“PageFault”异常,该异常推迟给进程分配新的页框,直到不能再推迟为止。相应的处理程序比较复杂,因为异常可能表示一个错误条件,页可能不表示一个错误条件

异常处理程序有一个标准的结构,由以下三部分组成

1、在内核堆栈中保存大多数寄存器的内容

2、用高级的C函数处理异常

3、通过ret_from_exception()函数从异常处理程序退出

[1]为异常处理程序保存寄存器的值

[2]进入和离开异常处理程序


6、中断处理

给当前进程发送一个Unix信号,这种方法并不适合中断

中断处理依赖于中断类型:

I/O中断: 某些I/O设备需要关注;相应的中断处理程序必须查询设备以确定适当的操作过程

时钟中断: 某种时钟产生一个中断;这种中断告诉内核一个固定的时间间隔已经过去。这些中断大部分是作为I/O中断来处理的

处理器间中断 :多处理器系统中一个CPU对另一个CPU发出一个中断

[1]I/O中断处理

I/O中断处理程序必须做够灵活以给多个设备同时提供服务。例如在PCI总线的体系结构中,几个设备可以共享同一个IRQ线。这就意味着仅仅中断向量不能说明所有问题,同一个向量43即分配给USB端口,也分配给声卡。

中断处理程序的灵活性是以两种不同的方式实现:

IRQ共享:

中断处理程序执行多个中断服务例程(interrupt servicer outine,ISR),每个ISR是一个与单独设备(共享IRQ线)相关的函数。因为不可能预先知道那个特定的设备产生IRQ,因此,每个ISR都被执行,以验证它的设备是否需要关注;如果是,当前设备产生中断时,就执行需要执行的所有操作

IRQ动态分配:

一条IRQ线在可能的最后时刻才与一个设备驱动程序相关联

当一个中断发生时,并不是所有的操作都具有相同的急迫性。事实上,把所有的操作都放进中断处理程序本身并不合适。需要时间长的、非重要的操作应该推后,因为当一个中断处理程序正在运行时,相应的IRQ线上发出的信号就被暂时忽略。更重要的是,中断处理程序是代表进程执行的,它代表的进程必须总处于TASK_RUNNING状态,否则,就可能出现系统僵死情况。因此,中断处理程序不能执行任何阻塞过程,如磁盘I/O操作。因此,Linux把紧随中断要执行的操作分为三类:

紧急的

非紧急的

非紧急可延迟的

所有的I/O中断处理程序都执行四个相同的基本操作

(1)在内核态堆栈中保存IRQ的值和寄存器的内容。

(2)为正在给IRQ线服务的PIC发送一个应答,这将允许PIC进一步发送中断

(3)执行共享这个IRQ的所有设备的中断服务例程(ISR)

(4)跳到ret_from_intr()的地址终止



**图4-4I/O中断处理

[2]中断向量

**表4-2,物理IRQ可以分配给32-238范围内的任何向量。不过,Linux使用向量128实现系统调用。



为IRQ可配置设备选择一条线有三种方式:

设置一些硬件跳接器

安装设备时执行一个实用程序

在系统启动时执行一个硬件协议

内核必须在启动中断前发现IRQ号与I/O设备之间的对应,否则,内核在不知道哪个向量对应哪个设备的情况下,怎么能处理来自这个设备的信号呢?

[3]IRQ数据结构

每个中断向量都有它自己的irq_desc_t描述符 ,参考表4-4



图4-5IRQ描述符

[4]IRQ在多处理器系统上的分发

Linux遵循对称多处理器模型(SMP);这就意味着,内核从本质上对任何一个CPU都不应该有偏爱。因而,内核视图以轮转的方式把来自硬件设备的IRQ信号在所有CPU之间分发。因此所有CPU服务于I/O中断的执行时间片几乎相同

简而言之,当硬件设备产生了一个中断信号时,多APIC系统就选择其中一个CPU,并把该信号传递给相应的本地APIC,本地APIC又依次中断它的CPU。这个事件不通报给其他所有的CPU

多APIC系统初始化后无需内核费心。遗憾的是在有些情况下,硬件不能以公平的方式在微处理器之间成功地分配中断,在必要的时候,Linux2.6利用叫做krqd的特殊内核线程来纠正对CPU进行的IRQ的自动分配

内核线程为多APIC系统开发了一种优良特性,叫做CPU的IRQ亲和力

[5]多种类型的内核栈

如果thread_union结构的大小为8KB,那么当前进程的内核栈被用于所有类型的内核控制路径:异常、中断和可延迟的函数。相反,如果thread_union结构的大小为4KB,内核就使用三种类型的内核栈:异常栈、硬中断请求栈、软中断请求栈

[6]为中断处理程序保存寄存器的值

当CPU接收一个中断时,就开始执行相应的中断处理程序代码,该代码的地址存放在IDT的相应门中,与其他上下文切换一样,需要保存寄存器这一点给内核开发者留下有点复杂的编码工作,因为寄存器的保存和恢复必须用汇编语言代码,但是,在这些操作内部,又期望处理器从C函数调用的返回。

保存寄存器是中断处理程序做的第一件事情

保存寄存器的值以后,栈顶的地址被存放到eax寄存器中,然后中断处理程序调用do_IRQ()函数。执行do_IRQ()的ret执行时,控制转到ret_from_intr()

[7]do_IRQ()函数

调用do_IRQ()函数执行与一个中断相关的所有中断服务例程:操作参考p166

[8]__do_IRQ()函数

接受IRQ号(通过eax寄存器)和指向pt_regs结构的指针(通过edx寄存器,用户态寄存器的值已经存在其中)作为它的参数

[9]挽救丢失的中断

在多处理器系统上事情可能不会这么顺利。

假定CPU有一条激活的IRQ线。一个硬件设备出现在这条IRQ线程上,且多APIC系统选择我们的CPU处理中断。在CPU应答中断前,这条IRQ线被另一个CPU屏蔽掉;结果,IRQ_DISABLED标志被设置。随后,我们的CPU开始处理挂起的中断;因此,do_IRQ()函数应答这个中断,然后返回,但没有执行中断服务例程,因为它发现IRQ_DISABLED标志被设置了,因此,在IRQ线禁用之前出现的中断丢失了。

为了应付这种局面,内核用来激活IRQ线的enable_irq()函数先检查是否发生了中断丢失,如果是,该函数就强迫硬件让丢失的中断再产生一次

[10]中断服务例程

一个中断服务例程(ISR)实现一种特定设备的操作。当中断处理程序必须执行ISR时,它就调用hand_IRQ_event()函数。步骤:参考p170

[11]IRQ线的动态分配

在激活一个准备利用IRQ线的设备之前,其相应的驱动程序调用request_irq()。这个函数建立一个新的irqaction函数,并用参数值初始化它。然后调用setup_irq()函数把这个描述符插入到适合的IRQ链表。如果setup_irq()返回一个出错码,设备驱动程序中止操作,这意味着IRQ线已有另一个设备所使用,而这个设备不允许中断共享。当设备操作结束时,驱动程序调用free_irq()函数从IRQ链表中删除这个描述符,并释放相应的内存区。

request_irq()

free_irq()

[12]处理器间中断处理

处理器间中断允许一个CPU向系统中的其他CPU发送中断信号。

Linux定义了三种处理器间中断:

CALL_FUNCTION_VECTOR:发送所有的CPU(不包含发送者),强制这些CPU运行发送者传递过来的函数。相应的中断处理程序叫做call_function_interrupe()

RESCHEDULE_VECTOR:当一个CPU接收这种类型的中断时,相应的处理程序限定自己来应答中断。

INVALIDATE_TLB_VECTOR:发往所有的CPU(不包含发送者),强制它们的转换后援缓冲器(TLB)变为无效

处理器间中断处理程序的汇编语言代码是由BUILD_INTERRUPTE宏产生的

7、软中断及tasklet

在由内核执行的几个任务之间有些不是紧急的:在必要情况下它们可以延迟一段时间。把可延迟中断从中断处理程序中抽出来有助于使内核保持较短的响应时间。这对于那些期望它们的中断能在几毫秒内得到处理的“急迫”应用来说是非常重要的

软中断和tasklet有密切的关系,tasklet是在软中断之上实现。 事实上,出现在内核代码中的术语“软中断(softirq)”常常表示可延迟函数的所有种类。另外一种被广泛使用的术语是“中断上下文”:表示内核当前正在执行一个中断处理程序或一个可延迟的函数。

软中断的分配是静态的(即在编译时定义),而tasklet的分配和初始化可以在运行时进行 (例如:安装一个内核模块时)。

软中断(即便是同一种类型的软中断)可以并发地运行在多个CPU上。 因此,软中断是可重入函数 而且必须明确地使用自旋保护其数据结构。tasklet不必担心这些问题,因为内核对tasklet的执行进行了更加严格的控制。相同类型的tasklet总是被串行地执行, 换句话说就是:不能在两个CPU上同时运行相同类型的taklet .但是,类型不同的tasklet可以在几个CPU上并发执行。tasklet的串行化使tasklet函数不必是可重入的 ,因此简化了设备驱动程序开发者的工作。

(重入即表示重复进入,它意味着这个函数可以被中断)

可延迟函数上可以执行四种操作:

初始化

定义一个新的可延迟函数;这个操作通常在内核自身初始化或加载模块时进行。

激活

标记一个可延迟函数为“挂起”(在可延迟函数的下一轮调度中执行)。激活可以在任何时候进行(即使正在处理中断)

屏蔽

有选择地屏蔽一个可延迟函数,这样,即使它被激活,内核也不执行它。

执行

执行一个挂起的可延迟函数和同类型的其他所有挂起的可延迟函数;执行是在特定的时间进行的。

激活和执行不知何故总是捆绑在一起:由给定CPU激活的一个可延迟函数必须在同一个CPU上执行

软中断:

Linux2.6使用有限个软中断。在很多场合,tasklet是足够用的,且更容易编写,因为tasklet不必是可重入的。

目前只定义了六种软中断 :表4-9**



一个软中断的下标决定了它的优先级:低下标意味着高优先级,因为软中断函数将从下标0开始执行

[1]软中断所使用的数据结构

表示软中断的主要数据结构是softirq_veq数组,改数组包含类型为softirq_action 的32个元素。一个软中断的优先级是相应的softeirq_aciton元素的数组内的下标

softirq_action数据结构包含两个字段:指向软中断函数的一个action指针和指向软中断函数需要的通用数据结构的data指针

另外一个关键的字段是32位的preempt_count字段,用它来跟踪内核抢占和内核控制路径的嵌套,该字段存放在每个进程描述符的thread_info字段中

[2]处理软中断

open_softirq()函数 处理软中断的初始化。它使用三个参数:软中断下标、指向要执行的软中断函数的指针及指向可能由软中断函数使用的数据结构的指针。

raise_softirq()函数 用来激活软件中断,操作:参考p177

[3]do_softirq()函数

如果在这样的一个检查点(local_softirq_pending()不为0)检测到挂起的软中断,内核就调用do_softirq()来处理它们。操作:参考p178

[4]__do_softirq()

__do_softirq()函数读取本地CPU的软中断掩码并执行与每个设置位相关的可延迟函数。由于正在执行一个软中断函数时可能出现新挂起的软中断,所以为了保证可延迟函数的低延迟性,__do_softirq()一直运行到执行完所有挂起的软中断,但是,这种机制可能迫使__do_softirq()运行很长一段时间,因而大大延迟用户态进程的执行。因此,__do_softirq()只做固定次数的循环,然后就返回。如果还有其余挂起的软中断,内核线程ksoftirqd将会在预期的时间内处理它们。

[5]ksoftirqd内核线程

在最近的内核版中,每个CPU都有自己的ksoftirqd/n内核线程。每个ksoftirqd/n内核线程都运行ksoftirqd()函数·

tasklet:

tasklet是I/O驱动程序中实现可延迟函数的首选方法 。tasklet建立在两个叫做HI_SOFTIRQTASKLET_SOFTIRQ 的软中断之上。几个tasklet可以与同一个软中断相关联,每个tasklet执行自己的函数。两个软中断之间没有真正的区别 ,只不过do_softirq()先执行HI_SOFTIRQ的tasklet,后执行TASKLET_SOFTIRQ的tasklet

tasklet描述符是一个tasklet_struct类型 的数据结构:表4-11**



两个标志:

TASKLET_STATE_SCHED

该标志被设置时,表示tasklet是挂起的(曾被调度执行);也意味着tasklet描述符被插入到tasklet_vec和task_hi_vec数组的其中国一个链表中

TASKLET_STATE_RUN

该标志设置时,表示tasklet正在被执行,在单处理器系统上不使用这个标志,因为没有必要检查特定的tasklet是否在运行

让我们假定,你正在写一个设备驱动程序,且想使用tasklet,应该做什么呢?首先,你应该分配一个新的tasklet_struct数据结构,并调用tasklet_init()初始化它;该函数接收的参数为tasklet描述符的地址、tasklet函数的地址和它的可选整型参数。

调用tasklet_disable_nosync()或tasklet_disable()可以选择性地禁止tasklet。这两个函数都增加tasklet描述符的count字段,但是后一个函数只有在tasklet函数已经运行的实例结束后才返回。为了重新激活你的tasklet,调用tasklet_enable()

为了激活tasklet,你应该根据自己tasklet需要的优先级,调用tasklet_schedule() 函数或tasklet_hi_schedule()函数,其中每个都执行下列操作

(1)检查TASKLET_STATE_SCHED标志;如果设置则返回(tasklet已经被调度)

(2)调用local_irq_save保存IF标志的状态并禁用本地中断

(3)在tasklet_vec[n]或tasklet_hi_vec[n]指向的链表的起始处增加tasklet描述符(n表示本地CPU的逻辑号)

(4)调用raise_softirq_irqoff()激活TASKLET_SOFTIRQ或HI_SOFTIRQ类型的软中断

(5)调用local_irq_restor恢复IF标志的状态

让我们看一下tasklet如何执行,软中断函数一旦被激活,就由do_softirq()函数执行。HI_SOFTIRQ软中断相关的软中断函数叫做tasklet_hi_action(),而与TASKLET_SOFTIRQ相关的函数叫做tasklet_action().这两个函数非常相似,它们都执行下列操作

(1)禁用本地中断

(2)获得本地CPU的逻辑号n

(3)把tasklet_vec[n]或tasklet_hi_vec[n]指向的链表的地址存入局部变量list

(4)把tasklet_vec[n]或tasklet_hi_vec[n]的值赋为NULL,因此,已调度的tasklet描述符的链表被清空

(5)打开本地中断

(6)对于list指向的链表中的每个tasklet描述符

注意,除非tasklet函数重新激活自己,否则,tasklet的每次激活至多触发tasklet函数的一次执行

8、工作队列

允许内核函数(非常像可延迟函数)被激活,而且稍后由一种叫做工作者线程 (workerthread)的特殊内核线程来执行。尽管可延迟函数和工作队列非常相似,但是它们的区别还是很大的。主要区别在于:可延迟函数运行在中断上下文中,而工作队列中的函数运行在进程上下文中。 执行可阻塞函数(例如:需要访问磁盘数据块的函数)的唯一方式是在进程上下文中运行。在中断上下文中不可能发生进程切换。可延迟函数的工作队列中的函数都不能访问进程的用户态地址空间。事实上,可延迟函数被执行时不可能有任何正在运行的进程。另一方面,工作队列中的函数是由内核线程来执行的,因此根本不存在它要访问的用户态地址空间

[1]工作队列的数据结构

workqueue_struct描述符表4-12





[2]工作队列函数

creat_workqueue("foo")函数 ,返回新创建工作队列的workqueue_struct描述符的地址

queue_work()把函数插入工作队列 ,步骤:参考p184

每个工作者线程在worker_thead()函数内部不断地执行循环操作,因而,线程在绝大多数时间里处于睡眠状态并等待某些工作被插入队列。工作线程一旦被唤醒就调用run_workqueue()函数,该函数从工作者线程的工作列链表中删除所有work_struct描述符并执行相应的挂起函数。由于工作队列函数可以阻塞,因此,可以让工作者线程睡眠,甚至可以让它迁移到另一个CPU上恢复执行

有些时候,内核必须等待工作队列中的所有挂起函数执行完毕。flush_workqueue()函数接收workqueue_struct描述符的地址,并且在工作队列中的所有挂起函数结束之前使调用进程一直处于阻塞状态

[3]预定义工作队列

在绝大多数情况下,为了运行一个函数而创建整个工作者线程开销太大了 。因此,内核引入叫做events的预定义工作队列,所有的内核开发者都可以随意使用它。预定义工作队列只是一个包括不同内核层函数和I\O驱动程序的标准工作队列,它的workqueue_struct描述符存放在keventd_wq数组中

9、从中断和异常返回

尽管终止阶段的主要目的很清楚,即恢复某个程序的执行,但是,在这样做之前,还需要考虑几个问题:

内核控制路径并发执行的数量

挂起进程的切换请求

挂起的信号

单步执行模式

[1]入口点

[2]恢复内核控制路径

[3]检查内核抢占

[4]恢复用户态程序

[5]检查重调度标志

[6]处理挂起信号,虚拟8086模式和单步执行

  • 大小: 19.7 KB
  • 大小: 49.9 KB
  • 大小: 28.5 KB
  • 大小: 25.9 KB
  • 大小: 50.9 KB
  • 大小: 26.7 KB
  • 大小: 19.2 KB
  • 大小: 51.1 KB
分享到:
评论

相关推荐

    linux 内核中断相关的源码阅读笔记

    关于linux 内核中断相关的源码阅读笔记

    linux内核笔记-(系统管理-内核分析-项目专题)

    启动 常用工具 系统安装配置 管理脚本语言 数据库 网络服务 安全 gcc socket编程 文件和设备编程 进程和线程编程 内核分析: 启动 中断 内存 进程 网络 系统调用 文件系统 驱动 经验 项目专题: LFYOS OSKit ...

    《Linux内核设计与实现》 第一章 读书笔记 Linux内核简介

    所以在这里看下《Linux内核设计与实现》,希望能对Linux内核有更加深入的理解。 第一章只是比较简单的介绍了Linux内核,包括它的历史,操作系统和内核简介,对比了Linux内核和传统Unix内核。并且介绍了Linux内核...

    Linux内核代码学习笔记

    叙述了自己对linux代码的理解, 在虚拟内存, 文件系统, 中断有比较直观的理解,

    嵌入式学习资料之Linux内核定时器笔记--千锋培训

    文档介绍了实时时钟(RTC),时间戳计时器(TSC),CPU本地定时器,高精度时间定时器(HPET),ACPI电源管理定时器,定时器的数据结构,定时插补,单处理器系统上的计时体系结构,时钟中断处理程序,多处理器系统上...

    第一章从开机加电到Main函数执行前 《linux内核设计的艺术》的笔记内容

    第一章从开机加电到Main函数执行前 《linux内核设计的艺术》的笔记内容 BIOS --创建中断向量和中断描述符表,加载第一扇区代码bootsect bootsect 规划内存,加载第2--5扇区 setup,加载之后的大约240个扇区内核 开始向...

    Linux内核分析(《joyfire linux笔记》)

    非常强大的Linux内核分析的资料,中文版,617页,内容包括:Linux启动,中断,内存,进程,网络,系统调用,文件系统,驱动和一些经验。很全面。

    Linux 驱动学习笔记pdf文档

    ·Linux设备驱动程序学习(7)-内核的数据类型 ·Linux设备驱动程序学习(9)-与硬件通信 ·Linux设备驱动程序学习(8)-分配内存 ·Linux设备驱动程序学习(10)-时间、延迟及延缓操作 ·Linux设备驱动程序学习...

    Linux内核设备驱动之内核的时间管理笔记整理

    * linux内核的时间管理 ******************/ (1)内核中的时间概念 时间管理在linux内核中占有非常重要的作用。 相对于事件驱动而言,内核中有大量函数是基于时间驱动的。 有些函数是周期执行的,比如每10毫秒刷新...

    Linux内核设备驱动之系统调用笔记整理

    在linux中,系统调用是用户空间访问内核的唯一手段,除异常和中断外,他们是内核唯一的合法入口。系统调用的数量很少,在i386上只有大概300个左右。 (2)c库和系统调用的关系 应用程序员通过C库中的应用程序接口(API)...

    linux平台下各种工具安装步骤及其linux驱动笔记新的

    笔记属于个人整理,分享给大家希望对大家有所帮助,该笔集中从前到后贯穿了几乎linux学习的全部笔记,有中断、软中断、串口驱动、读写设备文件、安装minicom、kermit、交叉编译器安装、内核编译、tftp的配置等等。

    joyfire linux 笔记

    非常强大的内核分析的资料,中文版,内容包括:Linux启动,中断,内存,进程,网络,系统调用,文仮......

    rog-core:实现华硕Zephyrus GX502,GX502,GX531,G712,G53,GA14GA401,GA15GA502缺少的功能,例如键盘LED控制,RGB模式,AniMe显示控制和缺少fn功能

    该应用程序直接读取和写入设备中断,并且可以进行广泛的自定义(在源代码中)以完成您想要的事情,例如直接控制笔记本电脑的背光灯,而不是发出按键让DE处理。 其他笔记本电脑功能(如风扇模式或电池电量限制)将...

    操作系统 SnailOS 学习 拼一个自己的操作系统

    对系统引导、保护模式、中断和异常、内存管理、线程及进程的实现和简单调度算法、简单图形界面、线程间通信机制、ide硬盘驱动、fat32文件系统进行了简单的介绍。本书大量参考了《操作系统真相还原》、《30天自己操作...

    若干源程序资料12.rar

    2012-06-11 21:44 6,947,979 Linux内核完全注释V3.0书签版(带源码).rar 2012-06-11 21:31 11,599 MATLAB仿真程序OFDM程序.txt 2012-06-11 21:37 14,584,477 msdn for vb6.0简体中文版.zip 2012-06-11 21:02 12,288 ...

    操作系统学习笔记1

    1、并发:OS对多个程序的管理和调度 2、共享:同时访问、互斥共享 3、虚拟:多道程序设计技术 4、异步:环境相同,结果相同 #操作系统实例 1、unix 2、Linux 3、windows #操作系统演变 1、单用户系统 2、批处理系统 ...

    不允许错过的Linux常用命令集合

    直接贴上学习笔记: 系统信息 arch 显示机器的处理器架构(1) uname -m 显示机器的处理器架构(2) uname -r 显示正在使用的内核版本 dmidecode -q 显示硬件系统部件 - (SMBIOS / DMI) hdparm -i /dev/hda 罗列一个...

    自己动手写操作系统(含源代码).part2

    Linus Torvalds当时是一名赫尔辛基大学计算机科学系的二年级学生,经常要用自己的电脑去访问大学主机上的新闻组和邮件,为了方便读写和下载文件,他自己编写了磁盘驱动程序和文件系统,这成为了 Linux第一个内核的...

    自己动手写操作系统(含源代码).part1

    Linus Torvalds当时是一名赫尔辛基大学计算机科学系的二年级学生,经常要用自己的电脑去访问大学主机上的新闻组和邮件,为了方便读写和下载文件,他自己编写了磁盘驱动程序和文件系统,这成为了 Linux第一个内核的...

    ARM 实验指导书第一册UCOS-II.doc

    这个实时内核包含了许多传统操作系统的特征,包括任务管理,任务间通信,同步与互相排斥,中断支持,内存管理等功能,此后一些公司也纷纷推出了自己的嵌入式操作系统。这些系统均具有嵌入式的典型特点:它们都采用占...

Global site tag (gtag.js) - Google Analytics