西电OS实验二:deferred work(李航)

个人博客: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),并对比分析这两种实现方式的异同点。

具体实验步骤如下:

  1. 分别采用 work queue 和 kernel thread 两种方式调用10个函数(函数内部打印学号后3
    位依次加1的方式区分。例如函数1中打印315,函数2中打印316,以此类推),观察并
    记录 work queue 与 kernel thread 在执行函数时的顺序差异。请注意,每个函数应当对
    应一个独立的 kernel thread,即10个函数需由10个不同的 kernel thread 分别执行。
  2. 探究 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的不同?

  1. 执行顺序
    work queue 中的任务是按照顺序执行的。也就是说,创建并提交的任务会按顺序在 work queue 中排队执行。这是因为 work queue 任务由内核的工作队列调度系统统一管理,它会在 CPU 空闲时依次调度这些任务,因此任务的执行顺序通常保持与提交顺序一致。
    kernel thread 中的任务则是并行执行的。每个线程独立运行,并且线程的执行顺序不一定按创建顺序严格排列。在实验结果中,可以看到 kernel thread 的输出顺序并不一定与创建顺序一致,这是因为线程调度由操作系统内核决定,它根据系统的负载情况决定哪个线程先运行。
  2. 执行机制
    work queue 是一种基于事件的异步执行机制。任务被提交到 work queue 后,由内核中的工作队列系统负责调度和执行,通常用于延迟执行或非实时任务,适合不需要立即处理的后台任务。
    kernel thread 则是一个独立的内核级线程。每个线程都是一个独立的调度单元,能够独立抢占 CPU,适合需要并行执行或需要长时间运行的任务。内核线程可以在后台持续执行任务,而不会受到其他任务的影响。
  3. 资源占用
    work queue 任务的执行由系统的工作线程统一管理,不会占用额外的内核线程资源。多个 work queue 任务可以共用一个系统级工作线程,因此通常对资源的占用更少。
    kernel thread 需要为每个线程单独分配资源,每个内核线程都是一个独立的调度单元,资源开销较大。如果需要并行执行多个任务,会增加系统的线程管理开销。
  4. 适用场景
    work queue 适用于轻量级、非实时、延迟或后台任务,例如在后台进行日志处理、定期清理资源等。这些任务不需要立即执行,因此可以放入 work queue。
    kernel thread 适用于需要并行执行且可能持续较长时间的任务,例如并发处理、持续监控或后台守护进程。kernel thread 允许创建多个线程并行执行多个任务,因此适合需要高实时性或高吞吐量的任务场景。

work queue 按顺序输出,且较为集中,这表明它在一个队列中有序执行。
kernel thread 输出顺序则比较随机,体现了线程的并行特性。
这种不同源于两者的调度机制和资源管理方式,使得 work queue 更适合有序的后台任务,而 kernel thread 更适合并行和实时处理。

仅仅是一个简陋的解决方案,还有很多地方有所疏漏,欢迎补充指点,希望这篇博客对你有所帮助吖,有时间欢迎逛逛我的博客哇。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alive~o.0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值