LXR | KVM | PM | Time | Interrupt | Systems Performance | Bootup Optimization

Linux时间子系统之(二):软件架构

专题文档汇总目录

Notes:从框架上讲解了时间子系统,从底向上包括CPU Local TImer、Global Counter、Clock Souce/Clock Events模块管理、Tick Device、高精度timer、低精度timer、Broadcast timer、Timekeeping、用户空间Time/Timer系统调用接口、用户空间函数库。然后介绍了时间子系统相关文件及其描述,不同功能需求的内核配置。低精度Timer/高精度Timer和周期Tick/Dynamic Tick两两组合。

原文地址:Linux时间子系统之(二):软件架构

一、前言

本文的主要内容是描述内核时间子系统的软件框架。首先介绍了从旧的时间子系统迁移到新的时间子系统的源由,介绍新的时间子系统的优势。第三章汇整了时间子系统的相关文件以及内核配置。最后描述各种内核配置下的时间子系统的数据流和控制流。

二、背景介绍

1、传统内核时间子系统的软件架构

让我们先回到远古的2.4内核时代,其内核的时间子系统的软件框架如下:

old time

首先,在每个architecture相关的代码中要有实现clock event和clock source模块。这里听起来名字是高大上,实际上,这里仅仅是借用了新时间子系统的名词,对于旧的内核,clock event就是通过timer硬件的中断处理函数完成的,在此基础上可以构建tick模块,tick模块维护了系统的tick,例如系统存在10ms的tick,每次tick到来的时候,timekeeping模块就增加系统时间,如果timekeeping完全是tick驱动,那么它精度只能是10ms,为了更高精度,clock source模块就是一个提供tick之间的offset时间信息的接口函数。

最初的linux kernel中只支持低精度的timer,也就是基于tick的timer。如果内核采用10ms的tick,那么低精度的timer的最高的精度也只有10ms,想要取得us甚至ns级别的精度是不可能的。各个内核的驱动模块都可以使用低精度timer实现自己的定时功能。各个进程的时间统计也是基于tick的,内核的调度器根据这些信息进行调度。System Load和Kernel Profiling模块也是基于tick的,用于计算系统负荷和进行内核性能剖析。

从用户空间空间来看,有两种需求,一种是获取或者设定当前的系统时间的接口函数:例如time,stime,gettimeofday等。另外一种是timer相关的接口函数:例如setitimer、alarm等,当timer到期后会向该进程发送signal。

2、为何会引入新的时间子系统软件架构?

但是随着技术发展,出现了下面两种新的需求:

(1)嵌入式设备需要较好的电源管理策略。传统的linux会有一个周期性的时钟,即便是系统无事可做的时候也要醒来,这样导致系统不断的从低功耗(idle)状态进入高功耗的状态。这样的设计不符合电源管理的需求。

(2)多媒体的应用程序需要非常精确的timer,例如为了避免视频的跳帧、音频回放中的跳动,这些需要系统提供足够精度的timer

和低精度timer不同,高精度timer使用了人类的最直观的时间单位ns(低精度timer使用的tick是和内核配置相关,不够直接)。本质上linux kernel提供了高精度timer之后,其实不必提供低精度timer了,不过由于低精度timer存在了很长的历史,并且在渗入到内核各个部分,如果去掉低精度timer很容易引起linux kernel稳定性和健壮性的问题,因此目前的linux kernel保持了低精度timer和高精度timer并存。

在新的需求的推动下,内核开发者对linux的时间子系统的软件框架进行修改,让代码层次更清晰,同时又是灵活可配置的,一个示意性的block图如下所示:

timehl

在引入multi-core之后,过去HW timer的功能被分成两个部分,一个是free running的system counter,是全局的,不属于任何一个CPU。另外一部分就是产生定时事件的HW block,我们称之timer,timer硬件被嵌入到各个cpu core中,因此,我们更准确的称之为CPU local Timer,这些timer都是基于一个Global counter运作的。在驱动层,我们提供一个clock source chip driver的模块来驱动硬件,这是模块是和硬件体系结构有关的。如果系统内存在多个HW timer和counter block,那么系统中可能会存在多个clock source chip driver。

