linux驱动之定时器的介绍和内核时间的学习

本文章摘自下面的网友:

http://blog.sina.com.cn/s/blog_6e5b342e0100m87d.html

一、内核中如何记录时间

任何程序都需要时间控制,其主要目的是:

  • 测量时间流逝和比较时间
  • 知道当前时间
  • 指定时间量的延时操作

为达到这个目的,应用程序使用日历时间(年月日时分秒)或者自1970年1月1日零时零分零秒到当前的秒数来度量时间的流逝,但内核中需要更加有精度的时间度量,因此内核使用时钟嘀嗒来记录时间。时钟中断发生后内核内部时间计数器增加1(即:增加1个时钟嘀嗒),系统引导时为0,当前值为自上次系统引导以来的时钟滴答数,程序可通过内核定义的全局变量jiffies_64或jiffies来访问。真实硬件上每秒的嘀嗒数从 50 到 1200 不等 ,x86上默认为1000,而s3c2440上默认为200,出于统一编程接口的考虑,内核定义了一个宏HZ,它表示1秒钟的嘀嗒数,可供程序使用。

Jiffies 和 jiffies_64是 unsigned long 类型只读变量,其用法如下:

  • #include <linux/jiffies.h>
  • unsigned long j, stamp_1, stamp_half, stamp_n;
  • j = jiffies;
  • stamp_1 = j + HZ;
  • stamp_half = j + HZ/2;
  • stamp_n = j + n * HZ / 1000;

注意:32-位 平台上当 HZ 是 1000 时, 计数器只是每 50 天溢出一次, 必要时你的代码应当准备处理这个事件

比较2个时间的大小,常用内核的如下宏定义:

  • #include <linux/jiffies.h>
  • int time_after(unsigned long a, unsigned long b);
  • int time_before(unsigned long a, unsigned long b);
  • int time_after_eq(unsigned long a, unsigned long b);
  • int time_before_eq(unsigned long a, unsigned long b);

它们分别在时间a在时间b之后、之前、之后或相等、之前或相等的时候为真,反之为假

    • 求 2 个 jiffies 实例之间的差:

      • diff = (long)t2 - (long)t1;.
    • 你可以转换一个 jiffies 差为毫秒, 一般地通过:
      • msec = diff * 1000 / HZ;
    • jiffies与日历时间的转换函数:
      • #include <linux/time.h>
      • unsigned long timespec_to_jiffies(struct timespec *value);
      • void jiffies_to_timespec(unsigned long jiffies, struct timespec *value);
      • unsigned long timeval_to_jiffies(struct timeval *value);
      • void jiffies_to_timeval_r(unsigned long jiffies, struct *timeval)

二、内核定时器

1、概述

  • 无论何时你需要调度一个动作以后发生, 就可以使用内核定时器,如:当硬件无法发出中断时, 可通过使用内核定时器,以定期的间隔检查一个设备的状态。
  • 一个内核定时器是一个数据结构, 它指导内核在一个用户定义的时间,使用一个用户定义的参数,执行一个用户定义的函数
  • 由内核线程——软中断(ksoftirqd/0)调度执行

    一个cpu,一个ksoftirqd

    ksoftirqd属于atomic context

    ksoftirqd运行时,不禁用irq

2、定时器 API

#include <linux/timer.h>

struct timer_list {
    unsigned long expires;
    void (*function)(unsigned long);
    unsigned long data;
    其它字段
};
    静态初始化定时器结构:
    struct timer_list timerval = TIMER_INITIALIZER(_function,_expires,_data)
    动态初始化定时器结构:
    setuo_timer(struct timer_list *timer,_function,_data);   //初始化function和data后,调用init_timer
    init_timer(struct timer_list *timer);
    timer.expires = jiffies+100;或者:timer.expires = jiffies + HZ/10(手动指定触发时间)
    将已经初始化的定时器加入系统定时器链表:
    void add_timer(struct timer_list *timer);
    注:定时器执行后,会自动退出系统定时器链表,如需再次执行,则需要更新expires后,再次加入系统定时器链表
    更新一个定时器的超时时间,同时加入系统链表
    int mod_timer(struct timer_list *timer,unsigned long expires)
    删除定时器,退出系统定时器链表
    int del_timer(struct timer_list *timer)
    三、如何在内核中实现延时
      设备驱动常常需要延后一段时间执行一个特定片段的代码, 以便允许硬件完成某个任务。延时一般区分为短延时和长延时
          1、短延时:当一个设备驱动需要等待硬件的反应时间, 涉及到的延时常常是最多几个毫秒 。此种延时就是短延时,一般采用忙等待。(忙等待我个人理解是此时处理器依然在本进程中)
          相关函数如下:
#include <linux/delay.h>
void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);

2、长延时:

如果需要延后较长时间,就可以采用长延时。长延时可分为忙等待和让出CPU两种方式。

1)、忙等待:

  unsigned long j1 = jiffies + 2*HZ;
      while (time_before(jiffies, j1))

cpu_relax();

  cpu_relex 的调用使用了一个特定于体系的方式,你此时没有用处理器做事情,比较浪费处理器的资源

2)、让出处理器

  unsigned long j1 = jiffies + 3600*HZ;

  while (time_before(jiffies, j))

  {

    set_current_state(TASK_INTERRUPTIBLE);

    schedule_timeout(30*HZ);

  }

3)此外,如果你的驱动使用一个等待队列来等待某些其他事件,但是你也想确保它在一个确定时间段内运行能够运行,而不是永久等待,那么可以使用超时

  

#include <linux/wait.h>
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);

