向下之旅(十四):定时器和时间管理

  相对于事件驱动而言,内核中有大量的函数都是基于时间驱动的。有些函数是周期执行的,有些操作是需要等待一个相对的时间后才运行。除了上述两类函数需要内核提供时间外,内核还必须管理系统的运行时间以及当前日期和时间。

  其中相对时间和绝对时间是不同的,若某个事件在5秒后被调度执行,那么系统所需要的是——相对时间(相对现在起5秒)。如果涉及到日期和时间,内核不但要计算流逝的时间还要计算绝对时间。

  周期性产生的事件和推迟执行的时间之间的差别:前者比如每10毫秒一次——都是由系统定时器驱动的。系统定时器是一种可编程硬件芯片,它能固定频率的产生中断。该中断就是所谓的定时器中断。系统定时器和时钟中断处理程序是Linux系统内核管理机制中的中枢。

  另一个重点是动态定时器——一种用来推迟执行程序的工具。比如,如果软驱动马达在一定时间内都未活动,那么软盘驱动程序会使用动态定时器关闭软驱马达。内核可以动态创建驱动或销毁动态定时器。

  内核中的时间概念

  硬件为内核提供了一个系统定时器用以计算流逝的时间,系统定时器以某种频率自行触发(经常被称为击中或射中)时钟中断,该频率可以通过编程预定,称作节拍率。连续两次时钟中断的间隔时间叫做节拍,它等于节拍率分之一秒。内核就是靠这种已知的时钟中断间隔来计算墙上时间和系统运行时间。墙上时间——实际时间——对用户空间的应用程序来说是最重要的。此外内核也为用户空间提供了一组系统调用来获取实际日期和实际时间。系统运行时间——自系统启动开始所经过的时间——对用户空间和内核都很有用,因为许多程序都必须清楚流逝过的时间。通过两次(现在和以后)读取运行时间在计算它们的差,就可得到相对的流逝过的时间。

  利用时间中断来周期的执行工作有:

  1.更新系统运行时间

  2.更新实际时间

  3.在smp系统上,均衡调度程序各处理器上的运行时间。如果运行队列负载不均衡的话,尽量使它们均衡。

  4.检查当前进程是否用尽了自己的时间片,如果用尽,就重新进行调度。

  5.运行超时的动态定时器

  6.更新资源消耗和处理器时间的统计值

  节拍率:HZ

  系统定时器频率(节拍率)是通过静态预处理定义的,也就是HZ,在系统启动时按照HZ值对硬件进行设置。体系结构的不同,HZ的值也不同。若系统定时器频率为1000HZ,也就是说每秒钟1000次(每毫秒一次),周期为 1/HZ 秒。各种体系结构的节拍率如下:

  

注意,大多数体系结构的节拍率是可调的,并不是一成不变的。

  理想的HZ值

  提高节拍率意味着时钟中断产生得更加频繁,所以中断处理程序也会更频繁的执行。其好处有:

  1.更高的时钟中断解析度可以提高时间驱动时间的解析度。

  2.提高了时间驱动时间的准确度。

  以上两点的提升带来的好处是:

  1.内核定时器能够以更高的频度和更高的准确度运行

  2.依赖定时值执行的系统调用,比如poll()和select(),能够以更高的精度运行。

  3.对诸如资源消耗和系统运行时间等的测量会有更精细的解析度

  4.提高进程抢占的准确度

  当然也有坏的一面,提高节拍率,节拍率越高,意味着时钟中断频率越高,也就意味着系统负担越重。因为处理器必须花时间来执行时钟中断处理程序,所以节拍率越高,中断处理程序占用的处理器的时间越多。

  jiffies

  全局变量jiffies用来记录自系统启动以来产生的节拍的总数。启动时,内核将该变量初始化为0,此后,每次时钟中断处理程序都会增加该变量的值。因为一秒内时钟中断的次数等于HZ,所以jiffies一秒内增加的值也就为HZ。系统运行时间以秒为单位计算,就等于jiffies/HZ。

  将以秒的单位的时间转化为jiffies:

  (seconds * HZ)

  将jiffies转换为以秒为单位的时间:

  (jiffies / HZ)

  通常将秒转化为jiffies用的多一些,比如在代码经常需要设置一些将来的时间:

  unsigned long time_stamp = jiffies;  // 现在

  unsigned long next_tick  = jiffies+_1  //从现在开始1个节拍

  unsigned long later     = jiffies+5 * HZ //从现在开始5秒

  Jiffies 的内部表示

  jiffies变量重视无符号长整数,所以,在32位体系结构中是32位,在64位的体系结构中是64位。

  ld(1)脚本用来连接主内核映像,然后用jiffies_64变量覆盖初值jiffies变量:

   jiffies = jiffies_64;

  在64位体系结构上,jiffies_64和jiffies指的是同一个变量,低32位的值和在32位体系结构上的jiffies值一样。通过get_jiffies_64()函数,可以获得jiffies的全部64位的值。

  Jiffies 的回绕

  当jiffies变量的值超过它的最大存放范围后就会发生溢出。对于32位无符号长整形,最大取值2的32次方减一,即定时器节拍计数最大为4294967295。如果超过最大值后还要增加,则它的值会回绕到0。  

  内核中提供了四个宏来帮助比较节拍计数,它们能正确的处理了节拍计数回绕情况,

  

