现在的位置: 首页 > 综合 > 正文

Linux设备驱动程序第三版学习(10)- 时间、延迟及延缓操作

2013年05月30日 ⁄ 综合 ⁄ 共 6075字 ⁄ 字号 评论关闭

接下来学习第七章:时间、延迟及延缓操作。本章主要学习了内核代码如何对时间问题进行处理。
关于Linux时钟处理机制的详细内容,参考本博客转载的“Linux 时钟处理机制”一文【赵 健博 (zhaojianbo@ncic.ac.cn), 硕士, 中国科学院计算技术研究所】

一、时间。
内核通过定时器中断来跟踪时间流。定时器中断是硬件产生的,是周期性的。不同的硬件平台的周期不一样,例如x86 PC上默认的是1000次/秒。内核维护一个内部的计数器,这个计数器在系统引导时被初始化为0,每次中断发生时,计数器+1。也就是在x86上此计数器每秒钟增加1000。这个计数器是一个64位变量,称为“jiffies_64”。而作为驱动程序的开发者,通常访问的是jiffies变量,unsigned long型。以x86 PC平台为例,该计数器每隔大约50天会溢出一次。计算方法是4294967296次(2的32次方)/1000次/3600秒/24小时=49.7102696

只要包含头文件,就可以读取jiffies变量。如果需要比较两个jiffies的快照的时间前后关系,可以使用下面的是个宏:

   1:  int time_after(unsigned long a, unsigned long b);
   2:  int time_before(unsigned long a, unsigned long b);
   3:  int time_after_eq(unsigned long a, unsigned long b);
   4:  int time_before_eq(unsigned long a, unsigned long b);

为了在必要时将用户空间的时间表述和内核表述进行转换,内核提供了下面四个辅助函数:

   1:  #include 
   2:  unsigned long timespec_to_jiffies(struct timespec *value);
   3:  void jiffies_to_timespec(unsigned long jiffies, struct timespec *value);
   4:  unsigned long timeval_to_jiffies(struct timeval *value);
   5:  void jiffies_to_timeval(unsigned long jiffies, struct timeval *value);

可以用看出上面的方法来度量时间是有局限的,主要在度量非常短的时间或者需要极高时间精度时上面方法就无能为力了,这时就要使用处理器特定寄存器。
绝大多数现代处理器都包含一个计数寄存器,其中最有名的当属Pentium处理器的TSC(timestamp counter,时间戳计数器)。TSC是一个64位的寄存器,记录了CPU时钟周期数,内核空间和用户空间都可以读这个寄存器。读取的方法是:

#include  //machine-specific registers,意为机器特有的寄存器 
rdtsc(low32, high32); //把TSC里的64位数值读到两个32位变量中,该操作是原子的
rdtscl(low32); //只把TSC的低半部分读入一个32位变量 
rdtscll(var64); //把TSC里的64位数值保存到一个long long变量中

注:使用处理器特定寄存器会带来代码的兼容性问题,因为该寄存器是平台相关的。

获取当前时间的方法:
* 一般情况,使用jiffies来获取时间
* 如果需要更精确的时间,需要使用TSC
* 内核提供了将墙钟时间(年,月,日)转换为jiffies值的函数:
#include 
extern 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);

* 内核中do_gettimeofday函数可以最高得到微秒级的分辨率,这取决于体系结构。
* 内核还提供了一个辅助函数current_kernel_time来获得时间:
#include
extern 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);

二、延迟的技术。
长延迟:
当需要延迟比较长的时间时,最好的方法是基于jiffies的超时延迟。
* 可以使用wait_event_timeout或者wait_event_interruptible_timeout函数,其中timeout值表示的是要等待的jiffies值。
#include   long wait_event_timeout(wait_queue_head_t q, condition, long timeout);   long wait_event_interruptible_timeout(wait_queue_head_t q, condition, long timeout);

   * 也可以使用schedule_timeout函数,其中timeout是用jiffies表示的延迟时间。
短延迟:
当需要延迟比较短的时间时,比如说微妙级的延迟,就需要用到几个短延迟的内核函数。
#include void ndelay(unsigned long nsecs); void udelay(unsigned long usecs); void mdelay(unsigned long msecs);

三、内核定时器

内核定时器的使用非常简单,内核专门提供了一组API用来声明、注册、删除内核定时器。