面对形形色色的timer和counter硬件,linux kernel抽象出了通用clock event layer和通用clock source模块,这两个模块和硬件无关。底层的clock source chip driver会调用通用clock event和clock source模块的接口函数,注册clock source和clock event设备。clock source设备对应硬件的system free running counter,提供了一个基础的timeline。当然了,实际的timeline象一条直线,无限延伸。对于kernel而言,其timeline是构建在system free running counter之上的,因此clocksource 对应的timeline存在溢出问题。如果选用64bit的HW counter,并且输入频率不那么高,那么溢出时间可能会长达50年甚至更多,那么从应用的角度来看,能维持50年左右的timeline也可以接受。如果说clock source是一个time line,那么clock event是在timeline上指定的点产生clock event的设备,之所以能产生异步事件,当然是基于中断子系统了,clock source chip driver会申请中断并调用通用clock event模块的callback函数来通知这样的异步事件。

tick device layer基于clock event设备进行工作的:一般而言,每个CPU形成自己的一个小系统,有自己的调度、有自己的进程统计等,这个小系统都是拥有自己的tick设备,而且是唯一的。对于clock event设备而言就不是这样了,硬件有多少个timer硬件就注册多少个clock event device,各个cpu的tick device会选择自己适合的那个clock event设备。tick device可以工作在periodic mode或者one shot mode,当然,这是和系统配置有关。因此,在tick device layer,有多少个cpu,就会有多少个tick device,我们称之local tick device。当然,有些事情(例如整个系统的负荷计算)不适合在local tick驱动下进行,因此,所有的local tick device中会有一个被选择做global tick device,该device负责维护整个系统的jiffies,更新wall clock,计算全局负荷什么的。

高精度的timer需要高精度的clock event,工作在one shot  mode的tick device工提供高精度的clock event。因此,基于one shot mode下的tick device,系统实现了高精度timer,系统的各个模块可以使用高精度timer的接口来完成定时服务。虽然有了高精度timer的出现, 内核并没有抛弃老的低精度timer机制(内核开发人员试图整合高精度timer和低精度的timer,不过失败了,所以目前内核中,两种timer是同时存在的)。当系统处于高精度timer的时候(tick device处于one shot mode),系统会setup一个特别的高精度timer(可以称之sched timer),该高精度timer会周期性的触发,从而模拟的传统的periodic tick,从而推动了传统低精度timer的运转。因此,一些传统的内核模块仍然可以调用经典的低精度timer模块的接口。

Notes:

 

三、时间子系统的文件整理

1、文件汇整

linux kernel 时间子系统的源文件位于linux/kernel/time/目录下,我们整理如下: 

文件名 描述
time.c 
timeconv.c
time.c文件是一个向用户空间提供时间接口的模块。具体包括:time, stime, gettimeofday, settimeofday,adjtimex。除此之外,该文件还提供一些时间格式转换的接口函数(其他内核模块使用),例如jiffes和微秒之间的转换,日历时间(Gregorian date)和xtime时间的转换。xtime的时间格式就是到linux epoch的秒以及纳秒值。 
timeconv.c中包含了从calendar time到broken-down time之间的转换函数接口。
timer.c

传统的低精度timer模块,基本tick的。

alarm系统调用,以及低精度timer API

timer_list.c 
timer_stats.c
向用户空间提供的调试接口。在用户空间,可以通过/proc/timer_list接口可以获得内核中的时间子系统的相关信息。例如:系统中的当前正在使用的clock source设备、clock event设备和tick device的信息。通过/proc/timer_stats可以获取timer的统计信息。
hrtimer.c 高精度timer模块(高精度timer的通用框架,提供给itimer/POSIX timers/nanosleep/kernel内定时)
itimer.c interval timer模块(setitimer和getitimer两个系统调用)
posix-timers.c 
posix-cpu-timers.c 
posix-clock.c

POSIX timer模块和POSIX clock模块

posix-timers.c系统调用:

timer_create/timer_gettime/timer_getoverrun/timer_settime/timer_delete

(CLOCK_REALTIME/CLOCK_MONOTONIC/CLOCK_MONOTONIC_RAW/CLOCK_REALTIME_COARSE/CLOCK_MONOTONIC_COARSE/CLOCK_BOOTTIME)

clock_settime/clock_gettime/clock_adjtime/clock_getres

clock_nanosleep

posix-cpu-timers.c(CLOCK_PROCESS_CPUTIME_ID/CLOCK_THREAD_CPUTIME_ID)

alarmtimer.c alarmtimer模块(也属于POSIX timer模块,CLOCK_REALTIME_ALARM/CLOCK_BOOTTIME_ALARM)
clocksource.c 
jiffies.c

clocksource.c是通用clocksource driver。其实也可以把system tick也看成一个特定的clocksource,其代码在jiffies.c文件中

clocksource.c clocksource的注册,以及suspend/resum管理。和clocksource sysfs相关节点,/sys/devices/system/clocksource/clocksource0。

jiffies.c注册一个clocksource_jiffies作为clocksource。

timekeeping.c 
timekeeping_debug.c

timekeeping模块

主要维护timekeeper变量

ntp.c NTP模块
clockevent.c

clockevent模块

管理clockevent设备,注册、模式设置等。

tick-common.c 
tick-oneshot.c 
tick-sched.c
这三个文件属于tick device layer。 
tick-common.c文件是periodic tick模块,用于管理周期性tick事件。 
tick-oneshot.c文件是for高精度timer的,用于管理高精度tick时间。 
tick-sched.c是用于dynamic tick的。
tick-broadcast.c 
tick-broadcast-hrtimer.c

broadcast tick模块。

broadcast tick用于进入cpudile之后唤醒cpu。

sched_clock.c 通用sched clock模块。这个模块主要是提供一个sched_clock的接口函数,调用该函数可以获取当前时间点到系统启动之间的纳秒值。 
底层的HW counter其实是千差万别的,有些平台可以提供64-bit的HW counter,因此,在那样的平台中,我们可以不使用这个通用sched clock模块(不配置CONFIG_GENERIC_SCHED_CLOCK这个内核选项),而在自己的clock source chip driver中直接提供sched_clock接口。 
使用通用sched clock模块的好处是:该模块扩展了64-bit的counter,即使底层的HW counter比特数目不足(有些平台HW counter只有32个bit)。

Notes:

cat /proc/timer_list

Timer List Version: v0.8
HRTIMER_MAX_CLOCK_BASES: 4
now at 448509857204 nsecs

