浅谈linux - 内核时间的处理

概述

对于嵌入式开发,经常会遇到一些定时、延时以及周期调度的情况,所以定时器是必不可少的一种资源。

相对于裸机开发,我们使用定时器只需先选择时钟源,然后设置分频系数和计数值,配置好中断后,就可以静静的等待定时中断的到来即可。

linux操作定时器同裸机基本一致,同样先选择时钟源(操作系统指定,工程师无需配置),然后设置系统频率(也是节拍率),最后指定定时时间即可。

注意

1. 系统时钟频率的配置方法,在linux内核目录中,运行make menuconfig,使用图形界面,选择Kernel Features -> Timer frequency (<choice> [=y])设置。

2. 高节拍率可以提高系统时间精度,但随之而来会使得系统中断更加频繁,增大系统的负担,所以用户进行配置时需要根据实际情况而定。

接口

HZ

内核全局常量,ARM架构默认HZ=100,表示一秒钟定时器硬件给CPU发送100次定时器中断,每发送一次中断的时间间隔为10ms。

通过make menuconfig通过界面配置。

jiffies_64和jiffies

内核全局常量,用来记录系统从启动以来的系统节拍数,系统启动的时候会初始化为 0,每发生一次硬件定时器中断加1。

注:jiffies表示jiffies_64的低32位。

/* jiffies_64和jiffies的定义,在linux/jiffy.h文件中 */
/*
 * The 64-bit value is not atomic - you MUST NOT read it
 * without sampling the sequence number in jiffies_lock.
 * get_jiffies_64() will do this for you as appropriate.
 */
extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;

当变量jiffies和jiffies_64计数溢出后,数据又会从0开始,这个时候需要判断时间是否绕回。linux提供以下接口用来判断时间是否绕回。

/* 判断时间是否绕回的函数,在linux/jiffy.h文件中 */
#define time_after(a,b)        \
    (typecheck(unsigned long, a) && \
     typecheck(unsigned long, b) && \
     ((long)((b) - (a)) < 0))
#define time_before(a,b)    time_after(b,a)

#define time_after_eq(a,b)    \
    (typecheck(unsigned long, a) && \
     typecheck(unsigned long, b) && \
     ((long)((a) - (b)) >= 0))
#define time_before_eq(a,b)    time_after_eq(b,a)

/* Same as above, but does so with platform independent 64bit types.
 * These must be used when utilizing jiffies_64 (i.e. return value of
 * get_jiffies_64() */
#define time_after64(a,b)    \
    (typecheck(__u64, a) && \
     typecheck(__u64, b) && \
     ((__s64)((b) - (a)) < 0))
#define time_before64(a,b)    time_after64(b,a)

#define time_after_eq64(a,b)    \
    (typecheck(__u64, a) && \
     typecheck(__u64, b) && \
     ((__s64)((a) - (b)) >= 0))
#define time_before_eq64(a,b)    time_after_eq64(b,a)

例:判断某段代码执行时间是否超过5s。

/* time_after使用示例 */
unsigned long timeout = jiffies + 5*HZ;  
/* 以下有一堆的代码,CPU执行这些代码也需要时间的,此时jiffies会10ms加1次   */
...    
if (time_after(jiffies, timeout))  
    /* 超时 处理 */
else  
    /* 没超时 处理 */

另外,为了方便开发, 内核提供jiffies和 ms、us、ns之间的转换函数。

/* jiffies与ms、us、ns之间的转换,在linux/jiffy.h文件中 */
extern unsigned int jiffies_to_msecs(const unsigned long j);
extern unsigned int jiffies_to_usecs(const unsigned long j);
static inline u64 jiffies_to_nsecs(const unsigned long j);

extern unsigned long msecs_to_jiffies(const unsigned int m);
extern unsigned long usecs_to_jiffies(const unsigned int u);
extern unsigned long nsecs_to_jiffies(u64 n);

