linux 时间管理——概念、注意点(一)【转】

时间:2021-06-05 13:30:56

转自:http://www.cnblogs.com/openix/p/3324243.html

参考:1、http://bbs.eyeler.com/thread-69-1-1.html                                                                           
         2、《Linxu Kernel Development》3ed_CN p166~p185
         3、《Professional Linux Kernel Architecture》1ed_CN p714~p760
         4、http://blog.csdn.net/droidphone/article/details/8017604
         5、2.6.34

此记录的主要目的记录下如上参考中的一些知识点、基本概念,以及作为后续记录的参考。

  对于unicore的内核2.6.32.9,其时间子系统是基于低分辨率周期时钟实现的,但是在此记录及后续的记录中将会谈下“低分辨率动态时钟、高分辨率动态时钟、高分辨率周期时钟”。

---------------------------------------------------------------------------------------------------------------------------------------

内核中的两种定时器:

1、timeout:表示将在一定时间之后发生的事件,但可以且通常会在发生之前取消。
2、timer: 用于实现时序,此类定时器通常都会到期,而且与超时类定时器相比,需要更高的时间分辨率。

timer wheel的实现要点在于:

linux 时间管理——概念、注意点(一)【转】
timer wheel的实现可以自行参考内核代码,网上讲的也很多。
1、void __run_timers(struct tvec_base *base)
在当前第一组tv1的元素全被遍历后(函数也会执行),将会调用cascade函数,主要的功能就在于取下特定tv数组下的某一个数组元素中的链表,然后重新加入前面的各个数组的各个数组元数的链表中。方法比较巧妙,这样每次执行定时函数时只需从tv1上取出过期的节点即可执行,但是我们也需要注意执行cascade的时间也可能会很长。 2、void internal_add_timer(struct tvec_base *base, struct timer_list *timer)
注意其中是如何索引到各个数组的数组元素下标的:
unsigned long idx = expires - base->timer_jiffies;下标
vec = base->tv[1..5].vec + i; 插入哪个链表
linux 时间管理——概念、注意点(一)【转】

低分辨率定时器的重要性在于:

1)处理全局jiffies计数器。该值周期性地增长(如果使用了低分辨率动态时钟,可能不会周期性增长),它是一种特别简单的时间基准。

2)进行各进程统计。

内核中的各种time,注意下struct timekeeper timekeeper:

linux 时间管理——概念、注意点(一)【转】
1)wall time
    RTC time
在SOC系统中,RTC可以集成到SOC芯片中,并做为一个单独的电压域,系统掉电时,可由后备电池供电,RTC中的时间信息不会丢失。内核和用户空间通过驱动程序访问RTC硬件来获取或设置时间信息。
    xtime
A value representation of the human time of day;日常生活中所见的钟表时间,精度可达纳秒级。
xtime和RTC时间一样,都是人们日常生活所使用的墙上时间,只是RTC时间的精度比较低,大多数情况下只能达到毫秒级的精度,如果是使用外部的RTC芯片,访问速度也比较慢,为此,内核维护了另一个wall time时间:xtime。因为xtime实际上是一个内存变量,它的访问速度非常快,内核大部分时间都是使用xtime来获得当前时间信息。xtime记录的是1970年1月1日到当前时刻所经历的纳秒数。
    xtime在正常情况下是递增的,但是用户可以主动向前或向后调整墙上时间,从而修改xtime。
2) monotonic time
A monotonically increasing value that represents the amount of time that the system has been running.
    开机后单调递增,它不像xtime可能因用户进行时间调整而产生改变,该时间不计算系统休眠的时间,即系统休眠时,monotonic不会递增。
monotonic时间不可以往后退,系统启动后只能不断递增。内核并没有直接定义一个特定的变量来记录monotonic时间,而是定义了一个变量wall_to_monotonic,记录了墙上时间和monotonic时间之间的偏移量,当需要获得monotonic时间时,把xtime和wall_to_monotonic相加即可。计算monotonic时间要去除系统休眠期间花费的时间,内核用total_sleep_time记录休眠的时间,每次休眠醒来后重新累加该时间,并调整wall_to_monotonic的值,使其在系统休眠醒来后,monotonic时间不会发生跳变。
3)raw_time
    monotonic时间虽然不受settimeofday的影响,但会受到ntp调整的影响,但是raw_time不受ntp的影响,它真的就是开完机后就单调地增加。
    xtime、monotonic time和raw_time可以通过用户空间的clock_gettime函数获得,对应的ID参数分别是CLOCK_REALTIME、CLOCK_MONOTONIC、CLOCK_MONOTONIC_RAW。
