Linux 内核中断底半部的引入

Last edited
Last updated July 13, 2023
Pages
Tags

1. 引入中断底半部的原因

在中断处理过程中一般不允许有延时、耗时甚至休眠的操作。但是有时候在中断到来时,在中断的处理过程中(回调函数中)又想多做一些事情,这样两者产生了矛盾。为了解决这个矛盾,引入了中断底半部:将中断分为顶半部和底半部两部分。
  • 中断顶半部:主要处理一些紧急但不耗时的任务
  • 中断底半部:主要处理一些不紧急但是耗时的任务
比如,当串口通信时,有数据到达,发起串口中断,在串口中断处理过程中开启底半部,在底半部中完成数据的接收

2. 中断底半部的实现机制

中断底半部可以通过软中断tasklet 以及工作队列三种机制实现。

3. 软中断

软中断能够处理底半部存在数量的限制(32个),一般软中断留给内核开发者使用。

4. tasklet

4.1 概述

tasklet 是基于软中断工作原理实现的一种底半部机制,但是没有软中断的数量限制。tasklet 这种机制属于中断的一部分,不可以脱离中断去执行。tasklet 底半部不允许有延迟、休眠操作,但是可以进行耗时操作。
👉
tasklet 机制是当中断顶半部执行即将结束时,中断标志位会被清除。此时 tasklet 机制就会判断 tasklet 底半部标志位是否被置位,如果底半部标志位被置位,说明要开启底半部,此时进入 tasklet 底半部进行任务处理。tasklet 同时能处理的底半部最多是 5 个,超过 5 个,超过的那一部分需要开启内核线程去处理。

4.2 API

//tasklet对象结构体 struct tasklet_struct { //执行下一个tasklet对象 struct tasklet_struct *next; //底半部标志位,想要开启底半部,就把这一位设置为1 unsigned long state; //记录底半部被触发的次数 atomic_t count; //用于设置使用哪一种底半部函数 true表示使用callback回调 false表示使用func bool use_callback; //底半部处理函数的函数指针 union { void (*func)(unsigned long data); void (*callback)(struct tasklet_struct *t); }; //传入func回调函数中的参数 unsigned long data; };
// 1.分配tasklet对象 struct tasklet tasklet; // 2.初始化tasklet对象 void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data) // 功能:用于初始化底半部函数是func形式的tasklet对象 void tasklet_setup(struct tasklet_struct *t, void (*callback)(struct tasklet_struct *)) // 功能:用于初始化底半部函数是callback形式的tasklet对象 // 3.开启tasklet底半部 void tasklet_schedule(struct tasklet_struct *t) // 参数:底半部对象指针

4.3 实例代码

5. 工作队列

5.1 概述

工作队列机制是基于从内核启动就开始存在的一个内核线程来实现的,这个内核线程默认是处于休眠状态的。当有任务需要执行的时候,将任务提交到工作队列中,然后唤醒休眠的内核线程,内核线程从队列中拿到任务去处理即可。工作队列既可以用于中断中,也可以用于线程中。
💡
相较于 tasklet,工作队列优势在于限制更少(不限制 5 个,不依赖中断),劣势在于消耗更多的资源。

5.2 API

//工作队列对象 struct work_struct { atomic_long_t data; struct list_head entry;//构成队列 work_func_t func; //typedef void (*work_func_t)(struct work_struct *work); }; // 1.分配工作队列对象 struct work_struct work; // 2.初始化工作队列 INIT_WORK(&work, 函数指针) // 3.唤醒线程处理工作队列底半部 bool schedule_work(struct work_struct *work)

5.3 实例代码