timer_list

内核使用timer_list结构体表示内核定时器。定义在文件include/linux/timer.h中

/* 描述定时器资源的结构, linux/timer.h */
struct timer_list {
    struct list_head entry;
    unsigned long expires;              /* 超时时间、单位是节拍数 */
    struct tvec_base *base;

    void (*function)(unsigned long);    /* 超时函数 */
    unsigned long data;                 /* 传递给超时函数的参数 */
    ...
};

init_timer

内核使用init_timer函数负责初始化timer_list类型变量。

/**
 * 初始化定时器,仅完成申请功能
 * @timer: 定时器对象
 */
#define init_timer(timer)                        \
    __init_timer((timer), 0)

注意:init_timer仅仅完成了资源申请等功能,对于timer_list的其余三个重要属性需要工程师手动初始化,示例如下。

/**
 * 初始化定时器示例
 * @timer: 定时器对象
 */
struct timer_list timer;  /* 定义对象 */
init_timer(&timer);       /* 这里仅仅完成申请  */

/* 额外需要工程师初始化关键的三个字段 */
unsigned long g_data = 250;  
timer.function = timer_timer_function;  /* 超时处理函数 */
timer.expires = jiffies + 2*HZ;         /* 指定超时时间 */
timer.data = (unsigned long)&g_data;    /* 传递参数,无参数时可省略 */

/* 根据用户需求编写超时处理函数,注意不能进行休眠操作 */
void timer_timer_function(unsigned long data)  
{  
    data = (unsigned long)&g_data;
    ...
}      

add_timer

内核使用add_timer函数向内核注册定时器。

/**
 * 向内核注册定时器,若设置了超时时间,直接启动定时器
 * @timer: 定时器对象
 */
extern void add_timer(struct timer_list *timer);

del_timer和del_timer_sync

内核使用del_timer和del_timer_sync函数删除定时器,del_timer_sync使用在SMP架构下。

/**
 * 删除定时器,不管定时器有没有被激活,均可删除
 * @timer: 定时器对象
 * @0,定时器还没被激活; 1,定时器已经激活
 */
extern int del_timer(struct timer_list * timer);

/**
 * 删除定时器,SMP中使用,删除时需要等待其它处理器使用完定时器才能删除
 * @timer: 定时器对象
 * @0,定时器还没被激活; 1,定时器已经激活
 */
#ifdef CONFIG_SMP
  extern int del_timer_sync(struct timer_list *timer);
#else
# define del_timer_sync(t)        del_timer(t)
#endif

mod_timer

内核使用mod_timer函数修改定时器超时时间,在定时器还没有激活的情况下,mod_timer函数还会激活定时器。

/**
 * 修改定时值,如果定时器还没有激活的话,mod_timer函数会激活定时器
 * @timer: 定时器对象   expires:修改后的超时时间
 * @0,定时器还没被激活; 1,定时器已经激活
 */
extern int mod_timer(struct timer_list *timer, unsigned long expires);

mdelay/ndelay/udelay

内核使用mdelay/ndelay/udelay三个函数用于短延时,其中mdelay和ndelay均是基于udelay实现,而udelay的实现由体系决定和实现。

注意:以上三个延时函数是忙等待机制,延时过程中,不会让出CPU使用权。

/**
 * 短延时,毫秒、微妙、纳秒级的延时,定义在linux/delay.h中
 * @n/x: 延时时间
 */
#ifndef mdelay
#define mdelay(n) (\
    (__builtin_constant_p(n) && (n)<=MAX_UDELAY_MS) ? udelay((n)*1000) : \
    ({unsigned long __ms=(n); while (__ms--) udelay(1000);}))
#endif

#ifndef ndelay
static inline void ndelay(unsigned long x)
{
    udelay(DIV_ROUND_UP(x, 1000));
}
#define ndelay(x) ndelay(x)
#endif

msleep/ssleep

