内核中时间相关的知识介绍

1、内核要解决的时间相关问题

(1)如何度量时间差,如何比较时间;
(2)如何获取当前时间;
(3)如何将操作延迟指定的一段时间;
(4)如何调度异步函数到指定的时间之后执行;

2、度量时间差

2.1、内核度量时间的原理

(1)Soc有时间相关的硬件,比如定时器、RTC等,其中就有计数器,就是每隔一段时间计数器的值就加一。计数器的值保存在
寄存器中,内核可以通过读取寄存器知道计数器的值;
(2)计数器的值增加的周期长短是可以计算得到的,和时钟频率有关。比如时钟频率是1000hz,则每1ms计数器的值加一。
(3)知道计数器的值和计数器值增加的时间间隔,就可以计算经过了多长的时间。比如计数器的值增加了1000,则经过的时间就是1000*1ms=1s;
(4)Soc自带的时钟模块可能计算不够精确,有时候需要外接时钟芯片;
参考博客:《ARM芯片开发(S5PV210芯片)——定时器、看门狗、RTC》

2.2、内核的宏定义:HZ

(1)系统时钟中断由系统定时硬件以周期性的间隔产生,这个间隔在内核中就是根据HZ的值来指定;
(2)HZ在内核中是个宏定义,表示内核中每秒发生多少次时钟中断,HZ的值和平台有关;
(3)假设HZ的值是100,则表示每10ms内核的计数器的值就加一;
(4)一般是不修改HZ值,内核开发中已经为我们选择了合适的HZ值,我们只需要使用HZ值;理论上HZ的值越大,计数器增加的周期就越短,我们就能获取更准确的时间,但是增加时钟中断频率会带来额外的开销,需要在二者中取平衡。

2.3、jiffies计数器

(1)内核中有两个计数器jiffies和jiffies_64。jiffies_64是64位的(即使在32位架构上也是64位),jiffies是unsigned long型变量,在64位架构中是64位,在32位架构中是32位。64位架构中,jiffies和jiffies_64相同,在32位架构中,jiffies是jiffies_64的低32位;
(2)通常首选使用jiffies,因为对jiffies的访问很快;
(3)jiffies的值在系统启动时被初始化为0,每次当时钟中断发生时,jiffies计数器的值就加一。所以jiffies计数器的值表示自系统启动以来,发生了多少次时钟中断。

2.4、HZ和jiffies计数器的关系

(1)HZ表示每秒发生多少次时钟中断,jiffies记录了系统启动后发生的中断次数;
(2)系统启动经过的时间=jiffies / HZ;
(3)计算时间差=(jiffies1-jiffies2) / HZ;

2.5、比较两个jiffies值的函数

int time_after(unsigned long jiffies1, unsigned long jiffies2);			//jiffies1代表的时间比jiffies2靠后,则返回真
int time_before(unsigned long jiffies1, unsigned long jiffies2);		//jiffies1代表的时间比jiffies2靠前,则返回真
int time_after_eq(unsigned long jiffies1, unsigned long jiffies2);		//jiffies1代表的时间比jiffies2靠后或相等,则返回真
int time_before_eq(unsigned long jiffies1, unsigned long jiffies2);		//jiffies1代表的时间比jiffies2靠前或相等,则返回真

2.6、处理器特定寄存器

(1)有时候需要特别高的时间精度,有的处理器在硬件上做了针对性的设计,提供专门的时间计数器寄存器,这是和具体处理器关联的,没有通用性;
(2)比如:在X86架构的处理器中就有TSC(timestamp counter,时间戳计数器)寄存器,是一个64位的寄存器,记录CPU时钟周期数;

2.7、jiffies和struct timespes、struct timeval结构体转换函数

unsigned long timeval_to_jiffies(const struct timeval *value);
void jiffies_to_timeval(const unsigned long jiffies, struct timeval *value);
unsigned long timespec_to_jiffies(const struct timespec *value);
void jiffies_to_timespec(const unsigned long jiffies,  struct timespec *value);

3、获取当前时间

//获取系统当前时间
do_gettimeofday(struct timeval * tv);

//将墙钟时间转换为jiffies
static inline unsigned long mktime(const unsigned int year,
			const unsigned int mon, const unsigned int day,
			const unsigned int hour, const unsigned int min,
			const unsigned int sec);

