Linux驱动开发学习笔记(三)中断处理(系统)

本文详细介绍了中断处理程序的上下半部概念,以及中断服务例程和设备驱动程序的角色。中断处理被分为上半部和下半部以避免重入问题并优化响应时间。下半部通过软中断(Softirqs)、Tasklets、Workqueues和ThreadedIRQs等机制在适当的时机执行耗时任务。软中断和Tasklets在中断上下文执行,不可抢占;Workqueues则在进程上下文,允许抢占。ThreadedIRQs则在内核线程中执行,简化调度。这些机制确保了系统的高效和响应性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

三个名词:

  • 中断处理程序(interrupt handle)

  • 中断服务例程(interrupt service routine,ISR)

  • 设备驱动程序(driver)

一个中断处理程序,一般分为两部分,上部分(top half)和下(底)半部(bottom half)。

为什么要这么分呢?

当一个中断触发请求后,中断处理程序会被调用,内核为了防止re-entrance(重入)问题,会禁止所有处理器(all processors)中对应中断线(IRQ line)——相当于屏蔽中断。而且在中断上下文中,也就是进行中断处理程序(interrupt handle)时,内核会禁止当前CPU的调度(preemption)。

When writing the interrupt handler, you don’t have to worry about re-entrance, since the IRQ line serviced is disabled on all processors by the kernel in order to avoid recursive interrupts.

为了在中断处理期间尽量不要错过中断信号,就应该尽快完成中断处理。于是乎,内核就引入了下半部(halves),将大部分耗时工作推迟到下半部去,在合适的时机下半部会被执行。这样还有一个好处就是可以让中断处理程序不会长时间禁止抢占,让CPU可以及时调度其它中断处理程序,降低整个系统的延时。

上下部

不仅中断处理程序可以分为上下部,其它的程序也可以有这个设计思想。也就是说,把程序分割成两部分,第一部主要处理比较紧急的任务,将耗时的任务推迟到第二部分去执行。

第二部分一般采用内核的延迟任务机制(work-deferring mechanism)来实现,包括以下四种:

  1. Softirqs(interrupt context)
  2. Tasklets(interrupt context)
  3. Workqueues(process context)
  4. Threaded IRQs(process context)

前两个工作在中断上下文中,因此无法被抢占。而后面两个工作在进程上下文中,可以被抢占。

后面两个可以通过设置 CONFIG_PREEMPT或CONFIG_PREEMPT_VOLUNTARY参数改变抢占机制,不过会影响整个系统。

Softirqs

软中断是内核一种用于推迟任务执行的机制(deferring mechanism),一般用在如网络设备和块设备,这样需要快速及时进行处理的驱动程序上。

内核使用Ksoftirqds线程来负责对软中断的调度执行,当有大量软中断到来时,它们会被堆积在一个队列上面,由Ksoftirqds负责进行调度,每个CPU都会有一个Ksoftirqds线程,它们以ksoftirqd/n的方式命名(n为第n个CPU)。
在这里插入图片描述
软中断在平时不常用,一般都是使用Tasklets就足够了。

Tasklets

Tasklets是软中断的一种实现方式,内核中的软中断能支持32个,由一个struct softirq_action结构体来描述每一个软中断,在kernel/softirq.c中定义了一个包含32个这样结构体的数组static struct softirq_action softirq_vec[NR_SOFTIRQS],而Tasklet占其中的两个,分别为HI_SOFTIRQ和TASKLET_SOFTIRQ,前者用于高优先级的Tasklets,后者用于正常的Tasklets。

内核使用struct tasklet_struct结构体来描述一个tasklet。

struct tasklet_struct
{
    struct tasklet_struct *next;
    unsigned long state;
    atomic_t count;
    void (*func)(unsigned long);
    unsigned long data;
};

正常情况下Tasklets是不可重入的(not re-entrant),除非是这个Tasklets本身可以被中断,然后重新调度自己。

“Code is called reentrant if it can be interrupted anywhere in the middle of its execution, and then be safely called again. ”

同一个Tasklets只能够运行在某个CPU上面,不能同时运行在多个CPU上,不同的Tasklets可以同时运行在不同的CPU上。

前面提到Tasklets的两种调度方式,分别对应于正常的和高优先级的。

void tasklet_schedule(struct tasklet_struct *t);
void tasklet_hi_schedule(struct tasklet_struct *t);

一般情况下,使用正常优先级的就够了,高优先级会比正常优先级先执行,频繁使用高优先级调度可能会造成整个系统的延迟。

High priority tasklets are always executed before normal ones. Abusive use of high priority tasks will increase system latency. Only use them for really quick stuff.

Workqueues

这是一种最常用的延迟任务机制(work-deferring mechanism),它工作在进程上下文中,如果需要在下半部进行休眠就可以考虑使用work queues来实现。

work queues是在内核线程(kernel threads)的基础上实现的,在内核中有两种work queues:

  • shared work queue:由内核中默认的一组全局线程进行处理,在每一个CPU上都有一个这样的线程(类似于软中断的ksoftirqd)。这些线程用events/n进行命名。

  • dedicated kernel thread:这种工作队列会创建自己的内核线程——dedicated kernel thread,根据创建方式不同,会产生不同数量的线程,这些线程会绑定到某个CPU上。

    “run the work queue in a dedicated kernel thread. It means whenever your work queue handler needs to be executed, your kernel thread is woken up to handle it"

    使用create_workqueue()进行创建时,会在每个CPU上创建一个内核线程,并绑定到对应CPU上。

    使用create_singlethread_workqueue()进行创建时,只会创建一个内核线程,绑定到当前CPU上。

Threaded IRQs
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
                         irq_handler_t thread_fn,
                         unsigned long irqflags,
                         const char *devname, void *dev_id)

这个下半部会在一个专用内核线程(dedicated kernel thread)中执行 。

  • irq_handler_t handler:这个代表的中断处理函数,和request_irq中的类似,负责上半部的中断处理,运行在中断上下文中。
  • irq_handler_t thread_fn:这个代表下半部的处理函数,当上半部返回值为 IRQ_WAKE_THREAD 时,内核的线程会关联这个函数,然后在合适时机进行执行。

不同上面几种方式,使用Threaded IRQs时,我们不需要自己进行显示的调度,由内核自动帮我们调度。

可以通过使用IRQF_ONESHOT标志,一直屏蔽中断直到下半部分执行完后,再打开中断。

if for any reason you need the IRQ line not to be re-enabled after the top half, and to remain disabled until the threaded handler has been run, you should request the threaded IRQ with the flag IRQF_ONESHOT enabled (by just doing an OR operation).

中断处理程序流程

  1. 一般我们会在驱动程序的init函数中,进行中断的注册:

    int request_irq(unsigned int irq, irq_handler_t handler,
                    unsigned long flags, const char *name, void *dev)
    /* 或者使用request_threaded_irq方式 */
    
  2. 当中断线irq上有信号到达时,会经过下面的流程最终调用我们在request_irq()指明的中断处理函数handler。

  3. 我们在中断处理程序(函数)中快速完成上半部的处理,然后通过使用deferring mechanism机制,将耗时操作推迟到下半部去执行。

  4. 最终在下半部完成全部的中断处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值