测试代码
用来演示timer和workqueue的工作方式,由timer每隔500ms触发一个event,该event用来在终端打印log。 代码如下
- Makefile
#ifneq ($(KERNELRELEASE),)
obj-m := myphone.o
myphone-objs := workqueue.o
#else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
rm -rf Module.* modules.*
.PHONY: default clean
#endif
- test module (workqueue.c)
#include <linux/module.h>
#define PHONE_VBUS_POLL_INTERVAL msecs_to_jiffies(500)
struct phone_core {
struct work_struct events;
struct timer_list vbus_timer;
spinlock_t lock;
};
static struct phone_core core;
static struct workqueue_struct *phone_wq;
static void phone_event(struct work_struct *work);
//static struct work_struct events;
static void phone_vbus_poll(unsigned long data)
{
struct phone_core *core = (struct phone_core *)data;
mod_timer(&core->vbus_timer, jiffies + PHONE_VBUS_POLL_INTERVAL);
printk(KERN_INFO "%s:%d.\n", __func__, __LINE__);
if (queue_work(phone_wq, &core->events))
return;
}
static void phone_event(struct work_struct *work)
{
printk(KERN_INFO "events happened.\n");
}
int phone_init(void)
{
printk(KERN_INFO "%s:%d.\n", __func__, __LINE__);
INIT_WORK(&core.events, phone_event);
setup_timer(&core.vbus_timer, phone_vbus_poll,
(unsigned long)&core);
add_timer(&core.vbus_timer);
phone_wq = alloc_workqueue("phone_wq", WQ_FREEZABLE, 0);
if (phone_wq) {
printk(KERN_INFO "init my phone successfully.\n");
return 0;
}
else
pr_err("can't alloc work queue.\n");
return -ENOMEM;
}
void phone_cleanup(void)
{
destroy_workqueue(phone_wq);
del_timer(&core.vbus_timer);
printk(KERN_INFO "remove my phone.\n");
}
static int __init work_init(void)
{
int retval;
printk(KERN_INFO "start working in GalaxyCore.\n");
retval = phone_init();
if (retval)
goto phone_init_fail;
goto out;
phone_init_fail:
phone_cleanup();
out:
return retval;
}
static void __exit work_exit(void)
{
phone_cleanup();
}
subsys_initcall(work_init);
module_exit(work_exit);
MODULE_LICENSE("GPL");
执行
$make
$insmod myphone.ko
$dmesg
查看kernel log可以看到如下信息:结果就是每隔500ms打印一句 “events happened”.
[ 7106.403102] start working in GalaxyCore.
[ 7106.403105] phone_init:39.
[ 7106.403110] init my phone successfully.
[ 7106.406779] phone_vbus_poll:27.
[ 7106.406821] events happened.
[ 7106.905355] phone_vbus_poll:27.
[ 7106.905366] events happened.
[ 7107.403956] phone_vbus_poll:27.
[ 7107.403994] events happened.
移除模块
$rmmod myphone
kernel log»
[ 7377.632309] phone_vbus_poll:27.
[ 7377.632330] events happened.
[ 7378.130887] phone_vbus_poll:27.
[ 7378.130906] events happened.
[ 7378.455502] remove my phone.
执行流程
- 初始化timer,workqueue和work struct
- 在timer回调函数中将work struct放到workqueue中,交由CPU调度
原理分析
问题分解
- 内核如何管理timer
- 关于jiffies
- 解剖timer
- 工作队列如何调度
- jiffies
```c
extern unsigned long __msecs_to_jiffies(const unsigned int m);
#if HZ <= MSEC_PER_SEC && !(MSEC_PER_SEC % HZ)
/*
- HZ is equal to or smaller than 1000, and 1000 is a nice round
- multiple of HZ, divide with the factor between them, but round
- upwards: / static inline unsigned long _msecs_to_jiffies(const unsigned int m) { return (m + (MSEC_PER_SEC / HZ) - 1) / (MSEC_PER_SEC / HZ); } #elif HZ > MSEC_PER_SEC && !(HZ % MSEC_PER_SEC) /
- HZ is larger than 1000, and HZ is a nice round multiple of 1000 -
- simply multiply with the factor between them. *
- But first make sure the multiplication result cannot overflow: / static inline unsigned long _msecs_to_jiffies(const unsigned int m) { if (m > jiffies_to_msecs(MAX_JIFFY_OFFSET)) return MAX_JIFFY_OFFSET; return m * (HZ / MSEC_PER_SEC); } #else /
- Generic case - multiply, round and divide. But first check that if
- we are doing a net multiplication, that we wouldn’t overflow: */ static inline unsigned long _msecs_to_jiffies(const unsigned int m) { if (HZ > MSEC_PER_SEC && m > jiffies_to_msecs(MAX_JIFFY_OFFSET)) return MAX_JIFFY_OFFSET;
return (MSEC_TO_HZ_MUL32 * m + MSEC_TO_HZ_ADJ32) » MSEC_TO_HZ_SHR32; } #endif
…
static __always_inline unsigned long msecs_to_jiffies(const unsigned int m)
{
if (__builtin_constant_p(m)) {
if ((int)m < 0)
return MAX_JIFFY_OFFSET;
return _msecs_to_jiffies(m);
} else {
return __msecs_to_jiffies(m);
}
}
```
以毫秒转换为jiffies为例
系统接口:msecs_to_jiffies
jiffies含义:从代码里的换算公式中可以看出, jiffies其实就是时钟的节拍数,频率f=节拍数/s,单位为HZ。
jiffies维护:和timer相关
- timer 下次和软中断一起整理