关于c:Linux内核中断顶半部和底半部的理解

9次阅读

共计 4721 个字符,预计需要花费 12 分钟才能阅读完成。

@[toc]

中断上半部、下半部的概念

  设施的中断会打断内核过程中的失常调度和运行,系统对更高吞吐率的谋求势必要求中断服务程序尽量短小精悍。然而,这个良好的欲望往往与事实并不吻合。在大多数实在的零碎中,当中断到来时,要实现的工作往往并不会是短小的,它可能要进行较大量的耗时解决。
  下图形容了 Linux 内核的中断解决机制。为了在中断执行工夫尽量短和中断解决需实现的工作尽量大之间找到一个平衡点,Linux 将中断处理程序合成为两个半部:顶半部和底半部。

  顶半部用于实现尽量少的 比拟紧急的性能 ,它往往只是简略地 读取寄存器中的中断状态 ,并在革除中断标记后就进行“ 注销中断”的工作。“注销中断”意味着将底半部处理程序挂到该设施的底半部执行队列中去。这样,顶半部执行的速度就会很快,从而能够服务更多的中断请求。

  当初,中断解决工作的重心就落在了底半部的头上,需用它来实现中断事件的绝大多数工作。底半部 简直做了中断处理程序所有的事件 ,而且能够被 新的中断打断 ,这也是底半部和顶半部的最大不同,因为 顶半部往往被设计成不可中断。底半部相对来说并不是十分紧急的,而且绝对比拟耗时,不在硬件中断服务程序中执行。

  只管顶半部、底半部的联合可能善零碎的响应能力,然而,僵化地认为 Linux 设施驱动中的中断解决肯定要分两个半部则是不对的。如果中断要解决的工作自身很少,则齐全能够间接在顶半部全副实现

  其余操作系统中对中断的解决也采纳了相似于 Linux 的办法,真正的硬件中断服务程序都斥尽量短。因而,许多操作系统都提供了中断上下文和非中断上下文相结合的机制,将中断的耗时工作保留到非中断上下文去执行。

实现中断下半部的三种办法

软中断

  软中断(Softirq)也是一种传统的底半部解决机制,它的执行机会通常是 顶半部返回的时候,tasklet 是基于软中断实现的,因而也运行于软中断上下文。

  在 Linux 内核中,用 softing_action 构造体表征一个软中断,这个构造体蕴含软中断解决函数指针和传递给该函数的参数。应用 open_softirq()函数能够注册软中断对应的处理函数,而 raise_softirq()函数能够触发一个软中断。

  软中断和 tasklet 运行于软 中断上下文 ,依然属于原子上下文的一种,而工作队列则运行于 过程上下文 。因而,在软中断和 tasklet 处理函数中 不容许睡眠 ,而在 工作队列处理函数中容许睡眠

  local_bh_disable()和 llocal_bh_enable()是内核中用于禁止和使能软中断及 tasklet 底半部机制的函数

软中断模版

asmlinkage void do_softirq(void)
{
    __u32 pending;
    unsigned long flags;

    /* 判断是否在中断解决中,如果正在中断解决,就间接返回 */
    if (in_interrupt())
        return;

    /* 保留以后寄存器的值 */
    local_irq_save(flags);

    /* 获得以后已注册软中断的位图 */
    pending = local_softirq_pending();

    /* 循环解决所有已注册的软中断 */
    if (pending)
        __do_softirq();

    /* 复原寄存器的值到中断解决前 */
    local_irq_restore(flags);
}

tasklet

  tasklet 的应用较简略,它的执行上下文是软中断,执行机会通常是顶半部返回的时候。咱们只须要定义 tasklet 及其处理函数,并将两者关联则可,例如

void my_tasklet_func(unsigned long); /* 定义一个处理函数 */
DECLARE_TASKLET(my_tasklet, my_tasklet_func, data);
/* 定义一个 tasklet 构造 my_tasklet,与 my_tasklet_func(data)函数相关联 */

  代码 DECLARE_TASKLET(my_tasklet,my_tasklet_func,data)实现了定义名称为 my_tasklet 的 tasklet,并将其与 my_tasklet_func()这个函数绑定,而传入这个函数的参数为 data。
在须要调度 tasklet 的时候援用一个 tasklet_schedule()函数就能使零碎在适当的时候进行调度运行:

tasklet_schedule(&my_tasklet);

  应用 tasklet 作为底半部解决中断的设施驱动程序模板下所示(仅蕴含与中断相干的部
分)。

tasklet 函数模版

/* 定义 tasklet 和底半部函数并将它们关联 */
void xxx_do_tasklet(unsigned long);
DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);

/* 中断解决底半部 */
void xxx_do_tasklet(unsigned long)

...


/* 中断解决顶半部 */
 irqreturn_t xxx_interrupt(int irq, void *dev_id)
{
 ...
 tasklet_schedule(&xxx_tasklet);
 ...
}

/* 设施驱动模块加载函数 */
 int __init xxx_init(void)
{
 ...
 /* 申请中断 */
 result = request_irq(xxx_irq, xxx_interrupt,
 0, "xxx", NULL);
 ...
 return IRQ_HANDLED;
}

/* 设施驱动模块卸载函数 */
 void __exit xxx_exit(void)
{
 ...
 /* 开释中断 */
 free_irq(xxx_irq, xxx_interrupt);
 ...
}

  上述程序在模块加载函数中申请中断(第 24~25 行),并在模块卸载函数 free_irq(xxx_irq, xxx_interrupt); 中开释它。对应于 xxx_irq 的中断处理程序被设置为 xxx_interrupt()函数,在这个函数中,tasklet_schedule(&xxx_tasklet)调度被定义的 tasklet 函数 xxx_do_tasklet()在适当的时候执行。

