Rickey 裘 一无所知

内核timer

2016-04-19
Kai Qiu

测试代码

用来演示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.

执行流程

  1. 初始化timer,workqueue和work struct
  2. 在timer回调函数中将work struct放到workqueue中,交由CPU调度

原理分析

问题分解

  • 内核如何管理timer
    • 关于jiffies
    • 解剖timer
  • 工作队列如何调度

  1. 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相关

  1. timer 下次和软中断一起整理

评论