历史上的今天
返回首页

历史上的今天

今天是:2025年01月31日(星期五)

2020年01月31日 | ARM Linux系统的时钟机制

2020-01-31 来源:eefocus

1. Linux下有两类时钟:


1.1 实时钟RTC


它由板上电池驱动的“Real Time Clock”也叫做RTC或者叫CMOS时钟,硬件时钟。当操作系统关机的时候,用这个来记录时间,但是对于运行的系统是不用这个时间的。


1.2 系统时钟


“System clock”也叫内核时钟或者软件时钟,是由软件根据时间中断来进行计数的,内核时钟在系统关机的情况下是不存在的,所以,当操作系统启动的时候,内核时钟是要读取RTC时间来进行时间同步.


2. 标准计时器


2.1 时钟滴答计时(jiffies)的几个基本参数


2.1.1 时钟周期(clock cycle)的频率-晶振频率

计时器Timer晶体振荡器在1秒内产生的时钟脉冲个数就是时钟周期的频率, 要注意把这个Timer的时钟周期频率与时钟中断的频率区别开来,  Linux用宏CLOCK_TICK_RATE来表示计时器的输入时钟脉冲的频率(比如我的为#define CLOCK_TICK_RATE  1193180),该宏定义在arm/mach-xxx/include/mach/timex.h头文件中。


2.1.2 时钟中断(clock tick)

我们知道当计数器减到0值时,它就在IRQ0上产生一次时钟中断,也即一次时钟中断, 计数器的初始值决定了要过多少时钟周期才产生一次时钟中断,因此也就决定了一次时钟滴答的时间间隔长度.


2.1.3 时钟中断的频率(HZ)

即1秒时间内Timer所产生的时钟中断次数。确定了时钟中断的频率值后也就可以确定Timer的计数器初值。Linux内核用宏HZ来表示时钟中断的频率,而且在不同的平台上HZ有不同的定义值。对于SPARC、MIPS、ARM和i386等平台HZ的值都是100。该宏在ARM平台上的定义如下(/arch/arm/include/asm/param.h)


2.1.4 计数器的初始值


计数器的初始值由宏LATCH定义在文件:include/linux/jiffies.h


#define LATCH  ((CLOCK_TICK_RATE + HZ/2) / HZ) /* For divider */


2.1.5 jiffies


在 Linux 内核中,时间由一个名为 jiffies 的全局变量衡量,该变量标识系统启动以来经过的滴答数。在最低的级别上,计算滴答数的方式取决于正在运行的特定硬件平台;但是,滴答计数通常在一次中断期间仍然继续进行。


标准计时器 API 作为 Linux 内核的一部分已经有很长一段时间了(自从 Linux 内核的早期版本开始)。尽管它提供的精确性比高精确度计时器要低,但它对于在处理物理设备时提供错误覆盖的传统驱动程序超时来说比较理想。在很多情况下,这些超时实际上从不触发,而是被启动,然后被删除。


简单内核计时器使用计时器轮(timer wheel) 实现。这个主意是由 Finn Arne Gangstad 在 1997 年首次引入的。它不理睬管理大量计时器的问题,而是很好地管理数量合理的计时器 — 典型情况。(原始计时器实现只是按照过期顺序将计时器实现双重链接。尽管在概念上比较简单,但这种方法是不可伸缩的。)时间轮是一个 buckets 集合,其中每个 bucker 表示将来计时器过期的一个时间块。这些 buckets 使用基于 5 个 bucket 的对数时间定义。使用 jiffies 作为时间粒度,定义了几个组,它们表示将来的过期时段(其中每个组通过一列计时器表示)。计时器插入使用具有 O(1) 复杂度的列表操作发生,过期发生在 O(N) 时间内。计时器过期以串联的形式出现,其中计时器被从高粒度 buckets 删除,然后随着它们的过期时间的下降被插入到低粒度 buckets 中。现在我们查看一下针对这个计时器实现的 API。


2.2 计时器 API


Linux 提供了一个简单的 API 来构造和管理计时器。它包含一些函数(和助手函数),用于创建、取消和管理计时器。


计时器通过 timer_list 结构定义,该结构包括实现一个计时器所需的所有数据(其中包括列表指针和在编译时配置的可选计时器统计数据)。从用户角度看,timer_list 包含一个过期时间,一个回调函数(当/如果计时器过期),以及一个用户提供的上下文。用户必须初始化计时器,可以采取几种方法,最简单的方法是调用 setup_timer,该函数初始化计时器并设置用户提供的回调函数和上下文。或者,用户可以设置计时器中的这些值(函数和数据)并简单地调用 init_timer。注意,init_timer 由 setup_timer 内部调用。


void init_timer( struct timer_list *timer );  

void setup_timer( struct timer_list *timer,   

void (*function)(unsigned long), unsigned long data );  


拥有一个经过初始化的计时器之后,用户现在需要设置过期时间,这通过调用 mod_timer 来完成。由于用户通常提供一个未来的过期时间,他们通常在这里添加 jiffies 来从当前时间偏移。用户也可以通过调用 del_timer 来删除一个计时器(如果它还没有过期):


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

void del_timer( struct timer_list *timer );  


最后,用户可以通过调用 timer_pending(如果正在等待,将返回 1)来发现计时器是否正在等待(还没有发出):

 

int timer_pending( const struct timer_list *timer );  


2.3 计时器示例


我们来检查一下这些 API 函数的实际运行情况。下面的代码提供了一个简单的内核模块,用于展示简单计时器 API 的核心特点。在 init_module 中,您使用 setup_timer 初始化了一个计时器,然后调用 mod_timer 来启动它。当计时器过期时,将调用回调函数 my_timer_callback。最后,当您删除模块时,计时器删除(通过 del_timer)发生。(注意来自 del_timer 的返回检查,它确定计时器是否还在使用。)


#include   

#include   

#include   

  

MODULE_LICENSE("GPL");  

  

static struct timer_list my_timer;  

  

void my_timer_callback( unsigned long data )  

{  

  printk( "my_timer_callback called (%ld).n", jiffies );  

}  

  

int init_module( void )  

{  

  int ret;  

  

  printk("Timer module installingn");  

  

  // my_timer.function, my_timer.data  

  setup_timer( &my_timer, my_timer_callback, 0 );  

  

  printk( "Starting timer to fire in 200ms (%ld)n", jiffies );  

  ret = mod_timer( &my_timer, jiffies + msecs_to_jiffies(200) );  

  if (ret) printk("Error in mod_timern");  

  

  return 0;  

}  

  

void cleanup_module( void )  

{  

  int ret;  

  

  ret = del_timer( &my_timer );  

  if (ret) printk("The timer is still in use...n");  

  

  printk("Timer module uninstallingn");  

  

  return;  

}  


3. 高精确度计时器(hrtimer)


高精确度计时器(简称 hrtimers)提供一个高精确度的计时器管理框架,这个框架独立于此前讨论过的标准计时器框架,原因是合并这两个框架太复杂。尽管计时器在 jiffies 粒度上运行,hrtimers 在纳秒粒度上运行。


hrtimer 框架的实现方式与标准计时器 API 不同。hrtimer 不使用 buckets 和串联操作,而是维护一个按时间排序的计时器数据结构(按时间顺序插入计时器,以最小化激活时的处理)。这个数据结构是一个 “红-黑” 树,对于注重性能的应用程序很理想(且恰好作为内核中的一个库普遍可用)。


hrtimer 框架作为内核中的一个 API 可用,用户空间应用程序也可以通过 nanosleep、itimers 和 Portable Operating System Interface (POSIX)-timers interface 使用它。hrtimer 框架被主线化(mainlined)到 2.6.21 内核中。


3.1 高精确度计时器 API


hrtimer API 与传统 API 有些相似,但它们之间的一些根本差别是它能够进行额外的时间控制。应该注意的第一点是:时间不是用 jiffies 表示的,而是以一种名为 ktime 的特殊数据类型表示。这种表示方法隐藏了在这个粒度上有效管理时间的一些细节。hrtimer API 正式确认(formalize)了绝对时间和相对时间之间的区别,要求调用者指定类型。 


union ktime {  

    s64 tv64;  

#if BITS_PER_LONG != 64 && !defined(CONFIG_KTIME_SCALAR)  

    struct {  

# ifdef __BIG_ENDIAN  

    s32 sec, nsec;  

# else  

    s32 nsec, sec;  

# endif  

    } tv;  

#endif  

};  

 


与传统的计时器 API 类似,高精确度计时器通过一个结构表示 — 这里是 hrtimer。这个结构从用户角度定义定时器(回调函数、过期时间等)并包含了管理信息(其中计时器存在于 “红-黑” 树、可选统计数据等中)。  


定义过程首先通过 hrtimer_init 初始化一个计时器。这个调用包含计时器、时钟定义和计时器模式(绝对或相对)。使用的时钟在 ./include/linux/time.h 中定义,表示系统支持的各种时钟(比如实时时钟或者单一时钟,后者只表示从一个起点[比如系统启动]开始的时间)。计时器被初始化之后,就可以通过 hrtimer_start 启动。这个调用包含过期时间(在 ktime_t 中)和时间值的模式(绝对或相对值)。


struct hrtimer {  

    struct rb_node          node;  

    ktime_t             _expires;  

    ktime_t             _softexpires;  

    enum hrtimer_restart        (*function)(struct hrtimer *);  

    struct hrtimer_clock_base   *base;  

    unsigned long           state;  

#ifdef CONFIG_TIMER_STATS  

    int             start_pid;  

    void                *start_site;  

    char                start_comm[16];  

#endif  

};  

 

void hrtimer_init( struct hrtimer *time, clockid_t which_clock,   

            enum hrtimer_mode mode );  

int hrtimer_start(struct hrtimer *timer, ktime_t time, const   

            enum hrtimer_mode mode);  

hrtimer 启动后,可以通过调用 hrtimer_cancel 或 hrtimer_try_to_cancel 来取消。每个函数都包含将被停止的计时器的 hrtimer 引用。这两个函数的区别在于:hrtimer_cancel 函数试图取消计时器,但如果计时器已经发出,那么它将等待回调函数结束;hrtimer_try_to_cancel 函数也试图取消计时器,但如果计时器已经发出,它将返回失败。

 

int hrtimer_cancel(struct hrtimer *timer);  

int hrtimer_try_to_cancel(struct hrtimer *timer);  


可以通过调用 hrtimer_callback_running 来检查 hrtimer 是否已经激活它的回调函数。注意,这个函数由 hrtimer_try_to_cancel 内部调用,以便在计时器的回调函数被调用时返回一个错误。

 

int hrtimer_callback_running(struct hrtimer *timer);  


3.2 一个 hrtimer 示例

 

#include   

#include   

#include   

#include   

  

MODULE_LICENSE("GPL");  

  

#define MS_TO_NS(x) (x * 1E6L)  

  

static struct hrtimer hr_timer;  

  

enum hrtimer_restart my_hrtimer_callback( struct hrtimer *timer )  

{  

  printk( "my_hrtimer_callback called (%ld).n", jiffies );  

  

  return HRTIMER_NORESTART;  

}  

  

int init_module( void )  

{  

  ktime_t ktime;  

  unsigned long delay_in_ms = 200L;  

  

  printk("HR Timer module installingn");  

  

  ktime = ktime_set( 0, MS_TO_NS(delay_in_ms) );  

  

  hrtimer_init( &hr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL );  

    

  hr_timer.function = &my_hrtimer_callback;  

  

  printk( "Starting timer to fire in %ldms (%ld)n", delay_in_ms, jiffies );  

  

  hrtimer_start( &hr_timer, ktime, HRTIMER_MODE_REL );  

  

  return 0;  

}  

  

void cleanup_module( void )  

{  

  int ret;  

  

  ret = hrtimer_cancel( &hr_timer );  

  if (ret) printk("The timer was still in use...n");  

  

  printk("HR Timer module uninstallingn");  

  

  return;  

}  


关于 hrtimer API,还有许多内容这里没有涉及到。一个有趣的方面是它能够定义回调函数的执行上下文(比如在 softirq 或 hardiirq 上下文中)。您可以在 ./include/linux/hrtimer.h 文件中进一步了解 hrtimer API。

推荐阅读

史海拾趣

Equinox公司的发展小趣事

随着科技的发展,Equinox意识到数字化对于提升客户体验的重要性。因此,公司开始投入大量资源建设数字平台,提供在线预约、课程购买、健康管理等服务。这些数字化功能不仅方便了客户,还提高了公司的运营效率。

ESS [ESS Technology,Inc]公司的发展小趣事

ESS科技公司的创始人在1984年创建了这家位于美国加利福尼亚州的公司,起初专注于将音响压缩重建技术应用于电子音响图书和问候卡。然而,随着个人电脑的普及和多媒体市场的快速增长,ESS决定调整战略,全力发展个人电脑音频技术。1993年,ESS推出了其首批PC音频半导体产品ES488,这是一个12位的单声道音质硅片。随后,ESS不断推出新产品,如ES688,一个16位立体音质硅片,这些产品为ESS带来了可观的收益。1995年,ESS成功在美国纳斯达克上市,标志着公司进入了一个全新的发展阶段。

DAYLIGHT公司的发展小趣事

随着全球环保意识的提高,DAYLIGHT公司也开始注重环保和可持续发展。公司投入大量资金用于研发环保型电子产品和技术,并积极参与环保公益活动。此外,DAYLIGHT还制定了严格的环保标准和生产流程,确保其产品的生产和使用过程中对环境的影响最小化。

A/D Electronics Inc公司的发展小趣事

A/D Electronics Inc深知人才是企业发展的核心力量。因此,公司高度重视研发团队的建设和人才培养。通过引进国内外优秀人才和加大研发投入,公司不断提升研发团队的实力和创新能力。同时,公司还建立了完善的培训体系,为员工提供持续的学习和成长机会,激发了员工的创造力和工作热情。

Carling Technologies公司的发展小趣事

在发展过程中,Carling Technologies还通过收购其他企业来增强自身实力。例如,公司收购了Maretron品牌,将其纳入旗下,并充分利用其在船舶监控和控制解决方案方面的优势,为前沿产品和解决方案的持续开发提供支持。这种收购与品牌整合的策略不仅丰富了公司的产品线,也扩大了其在特定市场的影响力。

FIDELIX公司的发展小趣事

2015年,中国半导体企业东芯半导体有限公司看中了FIDELIX在存储芯片领域的优势,决定对其进行收购。经过多轮谈判和协商,东芯半导体成功收购了FIDELIX 25.3%的股权,成为其第一大股东及实际控制人。这次收购不仅加强了东芯半导体在存储芯片领域的技术实力,也为FIDELIX提供了更广阔的市场和发展空间。

问答坊 | AI 解惑

关于AD590和AD0809

请问怎样将AD590输出电流转换为电压,和ACD0809相连接呢?需不需要放大信号呢?…

查看全部问答>

wince5.0的实体机windows下面文件更新问题.

大家好,我需要在wince5里面调用DS,但是这个实体机器在定制的时候好像没有把ds打包进去,请问我如何才可以加上ds的组件? PS:我手上就一台实体机,没有rom擦写设备的. 或者如何给定制时候不带windows media play的ce装上windows media player 并且 ...…

查看全部问答>

招聘嵌入式软件测试人员

【班竹:为什么我的两个招聘的帖子都被删除了???】 招聘2-3名软件测试人员 我部门是隶属于一工厂的研发部,工厂在深圳福永(机场附近)。主要从事GPS的研发工作。 工作地点:深圳市南山区科技园 要求: 1) 计算机软件及相关专业毕业,大专及 ...…

