定时器与时间管理笔记

内核中的时间

  • 时钟中断:内核中的系统定时器以某种频率触发中断,该频率可以通过编程预定。
  • 节拍率HZ:时钟中断的频率称为节拍率。
  • 节拍:相邻两次中断的时间间隔称为节拍,1/节拍率。

节拍率HZ

系统定时器的节拍率是通过静态预处理定义的,也就是HZ值,在系统启动时按照HZ值对硬件进行设置。比如,x86系统的HZ默认值为100,即每10ms触发一次时钟中断。

jiffies

该全局变量用来记录自系统启动以来产生的节拍数总数。在启动时,内核将该值初始化为0,每次时钟中断该值都会增加1。

time = jiffies /HZ;		//自启动以来经历的时间

jiffies在系统中的定义如下,它是无符号长整数,因此在32位系统是32位,在64位系统上是64位。

extern unsigned long jiffies;
extern u64 jiffies_64;

系统还定义了一个变量jiffies_64,该变量在32位系统也是64位长,这样可以保证该变量不会溢出。在32位系统上,通过赋值将jiffies_64的低32位给jiffies,因为我们使用jiffies主要是来计算经过的相对时间,因此低32位足够使用了。在64位机上两个变量值是相同的。

当jiffies值超过他的最大存放范围之后,就会发生溢出。对于32位系统而言,当jiffies超过 \(2^{32}-1\) 后,会再次回到0,这个过程称之为回绕。

我们在代码时需要注意jiffies是否会发生回绕,比如下面这个例子,当我们判断是否超时时,就需要考虑到回绕的情况。在下面的例子中,加入发生回绕,即使超时了,\(timeout > jiffies\) 也是成立的。

unsigned long timeout = jiffies + HZ/2;//0.5秒之后的时刻

if(timeout > jiffies)
printf("未超时");
else printf("已超时");

为了解决这种情况,内核提供了四个宏来比较节拍数,这是通过将 unsigned long 类型强制转换为 long 类型实现的。通过强制类型装换,即使发生回绕,也是从负数回绕到了正数,其大小关系不变。

#define time_after(unkown,kwon)		((long)(kown)-(long)(unkown))<0
#define time_before(unkown,kown) ((long)(unkown) -(long)(kown))<0
#deifne time_after_eq(unkown,kwon) ((long)(unkown) -(long)(kown))>=0
#define time_before_eq(unkown,kown) ((long)(kown)-(long)(unkown))>=0

硬时钟和定时器

系统中存在两种设备进行计时,分别是系统定时器和实时时钟。

实时时钟

实时时钟(RTC)是用来持久存放系统时间的设备,即使系统关闭后,它依然可以靠着主板上的微型电池供电保持系统计时。

当系统启动时,内核通过读取RTC来初始化墙上时间,改时间存放在xtime变量中。内核通常不会在系统启动后再读取xtime变量,有些体系结构会周期性的将当前时间存回RTC中。实时时钟的主要作用就是在启动时初始化xtime。

系统定时器

系统定时器提供一种周期性的触发中断机制,

时钟中断处理程序

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

与体系相关的例程作为系统定时器的中断被注册到内核中,以便在产生时钟中断时能够相应运行。该部分一般包括以下内容:

  • 获得xtime_lock锁,一遍对jiffies_64和墙上时间xtime进行保护
  • 需要时应答或重新设置系统时钟
  • 周期性使用墙上时间更新实时时钟
  • 调用体系结构无关例程tick_periodic()

中断服务主要通过体系结构无关部分执行更多工作,tick_periodic():

  • 给jiffies_64增加1
  • 更新资源的统计值
  • 执行一定到期的动态定时器
  • 更新墙上时间
  • 计算平均负载

定时器

定时器也称为动态定时器或内核定时器,是管理内核时间流逝的基础。

定时器结构如下:

struct timer_list{
struct list_head entry; //定时器链表入口
unsigned ling expires; // 以jiffies为单位的定时值
void (*function) (unsigned ling);//定时器处理函数
unsigned long data; // 传给处理函数的长整型
struct tvec_t_base_s *base; //定时器内部值,用户不需要
}

创建定时器时,首先要定义

struct timer_list  my_timer;

接着初始化定时器结构

