13 内核开发-任务调度-Work queues工作队列
目录
9.比较 workqueue_struct 与 tasklet
课程简介:
Linux内核开发入门是一门旨在帮助学习者从最基本的知识开始学习Linux内核开发的入门课程。该课程旨在为对Linux内核开发感兴趣的初学者提供一个扎实的基础,让他们能够理解和参与到Linux内核的开发过程中。
课程特点:
1. 入门级别:该课程专注于为初学者提供Linux内核开发的入门知识。无论你是否具有编程或操作系统的背景,该课程都将从最基本的概念和技术开始,逐步引导学习者深入了解Linux内核开发的核心原理。
2. 系统化学习:课程内容经过系统化的安排,涵盖了Linux内核的基础知识、内核模块编程、设备驱动程序开发等关键主题。学习者将逐步了解Linux内核的结构、功能和工作原理,并学习如何编写和调试内核模块和设备驱动程序。
3. 实践导向:该课程强调实践,通过丰富的实例和编程练习,帮助学习者将理论知识应用到实际的Linux内核开发中。学习者将有机会编写简单的内核模块和设备驱动程序,并通过实际的测试和调试来加深对Linux内核开发的理解。
4. 配套资源:为了帮助学习者更好地掌握课程内容,该课程提供了丰富的配套资源,包括教学文档、示例代码、实验指导和参考资料等。学习者可以根据自己的学习进度和需求,灵活地利用这些资源进行学习和实践。
无论你是计算机科学专业的学生、软件工程师还是对Linux内核开发感兴趣的爱好者,Linux内核开发入门课程都将为你提供一个扎实的学习平台,帮助你掌握Linux内核开发的基础知识,为进一步深入研究和应用Linux内核打下坚实的基础。
这一讲,主要分享如何在内核开模块开发中如何使用工作对了 workqueue。
1.定义
内核中调度任务除了tasklet 可以调度任务外,还可以使用 workqueue ,内核里面使用 CFS 执行队列里面的任务,所以熟悉workqueue 就要先了解 CFS 。
Completely Fair Scheduler (CFS)
完全公平调度程序 (CFS) 是 Linux 内核中的一种进程调度程序,它旨在为交互式应用程序提供公平且可预测的性能。它于 2007 年引入内核,并逐渐成为 Linux 系统中最常用的调度程序。
2.内涵
CFS 的主要目标是:
- 公平性: 确保所有进程获得公平的 CPU 时间片。
- 可预测性: 进程应该能够预测它们何时将获得 CPU 时间。
- 交互性: 交互式应用程序(例如桌面环境和游戏)应该获得优先级,以提供流畅的性能。
CFS 通过使用以下机制来实现这些目标:
- 虚拟运行时间 (vruntime): 每个进程都有一个 vruntime 值,它表示进程在没有被抢占的情况下运行的时间。
- 公平时间片: 每个进程在运行一段时间后都会被抢占,这个时间段称为公平时间片。公平时间片的大小是根据进程的优先级动态计算的。
- 赤字会计: 当一个进程被抢占时,它会累积赤字。赤字表示进程在被抢占之前应该运行的额外时间。
- 负载平衡: CFS 尝试将进程均匀地分布在所有可用的 CPU 上,以最大限度地提高系统吞吐量。
CFS 的工作原理
- CFS 为每个进程维护一个 vruntime 值。
- CFS 根据进程的优先级计算公平时间片。
- CFS 允许进程运行其公平时间片。
- 如果一个进程在公平时间片结束之前被抢占,则它的赤字会增加。
- 当一个进程的赤字足够大时,CFS 会提升它的优先级,以便它可以更快地运行。
- CFS 尝试将进程均匀地分布在所有可用的 CPU 上。
- CFS 是一种非常有效的调度程序,它可以为交互式应用程序提供公平且可预测的性能。它也是一个非常可配置的调度程序,可以根据系统的特定需求进行调整。
此外,CFS 是一个层次结构调度程序,这意味着它使用多个队列来管理进程。CFS 使用红黑树来跟踪进程的 vruntime 值。CFS 可以与其他调度程序(例如实时调度程序)结合使用。
3.使用示例
(a) stuct workqueue_struct *myqueue;
workqueue_struct 是内核中用于表示工作队列的数据结构。工作队列是一种机制,允许内核将任务委托给内核线程池异步执行。
(b)alloc_workqueue("my_workqueue", WQ_HIGHPRI, 0);
alloc_workqueue() 函数用于分配和初始化一个新的工作队列。它需要以下参数:
*const char name: 工作队列的名称。
int flags: 工作队列的标志。可以使用的标志包括:
WQ_HIGHPRI:创建一个高优先级工作队列。
WQ_UNBOUND:创建一个不绑定到任何特定 CPU 的工作队列。
WQ_SYSFS:创建一个在 sysfs 中可见的工作队列。
alloc_workqueue() 函数成功时返回一个 workqueue_struct 指针,失败时返回 NULL。
(c)destroy_workqueue(my_workqueue);
destroy_workqueue() 函数用于销毁一个工作队列。它需要一个 workqueue_struct 指针作为参数。
destroy_workqueue() 函数成功时返回 0,失败时返回 -1。
4.具体代码使用实践
#include <linux/init.h>
#include <linux/module.h>
#include <linux/workqueue.h>
static struct workqueue_struct *queue = NULL;
static struct work_struct work;
static void work_handler(struct work_struct *data)
{
pr_info("work handler function.\n");
}
static int __init sched_init(void)
{
queue = alloc_workqueue("HELLOWORLD", WQ_UNBOUND, 1);
INIT_WORK(&work, work_handler);
queue_work(queue, &work);
return 0;
}
static void __exit sched_exit(void)
{
destroy_workqueue(queue);
}
module_init(sched_init);
module_exit(sched_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Workqueue example");
5.注意事项
在内核中使用 workqueue_struct 时需要注意以下几点:
- 选择正确的标志: 在调用 alloc_workqueue() 函数时,必须仔细选择要使用的标志。不同的标志会影响工作队列的行为方式。例如,WQ_HIGHPRI 标志会创建一个高优先级工作队列,而 WQ_UNBOUND 标志会创建一个不绑定到任何特定 CPU 的工作队列。
- 正确使用工作队列: 必须正确使用工作队列。例如,不应在中断上下文中使用工作队列。
- 释放工作队列: 在使用完工作队列后,必须使用 destroy_workqueue() 函数释放它。
使用 workqueue_struct 更多具体事项:
- 避免死锁: 如果工作队列中的任务试图获取已经在持有该锁的内核对象上的锁,则可能会发生死锁。
- 避免饥饿: 如果工作队列中的任务具有非常高的优先级,则可能会饿死其他优先级较低的任务。
- 性能注意事项: 工作队列可能会对内核性能产生影响。例如,如果工作队列中的任务非常耗时,则可能会导致内核性能下降。
6.最佳实践
在内核中使用 workqueue_struct 的最佳实践包括:
- 选择正确的标志: 在调用 alloc_workqueue() 函数时,请仔细选择要使用的标志。不同的标志会影响工作队列的行为方式。例如,WQ_HIGHPRI 标志会创建一个高优先级工作队列,而 WQ_UNBOUND 标志会创建一个不绑定到任何特定 CPU 的工作队列。一般来说,应避免使用 WQ_HIGHPRI 标志,除非绝对必要。
- 正确使用工作队列: 必须正确使用工作队列。例如,不应在中断上下文中使用工作队列。此外,应避免在工作队列中执行耗时的任务。
- 释放工作队列: 在使用完工作队列后,必须使用 destroy_workqueue() 函数释放它。
- 避免死锁: 如果工作队列中的任务试图获取已经在持有该锁的内核对象上的锁,则可能会发生死锁。为了避免死锁,应注意不要在工作队列中获取内核对象的锁。
- 避免饥饿: 如果工作队列中的任务具有非常高的优先级,则可能会饿死其他优先级较低的任务。为了避免饥饿,应避免在工作队列中创建具有非常高优先级的任务。
- 性能注意事项: 工作队列可能会对内核性能产生影响。例如,如果工作队列中的任务非常耗时,则可能会导致内核性能下降。为了避免性能问题,应避免在工作队列中执行耗时的任务。
- 使用工作队列来执行异步任务: 工作队列最适合用于执行异步任务。例如,工作队列可用于将任务委托给内核线程池以在后台执行。
- 使用工作队列来提高可伸缩性: 工作队列可以提高内核的可伸缩性,因为它们允许内核在多个 CPU 上并行执行任务。
- 使用工作队列来提高模块性: 工作队列可以提高内核的模块性,因为它们允许内核以模块化方式安排任务。
7.总结
总的来说,与 tasklet 相比,在内核中使用 CFS workqueue_struct 的主要优点是更好的可伸缩性、更少的上下文切换开销、更好的模块性和更少的同步开销。
tasklet 在某些情况下仍然比 workqueue_struct 更合适。例如,tasklet 更适合处理需要非常快速响应时间的任务。workqueue_struct 更适合处理需要高吞吐量或低延迟的任务,而 tasklet 更适合处理需要非常快速响应时间的任务。
9.比较 workqueue_struct 与 tasklet
任务调度 | 优点 | 缺点 |
workqueue_struct | 更好的可伸缩性: workqueue_struct 可以跨多个 CPU 并行执行任务,而 tasklet 只能在单个 CPU 上执行。这使得 workqueue_struct 更适合处理需要高吞吐量或低延迟的任务。
|
延迟可能更高: workqueue_struct 中的任务可能需要比 tasklet 更长的时间才能执行,因为它们必须排队等待内核线程池中的线程可用。
|
tasklet | 非常低的延迟: tasklet 在中断上下文中执行,这意味着它们可以非常快速地响应事件。
|
可伸缩性较差: tasklet 只能在单个 CPU 上执行,这意味着它们不适合处理需要高吞吐量或低延迟的任务。
|