linux内核软件定时器编程框架

linux 内核软件定时器简介:

  • 定时器是一种软件功能:即允许在将来的某个时刻,函数在给定的时间间隔用完时被调用。
  • 内核软件定时器特点:可以指定一个超时时间,一旦超时时间到期,内核就会调用定会器的超时处理函数,
  • 切记: linux 内核软件定时器基于软中断实现,所以其超时处理函数不能进行休眠操作。

内核和进程广泛使用定时器。大多数设备驱动程序利用定时器检测反常情况,例如,软盘驱动程序使用定时器在软盘暂时不被访问后就关闭设备的引擎,而并行打印机设备利用定时器检测错误的打印机情况。
程序员也经常利用定时器在将来某一时刻强制执行特定的函数。

相对来说,实现一个定时器并不难。每个定时器都包含一个字段,表示定时器将需要多长时间才到期。这个字段的初值就是jiffies的当前值加上合适的节拍数。这个字段的值将不再改变。(包含了表示当前时刻的自增变量jiffies,这个字段的值怎么保持不变的呢?)每当内核检查定时器时,就把这个到期字段值和当前这一刻jiffies的值相比较,当jiffies大于或等于这个字段存放的值时,定时器到期。

警告:因为对定时器函数的检查总是由可延迟函数进行,而可延迟函数被激活以后很长时间才能被执行,因此,内核不能确保定时器函数正好在定时到期时就开始执行,只能保证在适当的时间执行它们,或者比如延迟到几百毫秒之后执行它们。因此,对于必须严格遵守定时时间的那些实时应用而言,定时器并不合适。

linux系统考虑两种类型的定时器,即动态定时器(dynamic timer)和间隔定时器(interval timer)。第一种类型由内核使用,而间隔定时器可以由进程在用户在创建。
(摘自《深入理解linux内核第三版》)

描述软件定时器的结构体timer_list

struct timer_list {
	/*
	 * All fields that change during normal runtime grouped to the
	 * same cacheline
	 */
	struct list_head entry;
	unsigned long expires;
	struct tvec_base *base;

	void (*function)(unsigned long);
	unsigned long data;

	int slack;

#ifdef CONFIG_TIMER_STATS
	int start_pid;
	void *start_site;
	char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
	struct lockdep_map lockdep_map;
#endif
};
  • entry :用于将软件定时器插入双向循环链表队列中,该链表根据定时器expires字段的值将软件定时器来分组存放。
  • expires:定时器的超时时间,时间用节拍数表示(1节拍代表10ms),该值为系统启动以来所经过的节拍数。例如:5 秒以后超时,expires=jiffies+5*HZ;(HZ在ARM结构为100,在X86架构中为1000),20毫秒以后超时,则为expires=jiffies+2;
  • function :定时器的超时处理函数地址,基于软中断实现 所以不能进行休眠操作。
  • data : 用于指定传递给定时器超时处理函数的参数。正是由于data字段,就可以定义一个单独的通用函数来处理多个设备驱动程序的超时问题,在data字段可以存放设备ID,或者其他有意义的数据,定时器函数可以用这些数据来区分不同的设备。也可以根据实际需求自定义传参

软件定时器相关的配套(宏)函数

init_timer(&定时器对象);

  • 定义在include\linux\timer.h
#define init_timer(timer)						\
	__init_timer((timer), 0)
