13 内核开发-任务调度-Work queues工作队列

发布于:2024-04-28 ⋅ 阅读:(19) ⋅ 点赞:(0)
13 内核开发-任务调度-Work queues工作队列

目录

13 内核开发-任务调度-Work queues工作队列

1.定义

2.内涵

3.使用示例

4.具体代码使用实践

5.注意事项

6.最佳实践

7.总结

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 的主要目标是:

  1. 公平性: 确保所有进程获得公平的 CPU 时间片。
  2. 可预测性: 进程应该能够预测它们何时将获得 CPU 时间。
  3. 交互性: 交互式应用程序(例如桌面环境和游戏)应该获得优先级,以提供流畅的性能。

CFS 通过使用以下机制来实现这些目标:

  1. 虚拟运行时间 (vruntime): 每个进程都有一个 vruntime 值,它表示进程在没有被抢占的情况下运行的时间。
  2. 公平时间片: 每个进程在运行一段时间后都会被抢占,这个时间段称为公平时间片。公平时间片的大小是根据进程的优先级动态计算的。
  3. 赤字会计: 当一个进程被抢占时,它会累积赤字。赤字表示进程在被抢占之前应该运行的额外时间。
  4. 负载平衡: CFS 尝试将进程均匀地分布在所有可用的 CPU 上,以最大限度地提高系统吞吐量。


CFS 的工作原理

  1. CFS 为每个进程维护一个 vruntime 值。
  2. CFS 根据进程的优先级计算公平时间片。
  3. CFS 允许进程运行其公平时间片。
  4. 如果一个进程在公平时间片结束之前被抢占,则它的赤字会增加。
  5. 当一个进程的赤字足够大时,CFS 会提升它的优先级,以便它可以更快地运行。
  6. CFS 尝试将进程均匀地分布在所有可用的 CPU 上。
  7. 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 时需要注意以下几点:

  1.     选择正确的标志: 在调用 alloc_workqueue() 函数时,必须仔细选择要使用的标志。不同的标志会影响工作队列的行为方式。例如,WQ_HIGHPRI 标志会创建一个高优先级工作队列,而 WQ_UNBOUND 标志会创建一个不绑定到任何特定 CPU 的工作队列。
  2.     正确使用工作队列: 必须正确使用工作队列。例如,不应在中断上下文中使用工作队列。
  3.     释放工作队列: 在使用完工作队列后,必须使用 destroy_workqueue() 函数释放它。

使用 workqueue_struct 更多具体事项:

  1.     避免死锁: 如果工作队列中的任务试图获取已经在持有该锁的内核对象上的锁,则可能会发生死锁。
  2.     避免饥饿: 如果工作队列中的任务具有非常高的优先级,则可能会饿死其他优先级较低的任务。
  3.     性能注意事项: 工作队列可能会对内核性能产生影响。例如,如果工作队列中的任务非常耗时,则可能会导致内核性能下降。

6.最佳实践


在内核中使用 workqueue_struct 的最佳实践包括:

  1. 选择正确的标志: 在调用 alloc_workqueue() 函数时,请仔细选择要使用的标志。不同的标志会影响工作队列的行为方式。例如,WQ_HIGHPRI 标志会创建一个高优先级工作队列,而 WQ_UNBOUND 标志会创建一个不绑定到任何特定 CPU 的工作队列。一般来说,应避免使用 WQ_HIGHPRI 标志,除非绝对必要。
  2. 正确使用工作队列: 必须正确使用工作队列。例如,不应在中断上下文中使用工作队列。此外,应避免在工作队列中执行耗时的任务。
  3. 释放工作队列: 在使用完工作队列后,必须使用 destroy_workqueue() 函数释放它。
  4. 避免死锁: 如果工作队列中的任务试图获取已经在持有该锁的内核对象上的锁,则可能会发生死锁。为了避免死锁,应注意不要在工作队列中获取内核对象的锁。
  5. 避免饥饿: 如果工作队列中的任务具有非常高的优先级,则可能会饿死其他优先级较低的任务。为了避免饥饿,应避免在工作队列中创建具有非常高优先级的任务。
  6. 性能注意事项: 工作队列可能会对内核性能产生影响。例如,如果工作队列中的任务非常耗时,则可能会导致内核性能下降。为了避免性能问题,应避免在工作队列中执行耗时的任务。
  7. 使用工作队列来执行异步任务: 工作队列最适合用于执行异步任务。例如,工作队列可用于将任务委托给内核线程池以在后台执行。
  8. 使用工作队列来提高可伸缩性: 工作队列可以提高内核的可伸缩性,因为它们允许内核在多个 CPU 上并行执行任务。
  9. 使用工作队列来提高模块性: 工作队列可以提高内核的模块性,因为它们允许内核以模块化方式安排任务。

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 则使用 softirq。内核线程池的上下文切换开销通常低于 softirq。


更好的模块性: workqueue_struct 可以更轻松地与其他内核子系统集成。例如,workqueue_struct 可以与内核事件通知机制一起使用,允许在事件发生时安排任务。


更少的同步开销: workqueue_struct 使用无锁数据结构来管理任务队列,而 tasklet 则使用自旋锁。这使得 workqueue_struct 在高并发情况下具有更好的可扩展性。

延迟可能更高: workqueue_struct 中的任务可能需要比 tasklet 更长的时间才能执行,因为它们必须排队等待内核线程池中的线程可用。


可能更难调试: workqueue_struct 中的任务在内核线程池中执行,这可能使调试更困难。

tasklet

非常低的延迟: tasklet 在中断上下文中执行,这意味着它们可以非常快速地响应事件。


更简单的调试: tasklet 在中断上下文中执行,这可以使调试更简单。

可伸缩性较差: tasklet 只能在单个 CPU 上执行,这意味着它们不适合处理需要高吞吐量或低延迟的任务。


更多的上下文切换开销: tasklet 使用 softirq 来执行任务,而 softirq 的上下文切换开销通常高于内核线程池。


模块性较差: tasklet 难以与其他内核子系统集成。
更多的同步开销: tasklet 使用自旋锁来管理任务队列,这可能导致高并发情况下出现性能问题。


网站公告

今日签到

点亮在社区的每一天
去签到