cpu: 0
clock 0:
.base: 0000000000000000
.index: 0
.resolution: 1 nsecs
.get_time: ktime_get
.offset: 0 nsecs
active timers:
#0: <0000000000000000>, tick_sched_timer, S:01, tick_nohz_restart, swapper/0/0
# expires at 448512000000-448512000000 nsecs [in 2142796 to 2142796 nsecs]
#1: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, gmain/981
# expires at 450000183497-450004179496 nsecs [in 1490326293 to 1494322292 nsecs]
#2: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, gmain/2423
# expires at 450790051148-450794049147 nsecs [in 2280193944 to 2284191943 nsecs]
#3: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, gmain/4003
# expires at 451953186124-451957182123 nsecs [in 3443328920 to 3447324919 nsecs]
#4: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, gmain/4003
# expires at 451953186124-451957182123 nsecs [in 3443328920 to 3447324919 nsecs]
#5: <0000000000000000>, watchdog_timer_fn, S:01, watchdog_enable, watchdog/0/10
# expires at 452104000000-452104000000 nsecs [in 3594142796 to 3594142796 nsecs]
#6: <0000000000000000>, hrtimer_wakeup, S:01, futex_wait_queue_me, pool/4029
# expires at 454196319000-454196369000 nsecs [in 5686461796 to 5686511796 nsecs]
#7: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, nmbd/2118
# expires at 455875897838-455885897837 nsecs [in 7366040634 to 7376040633 nsecs]
#8: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, rtkit-daemon/1775
# expires at 456184430650-456184430650 nsecs [in 7674573446 to 7674573446 nsecs]
#9: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, kerneloops/1208
# expires at 460000605750-461000605750 nsecs [in 11490748546 to 12490748546 nsecs]
#10: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, nxserver.bin/3396
# expires at 468561982144-468596982143 nsecs [in 20052124940 to 20087124939 nsecs]
#11: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, smbd/3533
# expires at 474101327238-474157457237 nsecs [in 25591470034 to 25647600033 nsecs]
#12: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, update-notifier/3119
# expires at 476790596224-476827430223 nsecs [in 28280739020 to 28317573019 nsecs]
#13: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, unity-panel-ser/2290
# expires at 478790730536-478821396535 nsecs [in 30280873332 to 30311539331 nsecs]
#14: <0000000000000000>, hrtimer_wakeup, S:01, do_nanosleep, cron/1123
# expires at 478943677926-478943727926 nsecs [in 30433820722 to 30433870722 nsecs]
#15: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, smbd/1366
# expires at 506184358390-506244358389 nsecs [in 57674501186 to 57734501185 nsecs]
#16: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, avahi-daemon/942
# expires at 524597537322-524694794321 nsecs [in 76087680118 to 76184937117 nsecs]
#17: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, whoopsie/1165
# expires at 7223000136053-7223100136053 nsecs [in 6774490278849 to 6774590278849 nsecs]
#18: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, nxnode.bin/3431
# expires at 2147902168859061-2147902268859061 nsecs [in 2147453659001857 to 2147453759001857 nsecs]
clock 1:
.base: 0000000000000000
.index: 1
.resolution: 1 nsecs
.get_time: ktime_get_real
.offset: 1499133783009063881 nsecs
active timers:
#0: <0000000000000000>, hrtimer_wakeup, S:01, futex_wait_queue_me, nxnode.bin/3748
# expires at 1499134232680604000-1499134232680654000 nsecs [in 1161682915 to 1161732915 nsecs]
clock 2:
.base: 0000000000000000
.index: 2
.resolution: 1 nsecs
.get_time: ktime_get_boottime
.offset: 0 nsecs
active timers:
clock 3:
.base: 0000000000000000
.index: 3
.resolution: 1 nsecs
.get_time: ktime_get_clocktai
.offset: 1499133783009063881 nsecs
active timers:
.expires_next : 448512000000 nsecs
.hres_active : 1
.nr_events : 21810
.nr_retries : 1686
.nr_hangs : 0
.max_hang_time : 0
.nohz_mode : 2
.last_tick : 448512000000 nsecs
.tick_stopped : 0
.idle_jiffies : 4295004424
.idle_calls : 77339
.idle_sleeps : 31916
.idle_entrytime : 448510137687 nsecs
.idle_waketime : 448301501167 nsecs
.idle_exittime : 448508610656 nsecs
.idle_sleeptime : 398677559195 nsecs
.iowait_sleeptime: 34182427223 nsecs
.last_jiffies : 4295004424
.next_timer : 448812000000
.idle_expires : 448812000000 nsecs
jiffies: 4295004424