init_timer(&my_timer);

然后给定时器结构中的成员赋值

my_timer.expires = jiffies + delay;
my_timer.data = 0;
my_timer.function = my_function;

定时器处理函数的定义如下

void my_function(unsigned long data);

最后,还需要激活定时器

add_timer(&my_timer);

当节拍数大于等于指定的额超时时,内核就开始执行定时器处理函数。虽然内核可以保证在超时时间到达之前不会执行处理函数,但是会有延误。一般来说,定时器在超时后会马上执行,但也有可能推迟到下次节拍再运行。

有时候需要更改已经激活的定时器时间,可以通过内核函数mod_timer来实现

mod_timer(&my_timer, jiffies + new_delay);

mod_timer()函数也可以操作那些已经初始化,但还没有激活的定时器。若还没有激活,mod_timer()就会激活该函数。一旦从mod_timer()函数返回,定时器将被激活,并设置新的定时时间。

若需要在定时器超时之前停止计时器可通过del_timer()来实现

del_timer(&my_timer);

被激活的或未被激活的定时器都可以使用该函数,若未被激活则返回0;否则返回1.

当删除定时器时,必须注意一个潜在的竞争条件,当del_timer()返回后,可以保证的只是,定时器将来不会再被激活,但是在多处理器机器上定时器中断可能已经在其他处理器上运行了。所以删除定时器时需要等待可能在其他处理器上运行的定时器处理程序都结束,这时就要用del_timer_sync()

del_timer_sync(&my_timer);

延迟执行

内核代码(尤其是驱动程序代码)除了使用定时器或下半部意外还需要其他方法来退出任务。这种推迟常发生在等待硬件完成某项工作时,而且等待时间很短。

忙等待

最简单的延迟方法是忙等待,实现方法如下:

unsigned long timeout = jiffies + 10;

