软中断
软中断简介
软中断一般而言在对时间要求较高的地方使用,常见的如网络
其分为八种类型
API
步骤 | 函数 | 参数 | 意义 |
---|---|---|---|
1 | open_softirq | int nr, void(* action)(struct softirq_action *) | 注册nr类型的软中断,处理函数为action |
2 | action | struct softirq_action * | 中断处理函数 |
3 | raise_softirq | int nr | 触发nr类型的软中断,设为挂起状态,下次周期运行 |
其中在软中断运行时,允许响应中断,也可被其他cpu再次激活软中断,两个同时运行,所以处理函数必须是可重入函数,唯一不响应中断的时候就是在触发中断时raise_softirq
,在设挂起后响应中断
tasklet
tasklet 简介
tasklet建立在软中断上,但是对比直接使用软中断来说,tasklet有以下几点区别
- 一种特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行
- 多个不同类型的tasklet可以并行在多个CPU上
- 软中断是静态分配的,tasklet可以动态改变
简单的说软中断可并发在cpu上运行,所以需要多考虑并发的同步和互斥
tasklet杜绝了同一个任务并发运行,但是可以不同的tasklet在不同的cpu上可以同时运行
struct tasklet_struct
{
struct tasklet_struct *next; //链表中的下一个tasklet
unsigned long state; //tasklet的状态
atomic_t count; //引用计数器
void (*func)(unsigned long); //tasklet处理函数
unsigned long data; //tasklet处理函数的传参
};
其中taskelet状态
TASKLET_STATE_SCHED //tasklet已被挂起,尚未运行
TASKLET_STATE_RUN //tasklet正在运行
0
count
为tasklet引用计数器
0 //使能tasklet,此状态下被设置挂起时,才可执行
1 //失能tasklet
tasklet API
步骤 | 函数 | 参数 | 意义 |
---|---|---|---|
1 | DECLARE_TASKLET | name,func,data | 创建一个引用计数器为0的tasklet |
1 | DECLARE_TASKLET_DISABLED | name,func,data | 创建一个引用计数器为1的tasklet |
2 | tasklet_init | struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data | 赋值一个名为t的tasklet,其调度函数为func,data为调度函数的传参,count为0 |
3 | func | unsigned long | 实现调度函数 |
4 | tasklet_schedule | struct tasklet_struct *t | 将名为t的tasklet标记挂起 |
5 | tasklet_disable | struct tasklet_struct *t | 失能名为t的tasklet |
6 | tasklet_enable | struct tasklet_struct *t | 使能名为t的tasklet,与tasklet_disable成对使用 |
7 | tasklet_kill | struct tasklet_struct *t | 将名为t的tasklet休眠,该函数可能会休眠 |
ksoftirqd
软中段(包括tasklet)是人为申请触发的,故而肯定会出现时而爆满时而空闲的状况,为了提高内核效率就必然会有一套动态效率控制的方法
ksoftirqd
为了解决软中断动态性数量而造成的内核效率问题,在每个处理器都有线程ksoftirqd
,它在软中断爆满时唤醒,此线程优先级为最低,达到不抢占其他任务,却肯定会执行的效果
示例场景:
当从中断返回时触发软中段执行中断下半部,在执行软中断时又可以重复触发自身,形成循环,无限占用内核,当ksoftirqd
出现时,触发的软中断优先级被调到最低,在执行其他的重要任务后才执行软中断,避免了内核被一直占用的问题
workqueue
均以2.6.19以后的内核代码为例
workqueue简介
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZhApkuij-1596530329712)(http://sswiki.sigmastar.com.tw:8090/download/attachments/1225160/workqueue.png?version=1&modificationDate=1596528398356&api=v2)]
先看看关键的数据结构:
-
work_struct:工作队列调度的最小单位,work item;
-
workqueue_struct:工作队列,work item都挂入到工作队列中;
-
worker:work item的处理者,每个worker对应一个内核线程;
-
worker_pool:worker池(内核线程池),是一个共享资源池,提供不同的worker来对work item进行处理;
-
pool_workqueue:充当桥梁纽带的作用,用于连接workqueue和worker_pool,建立链接关系;
struct work_struct {
atomic_long_t data; //低比特存放状态位,高比特存放worker_pool的ID或者pool_workqueue的指针
struct list_head entry; //用于添加到其他队列上
work_func_t func; //工作任务的处理函数,在内核线程中回调
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
其中,data
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fhR9USAR-1596530329714)(http://sswiki.sigmastar.com.tw:8090/download/attachments/1225160/work_struct-data.png?version=2&modificationDate=1596528634253&api=v2)]+
struct delayed_work {
struct work_struct work;
struct timer_list timer;
/* target workqueue and CPU ->timer uses to queue ->work */
struct workqueue_struct *wq;
int cpu;
};
struct workqueue_struct {
struct list_head pwqs; /* WR: all pwqs of this wq */ //所有的pool_workqueue都添加到本链表中
struct list_head list; /* PR: list of all workqueues */ //用于将工作队列添加到全局链表workqueues中
struct list_head maydays; /* MD: pwqs requesting rescue */ //rescue状态下的pool_workqueue添加到本链表中
struct worker *rescuer; /* I: rescue worker */ //rescuer内核线程,用于处理内存紧张时创建工作线程失败的情况
struct pool_workqueue *dfl_pwq; /* PW: only for unbound wqs */
char name[WQ_NAME_LEN]; /* I: workqueue name */
/* hot fields used during command issue, aligned to cacheline */
unsigned int flags ____cacheline_aligned; /* WQ: WQ_* flags */
struct pool_workqueue __percpu *cpu_pwqs; /* I: per-cpu pwqs */ //Per-CPU都创建pool_workqueue
struct pool_workqueue __rcu *numa_pwq_tbl[]; /* PWR: unbound pwqs indexed by node */ //Per-Node创建pool_workqueue
...
};
其中workqueue
可分两种
默认的工作者线程
自创工作者线程
一般而言工作队列会创建一个默认的内核线程也称为工作者线程worker thread
- 名为events/n ,n为处理器编号
- 每个处理器对应一个线程,比如0处理器为events/0
你也可以自己新开辟一个线程,一般用默认线程
默认工作者线程API:
步骤 | 函数 | 参数 | 意义 |
---|---|---|---|
1 | DECLARE_WORK | name,void(func)(void) | 创建一个名为name的woke_struct,处理函数为func,函数参数为data |
2 | func | void *data | 实现处理函数 |
3 | schedule_work | struct work_struct *work | 将work标记为挂起 |
4 | schedule_delayed_work | struct delayed_work *work,unsigned long delay | 延时delay后将work标记为挂起 |
5 | flush_scheduled_work | void | 等待工作队列项全部执行完毕,期间休眠状态 |
6 | cancel_delayed_work | struct delayed_work *work | 取消延时的work |
自创线程API:
步骤 | 函数 | 参数 | 意义 |
---|---|---|---|
1 | create_workqueue | const char *name | |
2 | INIT_WORK | struct work_struct *work,void(func)(void) | 赋值一个work,处理函数为func,函数参数为work_struct中的data |
3 | INIT_DELAYED_WORK | struct delayed_work *work, void(func)(void) | |
4 | queue_work | struct workqueue_struct*wq,struct work_struct *work | 将work在wq中标记为挂起 |
5 | queue_delayed_work | struct workqueue_struct*wq,struct delayed_work *work,unsigned long delay | 延时delay后将work在wq标记为挂起 |
6 | flush_workqueue | struct workqueue_struct*wq | 等待wq工作项全部执行完毕,期间休眠状态 |
7 | destroy_workqueue | struct workqueue_struct*wq | 释放创建的工作队列 |
下半部机制的抉择
软中断、tasklet、工作队列三者如何选择
软中断、tasklet基本相近,tasklet基于软中断实现,如果对并发有要求可以使用软中断,否则一般而言都使用接口简单的tasklet
工作队列不同与两者,其使用内核线程完成,工作在进程上下文,最大的特点就是可以睡眠,但是开销较大
所以总结而言
- 需要睡眠选工作队列
- 不需要睡眠但是需要并发选软中断
- 不需要睡眠也不需要并发选tasklet