个人博客:https://alive0103.github.io/
代码仓在GitHub:https://github.com/Alive0103/XDU-CS-lab
能点个Star就更好了,欢迎来逛逛哇~❣
为避免高质量博客突然被设置成VIP导致各位看不了,还请关注一下我再看,时时更新,关注不亏~
1. 实验内容
1.1 实验目的
通过本实验的学习,掌握信创操作系统内核定制中的 workqueue 技术,理解其与 kernel thread 的区别。
1.2 实验内容
设计并实现一个内核模块,该模块旨在通过 work queue 和 kernel thread 两种不同的机制来完成延迟工作(deferred work),并对比分析这两种实现方式的异同点。
具体实验步骤如下:
- 分别采用 work queue 和 kernel thread 两种方式调用10个函数(函数内部打印学号后3
位依次加1的方式区分。例如函数1中打印315,函数2中打印316,以此类推),观察并
记录 work queue 与 kernel thread 在执行函数时的顺序差异。请注意,每个函数应当对
应一个独立的 kernel thread,即10个函数需由10个不同的 kernel thread 分别执行。 - 探究 work queue 中的 delayed_work 功能,要求在模块加载成功的5秒后打印一条预设
的信息,以验证 delayed_work 的延迟执行效果。
2. 实验结果
使用 dmesg 命令可以观察到 work queue 的函数按顺序执行,而内核线程的函数执行不是顺序的。模块加载五秒后,显示 delayed work 的输出。
3.代码编写思路
(本博客思路仅供参考,禁止抄袭代码,否则后果自负,在此给出相关编写方法和伪代码,可运行代码将于2024年12月31日后更新)
含有GPT成分,请谨慎辨别。
步骤一:实现 kernel thread 线程处理函数
(1)在 kthread_handler 函数中添加代码
该函数是 kernel thread 的执行主体,接收参数 data。
读取 data 中的 id 信息,并打印相应信息,例如打印学号后三位。
在 kthread_handler 中加入代码,使其可以打印学号后三位依次加1的值(例如,315、316 等),区分每个线程。
使用 msleep_interruptible() 函数设置线程的短暂休眠,以便观察并行执行的顺序差异。
(2)创建内核线程
在 deferred_work_init 中,通过 kthread_create 创建10个内核线程,传入 kthread_handler 作为执行函数。
为每个线程设置唯一的 id(如 315 开始到324)。
使用 wake_up_process 启动每个线程,使其进入运行状态。
(3)终止内核线程
在 deferred_work_exit 函数中,通过 kthread_stop 停止每个内核线程,确保模块卸载时不会有残留线程。
步骤二:实现 work queue 的处理函数
work_queue_handler 是 work queue 的执行函数,接收 work 作为参数。
使用 container_of 宏获取 work_ctx 结构体中的 current_id,并打印信息,例如学号后三位的增量。
在 deferred_work_init 中使用 INIT_WORK 初始化 work,并传入 work_queue_handler 函数作为处理函数。
使用 schedule_work 提交 work 到全局 work queue 中,使其能够按顺序执行。
步骤三:验证 delayed_work 的延迟执行
(1)初始化 delayed_work
在 deferred_work_init 中,通过 INIT_DELAYED_WORK 初始化 my_work,并传入 delayed_work_handler 函数。
(2)调度 delayed_work
使用 schedule_delayed_work 提交 my_work 到 work queue 中,并设置延迟时间为5秒(jiffies)。
delayed_work_handler 中加入 printk,以便在模块加载5秒后输出预设的延迟信息。
步骤四:模块加载和卸载
(1)模块加载
加载模块后,使用 dmesg 查看输出信息。
(2)模块卸载
卸载模块时,通过 deferred_work_exit 停止所有线程并释放资源,确保系统稳定。
模块初始化函数:
输出 "加载模块成功" 表示模块已加载
// 初始化并调度 10 个 work queue 任务
对于 i 从 0 到 9,执行以下操作:
设置 works[i] 的 current_id 为 894 + i // 给每个任务分配唯一 ID
初始化 works[i].work 并绑定 work_queue_handler 作为执行体
调度 works[i].work,提交到全局 work queue 中
// 创建并启动 10 个 kernel thread
对于 i 从 0 到 9,执行以下操作:
创建内核线程 threads[i],传递 works[i] 作为参数并绑定 kthread_handler 作为执行体
如果线程创建成功,则启动线程 threads[i]
// 初始化并调度延迟工作(5 秒后执行)
初始化 my_work 并绑定 delayed_work_handler 作为执行体
将 my_work 延迟调度 5 秒
返回 0 // 初始化成功
work queue 执行体 (work_queue_handler):
获取传入的 work 对应的上下文 ctx
输出 "work queue : <ctx.current_id>"
kernel thread 执行体 (kthread_handler):
获取传入的 data 对应的上下文 ctx
输出 "kernel thread : <ctx.current_id>"
结束线程
延迟工作执行体 (delayed_work_handler):
输出 "延迟执行的任务输出"
模块退出函数:
// 停止所有 kernel thread
对于 i 从 0 到 9,执行以下操作:
如果 threads[i] 存在,则停止 threads[i]
输出 "卸载模块" 表示模块已成功卸载
4.操作步骤
和实验一一样,你写好c的文件之后粘贴进去保存,这次虚拟机自带的文件夹里面没有Makeflie,要自己创建添加。(其实是我没找到cd lab\2
之后再次cd deferred_work
就能看见.c文件和Makefile)。执行touch Makefile
,把老师给出的Makefile文件内容粘贴进去就行。
CC=gcc
CFLAGS=-std=c11
ifneq ($(KERNELRELEASE),)
obj-m:=deferred_work.o
else
KDIR := /lib/modules/$(shell uname -r)/build
PWD:=$(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -f *.ko *.o *.symvers *.cmd *.cmd.o .*.cmd *.mod *.mod.c *.order
install:
make -C $(KDIR) M=$(PWD) modules
sudo insmod deferred_work.ko
uninstall:
sudo rmmod deferred_work
endif
然后执行加载/卸载模块命令,和输出日志命令即可。
如果你上一次已经加载过模块且没有卸载记得先卸载。
本方法在执行make后会有警告,但没有任何影响,欢迎感兴趣的uu补充解决方法。
make
sudo insmod deferred_work.ko
dmesg
sudo rmmod deferred_work
然后就能看到如图效果。
5.问题回答
基于实验结果,解释workqueue与kernel thread的不同?
- 执行顺序
work queue 中的任务是按照顺序执行的。也就是说,创建并提交的任务会按顺序在 work queue 中排队执行。这是因为 work queue 任务由内核的工作队列调度系统统一管理,它会在 CPU 空闲时依次调度这些任务,因此任务的执行顺序通常保持与提交顺序一致。
kernel thread 中的任务则是并行执行的。每个线程独立运行,并且线程的执行顺序不一定按创建顺序严格排列。在实验结果中,可以看到 kernel thread 的输出顺序并不一定与创建顺序一致,这是因为线程调度由操作系统内核决定,它根据系统的负载情况决定哪个线程先运行。 - 执行机制
work queue 是一种基于事件的异步执行机制。任务被提交到 work queue 后,由内核中的工作队列系统负责调度和执行,通常用于延迟执行或非实时任务,适合不需要立即处理的后台任务。
kernel thread 则是一个独立的内核级线程。每个线程都是一个独立的调度单元,能够独立抢占 CPU,适合需要并行执行或需要长时间运行的任务。内核线程可以在后台持续执行任务,而不会受到其他任务的影响。 - 资源占用
work queue 任务的执行由系统的工作线程统一管理,不会占用额外的内核线程资源。多个 work queue 任务可以共用一个系统级工作线程,因此通常对资源的占用更少。
kernel thread 需要为每个线程单独分配资源,每个内核线程都是一个独立的调度单元,资源开销较大。如果需要并行执行多个任务,会增加系统的线程管理开销。 - 适用场景
work queue 适用于轻量级、非实时、延迟或后台任务,例如在后台进行日志处理、定期清理资源等。这些任务不需要立即执行,因此可以放入 work queue。
kernel thread 适用于需要并行执行且可能持续较长时间的任务,例如并发处理、持续监控或后台守护进程。kernel thread 允许创建多个线程并行执行多个任务,因此适合需要高实时性或高吞吐量的任务场景。
work queue 按顺序输出,且较为集中,这表明它在一个队列中有序执行。
kernel thread 输出顺序则比较随机,体现了线程的并行特性。
这种不同源于两者的调度机制和资源管理方式,使得 work queue 更适合有序的后台任务,而 kernel thread 更适合并行和实时处理。
仅仅是一个简陋的解决方案,还有很多地方有所疏漏,欢迎补充指点,希望这篇博客对你有所帮助吖,有时间欢迎逛逛我的博客哇。