msleep和ssleep用于更长的延时,此时的延时不再采用忙等待机制,程序调用msleep和ssleep后,让出CPU权,当然延时有一定的误差。

注意:msleep和msleep_interruptible区别在于,前者不可中断,后者可中断唤醒,当msleep_interruptible被唤醒后,返回初始请求睡眠周期中剩余的毫秒数。

/**
 * 休眠延时,毫秒、秒级的延时,定义在linux/delay.h中
 * @msecs/seconds: 延时时间
 * @msleep_interruptible返回值为初始请求睡眠周期中剩余的毫秒数
 */
void msleep(unsigned int msecs);
unsigned long msleep_interruptible(unsigned int msecs);
static inline void ssleep(unsigned int seconds);

示例

★一直以来,C语言一直被定义为面向过程语言,主要是因为其缺少一些面向对象的语法(class类),但是我们在构建大型程序的时候一定要具备面向对象的思想来构建。 

C语言实现面向对象的思路大多是通过结构体和函数指针的方式本示例中将定时器处理单独抽象为一个模块(timer.c/timer.h),使用结构体struct class_timer定义为类名。

属性:定时器。

行为:添加、删除、修改。

★示例仅用于展示定时器的应用,采用正点原子的阿尔法开发板进行验证。

★包含定时器头文件timer.h和源文件timer.c、驱动源文件timer_drv.c和编译规则文件Makefile(均已验证通过)。

 timer.h

/**
 * @Filename : timer.h
 * @Revision : $Revision: 1.00 $
 * @Author : Feng(更多编程相关的知识和源码见微信公众号:不只会拍照的程序猿,欢迎订阅)
 * @Description : 定时器类定义
**/

#ifndef __TIMER_H__
#define __TIMER_H__

#include <linux/init.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/timer.h>
#include <linux/slab.h>

/* 定时器类资源定义 */
struct class_timer {
    struct timer_list list;
    void (*del)(struct class_timer *timer);  /* 删除定时器 */ 
    void (*mod)(struct class_timer *timer, const unsigned int msec);     /* 修改定时器定时时间 */
    void (*start)(struct class_timer *timer, const unsigned int msec);   /* 启动定时器,初始化定时时间 */ 
};

/**
 * 创建定时器对象,成功返回类地址,失败返回NULL
 * @func: 超时函数,定时时间到执行
 */
struct class_timer *timer_create(void (*func)(unsigned long));

/**
 * 销毁定时器对象
 * @timer: 对象
 */
void timer_destroy(struct class_timer *timer);

#endif

 timer.c

/**
 * @Filename : timer.c
 * @Revision : $Revision: 1.00 $
 * @Author : Feng(更多编程相关的知识和源码见微信公众号:不只会拍照的程序猿,欢迎订阅)
 * @Description : 定时器类定义
**/
#include "timer.h"

/**
 * 删除定时器
 * @timer: 定时器对象
 */
static void _del(struct class_timer *timer)
{
    del_timer(&timer->list);
}

/**
 * 启动定时器,初始化定时时间
 * @timer: 定时器对象,msec:初始定时时间,毫秒为单位
 */
static void _start(struct class_timer *timer, const unsigned int msec)
{
    timer->list.expires = jiffies + msecs_to_jiffies(msec); /* 设定超时时间 */
    add_timer(&timer->list); /* 添加定时器 */
}

/**
 * 定时器定时时间修改
 * @timer: 定时器对象,msec:修改的定时时间,毫秒为单位
 */
static void _mod(struct class_timer *timer, const unsigned int msec)
{
    /* 修改定时器:mod_timer=del_timer+expires...+add_timer */
    mod_timer(&timer->list, jiffies + msecs_to_jiffies(msec));
}    


/**
 * 创建定时器对象,成功返回类地址,失败返回NULL
 * @func: 超时函数,定时时间到执行
 */
struct class_timer *timer_create(void (*func)(unsigned long))
{
    struct class_timer *timer = NULL;