cpu: 1
clock 0:
.base: 0000000000000000
.index: 0
.resolution: 1 nsecs
.get_time: ktime_get
.offset: 0 nsecs
active timers:
#0: <0000000000000000>, tick_sched_timer, S:01, tick_nohz_restart, swapper/1/0
# expires at 448512000000-448512000000 nsecs [in 2142796 to 2142796 nsecs]
#1: <0000000000000000>, hrtimer_wakeup, S:01, futex_wait_queue_me, expressvpnd/1235
# expires at 448573016693-448573066693 nsecs [in 63159489 to 63209489 nsecs]
#2: def_rt_bandwidth, sched_rt_period_timer, S:01, enqueue_task_rt, watchdog/0/10
# expires at 449000000000-449000000000 nsecs [in 490142796 to 490142796 nsecs]
#3: <0000000000000000>, hrtimer_wakeup, S:01, do_nanosleep, ninja/1215
# expires at 449150053560-449150103560 nsecs [in 640196356 to 640246356 nsecs]
#4: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, nxclient.bin/3767
# expires at 449859390436-449861390434 nsecs [in 1349533232 to 1351533230 nsecs]
#5: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, wpa_supplicant/2022
# expires at 450616597977-450626597976 nsecs [in 2106740773 to 2116740772 nsecs]
#6: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, gmain/2899
# expires at 450790054141-450794052140 nsecs [in 2280196937 to 2284194936 nsecs]
#7: <0000000000000000>, watchdog_timer_fn, S:01, watchdog_enable, watchdog/1/11
# expires at 452104000000-452104000000 nsecs [in 3594142796 to 3594142796 nsecs]
#8: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, rtkit-daemon/1774
# expires at 451184399045-453684399045 nsecs [in 2674541841 to 5174541841 nsecs]
#9: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, nxnode.bin/3423
# expires at 463570274679-463600274678 nsecs [in 15060417475 to 15090417474 nsecs]
#10: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, nxnode.bin/3727
# expires at 463645951265-463675951264 nsecs [in 15136094061 to 15166094060 nsecs]
#11: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, unity-settings-/2276
# expires at 472790806955-472815089954 nsecs [in 24280949751 to 24305232750 nsecs]
#12: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, udisksd/1950
# expires at 635000278459-635100278459 nsecs [in 186490421255 to 186590421255 nsecs]
#13: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, udisksd/1950
# expires at 635000278459-635100278459 nsecs [in 186490421255 to 186590421255 nsecs]
#14: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, zeitgeist-daemo/2875
# expires at 676953159523-677053159523 nsecs [in 228443302319 to 228543302319 nsecs]
#15: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, nxd/1612
# expires at 2147513004122646-2147513104122646 nsecs [in 2147064494265442 to 2147064594265442 nsecs]
#16: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, nxclient.bin/3761
# expires at 2147912850403159-2147912950403159 nsecs [in 2147464340545955 to 2147464440545955 nsecs]
#17: <0000000000000000>, hrtimer_wakeup, S:01, schedule_hrtimeout_range_clock, nxnode.bin/3735
# expires at 2147917248105867-2147917348105867 nsecs [in 2147468738248663 to 2147468838248663 nsecs]
#18: <0000000000000000>, hrtimer_wakeup, S:01, do_nanosleep, gnome-keyring-d/1941
# expires at 134217762762410213-134217762762460213 nsecs [in 134217314252553009 to 134217314252603009 nsecs]
clock 1:
.base: 0000000000000000
.index: 1
.resolution: 1 nsecs
.get_time: ktime_get_real
.offset: 1499133783009063881 nsecs
active timers:
#0: <0000000000000000>, hrtimer_wakeup, S:01, futex_wait_queue_me, nxnode.bin/3757
# expires at 1499134231562051000-1499134231562101000 nsecs [in 43129915 to 43179915 nsecs]
#1: <0000000000000000>, hrtimer_wakeup, S:01, futex_wait_queue_me, nxnode.bin/3510
# expires at 1499134231665922000-1499134231665972000 nsecs [in 147000915 to 147050915 nsecs]
#2: <0000000000000000>, hrtimer_wakeup, S:01, futex_wait_queue_me, nxnode.bin/3977
# expires at 1499134232091014000-1499134232091064000 nsecs [in 572092915 to 572142915 nsecs]
#3: <0000000000000000>, hrtimer_wakeup, S:01, futex_wait_queue_me, nxclient.bin/3953
# expires at 1499134246656655000-1499134246656705000 nsecs [in 15137733915 to 15137783915 nsecs]
#4: <0000000000000000>, hrtimer_wakeup, S:01, futex_wait_queue_me, nxnode.bin/3453
# expires at 1499134255025138000-1499134255025188000 nsecs [in 23506216915 to 23506266915 nsecs]
#5: <0000000000000000>, hrtimer_wakeup, S:01, futex_wait_queue_me, nxnode.bin/3515
# expires at 1499134295661824000-1499134295661874000 nsecs [in 64142902915 to 64142952915 nsecs]
clock 2:
.base: 0000000000000000
.index: 2
.resolution: 1 nsecs
.get_time: ktime_get_boottime
.offset: 0 nsecs
active timers:
clock 3:
.base: 0000000000000000
.index: 3
.resolution: 1 nsecs
.get_time: ktime_get_clocktai
.offset: 1499133783009063881 nsecs
active timers:
.expires_next : 448512000000 nsecs
.hres_active : 1
.nr_events : 18680
.nr_retries : 548
.nr_hangs : 0
.max_hang_time : 0
.nohz_mode : 2
.last_tick : 448512000000 nsecs
.tick_stopped : 0
.idle_jiffies : 4295004424
.idle_calls : 54744
.idle_sleeps : 20098
.idle_entrytime : 448510492257 nsecs
.idle_waketime : 448396000334 nsecs
.idle_exittime : 448508954285 nsecs
.idle_sleeptime : 413803663500 nsecs
.iowait_sleeptime: 23445842192 nsecs
.last_jiffies : 4295004424
.next_timer : 448800000000
.idle_expires : 448800000000 nsecs
jiffies: 4295004424

