《Linux内核设计与实现》学习笔记——中断、中断处理程序

中断和中断处理程序

  • 中断随时可能产生,打断CPU的执行,CPU转而处理中断。
  • 不同的设备对应的中断不同,每个中断都通过一个唯一的数字标志。
    • 这些中断值称为中断请求(IRQ)线,每个irq线关联一个数值。
  • 中断处理程序
    • 响应中断时,内核会执行一个函数,中断处理程序/中断服务例程ISR, 一个设备的中断处理程序是他的设备驱动的一部分。
  • IO资源包括 : 中断,I/O端口,共享RAM,DMA。驱动程序需要管理注册释放这些资源。

上半部:接收到中断就立即执行,只做有严格时限的工作,如对中断应答或复位硬件。
下半部 : 能够被允许稍后完成的工作推迟到下半部执行。

  • 注册中断处理程序
    • Request_irq(uint irq, irq_handlet_t handler, ulong flasgs,void* dev)注册中断处理程序,激活给定的中断线;这个函数可能睡眠,不能在中断上下文/其他不允许阻塞的代码中执行。
  • 卸载驱动程序时
    • 注销相应中断处理程序,释放中断线。 void free_irq(uint irq, void* dev);
  • Linux的中断处理程序是无需重入的,给定的中断处理程序在执行时,相应中断线在所有处理器上都会被屏蔽

中断上下文:没有后备进程,不可睡眠。中断处理程序打断了其他的代码。

中断处理机制的实现

《Linux内核设计与实现》学习笔记——中断、中断处理程序_第1张图片

  • 设备产生中断,通过总线把电信号发送给中断控制器
  • 如果中断线是激活的,中断控制器发送中断到处理器(处理器特定管脚)
  • 如果处理器没有禁止该中断,处理器会停止正在做的事关闭中断系统,调到预定义的中断处理程序入口。
  • 每条中断线调到一个唯一位置。初始入口点保存这个中断线号,存放寄存器的值调用do_irq()
    • 计算出中断号,对中断应答,禁止这条线上的中断传递
    • 确保这条中断线上有一个有效的处理程序,已经启动,但没有执行。
    • 调用handle_IRQ_event()调用中断线安装的中断处理程序。
    • 将中断禁止,返回到do_IRQ。
    • 做清理工作,返回初始入口点,跳到ret_from_intr()
  • 检查是否调度挂起;恢复寄存器;内核恢复到中断的点。

禁止当前处理器中断和激活中断,

Local_irq_disable();local_irq_enable();
Unsigned long flags; local_irq_save(flags);local_irq_restore(flags);

禁止指定中断

Disable_irq(int);禁止中断向所有处理器的中断。

中断处理程序上、下半步处理逻辑分配原则:

  • 上半部:
    • 任务对时间非常敏感
    • 任务和硬件相关
    • 任务保证不被其他中断打断,不并发,不阻塞
  • 下半部:
    • 对时间不敏感
    • 和硬件无关
    • 可以被其他中断打断,可以睡眠,可以并发

Linux的上半部就是中断处理程序,下半部有多种机制:

软中断

软中断是一组静态定义的下半部接口,有32个,可以在所有处理器上同时执行,类型相同也可以;在编译时静态注册。

实现:

struct softirq_action{ // 表示软中断
    void (*action)(struct softirq_action*);
}

32个目前用了6个。

static struct softirq_action soft_irq_vec[NR_SOFTIRQS];//kernel/softirq.c软中断数组
  • 中断处理程序:内核运行软中断处理程序的时候,执行action函数。
    一个软中断不会抢占另外一个软中断。唯一可以抢占软中断的是中断处理程序。其他的软中断甚至同类型的可以在其他处理器上同时执行
  • 执行软件中断:一个注册的软件中断在标记后才会执行,这称作触发中断。
    中断处理程序在返回前标记软中断。
    在:硬件中断代码返回时;在ksoftirq内核线程中;显示检查执行软中断 处,待处理的软中断会被检查和执行

软中断在do_softirq中执行

u32 pending;
pending = local_sofqirq_pending();
if(pending){
    struct softirq_action* h;
    set_softirq_pending(0);
    h = softirq_vec;
    do{
        if(pending & 1){
            h->action();
        }
        h++;
        pending >>=1;
    }while(pending);
}

使用软中断

软中断留给对时间要求最严格及最重要的下半部使用。目前只有网络,scsi使用内核定时器和tasklet都建立在软中断上。

  • 通过枚举类型静态声明软中断,并分配索引
  • 注册处理程序
    • open_softirq(NET_TX_SOFTIRQ,net_tx_action);软中断处里程序执行时,允许响应中断,但不能睡眠。由于只禁止当前处理器上的运行,其他处理器可以同时运行处理程序,需要加锁保护。
  • rase_softirq(NET_TX_SOFTIRQ)将软中断设置为挂起状态,下次再调用do_softirq时执行。

