Linux内核移植 part4:内核timer

kernel timer

标签: 定时器 工作队列


测试代码

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

#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
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相关
2. timer
下次和软中断一起整理

你可能感兴趣的:(Linux,Linux内核移植)