(1)墙钟时间:指日常生活使用的时间,用年月日时分秒来表达;
(2)在驱动程序中,一般是不需要墙钟时间的,大部分都是计算时间差,在上层应用中墙钟时间用的较多;
(3)内核中提供了获取当前时间戳的函数:do_gettimeofday()函数,用法和C库的gettimeofday是一样的;
(4)内核还提供了墙钟时间转换为jiffies的函数;

4、延时操作

4.1、延时操作思路介绍

(1)设备驱动程序中经常需要将某些特定代码延迟一段时间后执行,比如操作a需要等到操作b执行完成;
(2)实现延时操作有两种思路:忙等待和让出处理器;
(3)忙等待:在延时的这段时间内仍然占据CPU,直到满足延迟的时间,这种适合短延时,比较切换进程也是要开销的;
(4)让出处理器:在延时时间内让出处理器,等延迟时间到了再被调度,适合长延时;

4.2、等待队列

参考博客:《进程的休眠与唤醒(等待队列)》

4.3、短延时——忙等待

//延时函数,三个延迟函数都是忙等待函数,因而在延迟过程中无法运行其他任务
void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);

4.4、短延时——让出处理器

//延时msecs毫秒
void msleep(unsigned int msecs);
unsigned long msleep_interruptible(unsigned int msecs)

//延时seconds秒
void ssleep(unsigned int seconds)

5、内核定时器

5.1、定时器介绍

(1)我们需要在某个时间点调度执行某个动作,同时在该时间点到达之前不会阻塞当前进程,则可以使用内核定时器;内核定时器是利用中断来实现的,在到达指定时间点后发生中断,执行注册的函数;
(2)定时器的管理必须尽可能做到轻量级;
(3)其设计必须在活动定时器大量增加时具有很好的伸缩性;
(4)大部分定时器会在最多几秒或者几分钟内到期,而很少存在长期延迟的定时器;
(5)定时器应该在注册它的同一CPU上运行;

5.2、struct timer_list结构体

struct timer_list {
	struct hlist_node	entry;
	unsigned long		expires;	//预期定时器执行的jiffies值
	void			(*function)(unsigned long);	//绑定的执行函数
	unsigned long		data;	//传给执行函数的参数
	u32			flags;
};

struct timer_list结构体在内核中描述定时器,在到达expires指定的jiffies值时,执行function函数,并把data作为传参传给function函数;

5.3、定时器工作的逻辑流程

(1)构建定时器:初始化struct timer_list结构体,绑定执行函数、传参,设置定时的时间,有专门的初始化函数;
(2)将构建好的定时器向内核注册;
(3)到达设置的定时时间,调用注册函数;
(4)在注册函数中再次向内核注册该定时器;
(5)定时器重新开始计时,相当于从第二步开始循环执行;

5.4、示例代码

/* 实现每隔一秒向内核log中打印一条信息 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/time.h>
#include <linux/timer.h>

static struct timer_list tm;
struct timeval oldtv;

void callback(unsigned long arg)
{
    struct timeval tv;
    char *strp = (char*)arg;
    
    printk("%s: %lu, %s\n", __func__, jiffies, strp);

    do_gettimeofday(&tv);
    printk("%s: %ld, %ld\n", __func__,
        tv.tv_sec - oldtv.tv_sec,        //与上次中断间隔 s
        tv.tv_usec- oldtv.tv_usec);        //与上次中断间隔 ms
    

    oldtv = tv;
    tm.expires = jiffies+1*HZ;    
    add_timer(&tm);        //再次注册定时器,重新开始计时    
}

static int __init demo_init(void)
{
    printk(KERN_INFO "%s : %s : %d - ok.\n", __FILE__, __func__, __LINE__);

    init_timer(&tm);    //初始化内核定时器

    do_gettimeofday(&oldtv);        //获取当前时间
    tm.function= callback;            //指定定时时间到后的回调函数
    tm.data    = (unsigned long)"hello world";        //回调函数的参数
    tm.expires = jiffies+1*HZ;        //定时时间为1s
    add_timer(&tm);        //注册定时器

    return 0;
}

static void __exit demo_exit(void)
{
    printk(KERN_INFO "%s : %s : %d - ok.\n", __FILE__, __func__, __LINE__);
    del_timer(&tm);        //注销定时器
}

module_init(demo_init);
module_exit(demo_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("yikoupeng");
MODULE_DESCRIPTION("timerlist");

上面的代码摘抄自《一口linux》;

tasklet机制和工作队列

参考博客:《中断的顶半部和底半部介绍以及实现方式(tasklet 和 工作队列)》

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

正在起飞的蜗牛

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

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

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

打赏作者

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

抵扣说明:

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

余额充值