Tasklet

基于软件中断实现的,灵活性强,动态创建的下半部实现机制。两个不同类型的tasklet可以在不同处理器上运行,但相同的不可以。可以通过代码动态注册。

实现:基于软中断

struct tasklet_struct {
  struct tasklet_struct *next;
  unsigned long sate;//(0/TASKLET_STATE_SCHED/TASKLET_STATE_RUN)
  atomic_t count;/*引用计数器,0允许执行,否则禁止*/
  void (*func)(unsigned long);/*执行函数*/
  unsigned long data;//func的参数
};

调度:每个处理器有tasklet_vec和tasklet_hi_vec结构,分别为低、高优先级tasklet_strucu链表

由tasklet_schedule()和tasklet_hi_schdule()进行调度

  • 检查tasklet是否为TASKLET_STATE_SCHED.如果是返回
  • 调用_tasklet_schedule
  • 保存中断状态,禁止本地中断
  • 把需要调度的处理器加到tasklet_vec 或tasklet_hi_vec链表的头上
  • 唤起TASKLET_SOFTIRQ或TASKLET_HI_SOFTIRQ软中断
  • 恢复中断状态并返回

软中断处理程序:

tasklet_action(),tasklet_hi_action()的操作

  • 禁止中断
  • 将当前处理器置为null,清空链表
  • 允许中断
  • 循环遍历链表每一个待处理的tasklet
    • 如果是多处理器系统,判断是否TASKLET_STATE_SCHED,如果在运行,跳过。
    • 如果未在执行,设置TASKLET_STATE_RUN。
    • 检查count==0,否则tasklet被禁止,跳过。
    • 执行tasklet,清空TASKLET_STATE_RUN标志
    • 执行下一个tasklet

使用tasklet

  • 声明tasklet:
    • DECLARE_TASKLET(name,func,data)DECLARE_TASKLET_DISABLED(.)
      tasklet_init(t, tasklet_handler, dev);
  • 编写处理程序:因为是依靠软中断实现的,处理程序不能睡眠。Tasklet允许响应中断。
  • 调用tasklet_schedule(&my_tasklet);调度tasklet,实际上是标记/挂起,只要有机会,my_tasklet就会尽快执行。

tasklet_disable(&my_tasklet); tasklet_enable(&my_tasklet);禁止和激活

折衷

频繁中断或tasklet频繁发生的时候:尽快处理,用户进程得不到响应;滞后执行,中断处理也不快。
==》》使用低优先级核心进程专门处理软中断ksoftirqd/n

for(;;){
    if(!softirq_pending(cpu)){
        schedule();
    }
    set_current_state(TASK_RUNNING);
    while(softirq_pending(cpu)){
        do_softirq();
        if(need_schdule())schedule();
    }
}

工作队列:

将下半部功能交由内核线程执行,有着线程上下文环境,可以睡眠。
提供创建worker threads的接口,提供接口把需要推后执行的任务排到队列里,提供默认的工作者线程处理排到队列里的下半部工作。

实现:

数据结构

  • 每种工作者线程有一个workqueue_struct结构
  • 里面有NR_CPUS个cpu_work_queue_strcut对应于每个处理器的一个工作者线程
  • 工作用work_struct表示,含有一个执行函数fuc,每个cpu的工作线程都对应一个work_struct链表。

worker_thread()的核心,是一个死循环

  • 线程将自己放置为休眠状态
  • 若链表为空,休眠
  • 不为空,调用run_workqueue函数执行工作。
    • 链表不为空时,循环执行
      • 选取下一个节点对象,获取执行函数和参数
      • 待处理标志清0
      • 调用函数
    • 重复执行

使用:

  • 创建推后的工作
    DECLARE_WORK(name, void(func)(void), void*data);静态
    INIT_WORK(strut work_struct* task,…);动态
  • 工作队列处理函数
    运行于进程上下文,允许响应中断,不持有锁,可以睡眠。不能访问用户空间
  • 对工作进行调度
    schedule_work(&work);提交给工作者进程
    schedule_delay_work(&work,delay)
  • 刷新操作
    flush_scheduled_work(),函数等待队列中所有对象都被执行以后返回
  • 创建新的工作队列
    如果缺省的队列不能满足你的需要,你应该创建新的工作队列和与之相对应的工作线程。

各种机制的比较

下半部 上下文 顺序执行保障
软中断 中断 没有
Tasklet 中断 同类型不能同时执行
工作队列 进程 没有(和进程上下文一样,被调度)

如果任务需要推后到进程上下文完成,有休眠的需要 工作队列
任务队列接口简单,同种类型不能同时执行 tasklet
软中断提供的执行序列化的保障最少,必须格外小心采取一些步骤确保共享数据

你可能感兴趣的:(操作系统)