#ifdef CONFIG_LOCKDEP
#define __init_timer(_timer, _flags)					\
	do {								\
		static struct lock_class_key __key;			\
		init_timer_key((_timer), (_flags), #_timer, &__key);	\
	} while (0)

#define __init_timer_on_stack(_timer, _flags)				\
	do {								\
		static struct lock_class_key __key;			\
		init_timer_on_stack_key((_timer), (_flags), #_timer, &__key); \
	} while (0)
#else
#define __init_timer(_timer, _flags)					\
	init_timer_key((_timer), (_flags), NULL, NULL)
#define __init_timer_on_stack(_timer, _flags)				\
	init_timer_on_stack_key((_timer), (_flags), NULL, NULL)
#endif
  • 功能:初始化定时器对象
  • 参数:timer 指向定时器变量

void add_timer(struct timer_list *timer);

  • 定义在kernel\timer\timer.c
/**
 - add_timer - start a timer
 - @timer: the timer to be added
 -  - The kernel will do a ->function(->data) callback from the
 - timer interrupt at the ->expires point in the future. The
 - current time is 'jiffies'.
 -  - The timer's ->expires, ->function (and if the handler uses it, ->data)
 - fields must be set prior calling this function.
 -  - Timers with an ->expires field in the past will be executed in the next
 - timer tick.
 */
void add_timer(struct timer_list *timer)
{
	BUG_ON(timer_pending(timer));
	mod_timer(timer, timer->expires);
}
EXPORT_SYMBOL(add_timer);
/**
 - timer_pending - is a timer pending?
 - @timer: the timer in question
 -  - timer_pending will tell whether a given timer is currently pending or not. Callers must ensure serialization wrt. other operations done to this timer, eg. interrupt contexts, or other CPUs on SMP.
 -  - return value: 1 if the timer is pending, 0 if not.
 */
static inline int timer_pending(const struct timer_list * timer)
{
	return timer->entry.next != NULL;
}
  • 功能:向内核注册添加一个定时器,一旦添加完,定时器就开始倒计时一旦时间到期,内核就会调用其超时处理函数并且将定时器从内核中删除,所以内核定时器的超时处理函数只执行一次

  • 参数:timer是指向软件定时器的指针

int del_timer(struct timer_list * timer);

  • 定义在kernel\timer\timer.c
/**
 * del_timer - deactive a timer.
 * @timer: the timer to be deactivated
 *
 * del_timer() deactivates a timer - this works on both active and inactive timers.
 *
 * The function returns whether it has deactivated a pending timer or not.
 * (ie. del_timer() of an inactive timer returns 0, del_timer() of an
 * active timer returns 1.)
 */
int del_timer(struct timer_list *timer)
{
	struct tvec_base *base;
	unsigned long flags;
	int ret = 0;

	debug_assert_init(timer);

	timer_stats_timer_clear_start_info(timer);
	if (timer_pending(timer)) {
		base = lock_timer_base(timer, &flags);
		ret = detach_if_pending(timer, base, true);
		spin_unlock_irqrestore(&base->lock, flags);
	}

	return ret;
}
EXPORT_SYMBOL(del_timer);
  • 功能:从内核中删除定时器
  • 参数:timer 指向软件定时器的对象

int mod_timer(struct timer_list *timer, unsigned long expires);

  • 定义在kernel\timer\timer.c
/**
 * mod_timer - modify a timer's timeout
 * @timer: the timer to be modified
 * @expires: new timeout in jiffies
 * mod_timer() is a more efficient way to update the expire field of an active timer (if the timer is inactive it will be activated)
 *mod_timer() 是一种有效的方式去更新一个活跃的时间的超时时间,如果这个定时器是不活跃的,将被激活
 * mod_timer(timer, expires) is equivalent to:
 *     del_timer(timer); timer->expires = expires; add_timer(timer);
 *mod_timer(timer, expires) 函数等价于先删除软件定时器,再设置一个定时器超时时间,然后再添加这个软件定时器
 * Note that if there are multiple unserialized concurrent users of the
 * same timer, then mod_timer() is the only safe way to modify the timeout,
 * since add_timer() cannot modify an already running timer.
 *如果同一个定时器有多个未序列化的并发访问用户,那么mod_timer()函数是唯一修改超时的安全方法,因为add_timer()不能修改已经运行的定时器。
 * The function returns whether it has modified a pending timer or not.
 * (ie. mod_timer() of an inactive timer returns 0, mod_timer() of an
 * active timer returns 1.)
 * mod_timer()函数返回1:如果修改一个已经被激活的定时器(即这个定时器已经被挂起)
 * mod_timer()函数返回0:如果修改一个没有被激活的定时器(即这个定时器没有被挂起)
 */
int mod_timer(struct timer_list *timer, unsigned long expires)
{
	expires = apply_slack(timer, expires);
	/*
	 * This is a common optimization triggered by the
	 * networking code - if the timer is re-modified
	 * to be the same thing then just return:
	 */
	if (timer_pending(timer) && timer->expires == expires)
		return 1;

	return __mod_timer(timer, expires, false, TIMER_NOT_PINNED);
}
EXPORT_SYMBOL(mod_timer);
  • 功能:修改定时器的超时时刻的时间,该函数中有互斥访问机制
  • 参数:timer 指向软件定时器的对象
    expires :所要设置的定时器的超时时刻的时间。

linux内核软件定时器编程框架

在这里插入图片描述

代码的框架如下

//头文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/timer.h>
#include <linux/gpio.h>
#include <mach/platform.h>
/*其他
//声明描述LED的硬件信息的数据结构
//定义初始化4个LED硬件信息对象
*/
//定义定时器对象
static struct timer_list mytimer;

//定时器的超时处理函数
//定时器超时时间到期,内核执行此函数
//切记:千万不能进行休眠操作
//data = (unsigned long)&g_data
static void mytimer_function(unsigned long data)
{
    printk("%s: data = %#x\n", __func__, *(int *)data);
    //2.重新向内核添加定时器对象
    /*以下两条语句相当之危险,因为他们访问了一个全局变量mytimer,如果将来有高优先级的硬件中断和高优先级的软中断来打断
     * 这两条语句的执行,势必造成定时器的错误,需要考虑互斥问题
    mytimer.expires = jiffies + 2*HZ; //重新添加新的超时时间
    add_timer(&mytimer);
    */
    //del_timer+expire=...+add_timer
    //此函数非常安全,因为里面做了互斥访问机制
    mod_timer(&mytimer, jiffies+2*HZ);
}
/*
static irqreturn_t button_isr(int irq, void *dev)
{
    //此代码会篡改定时器的超时时间
    mytimer.expires = jiffies + 50*HZ;
    return IRQ_HANDLED; 
}
*/
static int g_data = 0x5555;

static int mytimer_init(void)
{
    //1.申请GPIO资源,配置GPIO为输出,输出1
    //2.初始化定时器对象
    init_timer(&mytimer);
    //3.额外指定定时器的超时时间
    mytimer.expires = jiffies + 2*HZ;
    //4.额外指定定时器的超时处理函数
    mytimer.function = mytimer_function;
    //5.额外指定给超时处理函数传递的参数
    mytimer.data = (unsigned long)&g_data;
    //6.向内核添加定时器对象,开始倒计时
    //一旦时间到期,内核调用超时处理函数并且删除定时器
    add_timer(&mytimer);
    return 0;
}

static void mytimer_exit(void)
{
    //1.释放GPIO资源
    //2.删除定时器对象
    del_timer(&mytimer);
}
module_init(mytimer_init);
module_exit(mytimer_exit);
MODULE_LICENSE("GPL");

举例

案例 :利用定时器,实现每隔 2 秒钟开关某个LED灯

//头文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/timer.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <mach/platform.h>
//声明描述LED灯硬件信息的数据结构
struct led_resource{
    int gpio;
    char* name;
};
//定义初始化4个LED灯的硬件信息
struct led_resource led_info[] = {
    {PAD_GPIO_C+12, "LED1"},
    {PAD_GPIO_C+7, "LED2"},
    {PAD_GPIO_C+11, "LED3"},
    {PAD_GPIO_B+26, "LED4"}
};
static int g_data; //定义该全局变量给软件定时器处理函数传递控制哪个led灯信息
//定义一个软件定时器对象
static struct timer_list vtimer;

//软件定时器的处理函数
static void vtimer_func(unsigned long data){
    printk("%s:data = %#x\n",__func__, *(int *)data);
    int state;
    state = gpio_get_value(led_info[g_data].gpio);
    gpio_set_value(led_info[g_data].gpio, !state);
    mod_timer(&vtimer, jiffies + 2*HZ);
}
#if 1
#define LED_ON 0X10001
#define LED_OFF 0X10002
//led_ioctl
static long led_ioctl(struct file* file, int cmd, unsigned long arg){
    //分配内核缓冲区
    copy_from_user(&g_data, (int *)arg, sizeof(g_data));
    switch(cmd){
        case LED_ON:
           // gpio_set_value(led_info[kindex-1].gpio, 0);
           // printk("%s:开第%d个灯\n", __func__, kindex);
        
    init_timer(&vtimer);
    vtimer.expires = jiffies + 2*HZ;
    vtimer.function = vtimer_func;
    vtimer.data = (unsigned long)&g_data;

    add_timer(&vtimer);
            break;
        case LED_OFF:
           // gpio_set_value(led_info[kindex-1].gpio, 1);
           // printk("%s:关第%d个灯\n",__func__, kindex);
            break;
        default:
            printk("%s:无效操作\n",__func__);
            return -1;
    }
    return 0;
}
//定义初始化LED的硬件操作接口
static struct file_operations led_fops = {
    .unlocked_ioctl = led_ioctl,
    .owner = THIS_MODULE
};

//定义初始化led的混杂设备对象
struct miscdevice led_misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .fops = &led_fops,
    .name = "vaccine_misc"
};
#endif
//入口函数
static int led_drv_init(void){
    //申请gpio资源
    int i;
    for(i = 0; i < ARRAY_SIZE(led_info); i++){
        gpio_request(led_info[i].gpio, led_info[i].name);
        gpio_direction_output(led_info[i].gpio, 1);
    }
    //向内核注册一个混杂设备对象,内核会自动创建一个名称为name的设备文件,
    //并且会自动分配一个次设备号
    misc_register(&led_misc);
    return 0;
}
//出口函数
static void led_drv_exit(void){
    //释放gpio资源
    int i;
    for(i = 0; i < ARRAY_SIZE(led_info); i++){
        gpio_set_value(led_info[i].gpio, 1);
        gpio_free(led_info[i].gpio);
    }
    //从内核卸载一个混杂设备对象,内核会删除创建的设备文件
    //且内核会释放掉申请的次设备号
    misc_deregister(&led_misc);

    del_timer(&vtimer);
}
//各种修饰和GPL规则
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");

遇到问题

在软件定时器的超时处理函数中:如果按照如下的写法


//定时器的超时处理函数
//定时器超时时间到期,内核执行此函数
//切记:千万不能进行休眠操作
//data = (unsigned long)&g_data
static void mytimer_function(unsigned long data)
{
    printk("%s: data = %#x\n", __func__, *(int *)data);
    
    
    add_timer(&mytimer);
  
}

以上会导致两个问题:

  1. 超时时间会越来越短,直到变成0,导致打印越来越快
  2. 打印越来越快,会导致linux内核奔溃,见下图
    在这里插入图片描述
    原因:软件定时器是基于 软中断来实现的,其优先级 高于 进程,而这里的超时处理函数 什么事情都不做,只是打印
    而printk的打印操作的低速设备串口,就会导致其他的进程无法获取cpu的资源。

猜你喜欢

转载自blog.csdn.net/weixin_43326587/article/details/107370091