    if ((timer = kmalloc(sizeof(struct class_timer), GFP_KERNEL)) == NULL) {
        printk(KERN_ERR "Allocation of timer class failed\n");
        return NULL;
    }

    init_timer(&timer->list);       /* 初始化定时器 */
    timer->list.function = func;    /* 指定一个超时处理函数 */

    timer->del = _del;
    timer->mod = _mod;
    timer->start = _start;

    return timer;
}

/**
 * 销毁定时器对象
 * @timer: 对象
 */
void timer_destroy(struct class_timer *timer)
{
    timer->del(timer);
    kfree(timer);
}


EXPORT_SYMBOL_GPL(timer_create);
EXPORT_SYMBOL_GPL(timer_destroy);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("feng");          /* 模块的作者 */
MODULE_VERSION ("1.00");        /* 模块版本号 */

 timer_drv.c

/**
 * @Filename : timer_drv.c
 * @Revision : $Revision: 1.00 $
 * @Author : Feng(更多编程相关的知识和源码见微信公众号:不只会拍照的程序猿,欢迎订阅)
 * @Description : 定时器操作示例
**/

#include <linux/init.h>
#include <linux/module.h>
#include "timer.h"

struct class_timer *feng_timer;

/**
 * @定时器的超时处理函数
 * @data: 无效
 */
static void _timer_function(unsigned long data)
{
    static unsigned int time = 0;

    time++;
    printk("the time is: %d s..\n", time);

    feng_timer->mod(feng_timer, 1000);      /* 修改定时器,延时1s */
}

/**
 * @模块入口函数
 */
static int __init timer_drv_init(void)
{
    /* 构造定时器对象 */
    if ((feng_timer = timer_create(_timer_function)) == NULL)   
        printk("timer class error...\n");

    feng_timer->start(feng_timer, 1000); /* 修改定时器,延时1s */

    printk("timer class ok...\n");

    return 0;
}

/**
 * @模块出口函数
 */
static void __exit timer_drv_exit(void)
{
    timer_destroy(feng_timer);          /* 销毁定时器对象 */
}

module_init(timer_drv_init);
module_exit(timer_drv_exit);
MODULE_LICENSE("GPL");

/* 调用modinfo xx(模块名)查看 */
MODULE_AUTHOR("feng");            /* 模块的作者 */
MODULE_VERSION ("1.00");          /* 模块版本号 */
/* MODULE_DESCRIPTION("xxxxx");      模块描述 */
/* MODULE_ALIAS("xxx");              模块别名 */

 Makefile

#根文件所在目录
ROOTFS_DIR = /home/feng/atomic/rootfs

#交叉编译工具链
CROSS_COMPILE = arm-linux-gnueabihf-
CC = $(CROSS_COMPILE)gcc

#目标文件名
TAR_NAME = timer

#应用程序名字
APP_NAME = $(TAR_NAME)

#驱动目录路径
DRV_DIR = $(ROOTFS_DIR)/home/drv
DRV_DIR_LIB = $(ROOTFS_DIR)/lib/modules/4.1.15

#动态库目录路径
LIB_DIR = $(ROOTFS_DIR)/home/lib

#应用程序目录路径
APP_DIR = $(ROOTFS_DIR)/home/app

#KERNELRELEASE由内核makefile赋值
ifeq ($(KERNELRELEASE), )

#内核路径
KERNEL_DIR =/home/feng/atomic/resource/linux-imx-rel_imx_4.1.15_2.1.0_ga

#当前文件路径
CURR_DIR = $(shell pwd)

all:
    #编译模块
    make -C $(KERNEL_DIR) M=$(CURR_DIR) modules

    #编译应用程序
    #-$(CC) -o $(APP_NAME) $(APP_NAME).c main.c

clean:
    #清除模块文件
    make -C $(KERNEL_DIR) M=$(CURR_DIR) clean

    #清除应用文件
    #-rm $(APP_NAME)