工作队列

  工作队列的应用办法和 tasklet 十分类似,然而工作队列的执行上下文是内核线程,因而能够调度和睡眠。上面的代码用于定义一个工作队列和一个底半部执行函数

struct work_struct my_wq; /* 定义一个工作队列 */
void my_wq_func(struct work_struct *work); /* 定义一个处理函数 */

  通过 INIT_WORK()能够初始化这个工作队列并将工作队列与处理函数绑定:

INIT_WORK(&my_wq, my_wq_func);
/* 初始化工作队列并将其与处理函数绑定 */

  与 tasklet_schedule()对应的用于调度工作队列执行的函数为 schedule_work(),如:

schedule_work(&my_wq); /* 调度工作队列执行 */

工作队列函数模版

/* 定义工作队列和关联函数 */
struct work_struct xxx_wq;
void xxx_do_work(struct work_struct *work);

/* 中断解决底半部 */
void xxx_do_work(struct work_struct *work)

...


/* 中断解决顶半部 */
 irqreturn_t xxx_interrupt(int irq, void *dev_id)
{
 ...
 schedule_work(&xxx_wq);
 ...
 return IRQ_HANDLED;
}

/* 设施驱动模块加载函数 */
 int xxx_init(void)
{
 ...
 /* 申请中断 */
 result = request_irq(xxx_irq, xxx_interrupt,
 0, "xxx", NULL);
 ...
 /* 初始化工作队列 */
 INIT_WORK(&xxx_wq, xxx_do_work);
 ...
}

/* 设施驱动模块卸载函数 */
 void xxx_exit(void)
{
 ...
 /* 开释中断 */
 free_irq(xxx_irq, xxx_interrupt);
 ...
}

  工作队列晚期的实现是在每个 CPU 核上创立一个 worker 内核线程,所有在这个核上调度的工作都在该 worker 线程中执行,其并发性显然差强人意。在 Linux 2.6.36 当前,转而实现“Concurrency-managedworkqueues”,简称 cmwq,cmwq 会主动保护工作队列的线程池以进步并发性,同时放弃了 API 的向后兼容。

过程上下文和中断上下文

谈谈过程上下文、中断上下文及原子上下文的一些概念

软中断和硬中断的区别

硬中断:

  1. 硬中断是由 硬件产生 的,比方,像磁盘,网卡,键盘,时钟等。每个设施或设施集都有它本人的 IRQ(中断请求)。基于 IRQ,CPU 能够将相应的申请散发到对应的硬件驱动上(注:硬件驱动通常是内核中的一个子程序,而不是一个独立的过程)。

  2. 解决中断的驱动是须要运行在 CPU 上的,因而,当中断产生的时候,CPU 会中断以后正在运行的工作,来解决中断。在有多外围的零碎上,一个中断通常只能中断一颗 CPU(也有一种非凡的状况,就是在大型主机上是有硬件通道的,它能够在没有主 CPU 的反对下,能够同时解决多个中断。)。

  3. 硬中断能够间接中断 CPU。它会引起内核中相干的代码被触发。对于那些须要破费一些工夫去解决的过程,中断代码自身也能够被其余的硬中断中断。

  4. 对于时钟中断,内核调度代码会将以后正在运行的过程挂起,从而让其余的过程来运行。它的存在是为了让调度代码(或称为调度器)能够调度多任务。

软中断:

  1. 软中断的解决十分像硬中断。然而,它们仅仅是由 以后正在运行的过程 所产生的。

  2. 通常,软中断是一些对 I / O 的申请。这些申请会调用内核中能够调度 I / O 产生的程序。对于某些设施,I/ O 申请须要被立刻解决,而磁盘 I / O 申请通常能够排队并且能够稍后解决。依据 I / O 模型的不同,过程或者会被挂起直到 I / O 实现,此时内核调度器就会抉择另一个过程去运行。I/ O 能够在过程之间产生并且调度过程通常和磁盘 I / O 的形式是雷同。

  3. 软中断仅与内核相分割。而内核次要负责对须要运行的任何其余的过程进行调度。一些内核容许设施驱动的一些局部存在于用户空间,并且当须要的时候内核也会调度这个过程去运行。

  4. 软中断并不会间接中断 CPU。也 只有以后正在运行的代码(或过程)才会产生软中断。这种中断是一种须要内核为正在运行的过程去做一些事件(通常为 I /O)的申请。有一个非凡的软中断是 Yield 调用,它的作用是申请内核调度器去查看是否有一些其余的过程能够运行。

硬中断、软中断和信号的区别

  硬中断是 外部设备对 CPU 的中断 ,软中断是 中断底半部的一种解决机制 ,而信号则是由 内核(或其余过程)对某个过程的中断 。在波及零碎调用的场合,人们也常说通过软中断(例如 ARM 为 swi)陷入内核,此时软中断的概念是指由 软件指令引发的中断 ,和咱们这个中央说的 softirq 是两个齐全不同的概念,一个是 software,一个是 soft。
  须要特地阐明的是,软中断以及基于软中断的 tasklet 如果在某段时间内大量呈现的话,内核会把后续软中断放入 ksoftirqd 内核线程中执行。总的来说, 中断优先级高于软中断 软中断又高于任何一个线程 。软中断适度线程化,能够缓解高负载状况下零碎的响应。
如遇到排版错乱的问题,能够通过以下链接拜访我的 CSDN。

**CSDN:[CSDN 搜寻“嵌入式与 Linux 那些事”]

正文完
 0