Linux中断处理

Linux中断处理

1、  Introduce 异常为同步事件,有当前进程执行引发,处于被打断的进程的上下文中,为当前被打断的进程服务,可以调用任何内核态的函数,也可以睡眠。中断时异步事件,虽然使用当前被打断的进程的上下文中,但和当前进程没有必然的关系,同时要求能快速处理以便能及时响应下一次中断,因此在中断处理过程中是不可睡眠的。

IA32体系允许256个中断、异常源,0~31用于不可屏蔽中断和处理器可以识别的异常,32~255用于外设的可屏蔽中断和处理器未知异常。

 

2、  中断上半部

2.1数据结构

在编译内核过程中,根据对内核的配置声明并初始化响应数量的irq_desc_t结构组成irq_desc[NR_IRQS].

irq_desc[]:该数组对每根中断请求线IRQ进行描述

irq_desc_t:中断请求线描述符,描述IRQ状态、处理函数、嵌套深度、中断请求线状态控制函数

hw_interrupt_type:中断通道控制器描述符,描述IRQ的相关操作函数,包括应答控制、禁止/使能中断等

irqaction:中断服务描述符,存储中断处理程序注册中断处理函数及该函数使用参数等信息。

2.2初始化

异常的初始化:异常部分的初始化由函数trap_init()完成<src/arch/i386/kernel/traps.c>,该函数由main.c中被系统初始化入口函数start_kernel()调用。该函数调用set_trap_gate(),set_system_gate(),set_intr_gate(),set_system_intr_gate(),set_task_gate()初始化系统的异常、不可屏蔽中断和系统调用入口。上述函数最终调用_set_gate()设置相应的门描述符。

中断的初始化:中断初始化由init_IRQ()完成<src/arch/i386/kernel/i8259.c>

     init_IRQ()函数:该函数完成

·调用pre_intr_init_hook()初始化IRQ的描述符数组irq_desc[NR_IRQS],该函数最终调用init_ISA_irqs初始化8259A中断控制器.status设置为DISABLED,action设置为NULL

·调用set_intr_gate()为中断请求线IRQ对应的中断向量设置门描述符

·调用setup_pit_timer()配置8253/8254时钟芯片来设置系统时钟间隔

·调用irq_ctx_init()完成初始化异常、中断程序使用的内核栈hardirq_ctx[NR_CPUS]softirq_ctx[NR_CPUS]

·调用intr_init_hook()初始化IRQ2用作8259A的级联

·调用setup_irq()上注册FPU处理函数

       setup_irq()函数:向中断请求线irq上注册处理函数,如果该中断请求线在此前没有处理函数,则此次注册会清除statusIRQ_INPROGRESS, IRQ_DISABLED, IRQ_WAITING, IRQ_QUTODECT标志。若已有处理函数且允许共享中断线,则不对status做改动。

       中断系统状态判断

  irq_disable():若本地cpu上的中断被禁止,返回非0

  in_interrupt():若内核处于中断上下文中返回非0值,此时内核正在执行中断处理程序或下半部处理程序

  in_irq():内核正在执行中断处理程序时返回非0

2.3具体处理过程(软件处理阶段)

2.3.1中断的软件处理过程

       首先用request_irq()函数<src/kernel/irq/manage.c>向系统注册中断处理程

序,在获知设备使用的中断向量号后,该函数首先设置irqaction action每个成员的值,然后调用函数setup_irq()设置中断处理函数。

注册了中断处理程序后系统就可以为相应的设备中断服务了,在外设发出中断请求,处理器响应中断后,对应的中断处理程序先把中断向量号压入内核态栈,然后调用汇编指令段 common_interrupt,该段代码如下:

  ALIGN

commom_interrupt:

  SAVE_ALL

  movl %esp,%eax

  call do_IRQ

  jmp ret_from_intr