后面会编写一个linux模块化驱动编程的实例和延时。

时间: 2024-10-09 20:11:40

linux驱动之定时器的介绍和内核时间的学习的相关文章

Linux驱动之定时器在按键去抖中的应用

机械按键在按下的过程中会出现抖动的情况,如下图,这样就会导致本来按下一次按键的过程会出现多次中断,导致判断出错.在按键驱动程序中我们可以这么做: 在按键驱动程序中我们可以这么做来取消按键抖动的影响:当出现一个按键中断后不会马上去处理它,而是延时一个抖动时间(一般10ms),如果在这个时间内再次出现中断那么再次延时10ms.这样循环,一直到在这个10ms内只有一个按键中断,那么就认为这次是真的按键值,然后在定时器处理函数里处理它.上述过程可以利用内核的定时器来实现. 定时器二要素:定时时间.定时时

linux驱动之定时器的使用

被文章摘自一下几位网友.非常感谢他们. http://blog.sina.com.cn/s/blog_57330c3401011cq3.html Linux的内核中定义了一个定时器的结构: #include<linux/timer.h> struct timer_list { struct list_head list; unsigned long expires; //定时器到期时间 unsigned long data; //作为参数被传入定时器处理函数 void (*function)(

嵌入式Linux驱动开发实战视频教程

嵌入式Linux驱动开发实战教程(内核驱动.看门狗技术.触摸屏.视频采集系统)适合人群:高级课时数量:109课时用到技术:嵌入式 Linux涉及项目:驱动开发.看门狗技术.触摸屏.视频采集咨询qq:1840215592 课程介绍:本课程即是针对有兴趣学习嵌入式linux驱动开发又不知道从何处着实开始学习嵌入式linux驱动开发的在校同学以及社会在职人员.本课程采用理论教学与实验相结合的方式,软件与硬件相结合的方式,重点给大家讲解嵌入式linux驱动开发的方法,系统地介绍嵌入式linux驱动开发的

嵌入式Linux驱动开发实战教程

嵌入式Linux驱动开发实战教程(内核驱动.看门狗技术.触摸屏.视频采集系统) http://www.ibeifeng.com/goods-475.html 咨询QQ2110053820 课程讲师:韩老师 课程分类:Linux 适合人群:高级 课时数量:109课时 更新程度:完成 用到技术:嵌入式 Linux 涉及项目:驱动开发.看门狗技术.触摸屏.视频采集 课程简介:    嵌入式软件开发无疑是当今最热门的行业,嵌入式软件工程师的薪资比普通的软件工 程师的薪资平均高50%以上.随着智能控制.物

Linux 驱动开发索引

1.嵌入开发环境搭建 Telnet 在 mini2440 上的移植 Opencv-2.4.9 在 mini2440 上的移植 搭建嵌入式开发环境总结 2.Linux 设备驱动 Linux 驱动程序头文件 一步一步学习Linux驱动之驱动模块MakeFile解析 一步一步学习 Linux 驱动之(Kconfig.Makefile) 一步一步学习 Linux 驱动之字符设备 LED 静态编译进 Linux 内核 内核怎么通过主设备号找驱动.次设备号找设备 Linux 驱动之内核空间分配内存 一步一步

linux驱动开发-模块驱动

linux内核是可以高度定制的,通过配置编译选项达到定制的目的. 在配置kernel编译选项时驱动程序的编译选项一般有三种,不编译.编译为内核驱动.编译为模块驱动.所以linux驱动一般分为两类,内核驱动和模块驱动,当然这两者之间并无绝对区别:当编译为内核驱动时,驱动程序将会随内核一起运行,就相当于内核的固有部分一样:当编译为模块驱动时,每个驱动程序都是独立的个体,在需要的时候安装,用完了再卸载,可以节约硬件资源.以下内容描述的是模块驱动的开发过程. 1.获取内核源码,按照目标器件(PC机或者开

linux设备驱动程序该添加哪些头文件以及驱动常用头文件介绍(转)

原文链接:http://blog.chinaunix.net/uid-22609852-id-3506475.html 驱动常用头文件介绍 #include <linux/***.h> 是在linux-2.6.29/include/linux下面寻找源文件.#include <asm/***.h> 是在linux-2.6.29/arch/arm/include/asm下面寻找源文件.#include <mach/***.h> 是在linux-2.6.29/arch/ar

linux 驱动学习笔记01--Linux 内核的编译

由于用的学习材料是<linux设备驱动开发详解(第二版)>,所以linux驱动学习笔记大部分文字描述来自于这本书,学习笔记系列用于自己学习理解的一种查阅和复习方式. #make config(基于文本的最为传统的配置界面,不推荐使用)#make menuconfig(基于文本菜单的配置界面)#make xconfig(要求 QT 被安装)#make gconfig(要求 GTK+被安装)在配置 Linux 2.6 内核所使用的 make config. make menuconfig. mak

Linux内核(17) - 高效学习Linux驱动开发

这本<Linux内核修炼之道>已经开卖(网上的链接为: 卓越.当当.china-pub ),虽然是严肃文学,但为了保证流畅性,大部分文字我还都是斟词灼句,反复的念几遍才写上去的,尽量考虑到写上去的每段话能够让读者产生什么疑惑,然后也都会紧接着尽量的去进行解释清楚,中间的很多概念也有反复纠结过怎么解释能够更容易的理解,力求即使对于初学者也可以有很少阻碍的一气读完.同时我也把书中一部分自己的感悟抽出来整理了精华版,share出来.当然水平有限,错漏之处有发现而修订时遗漏的,也有尚没有发现的.这本书