这组API如下:
   1:  void init_timer(struct timer_list *timer); 
   2:  void add_timer(struct timer_list *timer);
   3:  int del_timer(struct timer_list *timer);

 下面看看具体的应用代码,此代码就是jit模块中的jit_timer函数。
   1:  int jit_timer(char *buf, char **start, off_t offset,
   2:            int len, int *eof, void *unused_data)
   3:  {
   4:      struct jit_data *data; //timer_list结构和tasklet_struct结构都被放在了jit_data结构中
   5:      char *buf2 = buf;
   6:      unsigned long j = jiffies; //读取当前的jiffies值
   7:   
   8:      data = kmalloc(sizeof(*data), GFP_KERNEL); //给结构变量分配内存
   9:      if (!data)
  10:          return -ENOMEM;
  11:   
  12:      init_timer(&data->timer); //使用了第一个API init_timer来初始化定时器,参数是一个timer_list结构体变量
  13:      init_waitqueue_head (&data->wait); //初始化等待队列头
  14:   
  15:      /* write the first lines in the buffer */
  16:      buf2 += sprintf(buf2, "   time   delta  inirq    pid   cpu command/n"); /*我们想要打印的信息
,依次为:时间、时间差、是否在中断上下文、进程id、多处理器时cpu id、当前命令*/
  17:      buf2 += sprintf(buf2, "%9li  %3li     %i    %6i   %i   %s/n",
  18:              j, 0L, in_interrupt() ? 1 : 0,
  19:              current->pid, smp_processor_id(), current->comm);
  20:   
  21:      /* fill the data for our timer function */
  22:      data->prevjiffies = j; //保存原来的jiffies值到data->prevjiffies中
  23:      data->buf = buf2; //保存打印内容到data->buf中
  24:      data->loops = JIT_ASYNC_LOOPS; //保存想要循环打印的次数到data->loops中,#define JIT_ASYNC_LOOPS 5
  25:      
  26:      /* register the timer */
  27:      data->timer.data = (unsigned long)data;  //data中保存回调函数的参数,这里把jit_data结构的指针传递进来
  28:      data->timer.function = jit_timer_fn; //回调函数。就是定时器到期后要执行的操作。这里主要执行了打印操作和loops减操作
  29:      data->timer.expires = j + tdelay;  //到期时间,以时钟滴答为单位。int tdelay = 10
  30:      add_timer(&data->timer); //使用了第二个API add_timer将定时器添加到相应调用此函数的CPU base中,启动了定时器中断
  31:   
  32:      /* wait for the buffer to fill */
  33:      wait_event_interruptible(data->wait, !data->loops); /*在jit_timer_fn中,loops每循环一次减1,进程在此期间休眠,知道
loops为0*/
  34:      if (signal_pending(current))
  35:          return -ERESTARTSYS;
  36:      buf2 = data->buf;  //保存缓冲区内容
  37:      kfree(data); //释放内存
  38:      *eof = 1;
  39:      return buf2 - buf;
  40:  }

使用内核定时器需要特别注意的是,定时器的那个回调函数的设计有诸多限制,之所以有这些限制大概是因为这类似与中断处理程序吧,:
* 不能访问用户控件。因为没有进程上下文,无法将任何特定进程与用户空间关联起来。
* current指针在原子模式下是没有任何意义的,也不可用。因为相关代码和被中断的进程没有任何关联。只有一种情况,在持有自旋锁这种情况, current 可能
是有效的。但是禁止访问用户空间。
* 不能执行休眠或调度。不可以调用schedule或者wait_event,也不能调用任何引起休眠的函数,如kmalloc,也不能用信号量。当然更不能用sleep了。哈哈
我就试了一下sleep,效果杠杠滴。
四、tasklet(小任务)
tasklet的使用也非常简单,内核也提供了一些API来完成初始化和调度。这组API如下:
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data); /*初始化tasklet,其中func
就是调度tasklet要执行的函数。*/
void tasklet_disable(struct tasklet_struct *t); /*禁用tasklet,即使tasklet被调用也不会执行,直到tasklet_enable*/ void tasklet_disable_nosync(struct tasklet_struct *t); /*也是禁用tasklet,与上一个函数不同的是如果tasklet正在运行,不会等待而直接
返回*/
void tasklet_enable(struct tasklet_struct *t); /*启用一个被禁用的tasklet*/ void tasklet_schedule(struct tasklet_struct *t); /*调度执行一个tasklet */ void tasklet_hi_schedule(struct tasklet_struct *t); /*调度指定的tasklet以高优先级执行*/ void tasklet_kill(struct tasklet_struct *t); /*顾名思义,这个函数确保tasklet不会被再次调度*/

也是来看一下具体的代码吧,此代码就是jit模块中的jit_tasklet函数。基本上和jit_timer差不多。不解释了。
   1:  int jit_tasklet(char *buf, char **start, off_t offset, int len, int *eof, void *art)
   2:  {
   3:      struct jit_data *data;
   4:      char *buf2 = buf;
   5:      unsigned long j = jiffies;
   6:      long hi = (long)arg;
   7:   
   8:      data = kmalloc(sizeof(*data), GFP_KERNEL);
   9:      if (!data)
  10:          return -ENOMEM;
  11:   
  12:      init_waitqueue_head (&data->wait);
  13:   
  14:      /* write the first lines in the buffer */
  15:      buf2 += sprintf(buf2, "   time   delta  inirq    pid   cpu command/n");
  16:      buf2 += sprintf(buf2, "%9li  %3li     %i    %6i   %i   %s/n",
  17:              j, 0L, in_interrupt() ? 1 : 0,
  18:              current->pid, smp_processor_id(), current->comm);
  19:   
  20:      /* fill the data for our tasklet function */
  21:      data->prevjiffies = j;
  22:      data->buf = buf2;
  23:      data->loops = JIT_ASYNC_LOOPS;
  24:      
  25:      /* register the tasklet */
  26:      tasklet_init(&data->tlet, jit_tasklet_fn, (unsigned long)data);
  27:      data->hi = hi;
  28:      if (hi)
  29:          tasklet_hi_schedule(&data->tlet);
  30:      else
  31:          tasklet_schedule(&data->tlet);
  32:   
  33:      /* wait for the buffer to fill */
  34:      wait_event_interruptible(data->wait, !data->loops);
  35:   
  36:      if (signal_pending(current))
  37:          return -ERESTARTSYS;
  38:      buf2 = data->buf;
  39:      kfree(data);
  40:      *eof = 1;
  41:      return buf2 - buf;
  42:  }

五、工作队列
关于工作的内容以后进行补充。


 


 















 

抱歉!评论已关闭.