......

 

Tick Device: mode: 1
Broadcast device
Clock Event Device: hpet
max_delta_ns: 149983013277
min_delta_ns: 13410
mult: 61496111
shift: 32
mode: 1
next_event: 9223372036854775807 nsecs
set_next_event: hpet_legacy_next_event
shutdown: hpet_legacy_shutdown
periodic: hpet_legacy_set_periodic
oneshot: hpet_legacy_set_oneshot
resume: hpet_legacy_resume
event_handler: tick_handle_oneshot_broadcast
retries: 0

tick_broadcast_mask: 0
tick_broadcast_oneshot_mask: 0

Tick Device: mode: 1
Per CPU device: 0
Clock Event Device: lapic
max_delta_ns: 1422277590440
min_delta_ns: 1000
mult: 12969862
shift: 27
mode: 3
next_event: 448512000000 nsecs
set_next_event: lapic_next_deadline
shutdown: lapic_timer_shutdown
periodic: lapic_timer_set_periodic
oneshot: lapic_timer_set_oneshot
event_handler: hrtimer_interrupt
retries: 5
......

cat /proc/timer_stats

Timer Stats Version: v0.3
Sample period: 0.000 s
Collection: inactive
0 total events

 

2、通用clock source和clock event的内核配置

(1)CONFIG_GENERIC_CLOCKEVENTS和CONFIG_GENERIC_CLOCKEVENTS_BUILD:使用新的时间子系统的构架,如果不配置,那么将使用第二节描述的旧的时间子系统架构。

(2)曾经有一个CONFIG_ GENERIC_TIME的配置项对应clocksource的配置,不过在某个版本中删除了,也就是说目前的内核都是使用通用clocksource模块的,无法再退回到过去使用arch相关的clocksource的时代。为了兼容旧风格的timekeeping接口,kernel仍然提供了CONFIG_ARCH_USES_GETTIMEOFFSET这个配置项。由此可见,在软件框架在演化的过程中,如果这是一个被其他模块使用的基础组件,我们不可能是完全推到重来,必须考虑对旧的软件的兼容性,虽然是一个沉重的负担,但是必须这么做。

3、tick device的配置

Notes:关于TICK的配置:

CONFIG_NO_HZ 配置dynamic tick(tickless)
CONFIG_HZ_PERIODIC 无论何时都启用周期性tick
CONFIG_NO_HZ_IDLE 在idle是停掉周期性tick,同时打开NO_HZ_COMMON
CONFIG_NO_HZ_FULL cpu还在运行task也可能会跳掉tick,同时打开NO_HZ_COMMON

 

如果选择了新的时间子系统的软件架构(配置了CONFIG_GENERIC_CLOCKEVENTS),那么内核会打开Timers subsystem的配置选项,主要是和tick以及高精度timer配置相关。和tick相关的配置有三种,包括:

(1)无论何时,都启用用周期性的tick,即便是在系统idle的时候。这时候要配置CONFIG_HZ_PERIODIC选项。