while(time_before(jiffies,before);	// 忙等待10个节拍的时间

这种方法十分低效,尽量别用,丢人。。。

更好的方法是在等待的时候,运行内核重新调度执行其他任务:

unsigned long timeout = jiffies + 2*HZ;

while(time_before(jiffies,before)
// 忙等待2秒的时间
cond_resched();

cond_resched()函数将调度一个新的程序投入运行,但它只有在设置完need_resched标志后才能生效。也就是说,该方法有效的条件是系统中存在更重要的任务需要运行。注意,该方法需要调用调度程序,所以他不能再中断上下文中使用。事实上,所有的延迟方法在进程上下文中使用很好,而中断处理程序需要更快的执行(忙循环与这种目标相反)。延迟执行不管在那种情况下都不应该在持有锁时或者禁止中断时发生。

短延迟

内核提供了三个可以处理短延迟的函数

void udelay(unsigned long usecs);	// 微秒
void ndelay(unsigned long nsecs); // ns
void mdelay(unsigned long msecs); // ms

schedule_timeout()

更理想的执行延迟的方法是使用schedule_timeout()函数,该方法让延迟执行的任务睡眠到指定的延迟时间耗尽后再重新运行。但该方法也不能保证睡眠时间正好等于指定的延迟时间,只能尽量使睡眠时间接近指定的延迟时间。当指定的时间到期后,内核唤醒被延迟的任务并将其重新投入运行队列。

set_current_state(TASK_INTERRUPTIBLE);

schedule_timeout(seconds*HZ)

Linux内核设计笔记11——定时器的更多相关文章

  1. Linux内核设计笔记10——内核同步

    Linux内核同步笔记 几个基本概念 - 临界区(critical region):访问和操作共享数据的代码段: - 原子操作:操作在执行中不被打断,要么不执行,要么执行完: - 竞争条件: 两个线程 ...

  2. Linux内核设计笔记12——内存管理

    内存管理学习笔记 页 页是内核管理内存的基本单位,内存管理单元(MMU,管理内存并把虚拟地址转化为物理地址的硬件)通常以页为单位进行处理,从虚拟内存的角度看,页就是最小单位. struct page{ ...

  3. Linux内核设计笔记7——中断

    中断与中断处理 何为中断? 一种由设备发向处理器的电信号 中断不与处理器时钟同步,随时可以发生,内核随时可能因为中断到来而被打断. 每一个中断都有唯一一个数字标志,称之为中断线(IRQ) 异常是由软件 ...

  4. Linux内核设计笔记14——块I/O层

    块I/O层 基本概念 系统中可以随机访问固定大小数据片的硬件设备称做块设备,这些固定大小的数据片称之为块.还有一种基本的设备称之为字符设备,其需要按照顺序访问,比如键盘. 扇区:块设备中最小的寻址单元 ...

  5. Linux内核设计笔记8——下半部

    # 下半部笔记 1. 软中断 软中断实现 软中断是在编译期间静态分配,其结构如下所示,结构中包含一个接受该结构体指针作为参数的action函数. struct softirq_action{ void ...

  6. Linux内核设计笔记13——虚拟文件系统

    虚拟文件系统 内核在它的底层文件系统系统接口上建立一个抽象层,该抽象层使Linux可以支持各种文件系统,即便他们在功能和行为上存在很大差异. VFS抽象层定义了各个文件系统都支持的基本的.概念上的接口 ...

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

    转自:http://www.cnblogs.com/wang_yb/archive/2013/05/10/3070373.html 系统中有很多与时间相关的程序(比如定期执行的任务,某一时间执行的任务 ...

  8. 《Linux内核设计与实现》读书笔记 - 目录 (完结)

    读完这本书回过头才发现, 第一篇笔记居然是 2012年8月发的, 将近一年半的时间才看完这本书(汗!!!). 为了方便以后查看, 做个<Linux内核设计与实现>读书笔记 的目录: < ...

  9. 《Linux内核设计与实现》读书笔记(十九)- 可移植性

    linux内核的移植性非常好, 目前的内核也支持非常多的体系结构(有20多个). 但是刚开始时, linux也只支持 intel i386 架构, 从 v1.2版开始支持 Digital Alpha, ...

随机推荐

  1. plsql误删除数据,提交事务后如何找回?

    select * from tbs_rep_template as of timestamp to_timestamp('2018-07-12 14:23:00', 'yyyy-mm-dd hh24: ...

  2. Vue--- 一点车项目 连接数据库 数据使用

    Vue--- 一点车项目  连接数据库 数据使用 后台服务器 返回数据 处理 created 这个钩子在实例被创建之后被调用: async created(){ // 分类 catelist { le ...

  3. 用http.get()简单实现网络验证防止客户不给尾款_电脑计算机编程入门教程自学

    首发于:用http.get()简单实现网络验证防止客户不给尾款_电脑计算机编程入门教程自学 http://jianma123.com/viewthread.aardio?threadid=428 给软 ...

  4. #leetcode刷题之路14-最长公共前缀

    编写一个函数来查找字符串数组中的最长公共前缀. 如果不存在公共前缀,返回空字符串 "". 示例 1: 输入: ["flower","flow" ...

  5. zabbix基本监控各指标简解

    监控项目及使用模板 监控http和https: Template App HTTP Service     Template App HTTPS Service 监控cpu,内存,网络等: Templ ...

  6. Yii2中使用Soap WebSerivce

    Soap是一种轻量的.简单的.基于XML(标准通用标记语言下的一个子集)的协议 WebService顾名思义就是web服务,web服务主要有两种,一种是基于soap类型的服务,一种是基于rest类型的 ...

  7. 【转载】 旧版本Microsoft Office正在配置解决方法

    原文:https://blog.csdn.net/sinat_37215184/article/details/81053931 在运行Microsoft Office 2010等旧版本的Office ...

  8. 六个比较好用的php数组Array函数

    1. array_column 返回输入数组中某个单一列的值.2. array_filter 用回调函数过滤数组中的元素.3. array_map 将用户自定义函数作用到给定数组的每个值上,返回新的值 ...

  9. composer 基本概念与常用命令总结

    目录 composer 基本概念与常用命令总结 基本概念 软件安装 linux/mac安装 windows 配置镜像 如何使用 常用命令 全局参数 初始化 init 初始化参数 依赖安装 instal ...

  10. day30 进程

    推荐两本书:现代操作系统和操作系统原来,学习好python以后再去研究.   并发:任务的切换,保存状态,存在io的是实现空间和时间的 重复利用 操作系统的发展历史: 第一代(1940-1955)手工 ...