4)clock source
A representation of a free running counter running at a known frequency, usually in hardware.
    xtime、monotonic time、raw time都是基于该时钟源进行计时操作,当有新的精度更高的时钟源被注册时,通过timekeeping_notify函数,change_clocksource函数将会被调用,timekeeper.clock字段将会被更新,指向新的clocksource
5)tick
A periodic interrupt generated by a hardware-timer, typically with a fixed interval defined by HZ: jiffies
linux 时间管理——概念、注意点(一)【转】

对于SMP,内核区分如下两种时钟类型:

1)全局时钟(global clock),负责提供周期时钟,主要用于jiffies更新。
2)每个CPU一个局部时钟(local clock),用来进行进程统计、性能剖析和实现高分辨率定时器。
  全局时钟的角色,由一个明确选择的局部时钟承担(tick_setup_device函数中,会首次作出选择)

在设备第一次调用tick_seup_device时(即该时钟设备没有相关的时钟事件设备),内核执行如下操作:

linux 时间管理——概念、注意点(一)【转】
1)如果没有选定时钟设备来承担全局时钟设备的角色,那么将选择当前设备来承担此职责,而tick_do_timer_cpu将设置为当前设备所属的处理器编号(注意,如果放弃职责该如何处理,可以参考tick_do_timer_cpu定义处的注释)。tick_period是时钟周期,单位是纳秒,它根据HZ值设置。
2)该设中设备设置为按周期模式工作。 关于上文中提到的2),什么时候会切换成one shot模式?
关注下hrtimer_run_queues,里面调用了tick_check_oneshot_change来判断是否可以激活高分辨率定时器。此外该函数还检查是否可以在低分辨率系统上启用动态时钟。(如果有一个支持单触发模式的时钟,而且其精度可以达到高分辨率定时器所要求的分辨率,即设置了CLOCK_SOURCE_VALID_FOR_HRES标志,那么tick_check_oneshot_change将通知内核可以使用高分辨率定时器)
留意下,在设备被设置成高分辨率周期时钟、高分辨率动态时钟时的中断处理函数的选择。(tick_handle_periodic、hrtimer_interrupt、tick_nohz_handler)

/*
* tick_do_timer_cpu is a timer core internal variable which holds the CPU NR
* which is responsible for calling do_timer(), i.e. the timekeeping stuff. This
* variable has two functions:
*
* 1) Prevent a thundering herd issue of a gazillion of CPUs trying to grab the
* timekeeping lock all at once. Only the CPU which is assigned to do the
* update is handling it.
*
* 2) Hand off the duty in the NOHZ idle case by setting the value to
* TICK_DO_TIMER_NONE, i.e. a non existing CPU. So the next cpu which looks
* at it will take over and keep the time keeping alive. The handover
* procedure also covers cpu hotplug.
*/
int tick_do_timer_cpu __read_mostly = TICK_DO_TIMER_BOOT;

linux 时间管理——概念、注意点(一)【转】

动态时钟&WHY

在关注耗电量的系统上,周期性时钟要求系统在一定的频率下,周期性的处于活动状态。因此,长时间休眠是不可能的。引入动态时钟后,只有在有些任务需要实际执行时,才激活周期时钟。否则,会临时禁用周期时钟。对该技术的支持可以在编译时选择,启动此选项的系统也称为无时钟系统(tickless system)
在系统无事所做的idle阶段,我们可以通过停止周期时钟来达到降低系统功耗的目的,只要有进程处于活动状态,时钟事件依然会被周期性地发出。

在内核中,如定义了CONFIG_NO_HZ宏,则说明内核支持动态时钟;但是我们得明白,CONFIG_NO_HZ并不意味着没有HZ的概念(周期性更新系统统计量),主要区别在于当我们配置CONFIG_NO_HZ时说明:

1)系统支持动态时钟。启用动态时钟时,也定义且使用了HZ,因为它是许多计时任务的基本量。动态和周期时钟在表面上没有什么区别,主要的区别在于 进/退 IDLE时的不同处理方法。
2)系统可能需要停止时钟机制(此时将不会再周期性的产生时钟中断,例如系统进入IDLE)或重启时钟机制(系统退出IDLE)。
3)根据2),由于可以暂时停止时钟机制,因此单触发时钟是实现动态时钟的先决条件。(但是请注意,即使时钟设备处于单触发模式,也并不一定启用了动态时钟!例如,在高分辨率模式下,时钟总是基于单触发定时器实现的。)

