为 Eason 痴狂
喜欢 Eason 好多年,今年终于赶上他的演唱会,于是怀揣着激动万分的心情来到了天津,来到了水滴。来到了这个让国足曾经失意过的地方,BTW 在国内有国足没有失意过的球场吗?嗯…嗯,想起来了,有 ,五里河,不过拆了…… 靠!怎么又提到国足了,真扫兴。
水滴真的很美,可是再美的场馆没有精彩的比赛也衬托不出它的美,它注定会孤独(跟我一样,sigh ……),可是今晚它不会孤独,不会!!!
这场演唱会可以说是座无虚席,爆满,非常火爆,在体育场附近就有很多人手里拿着钱,四处询问,hi,哥们,卖票吗?
还没开始
开始了
第一首歌是 《今天等我来》
第二首歌是 《好歌献给你》 ,Eason “天津我来了!”,演唱会也真真正正的开始了。
紧接着是 《兄妹》,随后我觉得第一个高潮出现了,Eason 翻唱张国荣的《我》然后紧接着《浮夸》,太 High 了!!!
之后是 《爱是怀疑》《红玫瑰》《葡萄成熟时》《好久不见》《全世界失眠 》《不要说话》等歌曲,都是经典阿。
在《爱情转移》之后第二个高潮出现了,Eason 站在阶梯(真的很高)上演唱《十年》。全场陶醉 。。。。。。
Eason说好,最后一首歌,好像是《我的快乐时代》,记不清了,他感谢了他的团队的每个人,然后和全场所有观众说再见,最后和它的乐队一起消失在舞台中 。。。。。。 所有人都有些不太相信,就这么结束了吗,全场一起喊”Eason”,每个人似乎都不愿意离去,可是空空的舞台却始终没有一个人出现,我身边有的人都退场了。可是就在这时,舞台上走出了几个人,全场欢呼雀跃,紧接着那熟悉的旋律响了起来,《淘汰》,Eason 出现,全场沸腾了,我身边的每个人似乎都难以抑制住自己,有的已经热泪盈眶,包括我。。。。。。
</embed>
在整场演唱会中 Eason 话很少,几乎都在唱,但是他有一句话却给我留下了深刻的印象,他说,我姓黄,叫黄人,我很骄傲我是一个黄种人 !!
伴随着《你的背包》,演唱会真的结束了。
这场演唱会看的真 High,真的很痛快, 稍显遗憾的是目有《孤独患者》和《圣诞结》,真的没有听够,肯定还会去听!!
为 Eason 痴狂
Posix timers clock_gettime 分析
int clock_getres(clockid_t clk_id, struct timespec *res)
这个函数就是根据 clk_id 返回相应的 time:
CLOCK_REALTIME real_time clock 系统绝对时间
CLOCK_MONOTONIC 单调时间
关于这个函数更详细的介绍请参考 man 手册
首先假设在用户态
struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now)
走起:
首先要从初始化开始:
kernel/posix-timers.c
__initcall(init_posix_timers); #define __initcall(fn) device_initcall(fn) #define device_initcall(fn) __define_initcall("6",fn,6)
static __init int init_posix_timers(void) { struct k_clock clock_realtime = { .clock_getres = hrtimer_get_res, }; struct k_clock clock_monotonic = { .clock_getres = hrtimer_get_res, .clock_get = posix_ktime_get_ts, .clock_set = do_posix_clock_nosettime, }; struct k_clock clock_monotonic_raw = { .clock_getres = hrtimer_get_res, .clock_get = posix_get_monotonic_raw, .clock_set = do_posix_clock_nosettime, .timer_create = no_timer_create, .nsleep = no_nsleep, }; struct k_clock clock_realtime_coarse = { .clock_getres = posix_get_coarse_res, .clock_get = posix_get_realtime_coarse, .clock_set = do_posix_clock_nosettime, .timer_create = no_timer_create, .nsleep = no_nsleep, }; struct k_clock clock_monotonic_coarse = { .clock_getres = posix_get_coarse_res, .clock_get = posix_get_monotonic_coarse, .clock_set = do_posix_clock_nosettime, .timer_create = no_timer_create, .nsleep = no_nsleep, }; register_posix_clock(CLOCK_REALTIME, &clock_realtime); register_posix_clock(CLOCK_MONOTONIC, &clock_monotonic); // 注册 register_posix_clock(CLOCK_MONOTONIC_RAW, &clock_monotonic_raw); register_posix_clock(CLOCK_REALTIME_COARSE, &clock_realtime_coarse); register_posix_clock(CLOCK_MONOTONIC_COARSE, &clock_monotonic_coarse); posix_timers_cache = kmem_cache_create("posix_timers_cache", sizeof (struct k_itimer), 0, SLAB_PANIC, NULL); idr_init(&posix_timers_id); return 0; }
void register_posix_clock(const clockid_t clock_id, struct k_clock *new_clock) { if ((unsigned) clock_id >= MAX_CLOCKS) { printk("POSIX clock register failed for clock_id %d\n", clock_id); return; } posix_clocks[clock_id] = *new_clock; } // 这段代码真的很简单,不需要过多的解释 static struct k_clock posix_clocks[MAX_CLOCKS]; #define CLOCK_REALTIME 0 #define CLOCK_MONOTONIC 1 #define CLOCK_SGI_CYCLE 10 #define MAX_CLOCKS 16 #define CLOCKS_MASK (CLOCK_REALTIME | CLOCK_MONOTONIC) #define CLOCKS_MONO CLOCK_MONOTONIC
来了
SYSCALL_DEFINE2(clock_gettime, const clockid_t, which_clock, struct timespec __user *,tp) { struct timespec kernel_tp; int error; if (invalid_clockid(which_clock)) return -EINVAL; // 首先检查 clock_id 是否 valid,此时是 CLOCK_MONOTONIC error = CLOCK_DISPATCH(which_clock, clock_get, (which_clock, &kernel_tp)); // posix_ktime_get_ts(CLOCK_MONOTONIC, &kernel_tp); if (!error && copy_to_user(tp, &kernel_tp, sizeof (kernel_tp))) error = -EFAULT; return error; }
static inline int invalid_clockid(const clockid_t which_clock) { if (which_clock < 0) /* CPU clock, posix_cpu_* will check it */ return 0; if ((unsigned) which_clock >= MAX_CLOCKS) return 1; if (posix_clocks[which_clock].clock_getres != NULL) return 0; if (posix_clocks[which_clock].res != 0) return 0; return 1; }
#define CLOCK_DISPATCH(clock, call, arglist) \ ((clock) < 0 ? posix_cpu_##call arglist : \ (posix_clocks[clock].call != NULL \ ? (*posix_clocks[clock].call) arglist : common_##call arglist)) CLOCK_DISPATCH(CLOCK_MONOTONIC,clock_get,(CLOCK_MONOTONIC, &kernel_tp)) \ ((clock) < 0 ? posix_cpu_##call arglist : (posix_clocks[clock].clock_get != NULL ? (*posix_clocks[clock].call) arglist : common_##call arglist)) // 其实最终就是这个样子 posix_ktime_get_ts(CLOCK_MONOTONIC, &kernel_tp);
static int posix_ktime_get_ts(clockid_t which_clock, struct timespec *tp) { ktime_get_ts(tp); return 0; } void ktime_get_ts(struct timespec *ts) { struct timespec tomono; unsigned int seq; s64 nsecs; WARN_ON(timekeeping_suspended); do { seq = read_seqbegin(&xtime_lock); // 加读锁 *ts = xtime; // 当前时间,内核时间(UTC 时间) tomono = wall_to_monotonic; nsecs = timekeeping_get_ns(); // 得到距离上一次得到时间中间走过的时间(纳秒) } while (read_seqretry(&xtime_lock, seq)); set_normalized_timespec(ts, ts->tv_sec + tomono.tv_sec, ts->tv_nsee + tomono.tv_nsec + nsecs); }
这个函数和 getnstimeofday 很像,细节可以参考 gettimeofday
void set_normalized_timespec(struct timespec *ts, time_t sec, s64 nsec) { while (nsec >= NSEC_PER_SEC) { /* * The following asm() prevents the compiler from * optimising this loop into a modulo operation. See * also __iter_div_u64_rem() in include/linux/time.h */ asm("" : "+rm"(nsec)); nsec -= NSEC_PER_SEC; ++sec; } while (nsec < 0) { asm("" : "+rm"(nsec)); nsec += NSEC_PER_SEC; --sec; } ts->tv_sec = sec; ts->tv_nsec = nsec; }
其实原理就是记录一下开始的时间在 timekeeping_init() 中,然后用当前时间减去开始的时间就是经过的时间也就是单调时间~
但是 wall_to_monotonic 这个东东就不得不说一下了,他到底是神马玩意儿呢? 关于它还得追溯到 timekeeping_init() 中
struct timespec wall_to_monotonic __attribute__ ((aligned (16))); void __init timekeeping_init(void) { struct clocksource *clock; unsigned long flags; struct timespec now, boot; read_persistent_clock(&now); // 读取 RTC chip,get the UTC time read_boot_clock(&boot); ...... 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); ...... }
首先读取 RTC 获得 UTC 时间,保存到 xtime 中,最后记录到 wall_to_monotonic,只不过是个负值。其实就是记录一下系统启动时候的 real time.
clock_gettime() 函数 基本上也就说完了,这个函数的实现比较简单,但是仍然有几点需要说明:
1. 它和 gettimeofday() 的区别是什么 ?
当 clock_gettime() clock_id 指定为 CLOCK_REALTIME 时,它与 gettimeofday 完全一样,只不过它返回的是纳秒,而 gettimeofday 返回的是微秒。
struct timespec ts;
struct timeval tv;
clock_gettime(CLOCK_REALTIME,&ts);
gettimeofday(&tv,NULL);
SYSCALL_DEFINE2(clock_gettime, const clockid_t, which_clock, struct timespec __user *,tp) { ...... error = CLOCK_DISPATCH(which_clock, clock_get, (which_clock, &kernel_tp)); // 其实就是调用 common_clock_get(CLOCK_REALTIME, &kernel_tp); ...... } static int common_clock_get(clockid_t which_clock, struct timespec *tp) { ktime_get_real_ts(tp); return 0; } #define ktime_get_real_ts(ts) getnstimeofday(ts) // 就是调用 getnstimeofday(),同理再 gettimeofday() 中也是调用 getnstimeofday() ,关于这个函数细节参考下:<a href="http://www.bluezd.info/archives/gettimeofday">gettimeofday</a>
2.CLOCK_MONOTONIC 单调时间,那究竟什么是单调时间呢?
CLOCK_MONOTONIC 表示单调时间,此时间会一直增加,不会受系统时间改变的影响。其实就是表示系统启动了多长时间,可通过 uptime 查看.
比如我们用 settimeofday 往回设置时间,假设 current 20:00:00 往回设置 10 s,这样当用 gettimeofday 获得当前时间时是获得的绝对的时间,为 19:50:00,也就是说 gettimeofday 获得的”值”会比没设置前小。但是当用 clock_getime 并指定 clock_id 为 CLOCK_MONOTONIC 时,在时间设置前后其“值”都是递增的。因为在 do_settimeofday 中 wall_to_monotonic 也会被设置,会被往回设置 10 s.
举个例子:
比如系统启动时的 UTC 时间是 20:00:00 ,此时 wall_to_monotonic 记录着这个时间,假设当前时间为 21:00:00,那么系统启动了 1h,通过 clock_gettime(CLOCK_MONOTONIC, …. ) 即可得到这个 1h.好现在往回设置时间,设置到 20:30:00,那么 相应 wall_to_monotonic 的值也会往回设置,为 19:30:00,xtime 回在这个基础上增加,这就是为什么当我们用 clock_gettime 获得单调时间时它的值一直增加了:
下面的代码说明了这一点:
int do_settimeofday(struct timespec *tv) { ...... // tv 是要设置的时间 ts_delta.tv_sec = tv->tv_sec - xtime.tv_sec; ts_delta.tv_nsec = tv->tv_nsec - xtime.tv_nsec; wall_to_monotonic = timespec_sub(wall_to_monotonic, ts_delta); ...... } static inline struct timespec timespec_sub(struct timespec lhs, struct timespec rhs) { struct timespec ts_delta; set_normalized_timespec(&ts_delta, lhs.tv_sec - rhs.tv_sec, lhs.tv_nsec - rhs.tv_nsec); return ts_delta; }
写个测试程序来验证这一点:
#include <stdio.h> #include <time.h> #include <sys/time.h> int main(int argc, const char *argv[]) { struct timeval tv1,tv2; struct timespec ts1,ts2; struct timeval temp; gettimeofday(&tv1,NULL); clock_gettime(CLOCK_MONOTONIC,&ts1); temp = tv1; temp.tv_sec -= 10; settimeofday(&temp,NULL); gettimeofday(&tv2,NULL); clock_gettime(CLOCK_MONOTONIC,&ts2); printf("gettimeofday start = %ld.%6ld,end = %ld.%6ld, \n\t => diff = %f \n",tv1.tv_sec, tv1.tv_usec, tv2.tv_sec, tv2.tv_usec, ((tv2.tv_sec * 1000000 + tv2.tv_usec) - (tv1.tv_sec * 1000000 + tv1.tv_usec))/1000000.0); printf("clock_gettime start = %ld.%9ld,end = %ld.%9ld, \n\t => diff = %f \n",ts1.tv_sec, ts1.tv_nsec, ts2.tv_sec, ts2.tv_nsec, ((ts2.tv_sec * 1000000000 + ts2.tv_nsec) - (ts1.tv_sec * 1000000000 + ts1.tv_nsec))/1000000000.0); return 0; }
# ./clock_gettime_gettimeofday
gettimeofday start = 1345788337.265370,end = 1345788327.265383, << end 值明显变小
=> diff = -9.999987
clock_gettime start = 2595.332710380,end = 2595.332726631, << end 值增大
=> diff = 0.000016
最后 clock_gettime 就大致 BB 完了,还有几个与时间有关的 posix 函数(clock_getres,clock_settime),这几个函数的实现与 clock_gettime 大致相同
7 月
好久没有写啦,主要是最近忙毕业的事情,没心情。7月我终于上班啦,终于成为了一个 regular。
刚上班没什么感觉,因为我毕竟在这里实习了几个月,其实我也能称的上是一个老员工啦,哈哈。可是刚上班就有一个叫 Leap Second (俗称“闰秒”)的东东出现在我的面前,就是因为这一秒搞的我最近每一秒过的都很不爽。
其实工作后感觉生活挺单调乏味的,除了工作我不知道我还能做什么,有时候是既盼下班有害怕下班,一个对下班没有期待的人下了班能干嘛? 我现在都有点害怕放假了。。。。。。所以为了麻痹自己,我决定最近一段时间好好研究一下 Timer 的代码(这决定2b吧),一是提高一下自己(其实有时候我感觉我真的很烂),二是让自己忙起来可以尽量忘掉一些不开心的事情。好,就这么定了。
前几天买了个 filco 键盘,哈哈,手感真心不错。炫耀一下:
正面:
侧刻:
哈哈,装备齐了,但还差个耳机 ……(AKG K450 听说不错,但是我已经有了个 K420 了,纠结啊 )
Switch to High Resolution Mode
继续:
此时 global and local clock event device 已经注册完毕,系统现在就开始响应中断了,当内核从RTC 取出时间后,内核就应该自己更新时间了,所以中断处理程序的很重要的一部分工作就是计时。而在 timer_interrupt 函数中:
global_clock_event->event_handler(global_clock_event);
实际上执行的就是 tick_handle_periodic()
Note:
当系统启动时是默认工作在 low resolution mode 中,时钟源为 PIT,当系统中存在高精度的时钟源时,时钟源就应该切换到高精度的上(比如 tsc,hpet),在系统初始化末期 clocksource_done_booting() ,kernel 会选择 the best clocksource 然后完成切换操作,在大多数情况下都是从 PIT 切换到 HPET,details see 时钟源注册 .此时高精度的时钟源已经存在,在正常情况下系统就应该将 clock event device 切换到高分辨率模式(在 boot parameter “highres=off” 没有设置的情况下).那么究竟在何时内核回检查并且将时钟事件设备切换到高分辨率模式呢?那就是在每次的时钟中断处理程序中。
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; // 此时直接返回 时钟事件设备的 mode 应该为 CLOCK_EVT_MODE_UNUSED // 若为 one-shot 模式 ,则需要编程设置下一个时钟事件 /* * Setup the next period for devices, which do not have * periodic mode: */ next = ktime_add(dev->next_event, tick_period); for (;;) { if (!clockevents_program_event(dev, next, ktime_get())) return; /* * Have to be careful here. If we're in oneshot mode, * before we call tick_periodic() in a loop, we need * to be sure we're using a real hardware clocksource. * Otherwise we could get trapped in an infinite * loop, as the tick_periodic() increments jiffies, * when then will increment time, posibly causing * the loop to trigger again and again. */ 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) { // 如果当前 cpu 是 在 tick_setup_device () 中设置的 cpu,一般情况下为 CPU0,值允许一个 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); // 更新 wall time 和 jiffies write_sequnlock(&xtime_lock); } update_process_times(user_mode(get_irq_regs())); profile_tick(CPU_PROFILING); }
此时我们关注 update_process_times()
update_process_times()
-> run_local_timers()
-> raise_softirq(TIMER_SOFTIRQ) // 激活软中断下半部
软中断下半部处理函数在 init_timers () 中注册
run_timer_softirq()
-> hrtimer_run_pending()
-> hrtimer_switch_to_hres()
-> tick_init_highres()
-> tick_switch_to_oneshot()
-> tick_broadcast_switch_to_oneshot()
-> tick_broadcast_setup_oneshot()
void hrtimer_run_pending(void) { if (hrtimer_hres_active()) return; // 如果此时已经是高精度模式则无需转换 /* * This _is_ ugly: We have to check in the softirq context, * whether we can switch to highres and / or nohz mode. The * clocksource switch happens in the timer interrupt with * xtime_lock held. Notification from there only sets the * check bit in the tick_oneshot code, otherwise we might * deadlock vs. xtime_lock. */ if (tick_check_oneshot_change(!hrtimer_is_hres_enabled())) hrtimer_switch_to_hres(); // 检查系统中是否存在适用于高分辨率定时器的时钟事件设备 } static inline int hrtimer_is_hres_enabled(void) { return hrtimer_hres_enabled; // 一般情况下 为 1 }
int tick_check_oneshot_change(int allow_nohz) { // allow_nohz=0 struct tick_sched *ts = &__get_cpu_var(tick_cpu_sched); if (!test_and_clear_bit(0, &ts->check_clocks)) return 0; if (ts->nohz_mode != NOHZ_MODE_INACTIVE) return 0; if (!timekeeping_valid_for_hres() || !tick_is_oneshot_available()) return 0; if (!allow_nohz) return 1; // 一般情况下执行到此,返回 1 tick_nohz_switch_to_nohz(); return 0; }
/** * timekeeping_valid_for_hres - Check if timekeeping is suitable for hres */ int timekeeping_valid_for_hres(void) { unsigned long seq; int ret; do { seq = read_seqbegin(&xtime_lock); ret = timekeeper.clock->flags & CLOCK_SOURCE_VALID_FOR_HRES; // 当时钟源为高精度已经由 pit 切换到 hpet,此时 timekeeper 中使用的 clock source 是 hpet,而 clocksource_hpet 中的 flag 已经在注册时钟源时在 clocksource_enqueue_watchdog 被设置,所以此时 ret = 1,表示可以切换 } while (read_seqretry(&xtime_lock, seq)); return ret; }
Note:
在系统没有切换到 high resolution clock source 时,在每次时钟中断处理程序中也都会检查时钟时间设备是否能够切换到 high resolution mode.但是每当执行到 tick_check_oneshot_change()-> timekeeping_valid_for_hres() 时,由于 timekeeper 当前使用的是 clocksource_jiffies,PIT 并没有 CLOCK_SOURCE_VALID_FOR_HRES flag 所以每次 tick_check_oneshot_change() 函数都会返回 0,所以不会执行切换操作。但是一旦系统切换时钟源到某个高分辨率时钟源(tsc,hpet)上,就会执行真正切换操作!!
好继续 hrtimer_switch_to_hres() 执行时钟事件设备的注册。
hrtimer_switch_to_hres() -> tick_init_highres () int tick_init_highres(void) { return tick_switch_to_oneshot(hrtimer_interrupt); }
Note:
此函数应该被执行的次数就是 CPU 的个数
int tick_switch_to_oneshot(void (*handler)(struct clock_event_device *)) { struct tick_device *td = &__get_cpu_var(tick_cpu_device); // 还是获得那个 tick_device struct clock_event_device *dev = td->evtdev; // tick device 上的时钟时间设备 此时假设是 CVPU0,那 cpu0 的时钟事件设备此时就是 Lapic if (!dev || !(dev->features & CLOCK_EVT_FEAT_ONESHOT) || !tick_device_is_functional(dev)) { printk(KERN_INFO "Clockevents: " "could not switch to one-shot mode:"); if (!dev) { printk(" no tick device\n"); } else { if (!tick_device_is_functional(dev)) printk(" %s is not functional.\n", dev->name); else printk(" %s does not support one-shot mode.\n", dev->name); } return -EINVAL; } td->mode = TICKDEV_MODE_ONESHOT; dev->event_handler = handler; // 设置 event_handler 为 hrtimer_interrupt 就是这里啦 clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT); // 设置为 oneshot 模式 tick_broadcast_switch_to_oneshot(); // Select oneshot operating mode for the broadcast device return 0; }
void tick_broadcast_switch_to_oneshot(void) { struct clock_event_device *bc; unsigned long flags; spin_lock_irqsave(&tick_broadcast_lock, flags); tick_broadcast_device.mode = TICKDEV_MODE_ONESHOT; // 设置广播的时钟时间设备的模式 // tick_broadcast_device 不陌生吧,在 tick_check_broadcast_device() 中被设置,此时就是 HPET bc = tick_broadcast_device.evtdev; // bc 一般情况下为 &hpet_clockevent if (bc) tick_broadcast_setup_oneshot(bc); // 设置广播设备 spin_unlock_irqrestore(&tick_broadcast_lock, flags); }
Note:
此函数只会被执行一次
void tick_broadcast_setup_oneshot(struct clock_event_device *bc) { /* Set it up only once ! */ if (bc->event_handler != tick_handle_oneshot_broadcast) { // 此时 bc->event_handler 应该为 clockevents_handle_noop int was_periodic = bc->mode == CLOCK_EVT_MODE_PERIODIC; int cpu = smp_processor_id(); bc->event_handler = tick_handle_oneshot_broadcast; // 设置 hpet_clockevent 的 event_handler 为 tick_handle_oneshot_broadcast clockevents_set_mode(bc, CLOCK_EVT_MODE_ONESHOT); /* Take the do_timer update */ tick_do_timer_cpu = cpu; // 设置负责 do_timer 的 CPU. /* * We must be careful here. There might be other CPUs * waiting for periodic broadcast. We need to set the * oneshot_mask bits for those and program the * broadcast device to fire. */ cpumask_copy(to_cpumask(tmpmask), tick_get_broadcast_mask()); cpumask_clear_cpu(cpu, to_cpumask(tmpmask)); cpumask_or(tick_get_broadcast_oneshot_mask(), tick_get_broadcast_oneshot_mask(), to_cpumask(tmpmask)); if (was_periodic && !cpumask_empty(to_cpumask(tmpmask))) { tick_broadcast_init_next_event(to_cpumask(tmpmask), tick_next_period); tick_broadcast_set_event(tick_next_period, 1); } else bc->next_event.tv64 = KTIME_MAX; } }
好的,总结一下:
在完成从低分辨率到高分辨率时钟事件设备的切换过程中,hrtimer_switch_to_hres()被调用的次数正好为 CPU 的个数,将每个CPU 的 local apic clock event device 的 event_handler 设置为 hrtimer_interrupt;而在 tick_broadcast_setup_oneshot() 函数中将 global clock event device 的 event_handler 设置为tick_handle_oneshot_broadcast,tick_broadcast_setup_oneshot() 只会被某个 CPU 执行,不一定是 CPU0,但是只会执行一次,至此完成切换操作。之后在每个 CPU 相应时钟中断时还会去检查是否需要切换到高分辨率模式(连写这块代码的人都说这块有点 ugly),但是在 hrtimer_run_pending()中会首先检查如果已经切换到高分辨率则直接返回。
`</p>
cat /proc/timer_list | grep "event_handler" event_handler: tick_handle_oneshot_broadcast event_handler: hrtimer_interrupt event_handler: hrtimer_interrupt event_handler: hrtimer_interrupt event_handler: hrtimer_interrupt`
说明一下,不同的 boot parameter 相应的 event_handler 也会不一样,比如:
`</p>
boot parameter "highres=off" event_handler: tick_handle_oneshot_broadcast event_handler: tick_nohz_handler event_handler: tick_nohz_handler event_handler: tick_nohz_handler event_handler: tick_nohz_handler boot parameter "nohz=off highres=off" (1) event_handler: clockevents_handle_noop event_handler: tick_handle_periodic event_handler: tick_handle_periodic event_handler: tick_handle_periodic event_handler: tick_handle_periodic (2) event_handler: tick_handle_periodic_broadcast event_handler: tick_handle_periodic event_handler: tick_handle_periodic event_handler: tick_handle_periodic event_handler: tick_handle_periodic`
实习
听着陈奕迅的歌,看着地上的旅行袋和那几张泛黄的火车票,回头看这一年走过的路,百感交集…… 如今就要离去,心底有一丝伤感,一丝向往,还有一丝惆怅……
今天是我在公司的最后一天,从去年六月到现,时间在不长也不短,但却很值得回味……
首先感谢 eguan 和 adam8157 平时的帮助,从你们身上学习到了很多。以前在学校每当我折腾 Linux 或者看内核代码时我就不停问自己我这么付出究竟是为了什么?看那些破玩意究竟有什么用?直到后来来到了 Redhat,让我明白了一切…… 在这里真的很锻炼人,也许其他都是假的,但这一点绝对是真的。在这里你可以用任何语言写程序只要你会,C/shell/python。有时需要根据 testcase 的需要简单的可能 shell 或 python 就搞定,稍微复杂的需要用 C,甚至有时还需写 kernel Module。总之会接触到各种系统函数各种用法。同时通过看 patch 促使你有一种阅读代码的欲望,当你弄懂了内核的实现时那种感觉真的很美妙仿佛被打通了任督二脉。工作在这样一个 opensource 中真是一件美妙的事情。
哎,接下来还得回到那个让我上了一辈子火的学校,参加那个破 JB 考试,还得弄那个破 JB 毕设。我只求能顺利毕业,我做学生已经够了。
有时感觉自己就像一直蜗牛,背着重重的壳,蹒跚前行,却始终找不到属于自己的那片天 ……