中断处理与下半部的故事

说几句废fu之言,前几天没有接着写进程调度记录的文章,当然现在也不会写,如题,从现在开始记录linux内核基础知识。


中断处理程序原则:接受到一个中断,便立即开始执行应答或复位硬件,前提是在所有中断被禁止的额情况下完成。

中断和异常相关概念:

中断——异步的:

由硬件随机产生,在程序执行的任何时候可能出现

异常——同步的:

在(特殊的或出错的)指令执行时由CPU控制单元产生

中断处理程序重要函数:

First job.

1.注册、分配中断号irq、激活中断处理程序handler、设置标志掩码、署名设备ASCII名称、设置共享中断线:

kernel/irq/manage.c

request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char*name,void* dev)

注:request_irq可能睡眠,不能在中断上下文或不允许阻塞的代码中调用。

换句话可以知道,中断上下文不允许睡眠,而该函数可能引起阻塞。


2.注销、中断处理程序,释放中断线

kernel/irq/manage.c

void free_irq(unsigned int irq,void* dev_id)                       

通常在驱动卸载时使用,必须在进程上下文中调用。

3.启用中断

kernel/irq/manage.c 中的 enable_irq:

   static void __enable_irq(struct irq_desc *desc, unsigned int irq)

内部调用了 __enable_irq,首先上自旋锁,找到 irq_desc 结构体指针,判断嵌套深度,刷新 IRQ 状态,释放自旋锁。

参数desc: 指向 irq_desc 结构体的指针irq: 中断通道号

4.关闭中断

kernel/irq/manage.c 中的 disable_irq:

 void disable_irq(unsigned int irq)

参数irq: 中断通道号

5.关闭中断 (无等待)

disable_irq 会保证存在的 IRQ handler 完成操作,而 disable_irq_nosync 立即关中断并返回。事实上,disable_irq 首先调用 disable_irq_nosync,然后调用 synchronize_irq 同步。

  void disable_irq_nosync(unsigned int irq)

6.同步中断 (多处理器)

  void synchronize_irq(unsigned int irq)

7.设置 IRQ 芯片

kernel/irq/chip.c:

set_irq_chip()

int set_irq_chip(unsigned int irq, struct irq_chip *chip)

8.设置 IRQ 类型

kernel/irq/chip.c: set_irq_type()

int set_irq_type(unsigned int irq, unsigned int type)

9.设置 IRQ 数据

kernel/irq/chip.c: set_irq_data()

150 int set_irq_data(unsigned int irq, void *data)

10.设置 IRQ 芯片数据

kernel/irq/chip.c: set_irq_chip_data()

  int set_irq_chip_data(unsigned int irq, void *data)


重头戏

中断处理流程:

一、监视 IRQ 线,检查产生的信号。如果有两条以上的 IRQ 线上产生信号,就选择引脚编号较小的 IRQ 线。

二、如果一个引发信号出现在 IRQ 线上:

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

           2.把这个向量存放在中断控制器的一个 I/O 端口(0x20、0x21),从而允许 CPU 通过数据总线读此向量。

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

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

三、继续监控IRQ线。

处理完中断信号后,控制单元所执行的指令就是被选中处理程序的第一条指令。中断或异常被处理完后,相应的处理程序必须产生一条iret指令,把控制权转交给被中断的进程。

中断信息的保存

紧急的事情马上做,其他事情往后推。

中断处理程序首先要做:

1、将中断号压入栈中,以便找到对应的中断服务程序

2、将当前寄存器信息压入栈中,以便中断退出时恢复上下文

显然, 这两步都是不可重入的。因此在进入中断服务程序时,CPU 已经自动禁止了本 CPU 上的中断响应。

引为 n 的元素中存放着下列指令的地址:

pushl n-256

jmp common_interrupt

执行结果是将中断号 - 256 保存在栈中,这样栈中的中断都是负数,而正数用来表示系统调用。这样,系统调用和中断可以用一个有符号整数统一表示。

common_interrupt 的定义:

// arch/x86/kernel/entry_32.S

613 common_interrupt:

614        SAVE_ALL

615        TRACE_IRQS_OFF

616        movl %esp,%eax # 将栈顶地址放入 eax,这样 do_IRQ 返回时控制转到 ret_from_intr()

617        call do_IRQ # 核心中断处理函数

618        jmp ret_from_intr # 跳转到 ret_from_intr()

