linux时间子系统
来源:互联网 发布:淘宝大布包 编辑:程序博客网 时间:2024/05/29 08:40
1.1 struct clocksourse
struct clocksource {
/*
* Hotpath data, fits in a single cache line when the
* clocksource itself is cacheline aligned.
*/
cycle_t (*read)(struct clocksource *cs);
cycle_t cycle_last;
cycle_t mask;
u32 mult;
u32 shift;
u64 max_idle_ns;
u32 maxadj;
#ifdef CONFIG_ARCH_CLOCKSOURCE_DATA
struct arch_clocksource_data archdata;
#endif
const char *name;
struct list_head list;
int rating;
int (*enable)(struct clocksource *cs);
void (*disable)(struct clocksource *cs);
unsigned long flags;
void (*suspend)(struct clocksource *cs);
void (*resume)(struct clocksource *cs);
/* private: */
#ifdef CONFIG_CLOCKSOURCE_WATCHDOG
/* Watchdog related data, used by the framework */
struct list_head wd_list;
cycle_t cs_last;
cycle_t wd_last;
#endif
} ____cacheline_aligned;
- 1--99: 不适合于用作实际的时钟源,只用于启动过程或用于测试;
- 100--199:基本可用,可用作真实的时钟源,但不推荐;
- 200--299:精度较好,可用作真实的时钟源;
- 300--399:很好,精确的时钟源;
- 400--499:理想的时钟源,如有可能就必须选择它作为时钟源;
2) read回调函数
3) mult和shift字段
t = cycle/F;
t = (cycle * mult) >> shift;
F = (1 << shift) / mult;
static inline s64 clocksource_cyc2ns(cycle_t cycles, u32 mult, u32 shift)
{
return ((u64) cycles * mult) >> shift;
}
1.2 clocksource的注册和初始化
// include/linux/clocksource.h
static inline int clocksource_register_hz(struct clocksource *cs, u32 hz)
{
return __clocksource_register_scale(cs, 1, hz);
}
// kernel/time/clocksource.c
/**
* __clocksource_register_scale - Used to install new clocksources
* @cs: clocksource to be registered
* @scale: Scale factor multiplied against freq to get clocksource hz
* @freq: clocksource frequency (cycles per second) divided by scale
*
* Returns -EBUSY if registration fails, zero otherwise.
*
* This *SHOULD NOT* be called directly! Please use the
* clocksource_register_hz() or clocksource_register_khz helper functions.
*/
int __clocksource_register_scale(struct clocksource *cs, u32 scale, u32 freq)
{
/* Initialize mult/shift and max_idle_ns(该clocksource可接受的最大IDLE时间) */
__clocksource_updatefreq_scale(cs, scale, freq);
/* Add clocksource to the clcoksource list */
mutex_lock(&clocksource_mutex);
/* 按clocksource的rating的大小,把该clocksource按顺序挂在全局链表clocksource_list上,
* rating值越大,在链表上的位置越靠前
*/
clocksource_enqueue(cs);
clocksource_enqueue_watchdog(cs);
clocksource_select();//触发clocksource选择,看是否要更换timekeeping系统使用的clocksource
mutex_unlock(&clocksource_mutex);
return 0;
}
EXPORT_SYMBOL_GPL(__clocksource_register_scale);
1.3 clocksource的建立过程
// kernel/time/jiffies.c
struct clocksource clocksource_jiffies = {
.name = "jiffies",
.rating = 1, /* lowest valid rating,精度1/HZ秒*/
.read = jiffies_read,
.mask = 0xffffffff, /*32bits*/
.mult = NSEC_PER_JIFFY << JIFFIES_SHIFT, /* details above */
.shift = JIFFIES_SHIFT,
};
......
static int __init init_jiffies_clocksource(void)
{
return clocksource_register(&clocksource_jiffies);
}
core_initcall(init_jiffies_clocksource);
/* 如果平台的代码没有提供定制的clocksource_default_clock函数,它将返回该clocksource: */struct clocksource * __init __weak clocksource_default_clock(void){return &clocksource_jiffies;}
1.4 clocksource watchdog
#define WATCHDOG_INTERVAL (HZ >> 1)
#define WATCHDOG_THRESHOLD (NSEC_PER_SEC >> 4)
2 内核中表示时间的单位和结构
2.1 jiffies
extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;
time_after(a,b)
time_before(a,b)
time_after_eq(a,b)
time_before_eq(a,b)
time_in_range(a,b,c)
unsigned int jiffies_to_msecs(const unsigned long j);
unsigned int jiffies_to_usecs(const unsigned long j);
unsigned long msecs_to_jiffies(const unsigned int m);
unsigned long usecs_to_jiffies(const unsigned int u);
2.2 struct timeval
struct timeval {
__kernel_time_ttv_sec;/* seconds */
__kernel_suseconds_ttv_usec;/* microseconds */
};
static inline int timeval_compare(const struct timeval *lhs, const struct timeval *rhs);
static inline s64 timeval_to_ns(const struct timeval *tv);
extern struct timeval ns_to_timeval(const s64 nsec);
unsigned long timeval_to_jiffies(const struct timeval *value);
void jiffies_to_timeval(const unsigned long jiffies, struct timeval *value);
2.3 struct timespec
struct timespec {
__kernel_time_ttv_sec;/* seconds */
longtv_nsec;/* nanoseconds */
};
static inline int timespec_equal(const struct timespec *a, const struct timespec *b);
static inline int timespec_compare(const struct timespec *lhs, const struct timespec *rhs);
extern unsigned long mktime(const unsigned int year, const unsigned int mon,
const unsigned int day, const unsigned int hour,
const unsigned int min, const unsigned int sec);
extern void set_normalized_timespec(struct timespec *ts, time_t sec, s64 nsec);
static inline struct timespec timespec_add(struct timespec lhs,struct timespec rhs);
static inline struct timespec timespec_sub(struct timespec lhs,struct timespec rhs);
static inline s64 timespec_to_ns(const struct timespec *ts);
extern struct timespec ns_to_timespec(const s64 nsec);
static __always_inline void timespec_add_ns(struct timespec *a, u64 ns);
unsigned long timespec_to_jiffies(const struct timespec *value);
void jiffies_to_timespec(const unsigned long jiffies, struct timespec *value);
2.4. struct ktime
/* 这个定义兼容了32bit 64bit以及big-little endian系统
* 64位的系统可以直接访问tv64字段,单位是纳秒,32位的系统则被拆分为两个字段:sec和nsec,
*/
union ktime {
s64tv64;
#if BITS_PER_LONG != 64 && !defined(CONFIG_KTIME_SCALAR)
struct {
# ifdef __BIG_ENDIAN
s32sec, nsec;
# else
s32nsec, sec;
# endif
} tv;
#endif
};
ktime_t ktime_set(const long secs, const unsigned long nsecs);
ktime_t ktime_sub(const ktime_t lhs, const ktime_t rhs);
ktime_t ktime_add(const ktime_t add1, const ktime_t add2);
ktime_t ktime_add_ns(const ktime_t kt, u64 nsec);
ktime_t ktime_sub_ns(const ktime_t kt, u64 nsec);
ktime_t timespec_to_ktime(const struct timespec ts);
ktime_t timeval_to_ktime(const struct timeval tv);
struct timespec ktime_to_timespec(const ktime_t kt);
struct timeval ktime_to_timeval(const ktime_t kt);
s64 ktime_to_ns(const ktime_t kt);
int ktime_equal(const ktime_t cmp1, const ktime_t cmp2);
s64 ktime_to_us(const ktime_t kt);
s64 ktime_to_ms(const ktime_t kt);
ktime_t ns_to_ktime(u64 ns);
3 timekeeper
3.1 用户态或内核的其它部分对时间的要求各不相同,因此内核维护着多种时间:
1)xtime xtime和RTC时间一样,都是人们日常所使用的墙上时间,只是RTC时间的精度通常比较低,大多数情况下只能达到毫秒级别的精度,如果是使用外部的RTC芯片,访问速度也比较慢,为此,内核维护了另外一个wall time时间:xtime,取决于用于对xtime计时的clocksource,它的精度甚至可以达到纳秒级别,因为xtime实际上是一个内存中的变量,它的访问速度非常快,内核大部分时间都是使用xtime来获得当前时间信息。xtime记录的是自1970年1月1日0时到当前时刻所经历的纳秒数。
xtime虽然正常情况下是递增的,但是毕竟用户可以通过stime()和settimeofday()系统调用主动向前或向后调整墙上时间,从而修改xtime值。
2)monotonic time 可以把它理解为自系统启动以来所经过的时间,该时间只能单调递增,并且不计入休眠时间,也就是说,系统休眠时monotoic时间不会递增,因此休眠前后时间是连续的,平缓的。
3)boot time 与monotonic时间相同,会累加上系统休眠的时间,它代表着系统上电后的总时间。
xtime,monotonic time和 boot time都要受NTP时间调整的影响,用户态NTP程序可能要求内核每tick更新时间时走快点或走慢点,以此来渐进NTP认为是准确的网络上的时间。
4)raw monotonic time 该时间与monotonic时间类似,也是单调递增的时间,唯一的不同是:raw monotonic time“更纯净”,表示真正的硬件时间,它不受NTP时间调整的影响,它代表着系统独立时钟硬件对时间的统计。
3.2 struct timekeeper
// kernel/time/timekeeping.c
static struct timekeeper timekeeper;
// include/linux/timekeeper_internal.h
struct timekeeper {
struct clocksource *clock; /* Current clocksource used for timekeeping. */
u32mult; /* NTP adjusted clock multiplier */
intshift;/* The shift value of the current clocksource. */
cycle_t cycle_interval;/* Number of clock cycles in one NTP interval. */
u64xtime_interval;/* Number of clock shifted nano seconds in one NTP interval. */
s64xtime_remainder;/* shifted nano seconds left over when rounding cycle_interval */
u32raw_interval;/* Raw nano seconds accumulated per NTP interval. */
u64xtime_nsec;/* Clock shifted nano seconds remainder not stored in xtime.tv_nsec. */
/* Difference between accumulated time and NTP time in ntp
* shifted nano seconds. */
s64ntp_error;
/* Shift conversion between clock shifted nano seconds and
* ntp shifted nano seconds. */
intntp_error_shift;
struct timespec xtime;/* The current time */
struct timespec wall_to_monotonic;
struct timespec total_sleep_time;/* time spent in suspend */
struct timespec raw_time;/* The raw monotonic time for the CLOCK_MONOTONIC_RAW posix clock. */
ktime_t offs_real;/* Offset clock monotonic -> clock realtime */
ktime_t offs_boot;/* Offset clock monotonic -> clock boottime */
seqlock_t lock;/* Seqlock for all timekeeper values */
};
void get_monotonic_boottime(struct timespec *ts)
{
......
do {
seq = read_seqbegin(&timekeeper.lock);
*ts = timekeeper.xtime;
tomono = timekeeper.wall_to_monotonic;
sleep = timekeeper.total_sleep_time;
nsecs = timekeeping_get_ns();
} while (read_seqretry(&timekeeper.lock, seq));
set_normalized_timespec(ts, ts->tv_sec + tomono.tv_sec + sleep.tv_sec,
ts->tv_nsec + tomono.tv_nsec + sleep.tv_nsec + nsecs);
}
3.3 timekeeper的初始化
/*
* timekeeping_init - Initializes the clocksource and common timekeeping values
*/
void __init timekeeping_init(void)
{
struct timekeeper *tk = &timekeeper;
struct clocksource *clock;
unsigned long flags;
struct timespec now, boot, tmp;
read_persistent_clock(&now); //从RTC中获取当前时间
if (!timespec_valid_strict(&now)) {
pr_warn("WARNING: Persistent clock returned invalid value!\n"
" Check your CMOS/BIOS settings.\n");
now.tv_sec = 0;
now.tv_nsec = 0;
} else if (now.tv_sec || now.tv_nsec)
persistent_clock_exist = true;
read_boot_clock(&boot);
if (!timespec_valid_strict(&boot)) {
pr_warn("WARNING: Boot clock returned invalid value!\n"
" Check your CMOS/BIOS settings.\n");
boot.tv_sec = 0;
boot.tv_nsec = 0;
}
raw_spin_lock_irqsave(&timekeeper_lock, flags);
write_seqcount_begin(&timekeeper_seq);
ntp_init();
clock = clocksource_default_clock();//获取默认的clocksource,如果平台没有特殊指定,那就是jiffies
if (clock->enable)
clock->enable(clock);
tk_setup_internals(tk, clock);//把timekeeper和clocksource进行关联
//利用从RTC中读到的当前时间初始化timekeeper中的各种时间
tk_set_xtime(tk, &now);
tk->raw_time.tv_sec = 0;
tk->raw_time.tv_nsec = 0;
if (boot.tv_sec == 0 && boot.tv_nsec == 0)
boot = tk_xtime(tk);
set_normalized_timespec(&tmp, -boot.tv_sec, -boot.tv_nsec);
tk_set_wall_to_mono(tk, tmp);
tmp.tv_sec = 0;
tmp.tv_nsec = 0;
tk_set_sleep_time(tk, tmp);
memcpy(&shadow_timekeeper, &timekeeper, sizeof(timekeeper));
write_seqcount_end(&timekeeper_seq);
raw_spin_unlock_irqrestore(&timekeeper_lock, flags);
}
3.4 时间的更新
void do_timer(unsigned long ticks)
{
jiffies_64 += ticks;
update_wall_time();//更新墙上时间
calc_global_load(ticks);
}
3.5获取时间的接口
- void getboottime(struct timespec *ts); 获取系统启动时刻的实时时间
- void get_monotonic_boottime(struct timespec *ts); 获取系统启动以来所经过的时间,包含休眠时间
- ktime_t ktime_get_boottime(void); 获取系统启动以来所经过的c时间,包含休眠时间,返回ktime类型
- ktime_t ktime_get(void); 获取系统启动以来所经过的c时间,不包含休眠时间,返回ktime类型
- void ktime_get_ts(struct timespec *ts) ; 获取系统启动以来所经过的c时间,不包含休眠时间,返回timespec结构
- unsigned long get_seconds(void); 返回xtime中的秒计数值
- struct timespec current_kernel_time(void); 返回内核最后一次更新的xtime时间,不累计最后一次更新至今clocksource的计数值
- void getnstimeofday(struct timespec *ts); 获取当前时间,返回timespec结构
- void do_gettimeofday(struct timeval *tv); 获取当前时间,返回timeval结构
小结:
4 clock_event_device
4.1 架构
- 与clocksource一样,系统中可以存在多个clock_event_device,系统会根据它们的精度和能力,选择合适的clock_event_device对系统提供时钟事件服务。在smp系统中,为了减少处理器间的通信开销,基本上每个cpu都会具备一个属于自己的本地clock_event_device,独立地为该cpu提供时钟事件服务,smp中的每个cpu基于本地的clock_event_device,建立自己的tick_device,普通定时器和高精度定时器。
- 在软件架构上看,clock_event_device被分为了两层,与硬件相关的被放在了machine层,而与硬件无关的通用代码则被集中到了通用时间框架层,这符合内核对软件的设计需求,平台的开发者只需实现平台相关的接口即可,无需关注复杂的上层时间框架。
- tick_device是基于clock_event_device的进一步封装,用于代替原有的时钟滴答中断,给内核提供tick事件,以完成进程的调度和进程信息统计,负载平衡和时间更新等操作。
4.2 struct clock_event_device
struct clock_event_device {
/* 这个回调函数指针通常由通用框架层设置,在时间中断到来时,machine底层的的中断服务程序会调用该回调,框架层利用该回调实现对时钟事件的处理。 */
void(*event_handler)(struct clock_event_device *);
/* 设置下一次时间触发的时间,使用类似于clocksource的cycle计数值(离现在的cycle差值)作为参数。 */
int(*set_next_event)(unsigned long evt,
struct clock_event_device *);
/* 设置下一次时间触发的时间,直接使用ktime时间作为参数。 */
int(*set_next_ktime)(ktime_t expires,
struct clock_event_device *);
ktime_tnext_event;
u64max_delta_ns;//可设置的最大时间差,单位是纳秒。
u64min_delta_ns;//可设置的最小时间差,单位是纳秒。
/* mult shift 与clocksource中的类似,只不过是用于把纳秒转换为cycle。*/
u32mult;
u32shift;
/* 该时钟事件设备的工作模式,两种主要的工作模式分别是:
* CLOCK_EVT_MODE_PERIODIC 周期触发模式,设置后按给定的周期不停地触发事件;
* CLOCK_EVT_MODE_ONESHOT 单次触发模式,只在设置好的触发时刻触发一次;
*/
enum clock_event_modemode;
unsigned intfeatures;
unsigned longretries;
void(*broadcast)(const struct cpumask *mask);
/* 函数指针,用于设置时钟事件设备的工作模式。 */
void(*set_mode)(enum clock_event_mode mode,
struct clock_event_device *);
unsigned longmin_delta_ticks;
unsigned longmax_delta_ticks;
const char*name;
intrating;//该设备的精度等级。
intirq;
const struct cpumask*cpumask;
struct list_headlist; //系统中注册的所有clock_event_device用该字段挂在全局链表变量clockevent_devices上。
} ____cacheline_aligned;
static LIST_HEAD(clockevent_devices);
/* Notification for clock events */
static RAW_NOTIFIER_HEAD(clockevents_chain);
5 tick
5.1 tick_device
struct tick_device {
struct clock_event_device *evtdev;
enum tick_device_mode mode;
};
/*
* Check, if the new registered device should be used.
*/
static int tick_check_new_device(struct clock_event_device *newdev)
{
struct clock_event_device *curdev;
struct tick_device *td;
int cpu, ret = NOTIFY_OK;
unsigned long flags;
raw_spin_lock_irqsave(&tick_device_lock, flags);
cpu = smp_processor_id();
if (!cpumask_test_cpu(cpu, newdev->cpumask))//判断注册的clock_event_device是否可用于本cpu
goto out_bc;
td = &per_cpu(tick_cpu_device, cpu);//从per-cpu变量中取出本cpu原来的的tick_device
curdev = td->evtdev;
/* cpu local device ? */
if (!cpumask_equal(newdev->cpumask, cpumask_of(cpu))) {
/*
* If the cpu affinity of the device interrupt can not
* be set, ignore it.
*/
if (!irq_can_set_affinity(newdev->irq))
goto out_bc;
/*
* If we have a cpu local device already, do not replace it
* by a non cpu local device
*/
if (curdev && cpumask_equal(curdev->cpumask, cpumask_of(cpu)))
goto out_bc;
}
/*
* If we have an active device, then check the rating and the oneshot
* feature.以决定是否替换原来旧的clock_event_device
*/
if (curdev) {
/*
* Prefer one shot capable devices !
*/
if ((curdev->features & CLOCK_EVT_FEAT_ONESHOT) &&
!(newdev->features & CLOCK_EVT_FEAT_ONESHOT))
goto out_bc;// 新的不支持单触发,但旧的支持,所以不能替换
/*
* Check the rating
*/
if (curdev->rating >= newdev->rating)
goto out_bc;// 旧的比新的精度高,不能替换
}
/*
* Replace the eventually existing device by the new
* device. If the current device is the broadcast device, do
* not give it back to the clockevents layer !
* 通过所有检查,表明新的更好,就需要进行替换
*/
if (tick_is_broadcast_device(curdev)) {
clockevents_shutdown(curdev);
curdev = NULL;
}
clockevents_exchange_device(curdev, newdev);
tick_setup_device(td, newdev, cpu, cpumask_of(cpu));
if (newdev->features & CLOCK_EVT_FEAT_ONESHOT)
tick_oneshot_notify();
raw_spin_unlock_irqrestore(&tick_device_lock, flags);
return NOTIFY_STOP;
out_bc:
/*
* Can the new device be used as a broadcast device ?
*/
if (tick_check_broadcast_device(newdev))
ret = NOTIFY_STOP;
raw_spin_unlock_irqrestore(&tick_device_lock, flags);
return ret;
}
5.2 tick事件的处理--最简单的情况
if (td->mode == TICKDEV_MODE_PERIODIC)
tick_setup_periodic(newdev, 0);
else
tick_setup_oneshot(newdev, handler, next_event);
void tick_handle_periodic(struct clock_event_device *dev)
{
int cpu = smp_processor_id();
ktime_t next;
tick_periodic(cpu);
if (dev->mode != CLOCK_EVT_MODE_ONESHOT)
return;
/* 如果工作在单触发模式,则计算并设置下一次的触发时刻,这里用了一个循环,是为了防止当该函数被调用时,clock_event_device中的计时实际上已经经过了不止一个tick周期,这时候,tick_periodic可能被多次调用,使得jiffies和时间可以被正确地更新。 */
next = ktime_add(dev->next_event, tick_period);
for (;;) {
if (!clockevents_program_event(dev, next, false))
return;
if (timekeeping_valid_for_hres())
tick_periodic(cpu);
next = ktime_add(next, tick_period);
}
}
static void tick_periodic(int cpu)
{
if (tick_do_timer_cpu == cpu) {
write_seqlock(&xtime_lock);
/* Keep track of the next tick event */
tick_next_period = ktime_add(tick_next_period, tick_period);
do_timer(1);
write_sequnlock(&xtime_lock);
}
update_process_times(user_mode(get_irq_regs()));
profile_tick(CPU_PROFILING);
}
- 更新jiffies_64变量;
- 更新墙上时钟;
- 每10个tick,更新一次cpu的负载信息;
- 更新进程的时间统计信息;
- 触发TIMER_SOFTIRQ软件中断,以便系统处理传统的低分辨率定时器;
- 检查rcu的callback;
- 通过scheduler_tick触发调度系统进行进程统计和调度工作;
6 低精度定时器
/*
* Called by the local, per-CPU timer interrupt on SMP.
*/
void run_local_timers(void)
{
hrtimer_run_queues();
raise_softirq(TIMER_SOFTIRQ);//执行定时器软中断
}
7 高精度定时器
而随着内核的不断演进,大牛们已经对这种低分辨率定时器的精度不再满足,而且,硬件也在不断地发展,系统中的定时器硬件的精度也越来越高,这也给高分辨率定时器的出现创造了条件。内核从2.6.16开始加入了高精度定时器架构。在实现方式上,内核的高分辨率定时器的实现代码几乎没有借用低分辨率定时器的数据结构和代码,内核文档给出的解释主要有以下几点:
- 低分辨率定时器的代码和jiffies的关系太过紧密,并且默认按32位进行设计,并且它的代码已经经过长时间的优化,目前的使用也是没有任何错误,如果硬要基于它来实现高分辨率定时器,势必会打破原有的时间轮概念,并且会引入一大堆#if--#else判断;
- 虽然大部分时间里,时间轮可以实现O(1)时间复杂度,但是当有进位发生时,不可预测的O(N)定时器级联迁移时间,这对于低分辨率定时器来说问题不大,可是它大大地影响了定时器的精度;
- 低分辨率定时器几乎是为“超时”而设计的,并为此对它进行了大量的优化,对于这些以“超时”为目的而使用定时器,它们大多数期望在超时到来之前获得正确的结果,然后删除定时器,精确时间并不是它们主要的目的,例如网络通信、设备IO等等。
为此,内核为高精度定时器重新设计了一套软件架构,它可以为我们提供纳秒级的定时精度,以满足对精确时间有迫切需求的应用程序或内核驱动,例如多媒体应用,音频设备的驱动程序等等。以下的讨论用hrtimer(high resolution timer)表示高精度定时器。
7.1 组织hrtimer
对于高分辨率定时器,我们期望组织它们的数据结构至少具备以下条件:
- 稳定而且快速的查找能力;
- 快速地插入和删除定时器的能力;
- 排序功能;
struct hrtimer {
struct timerqueue_nodenode;//红黑树节点的简单封装
ktime_t_softexpires;//软超时时间
enum hrtimer_restart(*function)(struct hrtimer *);//超时函数,返回值决定是否需要重新激活
struct hrtimer_clock_base*base;
unsigned longstate;
......
};
enum hrtimer_restart {
HRTIMER_NORESTART,/* Timer is not restarted */
HRTIMER_RESTART,/* Timer must be restarted */
};
#define HRTIMER_STATE_INACTIVE0x00 // 定时器未激活
#define HRTIMER_STATE_ENQUEUED0x01 // 定时器已经被排入红黑树中
#define HRTIMER_STATE_CALLBACK0x02 // 定时器的回调函数正在被调用
#define HRTIMER_STATE_MIGRATE0x04 // 定时器正在CPU之间做迁移
enum hrtimer_base_type {
HRTIMER_BASE_MONOTONIC, // 单调递增的monotonic时间,不包含休眠时间
HRTIMER_BASE_REALTIME, // 平常使用的墙上真实时间
HRTIMER_BASE_BOOTTIME, // 单调递增的boottime,包含休眠时间
HRTIMER_MAX_CLOCK_BASES, // 用于后续数组的定义
};
struct hrtimer_cpu_base {
......
ktime_t expires_next;//所有时间基准中最先到期的高精度定时器
......
struct hrtimer_clock_baseclock_base[HRTIMER_MAX_CLOCK_BASES];//每种时间基准一棵红黑树
};
struct hrtimer_clock_base {
struct hrtimer_cpu_base*cpu_base; // 指向所属cpu的hrtimer_cpu_base结构
......
struct timerqueue_headactive; // 红黑树根,包含了所有使用该时间基准系统的hrtimer
ktime_tresolution; // 时间基准系统的分辨率
ktime_t(*get_time)(void); // 获取该基准系统的时间函数
ktime_tsoftirq_time;// 当用jiffies
ktime_toffset; //
};
struct timerqueue_node {
struct rb_node node; // 红黑树的节点
ktime_t expires; // 硬过期时间
};
struct timerqueue_head {
struct rb_root head; // 红黑树的根节点
struct timerqueue_node *next; // 该红黑树中最早到期的节点,也就是最左下的节点
};
7.2 hrtimer如何运转
7.3 hrtimer使用API
//首先我们需要定义一个hrtimer结构的实例
struct hrtimer my_hrtimer;
/* 然后用hrtimer_init函数对它进行初始化
* which_clock可以是CLOCK_REALTIME、CLOCK_MONOTONIC、CLOCK_BOOTTIME中的一种,
* mode则可以是相对时间HRTIMER_MODE_REL,也可以是绝对时间HRTIMER_MODE_ABS。
*/
void hrtimer_init(struct hrtimer *timer, clockid_t which_clock,
enum hrtimer_mode mode);
//设定回调函数
timer.function = hr_callback;
//如果定时器无需指定一个到期范围,可以在设定回调函数后直接使用hrtimer_start激活该定时器:
int hrtimer_start(struct hrtimer *timer, ktime_t tim,
const enum hrtimer_mode mode);
//如果需要指定到期范围,则可以使用hrtimer_start_range_ns激活定时器:
hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim,
unsigned long range_ns, const enum hrtimer_mode mode);
//要取消一个hrtimer,使用hrtimer_cancel:
int hrtimer_cancel(struct hrtimer *timer);
//推后定时器的到期时间:
extern u64
hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval);
/* Forward a hrtimer so it expires after the hrtimer's current now */
static inline u64 hrtimer_forward_now(struct hrtimer *timer,
ktime_t interval)
{
return hrtimer_forward(timer, timer->base->get_time(), interval);
}
//以下几个函数用于获取定时器的当前状态:
static inline int hrtimer_active(const struct hrtimer *timer)
{
return timer->state != HRTIMER_STATE_INACTIVE;
}
static inline int hrtimer_is_queued(struct hrtimer *timer)
{
return timer->state & HRTIMER_STATE_ENQUEUED;
}
static inline int hrtimer_callback_running(struct hrtimer *timer)
{
return timer->state & HRTIMER_STATE_CALLBACK;
}
7.4 hrtimer的到期处理
高精度定时器系统有3个入口可以对到期定时器进行处理,它们分别是:
- 没有切换到高精度模式时,在每个jiffie的tick事件中断中进行查询和处理;
- 在HRTIMER_SOFTIRQ软中断中进行查询和处理;
- 切换到高精度模式后,在每个clock_event_device的到期事件中断中进行查询和处理;
7.5 切换到高精度模式
/*
* Switch to high resolution mode
*/
static int hrtimer_switch_to_hres(void)
{
int i, cpu = smp_processor_id();
struct hrtimer_cpu_base *base = &per_cpu(hrtimer_bases, cpu);
unsigned long flags;
if (base->hres_active)//已经切换至高精度模式,直接返回
return 1;
local_irq_save(flags);
/* tick_init_highres()函数把tick_device切换到CLOCK_EVT_FEAT_ONESHOT模式,同时把clock_event_device的回调handler设置为hrtimer_interrupt,这样设置以后,tick_device的中断回调将由hrtimer_interrupt接管 */
if (tick_init_highres()) {
local_irq_restore(flags);
printk(KERN_WARNING "Could not switch to high resolution "
"mode on CPU %d\n", cpu);
return 0;
}
/* 设置hres_active标志,以表明高精度模式已经切换,然后把3个时间基准系统的resolution字段设为KTIME_HIGH_RES */
base->hres_active = 1;
for (i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++)
base->clock_base[i].resolution = KTIME_HIGH_RES;
/* 因为tick_device被高精度定时器接管,它将不会再提供原有的tick事件机制,所以需要由高精度定时器系统模拟一个tick事件设备,继续为系统提供tick事件能力 */
tick_setup_sched_timer();
/* "Retrigger" the interrupt to get things going */
retrigger_next_event(NULL);
local_irq_restore(flags);
return 1;
}
8 动态时钟框架(CONFIG_NO_HZ、tickless)
在前面章节的讨论中,我们一直基于一个假设:Linux中的时钟事件都是由一个周期时钟提供,不管系统中的clock_event_device是工作于周期触发模式,还是工作于单触发模式,也不管定时器系统是工作于低分辨率模式,还是高精度模式,内核都竭尽所能,用不同的方式提供周期时钟,以产生定期的tick事件,tick事件或者用于全局的时间管理(jiffies和时间的更新),或者用于本地cpu的进程统计、时间轮定时器框架等等。周期性时钟虽然简单有效,但是也带来了一些缺点,尤其在系统的功耗上,因为就算系统目前无事可做,也必须定期地发出时钟事件,激活系统。为此,内核的开发者提出了动态时钟这一概念,我们可以通过内核的配置项CONFIG_NO_HZ来激活特性。有时候这一特性也被叫做tickless,不过还是把它称呼为动态时钟比较合适,因为并不是真的没有tick事件了,只是在系统无事所做的idle阶段,我们可以通过停止周期时钟来达到降低系统功耗的目的,只要有进程处于活动状态,时钟事件依然会被周期性地发出。
在动态时钟正确工作之前,系统需要切换至动态时钟模式,而要切换至动态时钟模式,需要一些前提条件,最主要的一条就是cpu的时钟事件设备必须要支持单触发模式,当条件满足时,系统切换至动态时钟模式,接着,由idle进程决定是否可以停止周期时钟,退出idle进程时则需要恢复周期时钟。
8.1 struct tick_sched
struct tick_sched {
struct hrtimersched_timer;//在高精度模式下,模拟周期时钟的一个hrtimer
unsigned longcheck_clocks;//用于实现clock_event_device和clocksource的异步通知机制,帮助系统切换至高精度模式或者是动态时钟模式。
/* 保存动态时钟的工作模式,取值:
* NOHZ_MODE_INACTIVE 系统动态时钟尚未激活
* NOHZ_MODE_LOWRES 系统工作于低分辨率模式下的动态时钟
* NOHZ_MODE_HIGHRES 系统工作于高精度模式下的动态时钟
* 基于低分辨率和高精度模式下,动态时钟的实现稍有不同,
*/
enum tick_nohz_modenohz_mode;
ktime_tidle_tick;//该字段用于保存停止周期时钟是的内核时间,当退出idle时要恢复周期时钟,需要使用该时间,以保持系统中时间线(jiffies)的正确性
intinidle;
inttick_stopped;//该字段用于表明idle状态的周期时钟已经停止。
unsigned longidle_jiffies;//系统进入idle时的jiffies值,用于信息统计。
unsigned longidle_calls;//系统进入idle的统计次数。
unsigned longidle_sleeps;//系统进入idle且成功停掉周期时钟的次数。
intidle_active;//表明目前系统是否处于idle状态中。
ktime_tidle_entrytime;//系统进入idle的时刻。
ktime_tidle_waketime;// idle状态被打断的时刻。
ktime_tidle_exittime;// 系统退出idle的时刻。
ktime_tidle_sleeptime;//累计各次idle中停止周期时钟的总时间。
ktime_tiowait_sleeptime;
ktime_tsleep_length;//本次idle中停止周期时钟的时间。
unsigned longlast_jiffies;//系统中最后一次周期时钟的jiffies值。
unsigned longnext_jiffies;//预计下一次周期时钟的jiffies。
ktime_tidle_expires;//进入idle后,下一个最先到期的定时器时刻。
intdo_timer_last;
};
8.2 低分辨率下的动态时钟
void hrtimer_run_pending(void)
{
if (hrtimer_hres_active())
return;
......
if (tick_check_oneshot_change(!hrtimer_is_hres_enabled()))//不支持hrtimer,切换到NOHZ_MODE_LOWRES模式
hrtimer_switch_to_hres();//支持hrtimer,切换到NOHZ_MODE_HIGHRES模式
}
8.3 高精度模式下的动态时钟
8.4 动态时钟:停止周期tick时钟事件
开启动态时钟模式后,周期时钟的开启和关闭由idle进程控制,idle进程内最终是一个循环,循环的一开始通过tick_nohz_idle_enter检测是否允许关闭周期时钟若干时间,然后进入低功耗的idle模式,当有中断事件使得cpu退出低功耗idle模式后,判断是否有新的进程被激活从而需要重新调度,如果需要则通过tick_nohz_idle_exit重新启用周期时钟,然后重新进行进程调度,等待下一次idle的发生,我们可以用下图来表示:
8.5 动态时钟对中断的影响
- Linux时间子系统
- Linux时间子系统
- Linux时间子系统
- linux-时间子系统
- linux时间子系统
- linux下时间子系统
- linux时间子系统 - hrtimer
- Linux时间子系统
- linux时间子系统 - 时间概念
- linux时间子系统 - clocksource/timekeeper
- linux时间子系统 - 周期性任务
- linux时间子系统 - 总体框架
- linux时间子系统 - 动态任务
- Linux时间子系统(一) -- 原理
- Linux时间子系统之clocksource的注册
- Linux Kernel时间子系统之来龙去脉浅析
- Linux时间子系统之(四):timekeeping
- Linux时间子系统之(四):timekeeping
- QNX常用命令
- LKT4208 32位SD接口加密卡
- Cordova 3.x+ 基础 -- 环境搭建(Windows / Android)
- POJ3050--Hopscotch
- Shader第七讲 Surface Shader
- linux时间子系统
- 你的下拉刷新是否“抖”了一下
- Git 學習(1)—— 安裝以及創建版本庫
- Android开发设计模式之——单例模式
- 用android:clipChildren来实现红心变大特效
- android Mediaplayer各种属性和方法简单介绍
- c api example
- 图的遍历:BFS和DFS
- 双网卡的回环测试