该段代码首先保存现场,将处理器寄存器内容压入当前内核栈,然后在%eax设置函数do_IRQ使用的参数后调用do_IRQ()函数进行中断处理。最后调用ret_from_intr()从中断处理过程中恢复

       do_IRQ()<src/arch/i386/kernel/irq.c>负责完成中断上下文所使用的内核态栈的设置,然后调用_do_IRQ()进行实际的中断处理,最后调用irq_exit()函数退出上半部或启动中断下半部处理。

       _do_IRQ()<src/kernel/irq/handle.c>负责调用注册到中断请求线irq上的中断处理函数进行中断处理。该函数通过一个无限循环对中断线上的中断请求调用函数handle_IRQ_event()处理,知道没有新中断请求时退出。

       handle_IRQ_event()<src/kernel/irq/handle.c>调用中断注册的处理函数并判断是否是其对应的外设产生的中断请求,如果是,进行中断处理。

2.3.2异常的软件处理过程

       do_trap():

2.3.3中断处理的局限

       以异步方式执行,可能打断其他重要的代码,所以应该执行的越快越好

       执行时是关中断,会屏蔽其他与该中断同级的中断

       因为其不在进程上下文中,不能阻塞

3、  softirq软中断

3.1结构:

    

3.2初始化soft_irq

每个预设的softirq分别使用函数open_softirq()<src/kernel/softirq.c>

初始化相应的处理函数和参数

          系统使用宏DECLARE_PER_CPU(irq_cpustat_t,irq_stat)声明名为irq_stat

型为irq_cpustat_t的每处理器变量记录每个处理器上待处理的softirq的状态信息

      typedef struct{

           unsigned int _softirq_pending;/*每一位记录softirq_vec[]数组中对应每一个softirq是否有任务要处理*/

           unsigned long idle_timestamp;

           unsigned int _nmi_count;

           unsigned int apic_timer_irqs;

           }___cacheline_aligned irq_cpustat_t;

3.3激活soft_irq

中断上半部调用do_IRQ()函数,该函数最后调用irq_exit()检查是否有softirq要处理、运行下半部时机是否成熟。

       irq_exit()<src/kernel/softirq.c>,负责完成上半部退出,在系统允许的情况下( !in_interrupt()&&local_softirq_pending())激活softirq延迟处理。

3.4处理softirq

中断处理程序会在返回前标记其软终端,然后系统在以下点检查是否有softirq要处理:irq_exit,内核线程ksoftirqd,内核网络子系统显式调用,函数local_bh_enable。需要时调用do_softirq()处理

       do_siftirq()<src/kernel/softirq.c>在确保当前不处于中断(上下部)上下文中,然后在有softirq待处理的情况下调用_do_softirq()进行处理

       _do_softirq():依次对32softirq轮询,遇到一个设置了处理函数且需要处理的softirq进行处理,在循环次数大于设定值但又有softirq要处理的情况下,调用wakeup_softirq()唤醒内核线程softirq进行处理。

4、  tasklet延迟处理:利用系统预定义的HI_SOFEIRQ,TASKLET_SOFTIRQ软中断实现的一种下半部机制

4.1结构

linux为每类tasklet各声明了一个每处理器变量<src/kernel/softirq.c>

static DEFINE_PER_CPU(struct tasklet_head,taskle_vec)={NULL};

static DEFINE_PER_CPU(struct tasklet_head,taskle_hi_vec)={NULL};

其中:state可以取TASKLET_STATE_SCHED,TASKLET_STATE_RUN

      count记录tasklet被禁止运行次数

4.2初始化

DECLARE_TASKLET_DISABLE(name,func,data),DECLARE_TASKLET(name,func,data)来声明。前者声明的tasklet出示状态是禁止的,即count值为1

4.3激活

在采用tasklet下半部机制的中断程序中,在上半部处理过程中会调用函数tasklet_schedule(),tasklet_hi_schedule()来激活下半部处理。

tasklet_schedule():<src/include/linux/interrupt.h>该函数是一个封装函数,在调用test_and_set_bit()检测当前tasklet不是处于TASKLET_STATE_SCHED状态时,设置状态为激活状态并调用_tasklet_schedule()

