概述
本文是《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():
- jiffies变量加一 do_timer()
- 更新进程资源消耗统计 account_process_tick() 以tick为颗粒度进行统计,在这个函数根据当时的参数,会分为user/system/idle。
run_local_timers()标记一个软中断去处理所有到期的定时器。void run_local_timers(void) { hrtimer_run_queues(); raise_softirq(TIMER_SOFTIRQ); //执行软中断 softlockup_tick(); }
- 执行已经到期的动态定时器
- 执行scheduler_tick() 这个函数在step2中。
- 更新墙上时间 update_wall_time()
- 计算平均负载 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时,再将其放入运行队列。
最后
以上就是超级战斗机为你收集整理的Linux - 时钟的全部内容,希望文章能够帮你解决Linux - 时钟所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复