Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2149026
  • 博文数量: 361
  • 博客积分: 10828
  • 博客等级: 上将
  • 技术积分: 4161
  • 用 户 组: 普通用户
  • 注册时间: 2010-01-20 14:34
文章分类

全部博文(361)

文章存档

2011年(132)

2010年(229)

分类: LINUX

2010-04-28 14:06:31

 /*

*By Neil Chiao ()

* 转载请注明出处:neilengineer.cublog.cn

*欢迎到“新星 湾()”指导

*/

 
/*
Linux设备驱动简析”系列是本人学习Linux内 核过程中,对所学驱动的简单分析。 纯属个人观点,如有不当之处,敬请斧正。
*/

 

 

内核中时间相关的基本概念和简单应 用举例。

 

Linux内核中的时间来源主要 分:硬件时钟和软件时钟

硬件时钟有:

1)实时时钟RTC
      实时时钟(RTC)是用电池供电的一个时钟芯片,里面保存的就是我们一般意义上的时间。 如:
2009年4月26日

2)可编程间隔定时器PIT
      PIT会产生周期性的时钟中断信号。Linux中在PIT(Timer)的硬件中断处理函数中对jiffies的值
加1


   3)时间戳记数器TSC
      这里一个X86平台上有的用于记录CPU的每个时钟信号次数的寄存器。X86平台的Linux中如果时钟中
断 丢失,则用这个TSC来进行校正时钟。

软件时钟有:jiffies,内核 定时器等。

 

 

内核通过时钟中断来跟踪时间流。时钟中断由定时硬件以周期性的间隔产生,这个间隔由HZ

值设定。大多数平台上默认的HZ值是50~1200之间,X86平台从2.6内核起就是1000

对于linux-2.6.10内核中的ARM平 台,HZ的定义在include/asm/param.h文件中:

# ifndef HZ

#  define HZ         100         /* Internal kernel timer frequency */

# endif

       每次时钟中断发生时,内核计数器的值加1,这 个值就是系统时钟的滴答数(此计数器的值在系统启动时初始化为0)。这个内核计数器是一个64位的变量(32位架 构上也是64位),称为“jiffies_64 但一般使用unsigned long型的jiffies变量,jiffies等于jiffies_64或为jiffies_64的高(低)32位, 具体是高32位还是低32位, 取决于是big endian还是little endian

include/linux/jiffies.h

#define INITIAL_JIFFIES ((unsigned long)(unsigned int) (0))

arch/arm/kernel/time.c

u64 jiffies_64 = INITIAL_JIFFIES;

arch/arm/kernel/vmlinux.lds.S

#ifndef __ARMEB__      //gcc –EB时指定为big endian

jiffies = jiffies_64;

#else

jiffies = jiffies_64 + 4;

#endif

       如上所说,ARM平 台中HZ100jiffies,也就是说100jiffies=1s

除系统定时器外,还有一个与时间有关的时钟:实时时钟(RTC),这是一个硬件时钟,用来持久存放系统时间,系统关闭后靠主板上的微型电池保持计时。系统启动时, 内核通过读取RTC来初始化Wall Time,并存放在xtime变量中,这是RTC最 主要的作用。

使用jiffies计数器

使用jiffies很简单,如:

t1=jiffies;            //t1为运行此语句时的jiffies

t2=jiffies+HZ;        //t21秒之后的jiffies

diff=(long)t2-(long)t1;   //计算时间差

s=diff/HZ;                    //时间差转换成秒

ms=diff*1000/HZ;         //时间差转换成毫秒

内核提供了相应的宏来比较时间,其实现原理就是转换成long型后相减:

#include 

int time_after(unsigned long a, unsigned long b);         //ab靠后,返回真

int time_before(unsigned long a, unsigned long b);      //ab靠前,返回真

int time_after_eq(unsigned long a, unsigned long b);    //ab靠后或相等,返回真

int time_before_eq(unsigned long a, unsigned long b); //ab靠前或相等,返回真

      

使用jiffies计数器的实例,如arch/arm/kernel中的irq.c中的probe_irq_on函数:

unsigned long probe_irq_on(void)

{

。。。。。。

       for (delay = jiffies + HZ/10; time_before(jiffies, delay); ) //delay前一直循环

              /* min 100ms delay */ ;

。。。。。。

}

      

常用的延迟

1)忙等待

       上述irq.c的例子中这样使用time_before就是一个忙等待的例子。

2ndelayudelaymdelay函数

       这几个函数的原型定义如下:

#include

void ndelay(unsigned long nsecs);        //延迟nsecs纳秒

void udelay(unsigned long usecs);        //延迟usecs微秒

void mdelay(unsigned long msecs);      //延迟msecs毫秒

       这几个函数的实现和具体硬件相关,具体是在中实现。 其中,udelay函数在所有平台上都 会实现,其它函数就不一定了。实际上,当前所有平台都无法达到纳秒的精度。

       用这些函数实现的延迟,至少会达到请求的时间值,但可能更长。

linux-2.6.10内核中mdelayndelay都是基于udelay实现的,见include/linux/delay.h

ARM平台中的udelay函数实现在include/asm-arm/delay.h中,如下:

extern void __udelay(unsigned long usecs);

extern void __const_udelay(unsigned long);

#define MAX_UDELAY_MS  2

 

#define udelay(n)                                       \

       (__builtin_constant_p(n) ?                          \      

/* gcc的 内建函数__builtin_constant_p用于判断n是否为编译时常数,如果n是常数,返回 1,否则返回 0*/

         ((n) > (MAX_UDELAY_MS * 1000) ? __bad_udelay() :  \

                     __const_udelay((n) * 0x68dbul)) :       \

/*n> MAX_UDELAY_MS * 1000时出错返回,否则调用函数__const_udelay */

         __udelay(n))

       其中,__udelay__const_udelayarch/arm/lib/delay.S中实现,如下:

ENTRY(__udelay)

              mov r2,     #0x6800

              orr   r2, r2, #0x00db

              mul  r0, r2, r0

ENTRY(__const_udelay)                            @ 0 <= r0 <= 0x01ffffff

              ldr   r2, LC0

              ldr   r2, [r2]           @ max = 0x0fffffff

              mov r0, r0, lsr #11         @ max = 0x00003fff

              mov r2, r2, lsr #11         @ max = 0x0003ffff

              mul  r0, r2, r0        @ max = 2^32-1

              movs      r0, r0, lsr #6

              RETINSTR(moveq,pc,lr)

 

Note: ARM平台,函数中最前面4个参数通过,寄存器r0,r1,r2,r3来传递,其它的参数通过下降式满堆栈来传递。对于传递给__const_udelay函数的参数(n) * 0x68dbul),会保存在寄存器r0中。

 

3msleepssleep函数

       相关原型如下:

#include

void msleep(unsigned int msecs);

unsigned long msleep_interruptible(unsigned int msecs);

static inline void ssleep (unsigned int seconds);

       其中,msleepssleep不可中断。

 

4)其它

内核中除上述的时间相关机制外,还有tasklet,工作队列,共享队列来实现任务的延迟执行。对这几种机制的说明这里暂略。

 

内核定时器

       如果要在将来某个时间点调度执行某个动作,同时在该时间点到达之前不会阻塞当前进程,则可以使用内 核定时器。

       一个内核定时器是一个数据结构,它告诉内核在用户定义的时间点使用用户定义的参数来执行一个用户定 义的函数。

       定时器函数必须是原子的。

Note: 判断当前是否在原子操作上下文中用in_atomic();判断是否在中断上下文中用in_interrupt ()

       内核定时器相关函数在include/linux/timer.h中定义:

struct timer_list {

       struct list_head entry;

       unsigned long expires;                  //到此jiffies值时,定时器执行定义的函数

       spinlock_t lock;

       unsigned long magic;

       void (*function)(unsigned long); //要执行的函数

       unsigned long data;                      //传递给function函数的参数

       struct tvec_t_base_s *base;

};

 

static inline void init_timer(struct timer_list * timer);

static inline void add_timer(struct timer_list * timer);

int del_timer(struct timer_list * timer);

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

       在使用内核定时器时,只要初始化一个timer_list结构体,其中主要初始化Lexpires*functiondata3个就可以使用了。

       Linux-2.6.10floppy驱动为例。在drivers/block/floppy.cfloppy_init函数中:

init_timer(&motor_off_timer[dr]);        //初始化定时器

motor_off_timer[dr].data = dr;            //定义定时器使用的函数

motor_off_timer[dr].function = motor_off_callback; //定义定时器的时间

 

 

       在同一文件的floppy_off函数中:

。。。。。。

del_timer(motor_off_timer + drive);            //删除定时器

。。。。。。

add_timer(motor_off_timer + drive);          //实际增加此定时器

      

1. linux-2.6.10内核源码

2. Linux设备驱动程序 第三版

阅读(2059) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~