(2)在系统idle的时候,停掉周期性tick。对应的配置项是CONFIG_NO_HZ_IDLE。配置tickless idle system也会同时enable NO_HZ_COMMON的选项。

(3)Full dynticks system。即便在非idle的状态下,也就是说cpu上还运行在task,也可能会停掉tick。这个选项和实时应用相关。对应的配置项是CONFIG_NO_HZ_FULL。配置Full dynticks system也会同时enable NO_HZ_COMMON的选项。本文不描述该系统,有兴趣的同学可以自行阅读。

上面的三个选项只能是配置其一。上面描述的是新的内核配置方法,对于旧的内核,CONFIG_NO_HZ用来配置dynamic tick或者叫做tickless idle system(非idle时有周期性tick,idle状态,timer中断不再周期性触发,只会按照需要触发),为了兼容旧的系统,新的内核仍然支持了这个选项。

4、timer模块的配置

和高精度timer相关的配置比较简单,只有一个CONFIG_HIGH_RES_TIMERS的配置项。如果配置了高精度timer,或者配置了NO_HZ_COMMON的选项,那么一定需要配置CONFIG_TICK_ONESHOT,表示系统支持支持one-shot类型的tick device。

Notes:CONFIG_HIGH_RES_TIMERS和CONFIG_NO_HZ_COMMON依赖于CONFIG_TICK_ONESHOT。

5、 如何进行时间子系统的内核配置

根据上一节的描述,linux内核可以有下面的两种时间子系统的构架:

(1)新的通用时间子系统软件框架(配置了CONFIG_GENERIC_CLOCKEVENTS)

(2)传统时间子系统软件框架(不配置CONFIG_GENERIC_CLOCKEVENTS,配置CONFIG_ARCH_USES_GETTIMEOFFSET)

对于我们工程人员,除非你是维护一个旧的系统,否则当然使用新的通用时间子系统软件框架了,这时候可能的配置包括:

(1)使用低精度timer和周期tick。传统的linuxer应该会喜欢这个配置,保持和传统的unix的一致性。

(2)使用低精度timer和Dynamic tick

(3)使用高精度timer和周期tick

(4)使用高精度timer和Dynamic tick。新潮的linux应该会喜欢这个配置,一个字,cool……

注:本文主要描述普通的dynamic tick系统(tickless idle system),后续会有专门的文章描述full dynamic tick系统。

四、时间子系统的数据流和控制流

1、使用低精度timer + 周期tick

我们首先看周期性tick的实现。起始点一定是底层的clock source chip driver,该driver会调用注册clock event的接口函数(clockevents_config_and_register或者clockevents_register_device),一旦增加了一个clock event device,需要通知上层的tick device layer,毕竟有可能新注册的这个device更好、更适合某个tick device呢(通过调用tick_check_new_device函数实现)。要是这个clock event device被某个tick device收留了(要么该tick device之前没有匹配的clock event device,要么新的clock event device更适合该tick device),那么就启动对该tick device的配置(参考tick_setup_device)。根据当前系统的配置情况(周期性tick),会调用tick_setup_periodic函数,这时候,该tick device对应的clock event device的clock event handler被设置为tick_handle_periodic。底层硬件会周期性的产生中断,从而会周期性的调用tick_handle_periodic从而驱动整个系统的运转。需要注意的是:即便是配置了CONFIG_NO_HZ和CONFIG_TICK_ONESHOT,系统中没有提供one shot的clock event device,这种情况下,整个系统仍然是运行在周期tick的模式下。

下面来到低精度timer模块了,其实即便没有使能高精度timer,内核也会把高精度timer模块的代码编译进kernel的image中,这一点可以从Makefile文件中看出:

obj-y += time.o timer.o hrtimer.o itimer.o posix-timers.o posix-cpu-timers.o