其中unkown参数通常是jiffies,known的参数是需要比较的值。宏time_after(unknown,known),当时间unknown超过指定的known时,返回真,否则返回假。宏time_before(unknown,known)

  用户空间和HZ

  在2.6以前的内核,如果改变内核的HZ的值,会使用户空间中某种程序造成异常结果。因此内核定义了USER_HZ来表示用户空间看到的HZ值,内核可以使用宏jiffies_to_clockt()将一个由HZ表示的节拍计数器转换成一个由USER_HZ表示的节拍计数。通过函数jiffies_64_to_clock_t()将64位的jifies值的单位从HZ转换为USER_HZ。

  硬时钟和定时器

  体系结构提供了两种设备进行计时——一种是我们刚说的系统定时器,另一种是实时时钟(RTC)。

+9+ 实时时钟(RTC)是用来持久存放系统时间的设备,即便关闭系系统后,它也可以靠主板上的微型电池保持系统的计时。当系统启动时,内核通过读取RTC来初始化墙上时间,该时间存放在xtime变量中。实时时钟最主要的作用是在启动时初始化xtime变量。

  时钟中断处理程序

  时钟中断处理程序可以分为两个部分:体系结构相关部分和体系结构无关部分。

  与体系结构相关的例程作为系统定时器的中断处理程序而注册到内核中,以便在产生时钟中断时,它能够相应的运行。一般要执行以下工作:

  1.获得xtime_lock锁,以便对访问jiffies_64和墙上时间xtime进行保护。

  2.需要时应答或重新设置系统时钟。

  3.周期性的使用墙上时间更新实时时钟。

  4.调用体系结构无关的时钟例程:do_timer().

  中断服务程序主要通过调用与体系结构无关的例程do_timer()执行下面的工作:

  1.给jiffies_64变量增加1(这个操作即使是在32位体系结构上也是安全的,因为前面已经获得了xtime_lock锁)。

  2.更新资源消耗的统计值,比如当前进程所消耗的系统时间和用户时间。

  3.执行已经到期的动态定时器

  4.执行scheduler_tick()函数

  5.更新墙上时间,该时间存放在xtime变量中。

  6.计算平均负载值。

  实际时间

  xtime.tv_sec以秒为单位,存放着自1970年7月1日(UTC)以来经过的时间,1970年1月1日被称为纪元,多数Unix系统的墙上时间都是基于该纪元而言的。xtime.tv_nsec记录自上一秒开始经过的纳秒数。

  读写xtime变量需要使用xtime_lock锁,该锁不是普通的自旋锁而是一个seqlock锁(操作前后读取标示,来判断状态)。读取xtime时,也要使用read_seqbegin()和read_seqretry()函数。

  从用户空间取得墙上时间的主要接口是gettimeofday(),C库函数也提供了一些墙上时间相关的库调用。比如ftime()和ctime()。

  定时器

  定时器——也成为动态定时器或内核定时器——是管理内核时间的基础。有时我们需要一种工具,能够使工作在指定的时间点上执行——不长不短,正好在希望的时间点上。内核定时器正是解决这个问题的理想工具。

  定时器使用很简单,执行一些初始化操作,设置一个超时时间,指定超时后发生的函数,然后激活定时器就可以了。指定的函数在定时器到期时自动执行。

  使用定时器

  定时器的结构为time_list,结构如下:

  

  使用定时器时,不需要深入的了解该数据结构,内核提供了一组与定时器相关的接口用来简化管理定时器操作。

  创建定时器时需要先定义它:

  struct time_list my_timer;

  接着调用init_timer(&my_timer)来初始化定时器数据结构的内部值。

  然后填充结构中需要的值:

  my_timer.expires = jiffies + delay;   // 定时器超时时的节拍数

  my_timer.data = 0;          //给定时器处理函数传入0值

  my_timer.function = my_function;   //定时器超时时调用的函数

  处理函数必须符合下面函数的原型:

  void my_timer_function(unsigned long data);

  定时器处理函数有可能被延误执行,所以不能用定时器来实现任何硬实时任务。

  通过函数 mod_timer(&my_timer,jiffies+new_delay) 来改变已经激活的定时器超时时间。

  通过 del_timer (&my_timer) 在定时器超时前停止定时器。

  当删除定时器时,必须小心一个潜在的竞争条件,当del_timer()返回后,可以保证的只是:定时器不会再被激活(也就是,将来不会执行),但是在多处理器机器上定时器中断可能已经在其他处理器上运行了,所以删除定时器时需要等待可能在其他处理器上运行的定时器处理程序都退出,这是就要使用del_timer_sync()函数执行删除工作:

  del_timer_sync(&my_timer);

  与del_timer()函数不同,该函数不能在中断上下文中使用。

  内核在时钟中断发生后执行定时器,定时器作为软中断在下半部上下文中执行。通过函数来处理当前处理器上运行的所有超时定时器。

  延迟执行

  内核代码(尤其是驱动程序)除了使用定时器或下半部机制以外还需要其他方法来推迟执行任务。内核提供了许多延迟方法处理各种延迟要求。

  1. 忙等待

  最简单的延迟方法(也通常是最不理想的方法)是忙等待。但要注意该方法仅仅在想要延迟的时间是节拍的整数倍,或者精确率要求不高的时候才可以使用。

  2.短延迟

  这种方法在内核需要很短的延迟和要求延迟的时间很精确的时候使用。多发生在与硬件同步时,等待时间往往小于1毫秒。

  3.schedule_timeout()

  此为更理想的延迟执行方法,该方法会让需要延迟执行的任务睡眠到执行的延迟时间耗尽后在重复运行。但该方法也不能保证睡眠时间正好等于指定的延迟时间——只能尽量的使睡眠时间接近指定的延迟时间。当指定的时间到期后,内核唤醒被延迟的任务并将其重新放回运行队列。

  参考自:《Linux Kernel Development》.