_tasklet_schedule():<src/kernel/softirq.c>函数禁止中断,将当前tasklet插入到当前处理器tasklet链表表头,使用raise_softirq_irqoff()激活tasklet所在softirq,最后lacal_irq_restore恢复系统的EFLAGS

4.4处理tasklet

    raise_softirq_irqoff通过在每处理器变量irq_cpustat_t的本地拷贝_soft_pending成员中标记TASKLET_SOFTIRQ标记有任务要处理。系统最终调用do_softirq进行处理,此时会调用tasklet_action()来处理tasklet.

    tasklet_action():<src/kernel/softirq.c>依次处理每处理器变量tasklet_vec上保存的tasklet任务。首先禁止中断将待处理tasklet链表拷贝到list中,然后依次处理链表中的任务,如果遇到一个禁止处理的taskletcount>0),跳过该tasklet并将其插入到本地待处理tasklet表头,并设置TASKLET_SOFTIRQ标记。

5work queue延迟处理

与软中断和tasklet运行于中断上下文不同,基于工作队列的下半部由一个内核线程执行并运行于该内核进程的上下文中,可以睡眠。

5.1结构

  

其中:

·workqueue_struct记录工作队列相关信息

·cpu_workqueue_struct指示同一个工作队列在不同处理器上的状态

·work_struct表示一个具体的待处理任务

·list将系统中所有工作队列连接成一个双向链表,表头为struct LIST_HEAD(workqueues)

      ·cpu_wq指向一个每处理器变量结构struct cpu_workqueue_struct

      ·worklist是当前处理器上待处理工作队列节点组成的双向链表表头

      ·more_work指示睡眠在该结构上的工作队列处理线程,激活工作队列时通过wake_up(&cwq->more_work)唤醒

      ·work_done指示睡眠在该结构上等待工作队列节点所描述的任务完成的进程队列

      ·wq指向该工作队列每处理器结构所在的工作队列

      ·run_depth嵌套深度

      ·entry将工作队列节点插入到worklist

      ·thread当前工作队列节点的工作队列处理线程

5.2初始化

在使用工作队列机制,首先要一个处理该工作队列节点的工作队列,可以创建一个新的工作队列或者使用内核默认的工作队列eventevent在系统初始化时调用init_workqueue被创建。

_create_workqueue():<src/kernel/qorkqueue.c>创建一个新的工作队列,并为工作队列设置内核处理线程

在创建工作队列之后,可以使用宏DECLARE_WORK(name,function,data)INIT_WORK(name,function,data)创建工作队列节点。

5.3激活work queue

在中断处理上半部完成时,可以使用函数queue_work()将表示下半部的工作队列节点插入到制定的工作队列,或者使用函数schedule_work()将节点插入到系统默认的event工作队列。

queue_work():<src/kernel/workqueue.c>将节点插入到工作队列中,

_queue_work()< src/kernel/workqueue.c >将节点插入到工作队列的本地拷贝cwq的待处理链表中,调用wake_up(&cwq->more_work)唤醒处理线程,实际完成节点的插入和激活

5.4处理work queue

在激活一个节点后,系统唤醒当前处理器上的内核线程worker_threat来处理节点中的任务,该线程最终调用run_workqueue()处理工作队列节点

run_wirkqueue():<src/kernel/workqueue.c>依次处理cwq上待处理工作队列节点,完成工作队列节点的任务。

5、  总结

在中断的下半部机制中,软中断提供的执行序列化保障最少,其处理函数必须格外小心地保证共享数据的安全,两个甚至更多的相同类别的软中断有可能在不同的处理器上同时执行,对于时间要求严格和执行频率很高的应用来说,他的执行也最快。如果代码的多线索化考虑的不充分,那么应该选择tasklet,他的接口简单,而且两个同类型的tasklet不能同时执行,所以实现起来也比较简单,tasklet是有效的软中断,但不能并发执行。如果你要把任务退后到进程上下文执行,work queue就是唯一选择了,工作队列的开销最大,因为他要牵涉到内核线程甚至上下文切换。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值