Linux - 时钟

本文是《Linux内核设计与实现》中“定时器与时间管理”的学习笔记。

节拍率Hz

系统定时器频率是通过静态预处理定义的。我的Ubuntu配置的是250Hz(4ms一个周期)。

root@john-virtual-machine:/boot# grep CONFIG_HZ config-4.15.0-154-generic
# CONFIG_HZ_PERIODIC is not set
# CONFIG_HZ_100 is not set
CONFIG_HZ_250=y
# CONFIG_HZ_300 is not set
# CONFIG_HZ_1000 is not set
CONFIG_HZ=250

Hz越高也就意味着中断产生的越频繁,就可以提高事件驱动事件的解析度。

提高Hz的好处

内核定时器能够以更高的频度和准确度运行(调度系统等)

依赖定时执行的系统调用,可以以更高的精度运行,如select(),poll()

关于select() 和poll()的介绍,参看这里

提高Hz的坏处

时钟中断频率的提高,也就意味了加大了系统负载,用于处理中断。

Jiffies

jiffies记录了系统自启动以来的节拍总数。每次时钟中断处理程序都会 jiffies+=1。在Linux中,用unsigned long类型定义jiffies。对于32位系统(100hz)497天就会溢出,对于64位系统,有生之年是看不到它溢出的。。。

硬时钟和定时器

实时时钟 RTC:输出的是UTC,由外部电池供电,所以在系统关机时,它仍然在工作。精度自然不会高(秒级)。在系统启动时,内核通过读取RTC来初始化墙上时间,该时间存放在xtime中。

TSC:time stamp counter,在x86 CPU中有一个CLK引脚,它接收外部振荡器的时钟,来给CPU提供时钟。TSC的值每个时钟增加1.  Rating值为300

PIT:可编程间隔定时器,已被HPET取代。

HPET:高精度事件定时器,一个HPET芯片包含了8个32位或64位的独立计数器,每个计数器由自己的时钟信号驱动,每个计时器又包含了一个比较器和一个寄存器(保存一个数值,表示触发中断的时机)。每一个比较器都比较计数器中的数值和寄存器的数值,相等就会产生中断。Rating值为250.

查看当前时钟源:

root@john-virtual-machine:~# cat /sys/devices/system/clocksource/clocksource0/current_clocksource
tsc

系统支持的时钟源:

root@john-virtual-machine:~# cat /sys/devices/system/clocksource/clocksource0/available_clocksource
tsc hpet acpi_pm 

rating值越高,作为时钟源就越准确,所以就目前而言,选择TSC就好了。

时钟中断处理程序

分为体系结构相关和体系结构无关,体系结构相关的例程作为系统定时器的中断处理程序注册到内核中。调用体系结构无关的时钟例程tick_periodic():

  1. jiffies变量加一 do_timer()
  2. 更新进程资源消耗统计 account_process_tick() 以tick为颗粒度进行统计,在这个函数根据当时的参数,会分为user/system/idle。
    run_local_timers()标记一个软中断去处理所有到期的定时器。
    void run_local_timers(void)
    {
        hrtimer_run_queues();
        raise_softirq(TIMER_SOFTIRQ); //执行软中断
        softlockup_tick();
    }
  3. 执行已经到期的动态定时器
  4. 执行scheduler_tick() 这个函数在step2中。
  5. 更新墙上时间 update_wall_time()
  6. 计算平均负载 calc_global_load()

实际时间  

struct timespec xtime {
    _kernel_time_t tv_sec; // 存放自1970年1月1日到现在经过的秒数
    long tv_nsec; // 记录自上一秒开始经过的纳秒数
}

读写xtime,要先获得一个seqlock。在程序中获取系统事件的函数gettimeofday()就是去读xtime。

 定时器

定时器能够使某些任务在指定的某个时间点上执行,只需设置超时时间,到期后自动执行指定的函数,并销毁这个定时器。

Linux提供了低精度和高精度的定时器,如采用链表的方式,按timeout排序,但是时间复杂度高O(n)。在这里介绍基于时间轮的计算方式,本质思想是空间换时间。

每一个CPU都有一个struct tvec_base结构,代表这个CPU使用的时间轮。

struct tvec_base
{
   spinlock_t lock;                      // 同步锁
   struct timer_list * running_timer;    // 当前正在运行的定时器
   unsigned long timer_jiffies;          // 当前运行到的jiffies
   struct tvec_root tv1;                          
   struct tvec tv2;
   struct tvec tv3;
   struct tvec tv4;
   struct tvec tv5;
}

struct tvec_root与struct tvec都是数组,数组中的每一项都指定一个链表。struct tvec_root定义的数组大小是256(2的8次方);struct tvec_root定义的数组大小是64(2的6次方)。所以,tv1~6定义的数组总大小是2的(8 + 4*6 = 32)次方,正好对应32位处理器中jiffies的定义(unsigned long)。

因为使用的是wheel算法,tv1~5就代表5个wheel。  tv1是转速最快的wheel,所有在256个jiffies内到期的定时器都会挂在tv1的某个链表头中。  tv2是转速第二快的wheel,里面挂的定时器超时jiffies在2^8 ~ 2^(8+6)之间。  tv3是转速第三快的wheel,超时jiffies在2^(8+6) ~ 2^(8+2*6)之间。  tv4、tv5类似。 

延迟执行 

忙等待

以jiffies为颗粒度,在循环中不断旋转直到希望的节拍数耗尽。

短延迟

void mdelay()
void udelay()
void ndelay()

精度可以达到纳秒级,通过CPU忙循环次数,来计时。

BogoMIPS:记录CPU在给定时间内忙循环的次数。

schedule_timeout()

将需要延迟的任务放入可中断睡眠队列,待timeout时,再将其放入运行队列。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值