Linux Kernel的时钟
Jiffies
-
背景:从时钟中断说起
- Linux系统有一个时钟中断,被称为tick,用来做进程调度,周期为Hz
- Jiffies,就是tick的计数,每一个时钟中断发生时被加1
- Jiffies可以用来做计时,以系统启动时间为起点,粒度为一个tick
-
为了尽可能减少jiffies环绕,jiffies定义为64位,在32位上读写就不是一个原子操作。用顺序锁来避免竞争
-
读侧
u64 get_jiffies_64(void) { unsigned long seq; u64 ret; do { seq = read_seqbegin(&jiffies_lock); ret = jiffies_64; } while (read_seqretry(&jiffies_lock, req)); return ret; }
-
更新侧
void xtime_update(unsigned long ticks) { write_seqlock(&jiffies_lock); do_timer(ticks); write_sequnlock(&jiffies_lock); update_wall_time(); }
-
-
顺序锁
-
类似于读写锁,用于读很频繁写较少的情况
-
用于读者用来获得一致的数据
-
更新端需要同步多个写者
-
特别需要注意的是,读端需要自行注意数据的有效性。比如访问一个指针,读写锁无法保护指针的合法性
-
API
-
初始化:
SEQCOUNT_DEP_MAP_INIT()
,seqcount_init()
-
读者:
do { x = read_seqcount_begin(lock); read_something(); } while(read_seqcount_retry(lock, x));
-
写者:
write_seqcount_begin(lock); write_something(); write_seqcount_end(lock);
-
更新侧自带锁同步的API
- 带有
spinlock
:seqlock_init()
,seqlock_init()
,read_seqretry()
,write_seqlock()
,write_sequnlock()
- 带有
spinlock_bh
:写者API变成write_seqlock_bh()
/write_sequnlock_bh()
- 带有
spinlock_irq
- 带有
-
-
顺序锁的实现:简化版本
write_seqcount_begin(lock) { lock->sequence++; // 开始写变成奇数 smb_wmb(); // 写屏障(疑似smp_wmb?) } write_seqcount_end(lock) { smb_wmb(); return lock->sequence++; // 写结束后变成偶数 } read_seqcount_begin(lock) { repeat: ret = ACCESS_ONCE(lock->sequence); if (ret & 1) { // 如果是奇数表示正在写 cpu_relax(); // 就让CPU等待 goto repeat; } smb_rmb(); return ret; } read_seqcount_retry(lock, begin) { smb_rmb(); return lock->sequence != begin; }
-
-
tickless
- CONFIG_NO_HZ_IDLE
如果CPU是空闲的,则不在此CPU上产生tick,主要目的是省电 - CONFIG_NO_HZ_FULL
如果CPU上只有一个任务,则不需要时钟中断来打断这个CPU,因为没有其他的任务需要调度,用于HPC(高性能计算)的场景。
- CONFIG_NO_HZ_IDLE
墙上时间 xtime
- xtime表示现实生活中的时间,实际计时之用
- gettimeofday();
- 文件系统访问时间戳
- 从cmos中得到初始值,每个tick也将此值更新
- ntp也会更新时间
定时器
timer
- 普通定时器
- APIs
- 初始化
TIMER_INITIALIZER(_function, _expires, _data)
setup_timer(timer, fn, data)
- 更新timer到期时间
mod_timer(timer, expires)
- 把timer加到运行队列
add_timer(timer)
- 撤销timer
del_timer(timer)
- 撤销timer并等待callback运行完成
del_timer_sync()
- 初始化
hrtimer
-
基于高精度的外部硬件
-
提供高精度的延迟,纳秒级
-
基本APIs
-
初始化:
hrtimer_init(timer, which_clock, mode)
which_clock: 定义定时器所使用的时钟CLOCK_MONOTONIC
: 单调递增的时钟CLOCK_REALTIME
: 墙上时钟,在系统时间更改时,此时间可能倒退
mode:
HRTIMER_MODE_ABS
提供的超时时间是绝对值HRTIMER_MODE_REL
提供的超时时间是相对值
-
指定hrtimer的回调函数
hrtimer->function()
- 如果该函数返回
HRTIMER_RESTART
表示需要再次被触发,否则返回HRTIMER_NORESTART
-
启动hrtimer
-
hrtimer_start(timer, tim, mode)
tim 指定的时间
mode
- HRTIMER_MODE_ABS 相对时间
- HRTIMER_MODE_REL 相对时间
-
-
取消hrtimer
hrtimer_cancel(hrtimer)
-
-
实现
- 在每个CPU上面,每个时钟都有一个红黑树,每个
hrtimer
都按照超时时间为key插入到该树 - 在hrtimer初始化的时候,将该hrtimer关联到对应的时钟上 (
__hrtimer_init(struct hrtimer *timer, clockid_t clock_id, ..)
) - 在
hrtimer
start的时候,先将相对时间转换为绝对时间,然后插入到红黑树中 - 如果新插入的hrtimer是最左边的节点,即它的超时时间是最小的,配置clock device的触发事件为最低的超时时间
- hrtimer到期后的处理
- 在每个CPU上面,每个时钟都有一个红黑树,每个
itimer
-
interval timer,间隔定时器,用于用户空间的高精度定时
-
itimer有3个时钟,分别为
ITIMER_REAL
: 真实时钟ITIMER_VIRTUAL
: 进程实际运行时间计时ITIMER_PROF
:进程实际运行时间+进程在内核态的运行时间
-
触发信号
ITIMER_REAL
:触发SIGALRMITIMER_VIRTUAL
:触发SIGVTALRMITIMER_PROF
:触发SIGPROF
-
用户空间接口(封装在glibc中)
-
设置itimer
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value) struct itimerval { struct timeval it_interval; // next value struct timeval it_value; // current value };
-
查看itimer
int getitimer(int which, struct itimerval *curr_value) // 获得指定时钟上的itimer信息
-
-
实现原理
-
ITIMER_REAL
-
对于
ITIMER_REAL
,kernel中直接用hrtimer来实现
在每次fork一个新进程的时候,会初始化real_timer
-
在
setitimer
系统调用处理的时候,启用该timer (调用hrtimer_start
)expires = timeval_to_ktime(value->it_value); if (expires.tv64 != 0) { tsk->signal->it_real_incr = timeval_to_ktime(value->it_interval); hrtimer_start(timer, expires, HRTIMER_MODE_REL); } else tsk->signal->it_real_incr.tv64 = 0;
-
超时后的处理
/* * The timer is automagically restarted, when interval != 0 */ enum hrtimer_restart it_real_fn(struct hrtimer *timer) { struct signal_struct *sig = container_of(timer, struct signal_struct, real_timer); kill_pid_info(SIGALRM, SEND_SIG_PRIV, sig->leader_pid); return HRTIMER_NORESTART; }
-
重启itimer
if (unlikely(signr == SIGALRM)) { struct hrtimer *tmr = &tsk->signal->real_timer; if (!hrtimer_is_queued(tmr) && tsk->signal->it_real_incr.tv64 != 0) { hrtimer_forward(tmr, tmr->base->get_time(), tsk->signal->it_real_incr); hrtimer_restart(tmr); } }
hrtimer_forward()
语句用来计算相对于当前时钟的绝对超时时间hrtimer_restart()
重启计时器,以ABS的方式
-
-
ITIMER_VIRTUAL
和ITIMER_PROF
-
在每一个tick发生的时候都对当前进程进行统计,如果tick发生时,进程运行在用户空间(CPL==3),则更新utime,否则更新systime。ITIMER_PROF统计的是utime+systime
-
在tick发生时的处理
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); }
-
判断是否在用户空间:
user_mode(get_irq_regs()
,它根据压入堆栈的CS判断 -
以
account_user_time()
为例void account_user_time(struct task_struct *p, cputime_t cputime, cputime_t cputime_scaled) { ... }
-
超时的判断
void check_process_timers(tsk, firing)
-
-