f0b6ae7287df6c6bbf8a8f387d523a71.png

作者:罗宇哲,中国科学院软件研究所智能软件研究中心

上一期中我们介绍了openEuler内核中的小任务机制,这一期我们将介绍介绍如何在openEuer内核中使用工作队列周期性打印日期。

一、向openEuler内核中使用工作队列周期性打印日期

我们在第三十八期和第三十九期介绍过工作队列相关的知识。回顾一下,工作队列可以看做是工作项、工作者和工作池的集合体。工作项包含了需要处理的工作;工作者将工作项与执行该工作项的线程对应起来,是工作队列中资源调度的基本单位;工作池维护了空闲的工作者和正在处理工作的工作者的列表,记录了工作者的调度状态信息。工作项在openEuler 内核中的定义代码如下图所示:

88d1013e002cb8d542f5ea98879a85d9.png
工作项的数据结构定义

其中func是工作项的处理函数,用户可以通过使用INIT_WORK(_work,_func)函数可以初始化一个工作项,其中_work是工作项的地址,_func是需要异步执行的函数。work_func_t在openEuler内核中的类型定义如下:

1dfa3da599a29de1f6d2dbc253b725db.png
work_func_t定义

INIT_DELAYED_WORK(_work,_func)可以用于初始化一个延迟的工作项。

void flush_workqueue(struct workqueue_struct *wq)函数用于冲刷工作队列,使工作队列中的工作都完成。void destroy_workqueue(struct workqueue_struct* wq)函数用于销毁工作队列。

queue_work()和queue_delayed_work()函数分别可以将工作项和延迟工作项加入工作队列中。延迟工作项中包含了定时器,在将延迟工作项加入队列时会延迟一段时间再将该工作项加入工作队列。

workqueue_struct* alloc_workqueue(fmt,flags,max_active,args...)函数用于分配工作队列,其输入参数列表各参数的含义如下[1]

  • fmt: 工作队列的名称,通常是一个字符串;
  • flags:标志位,可以是0,也可以是以下标志位的组合:

1)WQ_UNBOUND:处理工作项的内核线程不绑定到任何特定的处理器;

2)WQ_FREEZABLE:在系统挂起的时候冻结;

3)WQ_MEM_RECLAIM:在内存回收的时候可能使用这个工作队列;

4)WQ_HIGHPRI:高优先级;

5)WQ_CPU_INTENSIVE:处理器密集型;

6)WQ_POWER_EFFICIENT:省电。

  • max_active:每个处理器可以同时执行的工作项的最大数量,0表示默认值;
  • args:传递给fmt的参数。

下面我们编写一个名为workqueue_test.c的模块来实现用工作队列周期性打印日期的功能,其代码如下:

#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/workqueue.h>
#include<linux/timer.h>
#include<linux/timex.h>
#include<linux/rtc.h>
#include<linux/delay.h>
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Theory&Model Group");
MODULE_DESCRIPTION("Use workqueue to print the time periodically");
static struct workqueue_struct *queue = NULL;
 
//static struct work_struct work;
static struct delayed_work work;
static int i=0;
//work handler
static void work_handler(struct work_struct *work)
{
 struct timex  txc;
 struct rtc_time tm;
 do_gettimeofday(&(txc.time));     //得到系统时间 rtc_time_to_tm(txc.time.tv_sec,&tm); // 转变为世界协调时(UTC)
 printk("%d:",++i);
 printk ("%d-%d-%d  %d:%d:%d n",tm.tm_year+1900,tm.tm_mon+1, tm.tm_mday,tm.tm_hour+8,tm.tm_min,tm.tm_sec); //打印时间,从1900年开始计算
}
 
static int init_workqueue(void)
{
 queue = alloc_workqueue("workqueue_test", 0, 0);/*使用默认配置创建工作队列*/
 
 if(queue == NULL)
 {
  printk(KERN_ALERT "create workqueue_test instance errorn");
  return -1;
 }
 INIT_DELAYED_WORK(&work, work_handler);
 for(;i<=3;)
 {
 queue_delayed_work(queue,&work,0);//将延迟工作项加入工作队列并唤醒线程提交工作
  ssleep(15);
 
 }
 
   return 0;
}
 
static void exit_workqueue(void)
 
{
 flush_workqueue(queue);//冲刷工作队列以确保所有工作都完成 
 destroy_workqueue(queue);//销毁工作队列
 printk(KERN_ALERT "unloading OK");
}
module_init( init_workqueue );
module_exit( exit_workqueue );

在这段代码中,我们首先编写了一个工作项的处理函数work_handler(),它获取了系统时间并将其转化为世界协调时[2](UTC),然后将该时间打印出来。在init_workqueue()函数中我们调用alloc_workqueue()函数分配了一个工作队列,接着调用INIT_DELAYED_WORK()函数用work_handler()初始化一个延迟工作项,然后调用queue_delayed_work()依次将四个工作项放入延迟工作队列中。在exit_workqueue()函数中我们调用flush_workqueue()函数清空工作队列然后调用destroy_workqueue()函数销毁工作队列。我们将init_workqueue()函数注册为模块初始化函数并将exit_workqueue ()注册为模块退出函数。

我们编写一个Makefile编译这个模块,其代码如下:

654fdfe70fc585b2d0961a9033e70e24.png
Makefile

注意不同的环境中内核源码路径可能不同。然后我们用sudo make命令编译模块,并用sudo insmod workqueue_test.ko模块将模块插入内核。最后我们用dmesg|tail -n -20命令可以查看内核运行日志的最后二十行,得到如下实验结果:

1a34795deab2aa5a3fa77d212bdef09f.png
实验结果

二、结语

本期我们介绍了如何在openEuler内核使用工作队列周期性打印日期,下一期我们将进入新的一章:openEuler中的进程机制。

致谢

感谢Theory&Model Group提供的原版实验代码。

参考

  1. ^[1]《Linux内核深度解析》,余华兵著,2019
  2. ^[2] https://baike.baidu.com/item/%E5%8D%8F%E8%B0%83%E4%B8%96%E7%95%8C%E6%97%B6/787659?fr=aladdin
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