概述
每个architecture相关的代码中要有实现clock event会让clock source模块。
一般而言,每个CPU形成自己的一个小系统,有自己的调度,有自己的进程统计,这个小系统都是拥有自己的tick设备,而且是唯一的。
硬件有多少个timer硬件就注册多少个clock event device,各个CPU的tick device会选择自己合适的那个clock event设备。
clock_event_device主要用于实现普通定时器和高精度定时器,有时也用于产生tick事件。
为了完成 time keeping功能,硬件需要提供至少一种clock source和clock event source.
timerde中断在kernel即对应jiffies,每次timer中断,jiffies就增加1.
定时测量是由于基于固定频率振荡器和计数器的几个硬件电路完成的。
linux只用RTC来获取时间和日期。
内核通过0x70和0x71I/O端口来访问RTC。
HZ产生每秒钟时钟中断的近似个数,也就是时钟中断的近似个数。
内核使用里那个个基本的计数函数:一个保持当前最新的时间,另一个计算在当前秒内走过的纳秒数。
jiffies变量是一个计数器,用来记录自系统启动以来产生的节拍总数,每次时钟中断发生时它便增加一。
jiffies被初始化为-300×HZ。是一个32位的有符号值。这样做的目的,使得那些不对jiffies作溢出检测的有缺陷的内核代码在开发阶段被及时发现,从而不再出现稳定的内核版本中。
xtime变量存放当前时间和日期。
大多数设备驱动程序利用定时器检测反常情况。
linux考虑两种类型的定时器,即动态定时器和间隔定时器。第一种类型由内核使用,而间隔定时器由进程在用户态创建。
动态定时器存放在timer_list变量中。
被异步激活的动态计时器有参与竞争条件的倾向。
动态定时器的主要数据结构是一个叫做tvec_bases的每CPU变量。
sys_nanosleep();接受一个指向timespec结构的指针作为参数,并将调用进程挂起知道特定的时间间隔用完。
loop_per_jiffy变量用来揭露一个节拍里面有多少个“loop”。
下面是我看linux代码的的一些收获和感想:
一个操作系统如果需要正常运行,RTC也就是linux里所谓的CMOS clock不是必要的,但是定时器是必需的,也就是心跳,也是进程切换的基本条件。所以在系统一起来之后首先需要初始化timer。如果需要作为系统定时器,需要创建并且初始化结构体 struct clocksource,然后注册进去,系统会从中选择最优的来作为系统的定时器。
注册时钟源的接口
int clocksource_register(struct clocksource *cs);
int clocksource_register_hz(struct clocksource *cs, u32 hz);
其中第一个接口是系统默认的时钟源,后者是板级自己需要注册的时钟源。系统默认的时钟源的如下:
struct clocksource clocksource_jiffies = {
.name = "jiffies",
.rating = 1, /* lowest valid rating*/
.read = jiffies_read,
.mask = 0xffffffff, /*32bits*/
.mult = NSEC_PER_JIFFY << JIFFIES_SHIFT, /* details above */
.shift = JIFFIES_SHIFT,
};
在系统初始化start_kernel()函数中相关的是
tick_init();
init_timers();
hrtimers_init();
softirq_init();
timekeeping_init();
time_init();
其中hrtimer是高精度定时器,time_init()这个函数是在SoC厂商,在板级相关需要自己实现的,timekeeping这个是时间子系统很关键的概念,时间系统调用相关的函数(gettimeofday等)都只是获得其中结构体成员的值。
struct timekeeper {
/* Current clocksource used for timekeeping. */
struct clocksource *clock;
/* The shift value of the current clocksource. */
int shift;
/* Number of clock cycles in one NTP interval. */
cycle_t cycle_interval;
/* Number of clock shifted nano seconds in one NTP interval. */
u64 xtime_interval;
/* shifted nano seconds left over when rounding cycle_interval */
s64 xtime_remainder;
/* Raw nano seconds accumulated per NTP interval. */
u32 raw_interval;
/* Clock shifted nano seconds remainder not stored in xtime.tv_nsec. */
u64 xtime_nsec;
/* Difference between accumulated time and NTP time in ntp
* shifted nano seconds. */
s64 ntp_error;
/* Shift conversion between clock shifted nano seconds and
* ntp shifted nano seconds. */
int ntp_error_shift;
/* NTP adjusted clock multiplier */
u32 mult;
};
需要强调的是clock成员就是之前说的系统的最佳的定时器。
其中的所有的成员值都是系统自己维护的,和板级的硬件没有任何关系,当然linux系统功能比较完善,还考虑到了ntp相关的东西,ntp是网络时间协议,它其实是很简单的,就是从某一服务器中通过ntp协议来获得同一的时间。具体相关可以看一下busybox中的ntpd中代码,当然其中如何去建立网络连接并且从协议中获得统一时间逻辑还是比较复杂的,和系统相关的主要有两个系统调用分别是settimeofday和adjtimx。void __init timekeeping_init(void)
{
struct clocksource *clock;
unsigned long flags;
struct timespec now, boot;
read_persistent_clock(&now);
read_boot_clock(&boot);
write_seqlock_irqsave(&xtime_lock, flags);
ntp_init();
clock = clocksource_default_clock();
if (clock->enable)
clock->enable(clock);
timekeeper_setup_internals(clock);
xtime.tv_sec = now.tv_sec;
xtime.tv_nsec = now.tv_nsec;
raw_time.tv_sec = 0;
raw_time.tv_nsec = 0;
if (boot.tv_sec == 0 && boot.tv_nsec == 0) {
boot.tv_sec = xtime.tv_sec;
boot.tv_nsec = xtime.tv_nsec;
}
set_normalized_timespec(&wall_to_monotonic,
-boot.tv_sec, -boot.tv_nsec);
total_sleep_time.tv_sec = 0;
total_sleep_time.tv_nsec = 0;
write_sequnlock_irqrestore(&xtime_lock, flags);
}
//其中read_persistent_clock是读取RTC的值,当然如果没有的话都初始化为0.
timekeep_setup_internal是建立系统时钟源。主要注册一个新的时钟源之后,会通过clocksource_select来判断是否是最佳的系统时钟源,如果是最佳的时钟源,就调用timekeeping_notify来安装新的最佳的时钟源。
/**
* timekeeping_notify - Install a new clock source
* @clock: pointer to the clock source
*
* This function is called from clocksource.c after a new, better clock
* source has been registered. The caller holds the clocksource_mutex.
*/
void timekeeping_notify(struct clocksource *clock)
{
if (timekeeper.clock == clock)
return;
stop_machine(change_clocksource, clock, NULL);
tick_clock_notify();
}
需要注意的是linux的notify机制,这个机制还不是很了解。notify会在tick_init中被初始化。
在linux中除了clocksource时钟源之外,还有相关定时器的描述符clock_event_device
/**
* struct clock_event_device - clock event device descriptor
* @event_handler: Assigned by the framework to be called by the low
* level handler of the event source
* @set_next_event: set next event function
* @next_event: local storage for the next event in oneshot mode
* @max_delta_ns: maximum delta value in ns
* @min_delta_ns: minimum delta value in ns
* @mult: nanosecond to cycles multiplier
* @shift: nanoseconds to cycles divisor (power of two)
* @mode: operating mode assigned by the management code
* @features: features
* @retries: number of forced programming retries
* @set_mode: set mode function
* @broadcast: function to broadcast events
* @min_delta_ticks: minimum delta value in ticks stored for reconfiguration
* @max_delta_ticks: maximum delta value in ticks stored for reconfiguration
* @name: ptr to clock event name
* @rating: variable to rate clock event devices
* @irq: IRQ number (only for non CPU local devices)
* @cpumask: cpumask to indicate for which CPUs this device works
* @list: list head for the management code
*/
struct clock_event_device {
void (*event_handler)(struct clock_event_device *);
int (*set_next_event)(unsigned long evt,
struct clock_event_device *);
ktime_t next_event;
u64 max_delta_ns;
u64 min_delta_ns;
u32 mult;
u32 shift;
enum clock_event_mode mode;
unsigned int features;
unsigned long retries;
void (*broadcast)(const struct cpumask *mask);
void (*set_mode)(enum clock_event_mode mode,
struct clock_event_device *);
unsigned long min_delta_ticks;
unsigned long max_delta_ticks;
const char *name;
int rating;
int irq;
const struct cpumask *cpumask;
struct list_head list;
} ____cacheline_aligned;
注册完定时器中断后,会调用该结构体中的event_handler该回调函数为
void tick_handle_periodic(struct clock_event_device *dev)
至于该回调函数何时被注册,则是在添加新的系统tick定时器的时候,也就是在之前所说的notify机制中的CLOCK_EVT_NOTIFY_ADD情况的时候。
/*
* Notification about clock event devices
*/
static int tick_notify(struct notifier_block *nb, unsigned long reason,
void *dev)
{
switch (reason) {
case CLOCK_EVT_NOTIFY_ADD:
return tick_check_new_device(dev);
case CLOCK_EVT_NOTIFY_BROADCAST_ON:
case CLOCK_EVT_NOTIFY_BROADCAST_OFF:
case CLOCK_EVT_NOTIFY_BROADCAST_FORCE:
tick_broadcast_on_off(reason, dev);
break;
case CLOCK_EVT_NOTIFY_BROADCAST_ENTER:
case CLOCK_EVT_NOTIFY_BROADCAST_EXIT:
tick_broadcast_oneshot_control(reason);
break;
case CLOCK_EVT_NOTIFY_CPU_DYING:
tick_handover_do_timer(dev);
break;
case CLOCK_EVT_NOTIFY_CPU_DEAD:
tick_shutdown_broadcast_oneshot(dev);
tick_shutdown_broadcast(dev);
tick_shutdown(dev);
break;
case CLOCK_EVT_NOTIFY_SUSPEND:
tick_suspend();
tick_suspend_broadcast();
break;
case CLOCK_EVT_NOTIFY_RESUME:
tick_resume();
break;
default:
break;
}
return NOTIFY_OK;
}
接下来回到event_handle回调函数,该函数中最终会去调用do_timer()函数,该函数就整个系统时间的运转的核心,只要被调用到,所有相关的几个时间概念的全局变量都会被相应的改变;xtime, wall_to_monotonic,xtime等等。
/*
* The 64-bit jiffies value is not atomic - you MUST NOT read it
* without sampling the sequence number in xtime_lock.
* jiffies is defined in the linker script...
*/
void do_timer(unsigned long ticks)
{
jiffies_64 += ticks;
update_wall_time();
calc_global_load(ticks);
}
注意,该函数中主要是update_wall_time函数,里面有自己去修正时间计数的算法,还是比较复杂的,我看了很久也没有看懂。
就这样,每一次系统timer中断来一次,就不断更新时间值,系统不断心跳,就不断进程切换和运行下去。
最后
以上就是拼搏季节为你收集整理的linux下时间子系统的全部内容,希望文章能够帮你解决linux下时间子系统所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复