高精度timer总是会被编入最后的kernel中。在这种构架下,各个内核模块也可以调用linux kernel中的高精度timer模块的接口函数来实现高精度timer,但是,这时候高精度timer模块是运行在低精度的模式,也就是说这些hrtimer虽然是按照高精度timer的红黑树进行组织,但是系统只是在每一周期性tick到来的时候调用hrtimer_run_queues函数,来检查是否有expire的hrtimer。毫无疑问,这里的高精度timer也就是没有意义了。

Notes:这里的所谓高精度timer的粒度也等同于周期性tick。

由于存在周期性tick,低精度timer的运作毫无压力,和过去一样。

2、低精度timer + Dynamic Tick

系统开始的时候并不是直接进入Dynamic tick mode的,而是经历一个切换过程。开始的时候,系统运行在周期tick的模式下,各个cpu对应的tick device的(clock event device的)event handler是tick_handle_periodic。在timer的软中断上下文中,会调用tick_check_oneshot_change进行是否切换到one shot模式的检查,如果系统中有支持one-shot的clock event device,并且没有配置高精度timer的话,那么就会发生tick mode的切换(调用tick_nohz_switch_to_nohz),这时候,tick device会切换到one shot模式,而event handler被设置为tick_nohz_handler。由于这时候的clock event device工作在one shot模式,因此当系统正常运行的时候,在event handler中每次都要reprogram clock event,以便正常产生tick。当cpu运行idle进程的时候,clock event device不再reprogram产生下次的tick信号,这样,整个系统的周期性的tick就停下来。

高精度timer和低精度timer的工作原理同上。

3、高精度timer + Dynamic Tick

同样的,系统开始的时候并不是直接进入Dynamic tick mode的,而是经历一个切换过程。系统开始的时候是运行在周期tick的模式下,event handler是tick_handle_periodic。在周期tick的软中断上下文中(参考run_timer_softirq),如果满足条件,会调用hrtimer_switch_to_hres将hrtimer从低精度模式切换到高精度模式上。这时候,系统会有下面的动作:

(1)Tick device的clock event设备切换到oneshot mode(参考tick_init_highres函数)

(2)Tick device的clock event设备的event handler会更新为hrtimer_interrupt(参考tick_init_highres函数)

(3)设定sched timer(也就是模拟周期tick那个高精度timer,参考tick_setup_sched_timer函数)

这样,当下一次tick到来的时候,系统会调用hrtimer_interrupt来处理这个tick(该tick是通过sched timer产生的)。

在Dynamic tick的模式下,各个cpu的tick device工作在one shot模式,该tick device对应的clock event设备也工作在one shot的模式,这时候,硬件Timer的中断不会周期性的产生,但是linux kernel中很多的模块是依赖于周期性的tick的,因此,在这种情况下,系统使用hrtime模拟了一个周期性的tick。在切换到dynamic tick模式的时候会初始化这个高精度timer,该高精度timer的回调函数是tick_sched_timer。这个函数执行的函数类似周期性tick中event handler执行的内容。不过在最后会reprogram该高精度timer,以便可以周期性的产生clock event。当系统进入idle的时候,就会stop这个高精度timer,这样,当没有用户事件的时候,CPU可以持续在idle状态,从而减少功耗。

4、高精度timer + 周期性Tick

这种配置不多见,多半是由于硬件无法支持one shot的clock event device,这种情况下,整个系统仍然是运行在周期tick的模式下。

Notes:四种搭配的对比:

低精度timer + 周期tick 由于只能产生周期性中断,高精度timer和低精度timer一样运行在低精度模式。
低精度timer + Dynamic Tick 每次低精度timer到期都会reprogram产生下次tick信号。
高精度timer + Dynamic Tick 使用高精度timer模拟了周期性tick,都通过one shot触发。
高精度timer + 周期性Tick 硬件局限,无法实现高精度。

 

原创文章,转发请注明出处。蜗窝科技

http://www.wowotech.net/timer_subsystem/time-subsyste-architecture.html

div64_u64((u64)(0x7fffffff-current_cycle1)*USEC_PER_SEC, (u64)PERSISTENT_TIMER_CLOCK_RATE)

posted on 2017-06-25 22:01  ArnoldLu  阅读(5348)  评论(0编辑  收藏  举报

导航