时间: 2024-10-16 19:48:48

向下之旅(十四):定时器和时间管理的相关文章

Linux内核——定时器和时间管理

定时器和时间管理 系统定时器是一种可编程硬件芯片.它能以固定频率产生中断.该中断就是所谓的定时器中断.它所相应的中断处理程序负责更新系统时间,还负责执行须要周期性执行的任务. 系统定时器和时钟中断处理程序是Linux系统内核管理机制中的中枢. 另外一个关注的焦点是动态定时器--一种用来推迟运行程序的工具. 比方说.假设软驱马达在一定时间内都未活动,那么软盘驱动程序会使用动态定时器关闭软驱马达. 内核能够动态创建或销毁动态定时器. 内核中的时间观念 内核在硬件的帮助下计算和管理时间. 硬件为内核提

Linux之定时器与时间管理 【转】

转自:http://blog.chinaunix.net/uid-23228758-id-154820.html 定时器与时间管理: 1.节拍率——HZ:在alpha体系结构上1024,而在其它平台上,都为10数量级倍.在嵌入式ARM上为100(2.6内核).这个值的意义是什么呢,也就是在ARM平台上时钟中断100次,为一秒.一般的情况下编程者不要改变这个值,因为内核编很多代码都是有时间要求的,而且内核编写都在很多地方都做了相应的优化与折衷处理,改变HZ的值会对系统的性能有很大的影响. 2.ji

(笔记)Linux内核学习(八)之定时器和时间管理