高分辨率时钟如何实现周期时钟的仿真:

在内核切换到高分辨率模式时,将调用tick_setup_sched_timer来激活时钟仿真层。这将为每个CPU安装一个高分辨率定时器。所需的struct hrtime实例保存在CPU变量tick_cpu_sched中:该定时器的回调函数选择了tick_sched_timer,通过返回HRTIMER_RESTART,定时器将自动重新进入队列,并在下一个时钟到期时激活。

内核需要处理的情形:

1、没有动态时钟的低分辨率系统,总是使用周期时钟。该内核不包括任何对单触发操作的支持。
2、启用了动态时钟特性的低分辨率系统,以单触发模式使用时钟设备。
3、高分辨率系统总是使用单触发模式,无论是否启用了动态时钟特性。

关于宏:

这一项显得“理论、教条”,因为我很少接触SMP、至于龙芯系类也只接触了一小段时间。
1、启用动态时钟:CONFIG_NO_HZ
2、启用高分辨率定时器支持:CONFIG_HIGH_RES_TIMERS
3、支持动态时钟和高分辨率定时器的必要前提:GENERIC_TIME、GENERIC_CLOCKEVENTS
4、支持时钟事件的单触发模式:CONFIG_TICK_ONESHOT,如果启用了动态时钟或高分辨率定时器,则自动选中该项

时钟源(struct clocksource):时间管理的支柱。本质上每个时钟源都提供了一个单调增加的计数器,通用的内核代码只能进行只读访问。不同时钟源的精度取决于底层硬件的能力。clocksource不能被编程、没有事件产生能力。

linux 时间管理——概念、注意点(一)【转】
关于clocksource中几个关键域:
read:时钟源本身不会产生中断,要获得时钟源的当前计数,只能通过主动调用它的read回调函数来获得当前技术值,注意这里只获
得计数值,也就是所谓的cycle,要获得相应的时间,必须要借助clocksource的mult和shift字段进行转换计算。
void __clocksource_updatefreq_scale(struct clocksource *cs, u32 scale, u32 freq) mult和shift域:因为从clocksource中读到的值是一个cycle计数值,要转换为时间,必须知道驱动clocksource的时钟频率F,但是clocksource并没有保存时钟的频率F,内核使用的方法是:根据时钟的频率和期望的精度,事先计算出两个辅助常数mult和shift,然后使用下公式进行cycle和t的转换:
t = (cycle * mult) >> shift;
但是要保证:F = (1 << shift ) / mult;(一般PLL都是先倍频,再分频,很容易计算)
内核内部使用64位进行该转换计算:
static inline s64 clocksource_cyc2ns(cycle_t cycles, u32 mult, u32 shift)
|---->return ((u64) cycle * mult) >> shift;
问题在于如果mult太大,计算会发生溢出,因此mult值不能太大。内核假设cycle计数值被转换后的最大时间值为:10分钟(600s),原因在于CPU进入IDLE状态后,时间信息不会被更新,只要在10分钟内退出IDLE,clocksource的cycle计数值就可以被正确的转换为相
应的时间,然后系统的时间信息可以被正确的更新。结果不一定是10分钟,该值由clocksource_max_deferment进行计算,并保存在max_idle_ns字段中,tickless的代码需要考虑这个值,以防止在使用动态时钟时,系统保持IDLE状态的时间过长。
linux 时间管理——概念、注意点(一)【转】

时钟事件设备(struct clock_event_device):向时钟增加了事件功能,在未来的某个时刻发生。这种设备也称为时钟事件源(clock event source)。clock_event_device可以被编程,可以工作在周期模式或单次触发模式,系统可以对它进行编程,以确定下次事件触发的时间,clock_event_device主要用于实现普通定时器和高精度定时器,同时也用于产生tick事件,供给进程调度子系统使用。在软件架构上,clock_evetn_device被分为两层,与硬件相关的放在machine层,与硬件无关的通用代码则被集中到了通用时间框架。
时钟设备(struct tick_device):扩展了时钟事件源的功能,各个时钟事件定期触发。但可以使用动态时钟机制,在一定时间间隔内停止周期时钟。                                                                   
下图引述自:http://blog.csdn.net/droidphone/article/details/8017604

linux 时间管理——概念、注意点(一)【转】