其中 SAVE_ALL 宏将被展开成:

cld

push %es # 保存除 eflags、cs、eip、ss、esp (已被 CPU 自动保存) 外的其他寄存器

push %ds

pushl %eax

pushl %ebp

pushl %edi

pushl %edx

pushl %ecx

pushl %ebx

movl $ _ _USER_DS, %edx

movl %edx, %ds # 将用户数据段选择符载入 ds、es

movl %edx, %es


处理中断

前面汇编代码的实质是,以中断发生时寄存器的信息为参数,调用 arch/x86/kernel/irq32.c 中的 do_IRQ 函数。

我们注意到 unlikely 和 unlikely 宏定义,它们的含义是

#define likely(x)      __builtin_expect((x),1)

#define unlikely(x)    __builtin_expect((x),0)

__builtin_expect 是 GCC 的内部机制,意思是告诉编译器哪个分支条件更有可能发生。这使得编译器把更可能发生的分支条件与前面的代码顺序串接起来,更有效地利用 CPU 的指令流水线。

do_IRQ 函数流程:

           1、保存寄存器上下文

           2、调用 irq_enter:// kernel/softirq.c

void irq_enter(void)

282 {

283 #if def CONFIG_NO_HZ

// 无滴答内核,它将在需要调度新任务时执行计算并在这个时间设置一个时钟中断,允许处理器在更长的时间内(几秒钟)保持在最低功耗状态,从而减少了电能消耗。

284        int cpu = smp_processor_id();

285        if (idle_cpu(cpu) && !in_interrupt())

286                tick_nohz_stop_idle(cpu); // 如果空闲且不在中断中,则停止空闲,开始工作

287 #end if

288        __irq_enter();

289 #if def CONFIG_NO_HZ

290        if (idle_cpu(cpu))

291                tick_nohz_update_jiffies(); // 更新 jiffies

292 #end if

293 }

// include/linux/hardirq.h

135 #define __irq_enter()                                  \

/* 在宏定义函数中,do { ... } while(0) 结构可以把语句块作为一个整体,就像函数调用,避免宏展开后出现问题 */

136        do {                                            \ 

137                rcu_irq_enter();                        \

138                account_system_vtime(current);          \

139                add_preempt_count(HARDIRQ_OFFSET);      \ /* 程序嵌套数量计数器递增1 */

140                trace_hardirq_enter();                  \

141        } while (0)

           3、如果可用空间不足 1KB,可能会引发栈溢出,输出内核错误信息

           4、如果 thread_union 是 4KB 的,进行一些特殊处理

           5、调用 desc->handle_irq(irq, desc),调用 __do_IRQ() (kernel/irq/handle.c)

5.1 取得中断号,获取对应的 irq_desc

5.2 如果是 CPU 内部中断,不需要上锁,简单处理完就返回了

5.3 上自旋锁

5.4 应答中断芯片,这样中断芯片就能开始接受新的中断了。

5.5 更新中断状态。

        IRQ_REPLAY:如果被禁止的中断管脚上产生了中断,这个中断是不会被处理的。当这个中断号被允许产生中断时,会将这个未被处理的中断转为 IRQ_REPLAY。

        IRQ_WAITING:探测用,探测时会将所有没有中断处理函数的中断号设为 IRQ_WAITING,只要这个中断管脚上有中断产生,就把这个状态去掉,从而知道哪些中断管脚上产生过中断。

         IRQ_PENDING、IRQ_INPROGRESS 是为了确保同一个中断号的处理程序不能重入,且不能丢失这个中断的下一个处理程序。具体地说,当内核在运行某个中断号对应的处理程序时,状态会设置成IRQ_INPROGRESS。如果发现已经有另一实例在运行了,就将这下一个中断标注为 IRQ_PENDING 并返回。这个已在运行的实例结束的时候,会查看是否期间有同一中断发生了,是则再次执行一遍。

5.6 如果链表上没有中断处理程序,或者中断被禁止,或者已经有另一实例在运行,则进行收尾工作。

5.7  循环:

释放自旋锁

执行函数链:handle_IRQ_event()。其中主要是一个循环,依次执行中断处理程序链表上的函数,并根据返回值更新中断状态。如果愿意,可以参与随机数采样。中断处理程序执行期间,打开本地中断。

上自旋锁