查看全部问答>

烧写时需要API 文件

烧写时需要API 文件,这个文件需要在flash program setting中设置,一般按browse 就可以看到…

查看全部问答>

解读LED结温产生原因是什么?降低LED结温的途径有哪些?

replyreload += \',\' + 702865;Timson,如果您要查看本帖隐藏内容请回复…

查看全部问答>

SMT32 RCC初始化问题

/* Includes ------------------------------------------------------------------*/#include \"stm32f10x.h\" GPIO_InitTypeDef GPIO_InitStructure;#define LED1_ON  GPIO_SetBits(GPIOB,GPIO_Pin_5);#define LED1_OFF GPIO_ResetBits(GPI ...…

查看全部问答>

6折出售LPC2368板子

,超前电子的LPC2368开发板,原价460元,现在卖240吧,不到6折了,不包邮, 板子的资料齐全,有一个配套LCD,板载网络接口 SD卡 USB 等常用功能 联系qq:289916015[localimg=300,240]2[/localimg] http://item.taobao.com/item.htm?id=988308063 ...…

查看全部问答>

电阻阻值变大问题

我的电路电源两端的阻值在短时间里从1.1M增加到6.5M,不知是什么原因?还是就是因为电容的容抗问题使该电路的总电阻变大?…

查看全部问答>

【问TI】器件鉴别真伪及选型

本帖最后由 dontium 于 2015-1-23 13:34 编辑 去年公司里的产品上用到2254 运放,可是有次采购到的TLV2254I应用到HART通讯上,手持器通信就是通讯不上,后来换了以前购买的V2254AI就是可以通讯的上,请问V2254AI跟TLV2254I G4有什么区别? 片 ...…

查看全部问答>

谁有瑞萨R8C/1A、1B单片机的资料啊???

本帖最后由 paulhyde 于 2014-9-15 09:26 编辑 如题  …

查看全部问答>