驱动学习之时间管理

一、jiffies

1.1概念

	通常情况下,jiffies在linux系统启动引导阶段初始化为0,当系统完成了对时钟中断的初始化之后,在每个时钟中断处理例程中,该值都会被+1,因此该值存储了系统自最近一次启动以来的时钟滴答数。除了时钟中断程序对该值有修改的权利,其它任何模块都只有读的权利。

这里再介绍一个概念HZ,该值在内核源码根目录下.config文件中。一般为1000,代表系统一秒中发生1000次时钟中断。

jiffies在内核源码中作为一个全局性变量被导出,所以在驱动程序中如过想使用,必须包含头文件:

	#include<linux/jiffies.h>
 	unsigned long j = jiffies;
 	unsigned long j+1 = jiffies+1*HZ/1000;未来一秒

linux驱动程序中,用到jiffies的场景分别有时间比较,时间转换以及设置定时器。我们这里先讨论前两个。

1.2时间比较

jiffies在32位操作系统中,最多五十天就会溢出,虽然在大多数情况下不会出现这种情况,但是为了防止这种情况的出现,linux内核为此专门提供了一组时间比较的宏,来避免溢出。

time_after(a,b);         若时间a,在时间b之后,返回true
time_before(a,b);      若时间a,在时间b之前,返回true
time_after_eq(a,b);   若时间a,在时间b之后,返回true,当两个时间点相等时,也返回true
time_before_eq(a,b);若时间a,在时间b之前,返回true,当两个时间点相等时,也返回true
time_in_range(a,b,c);  用来检查时间点a,是否包含在时间间隔b,c中,包含临界点。

1.3 时间转换

有时候设备驱动程序需要将jiffies转换为ms或者us,这里列出系统提供的函数:

#include<linux/jiffies.h>
unsigned int jiffies_to_msecs(const unsigned long j);
unsigned int jiffies_to_usecs(const unsigned long j);
unsigned long msecs_to_jiffies(const unsigned long m);
unsigned long usecs_to_jiffies(const unsigned long u);

时间转换的另外一种情形发生在用户态进程和设备驱动程序的交互上,应用程序员更多的使用秒及毫秒等形式,为此,内核定义了struct timeval 和struct timespec两种数据结构。内核同样提供了jiffies变量与这两个数据结构的实例转换函数:

#include<linux/jiffies.h>
unsigned long timespec_to_jiffies(const struct timespec *value);
void jiffies_to_timespec(const unsigned long jiffise,struct timespec *value)
unsigned longtimeval_to_jiffies(const struct timeval *value);
void jiffies_to_timeval(const unsigned long jiffise,struct timeval *value)

二、延时操作

延时操作在设备驱动程序中也是经常使用。通常情况下cpu在对外设发出操作指令之后,需要延时一段时间,再去读取结果。延时分为长延时与短延时,我们下面分析。

2.1长延时

基于时钟滴答jiffies的长延时函数有几种实现办法,主要围绕在实现过程中是否让出cpu来分为“忙等待”和“让出cpu“两大类。
忙等待
忙等待作为设备驱动早起开发阶段用来调试设备是否能像预期那样工作经常使用,虽然可能因为忙等到而造成资源的浪费。最简单的忙等待就是我们能立马想到的延时如while循环之类:

 unsigned long i=0xfffffff;
 while(i--);

这是一种相当粗糙的实现方法,显然在最终的发布中不应该有它,我们可以利用jiffies实现一种比较理想的忙等待,比如为了实现1S的等待,可以用如下代码实现:

unsigned long j=jiffies+HZ;
while(timer_before(jiffies,j)
cpu_relax(); //空指令

但是,上面的实现有两个很直白的缺点:
(1)如果进入循环之前,处理器关闭了中断,这时jiffies就不会改变了,所以while将一直满足;
(2)在支持抢占的系统中,可能会在等待过程中被更高优先级的进程中断,然后再次调度回来的时候,可能已经超过了延时的时间。
问题(2)对于驱动程序来说不是什么大问题,如果没有cpu资源的浪费,那么即便延时函数多延时了一会,也不会造成性能的实质性影响。但是对于问题(1)的改进,则导致了让出处理器解决方法的出现。

让出处理器
下面是让出处理器的长延时的办法,但还是有缺陷,我们先看代码:

unsigned long j=jiffies+HZ;
while(timer_before(jiffies,j)
schedule();

看似很完美,会让出cpu的资源,但是在实际运行中,该进程会被很快的再次调度,然后循环放弃cpu,循环调度,也是很费资源的。这里根本的原因是调用schedule函数的进程还会放在进程调度的队列中。所以我们想解决这一问题,应该使用下面的办法:
delag_1s()
{
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(jiffies+HZ);
}
这种情况下,函数在schedule_timeout进而调用schedule的时候,会因为进程的状态为TASK_UNINTERRUPTIBLE把进程移除调度队列,然后把该进程添加到定时器的数据节点上,时间到了之后,唤醒进程使其重新进入调度队列。

事实上,linux内核提供了一个基于schedule_timeout的延时函数msleep(unsigned int msecs),这里不分析实现的源码了,msleep正常情况下返回0,表示系统延时了正常的时间,但是如果被信号打断,则返回了未延时的时间。所以,再让出处理器的方法中,直接使用msleep是最简单的方法了。

2.2 短延时

有时候需要更短的延时,比如微妙或者纳秒,所以出现了短延时。
由于jiffies一般情况下HZ都为1000,意味着1msjiffies就会增加1,所以对于us与ns,不能基于jiffies实现了。而且处理器的调度就在几微妙到几百微秒之间,所以不能才去让出处理器的方式了,应该采用忙等待的方式实现。
linux内核提供了几个宏来进行短延时:
#inculde<linux/delay.h>
void mdelay(unsigned long msecs);
void udelay(unsigned long usecs);
void ndelay(unsigned long nsecs);
最基础的是udelay,另外两个是基于它实现的。

三、内核定时器

如果驱动程序希望在将来某可度量的时间点到期后,去执行一个任务,该任务一般都是驱动程序自己的函数,便可以使用定时器来完成。
定时器实现的原理其实跟我们之前分析的很多东西都比较类似,首先它是通过中断实现的。我们在使用定时器的时候,会通过函数add_tiemr向内核中的定时器链表中添加一个定时器。当定时器时间到了之后,内核会调用该定制器绑定的函数。
驱动程序中使用例子:

1.定义对象
struct timer_list {
			unsigned long expires;                //定时的时间
			void (*function)(unsigned long data); //定时器处理函数
			unsigned long data;                   //向定时器处理函数传递的参数       
};

struct timer_list time;

2.初始化

init_timer(&time);
time.expires = jiffies + n
time.function = timer_function;
time.data = (unsigned logn )dev;

或者

setup_timer(timer,function,data);
time.expires = jiffies + n

3.添加定时器

void add_timer(struct timer_list *timer)  //添加定时器,直接启动,执行一次
int mod_timer(struct timer_list *timer, unsigned long expires) //重新启动定时器,只运行一次,定时的时间需要重新设置

4.删除定时器

int del_timer(struct timer_list *timer)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值