如果当前中断已经处理完,则退出;不然取消中断的 PENDING 标志,继续循环。

5.8 取消中断的 INPROGRESS 标志

5.9 收尾工作:有的中断在处理过程中被关闭了,->end() 处理这种情况;释放自旋锁。

           6、执行 irq_exit(),在 kernel/softirq.c 中

6.1 递减中断计数器

6.2 检查是否有软中断在等待执行,若有则执行软中断。

6.3 如果使用了无滴答内核看是不是该休息了。

           7、恢复寄存器上下文,跳转到 ret_from_intr (跳转点早在 common_interrupt 中就被指定了)

在中断处理过程中,我们反复看到对自旋锁的操作。在单处理器系统上,spinlock是没有作用的;在多处理器系统上,由于同种类型的中断可能连续产生,同时被几个 CPU处理(注意,应答中断芯片是紧接着获得自旋锁后,位于整个中断处理流程的前部,因此在中断处理流程的其余部分,中断芯片可以触发新的中断并被另一个CPU 开始处理),如果没有自旋锁,多个 CPU 可能同时访问 IRQ 描述符,造成混乱。因此在访问 IRQ 描述符的过程中需要有spinlock 保护。

下半部的事

三足鼎力:软中断、tasklet、工作队列

软中断:

软中断作为下半部机制的代表,是随着SMP(share memory processor)的出现应运而生的,它也是tasklet实现的基础(tasklet实际上只是在软中断的基础上添加了一定的机制)。

软中断一般是“可延迟函数”的总称,有时候也包括了tasklet(请读者在遇到的时候根据上下文推断是否包含tasklet)。它的出现就是因为要满足上面所提出的上半部和下半部的区别,使得对时间不敏感的任务延后执行,而且可以在多个CPU上并行执行,使得总的系统效率可以更高。

它的特性包括:

a)产生后并不是马上可以执行,必须要等待内核的调度才能执行。软中断不能被自己打断,只能被硬件中断打断(上半部)。

b)可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作),因此也需要使用自旋锁来保护其数据结构。

tasklet:

由于软中断必须使用可重入函数,这就导致设计上的复杂度变高,作为设备驱动程序的开发者来说,增加了负担。而如果某种应用并不需要在多个CPU上并行执行,那么软中断其实是没有必要的。因此诞生了弥补以上两个要求的tasklet。

tasklet是由软中断引出的,是IO驱动程序实现可延迟函数的首选方法。 内核定义了两个软中断掩码HI_SOFTIRQ和TASKLET_SOFTIRQ(两者优先级不同), 这两个掩码对应的软中断处理函数作为入口, 进入tasklet处理过程.

它具有以下特性:

a)一种特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行。

b)多个不同类型的tasklet可以并行在多个CPU上。

c)软中断是静态分配的,在内核编译好之后,就不能改变。但tasklet就灵活许多,可以在运行时改变(比如添加模块时)。

tasklet是在两种软中断类型的基础上实现的,因此如果不需要软中断的并行特性,tasklet就是最好的选择

工作队列

软中断不能睡眠、不能阻塞。由于中断上下文出于内核态,没有进程切换,所以如果软中断一旦睡眠或者阻塞,将无法退出这种状态,导致内核会整个僵死。但可阻塞函数不能用在中断上下文中实现,必须要运行在进程上下文中,例如访问磁盘数据块的函数。因此,可阻塞函数不能用软中断来实现。但是它们往往又具有可延迟.

工作队列有着自己的处理线程, 这些work被推迟到这些线程中去处理. 处理过程只可能发生在这些工作线程中, 所以这里可以睡眠.

内核默认启动了一个工作队列, 对应一组工作线程events/n(n代表处理器编号, 这样的线程有n个). 驱动程序可以直接向这个工作队列添加任务. 某些驱动程序还可能会创建并使用属于自己的工作队列.

因此在2.6版的内核中出现了在内核态运行的工作队列(替代了2.4内核中的任务队列)。它也具有一些可延迟函数的特点(需要被激活和延后执行),但是能够能够在不同的进程间切换,即可在进程上下文中完成任务,这就是工作队列的关键。

(未完)

中断处理程序编写

(1)他运行在中断上下文,所以不能使用可能引起阻塞或者调度的函数。否则实时性得不到满足。

(2)一开始判断是否产生中断

(3)清除中中断标志

(4)硬件相关操作

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容