linux 中高精度timer的实现

相关结构体

每个cpu上绑定一个hrtimer_cpu_base结构体,它的active_bases用位记录了有哪些种类的timer,对应每种类型的计时器红黑树(hrtimer_clock_base)在hrtimer_cpu_base->clock_base[]上。

软硬超时调试时机

软超时处理线程的调度时机有两个,一个是在硬中断中顺带触发,另一个是在每个时间片调度时通过run_local_timers来触发。

硬超时处理线程的调度时机是在hrtimer_reprogram和hrtimer_force_reprogram中设置到硬件上的,具体实现是通过tick_program_event设置到硬件上,计时器的触发有一次触发(CLOCK_EVT_STATE_ONESHOT)与周期性触发(CLOCK_EVT_STATE_PERIODIC)两种模式,一般是单次触发,再底层调用clockevents_program_event来配置硬件(dev->set_next_ktime,设置是时间还是时长由dev->features上有没有CLOCK_EVT_FEAT_KTIME来决定)。

软超时的处理

软超时处理的入口是hrtimer_run_softirq,这个线程中会找到达到了软超时的task(__hrtimer_run_queues),判断它没有disable(tasklet_struct->count==0)且处于TASKLET_STATE_SCHED的task(软超时的任务,__run_hrtimer),执行它的func或callback,并更新下一个即将到达的软超时时间。

硬超时的处理

硬超时是在超时hrtimer_interrupt到来时就直接在中断中处理的。

当时间到达时,触发hrtimer_interrupt,如果超过了软中断时间,将唤醒软中断处理线程,然后调用__hrtimer_run_queues找到硬超时的active 的 clock红黑树中所有硬超时的计时器,执行__run_hrtimer,它实际会从对应红黑树移除timer,触发绑定的函数:timer->function(timer),这个绑定函数的返回值表示是否需要将计时器更新后重加回红黑树中(红黑树操作函数为timerqueue_add和timerqueue_del)。调用hrtimer_update_next_event更新距离下次最早的软硬中断的等待时长,如果下一个时间点已经过了(虚拟机中运行或是callback过长将有可能导致硬中断处理太久),则尝试设置一个最小延时时长,并设置hang_detected标记,以防不断触发hrtimer_interrupt,最多尝试三次。

超时时间的设置

用户可以设置到期时间和宽限期(schedule_hrtimeout_range_clock),记录在timer->_softexpires和timer->node.expires上,在达到_softexpires时会在软超时处理线程中专门处理。如果达到expires,这个线程还没有处理,就在此时触发的硬超时里直接处理了。

注册定时器的接口是hrtimer_start_range_ns,如果是一个已经注册过的timer,会先把它从红黑树里取出来,并重新计算下一个触发的timer(可能是软计时也可能硬计时),更新到计时器硬件上(remove_hrtimer)。当没有下一个计时器时,超时时间是KTIME_MAX。更新timer的时长后再插入回计时队列(enqueue_hrtimer),如果发现它还是第一个计时器,则重新计算下一个触发的timer来将它的时间更新在计时器硬件上。

这里发现如果它真得是第一个计时器,则移除队列与加入队列后都有一个更新计时器硬件的过程,这两个过程是可以合并的,在remove_hrtimer上有一个reprogram参数把它置为false就可以了。但有个例外情况,就是计时器在其它cpu上。

另外,实时任务的软硬超时时间是一样的。

纳秒睡眠的实现

系统调用nanosleep(struct old_timespec32 __user *, rqtp, struct old_timespec32 __user *, rmtp) 中rqtp表示请求睡眠时间,rmtp表示剩余睡眠时间,当在指定的睡眠时间内被信号中断,则rmtp指向一个__kernel_timespec结构体,该结构体包含睡眠剩余时间的信息,以便调用者可以知道剩余的睡眠时间。如果不关心剩余的睡眠时间,可以将rmtp设置为NULL。

实现中调用hrtimer_nanosleep,核心在do_nanosleep,在hrtimer_sleeper_start_expires中将timer设置上,注册的回调是hrtimer_wakeup。然后调用schedule,提交当前线程(如果是内核中任务的话)并调度下一task。当调度回来时,删除timer(hrtimer_cancel),如果是超时唤醒的,则在hrtimer_wakeup中已将t->task为空,正常返回。如果是中断唤醒的,则do_nanosleep返回ERESTART_RESTARTBLOCK,它会使current->restart_block->fn 设置为hrtimer_nanosleep_restart,并更新超时时间,这样如果是用户调用的nanosleep,则在返回用户空间前,会自动重新调nanosleep。

你可能感兴趣的:(java,开发语言)