install:
    #拷贝模块文件
    #cp -raf $(TAR_KEY_NAME)_drv.ko $(TAR_KEY_NAME)_dev.ko $(DRV_DIR)
    #cp -raf keyin.ko wq.ko timer.ko $(DRV_DIR_LIB)
    cp -raf *.ko $(DRV_DIR_LIB)

    #拷贝应用文件
    #-cp -raf $(APP_NAME) $(APP_DIR)
else
#指定编译什么文件
obj-m += $(TAR_NAME)_drv.o timer.o
#obj-m += $(TAR_NAME).o 

endif

结论

1、进入模块目录,执行make命令编译模块;然后执行make install命令,拷贝模块到目标机指定目录。

feng:timer$ make
#编译模块
make -C /home/feng/atomic/resource/linux-imx-rel_imx_4.1.15_2.1.0_ga M=/mnt/hgfs/Share/linux/atomic/driver/timer modules
make[1]: 进入目录“/home/feng/atomic/resource/linux-imx-rel_imx_4.1.15_2.1.0_ga”
  CC [M]  /mnt/hgfs/Share/linux/atomic/driver/timer/timer.o
  Building modules, stage 2.
  MODPOST 2 modules
  CC      /mnt/hgfs/Share/linux/atomic/driver/timer/timer.mod.o
  LD [M]  /mnt/hgfs/Share/linux/atomic/driver/timer/timer.ko
  CC      /mnt/hgfs/Share/linux/atomic/driver/timer/timer_drv.mod.o
  LD [M]  /mnt/hgfs/Share/linux/atomic/driver/timer/timer_drv.ko
make[1]: 离开目录“/home/feng/atomic/resource/linux-imx-rel_imx_4.1.15_2.1.0_ga”
#编译应用程序
#-arm-linux-gnueabihf-gcc -o timer timer.c main.c
feng:timer$ make install 
#拷贝模块文件
#cp -raf _drv.ko _dev.ko /home/feng/atomic/rootfs/home/drv
#cp -raf keyin.ko wq.ko timer.ko /home/feng/atomic/rootfs/lib/modules/4.1.15
cp -raf *.ko /home/feng/atomic/rootfs/lib/modules/4.1.15
#拷贝应用文件
#-cp -raf timer /home/feng/atomic/rootfs/home/app
feng:timer$ 

2、在目标机上执行modprobe命令加载模块。

注意:在模块加载之前,需要先调用depmod命令,生成模块依赖文件。

/ # depmod
/ # modprobe timer_drv.ko
timer class ok...
/ # the time is: 1 s..
the time is: 2 s..
the time is: 3 s..
the time is: 4 s..
the time is: 5 s..
the time is: 6 s..
the time is: 7 s..
the time is: 8 s..
the time is: 9 s..
the time is: 10 s..

3、在目标机上执行modprobe -r命令卸载模块。

/ # modprobe -r timer_drv.ko
/ # lsmod
Module                  Size  Used by    Tainted: G  
/ # 

4、综上、示例展示了定时器的应用,实现内核每秒打印时间提示信息。

往期 · 推荐

浅谈linux - 字符设备框架

帮你自动化办公的python-自动提取pdf指定页(项目概述)

也没想象中那么神秘的数据结构-一种通用化的双向链表设计(底层源码)

也没想象中那么神秘的数据结构-一环扣一环的“链表”(双向链表)

我用C语言玩对象,偷偷关注着你的观察者模式(基类设计)

关注

更多精彩内容,请关注微信公众号:不只会拍照的程序猿,本人致力分享linux、设计模式、C语言、嵌入式、编程相关知识,也会抽空分享些摄影相关内容,同样也分享大量摄影、编程相关视频和源码,另外你若想要本文章源码请关注公众号:不只会拍照的程序猿,后台回复:linux驱动源码。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不只会拍照的程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值