Linux内核中的软中断、tasklet和工作队列详解( 五 )
文章插图
工作队列从上面的介绍看以看出 , 软中断运行在中断上下文中 , 因此不能阻塞和睡眠 , 而tasklet使用软中断实现 , 当然也不能阻塞和睡眠 。 但如果某延迟处理函数需要睡眠或者阻塞呢?没关系工作队列就可以如您所愿了 。把推后执行的任务叫做工作(work) , 描述它的数据结构为work_struct, 这些工作以队列结构组织成工作队列(workqueue) , 其数据结构为workqueue_struct, 而工作线程就是负责执行工作队列中的工作 。 系统默认的工作者线程为events 。工作队列(work queue)是另外一种将工作推后执行的形式 。 工作队列可以把工作推后 , 交由一个内核线程去执行—这个下半部分总是会在进程上下文执行 , 但由于是内核线程 , 其不能访问用户空间 。 最重要特点的就是工作队列允许重新调度甚至是睡眠 。通常 , 在工作队列和软中断/tasklet中作出选择非常容易 。 可使用以下规则: - 如果推后执行的任务需要睡眠 , 那么只能选择工作队列 。- 如果推后执行的任务需要延时指定的时间再触发 , 那么使用工作队列 , 因为其可以利用timer延时(内核定时器实现) 。- 如果推后执行的任务需要在一个tick之内处理 , 则使用软中断或tasklet , 因为其可以抢占普通进程和内核线程 , 同时不可睡眠 。- 如果推后执行的任务对延迟的时间没有任何要求 , 则使用工作队列 , 此时通常为无关紧要的任务 。实际上 , 工作队列的本质就是将工作交给内核线程处理 , 因此其可以用内核线程替换 。 但是内核线程的创建和销毁对编程者的要求较高 , 而工作队列实现了内核线程的封装 , 不易出错 , 所以我们也推荐使用工作队列 。
相关数据结构
- 正常工作结构体
struct work_struct {atomic_long_t data; //传递给工作函数的参数#define WORK_STRUCT_PENDING 0/* T if work item pending execution */#define WORK_STRUCT_FLAG_MASK (3UL)#define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)struct list_head entry; //链表结构 , 链接同一工作队列上的工作 。work_func_t func; //工作函数 , 用户自定义实现#ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map;#endif};//工作队列执行函数的原型:void (*work_func_t)(struct work_struct *work);//该函数会由一个工作者线程执行 , 因此其在进程上下文中 , 可以睡眠也可以中断 。 但只能在内核中运行 , 无法访问用户空间 。
- 延迟工作结构体(延迟的实现是在调度时延迟插入相应的工作队列)
struct delayed_work {struct work_struct work;struct timer_list timer; //定时器 , 用于实现延迟处理};
- 工作队列结构体
struct workqueue_struct {struct cpu_workqueue_struct *cpu_wq; //指针数组 , 其每个元素为per-cpu的工作队列struct list_head list;const char *name;int singlethread; //标记是否只创建一个工作者线程int freezeable;/* Freeze threads during suspend */int rt;#ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map;#endif};
- 每cpu工作队列(每cpu都对应一个工作者线程worker_thread)
struct cpu_workqueue_struct {spinlock_t lock;struct list_head worklist;wait_queue_head_t more_work;struct work_struct *current_work;struct workqueue_struct *wq;struct task_struct *thread;} ____cacheline_aligned;
相关API- 缺省工作队列
静态创建 DECLARE_WORK(name,function); //定义正常执行的工作项DECLARE_DELAYED_WORK(name,function);//定义延后执行的工作项动态创建INIT_WORK(_work, _func) //创建正常执行的工作项INIT_DELAYED_WORK(_work, _func)//创建延后执行的工作项调度默认工作队列int schedule_work(struct work_struct *work)//对正常执行的工作进行调度 , 即把给定工作的处理函数提交给缺省的工作队列和工作者线程 。 工作者线程本质上是一个普通的内核线程 , 在默认情况下 , 每个CPU均有一个类型为“events”的工作者线程 , 当调用schedule_work时 , 这个工作者线程会被唤醒去执行工作链表上的所有工作 。 系统默认的工作队列名称是:keventd_wq,默认的工作者线程叫:events/n , 这里的n是处理器的编号,每个处理器对应一个线程 。 比如 , 单处理器的系统只有events/0这样一个线程 。 而双处理器的系统就会多一个events/1线程 。 默认的工作队列和工作者线程由内核初始化时创建:start_kernel()-->rest_init-->do_basic_setup-->init_workqueues调度延迟工作int schedule_delayed_work(struct delayed_work *dwork,unsigned long delay)刷新缺省工作队列void flush_scheduled_work(void)//此函数会一直等待 , 直到队列中的所有工作都被执行 。 取消延迟工作static inline int cancel_delayed_work(struct delayed_work *work)//flush_scheduled_work并不取消任何延迟执行的工作 , 因此 , 如果要取消延迟工作 , 应该调用cancel_delayed_work 。
- AMD Zen3 APU内核图提前偷跑:三级缓存质变
- 苹果M1、A14内核设计对比:差别很大
- Linux Kernel 5.10.5发布:禁用FBCON加速滚动特性
- 田伟院士:我眼中的医疗机器人
- Mozilla将默认禁用Firefox中的退格键以防止用户编辑数据丢失
- Linux 5.11开始围绕PCI Express 6.0进行早期准备
- Fedora正在寻求协助 希望加快Linux 5.10 LTS内核测试进度
- Linux Mint 20.1 Ulyssa稳定版已确定延期至2021年初发布
- 英特尔Xe GPU在Linux 5.11上的性能表现不错
- MIPS架构厂商日渐式微 Linux报告其漏洞遭遇困难