一 内核中的时间观念 内核在硬件的帮助下计算和管理时间.硬件为内核提供一个系统定时器用以计算流逝的时间.系 统定时器以某种频率自行触发,产生时钟中断,进入内核时钟中断处理程序中进行处理. 墙上时间和系统运行时间根据时钟间隔来计算. 利用时间中断周期执行的工作: 更新系统运行时间: 更新实际时间: 在smp系统上,均衡调度程序中各处理器上运行队列: 检查当前进程是否用尽了时间片,重新进行调度: 运行超时的动态定时器: 更新资源消耗和处理器时间的统计值: 二 节拍率 系统定时器的频率:通过静态预处理

《Linux内核设计与实现》读书笔记(十一)- 定时器和时间管理

系统中有很多与时间相关的程序(比如定期执行的任务,某一时间执行的任务,推迟一段时间执行的任务),因此,时间的管理对于linux来说非常重要. 主要内容: 系统时间 定时器 定时器相关概念 定时器执行流程 实现程序延迟的方法 定时器和延迟的例子 1. 系统时间 系统中管理的时间有2种:实际时间和定时器. 1.1  实际时间 实际时间就是现实中钟表上显示的时间,其实内核中并不常用这个时间,主要是用户空间的程序有时需要获取当前时间, 所以内核中也管理着这个时间. 实际时间的获取是在开机后,内核初始化时

Android之旅十四 android中的xml文件解析

在我们做有关android项目的时候,肯定会涉及到对xml文件的解析操作.以下给大家介绍一下xml文件的解析.包括DOM.SAX.Pull以及曾经我们用到的DOM4J和JDOM: 要解析的XML文件:person.xml <? xml version="1.0" encoding="UTF-8"? > <persons> <person id="001"> <name>zhangsan</na

Cocos2D:塔防游戏制作之旅(十四)

塔之战:炮塔的攻击 炮塔就位了?检查.敌人前进中?再次检查 - 它们看起来就是如此!看起来到了击溃这些家伙的时候了!这里我们将智能置入炮塔的代码中去. 每一个炮塔检查是否有敌人在其攻击范围.(炮塔一次只能攻击一个敌人.猫猪注)如果有,炮塔将开始向敌人开火直到两件事之一发生:敌人移出攻击范围或者敌人被摧毁.炮塔接着开始寻找其他欠扁的家伙 :] 将它们放到一起,新建新炮塔!你已经有一个防御基础了! 因为敌人和炮塔类相互依赖彼此,你不得不首先更新它们类的头文件,去避免你在修改实现代码时Xcode发生显

十四、oracle 数据库管理--管理表空间和数据文件

一.概念表空间是数据库的逻辑组成部分.从物理上讲,数据库数据存放在数据文件中:从逻辑上讲,数据库数据则是存放在表空间中,表空间由一个或多个数据文件组成. 二.数据库的逻辑结构oracle中逻辑结构包括表空间.段.区和块.说明一下数据库由表空间构成,而表空间又是由段构成,而段又是由区构成,而区又是由oracle块构成的这样的一种结构,可以提高数据库的效率. 三.表空间1.概念表空间用于从逻辑上组织数据库的数据.数据库逻辑上是由一个或是多个表空间组成的.通过表空间可以达到以下作用:1).控制数据库占

cortex_m3_stm32嵌入式学习笔记(二十四):内存管理实验(动态内存)

有用过C语言编程的童鞋对动态管理内存肯定有点了解..好处就不多说了 今天实现STM32的动态内存管理 内存管理,是指软件运行时对计算机内存资源的分配和使用的技术.其最主要的目的是如何高效,快速的分配,并且在适当的时候释放和回收内存资源. 内存管理的实现方法有很多种,他们其实最终都是要实现两个函数:malloc 和 free(好熟悉): malloc 函数用于内存申请, free 函数用于内存释放. 实现方式:分块式内存管理 从上图可以看出,分块式内存管理由内存池和内存管理表两部分组成.内存池被等

【原创】(十四)Linux内存管理之page fault处理

背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本:4.14 ARM64处理器,Contex-A53,双核 使用工具:Source Insight 3.5, Visio 1. 概述 上篇文章分析到malloc/mmap函数中,内核实现只是在进程的地址空间建立好了vma区域,并没有实际的虚拟地址到物理地址的映射操作.这部分就是在Page Fault异常