android手机高精度定时机制--higher timer tick 笔记版 转载请注明出处--- crosskernel@gmail.com

来源:互联网 发布:淘宝新品开直通车 编辑:程序博客网 时间:2024/05/16 23:37

作者   crosskernel@gmail.com


没想到这篇随意记录的笔记成为老夫博客里阅读量最多的。看来这的确是兄弟们关注较多的地方,待老夫抽时间把这个部分重新整理下,写一个从内核到应用API的更新吧。

这里简单说一下android系统定时的原理。


首先,万佛归宗,在ARM SOC硬件体系中,通常会提供几个硬件计数器,输入为PLL过来的脉冲,计数到一定数量产生中断。这就是硬件时钟,可见其精度可以达到PLL的脉冲周期。

但是这个时钟只能被内核感知,而且由于内核有时会关闭中断,所以这个时钟中断有时不能被及时收到,产生偏移。但这不要紧,这只是脉冲周期级别的偏移,精度依然可以接受。

再扯远一点,以前内核的定时机制是以tick为单位,而tick通常是1ms到数十ms产生一次,这依据具体的硬件系统而变。这样其精度就只能以tick为单位,所以早期的内核里tick就以为可控制的时间精度。

再往后人们希望更精确的定时,于是希望依据硬件时钟精度来控制定时,于是就有了一个内核patch,在tick之间插入时候时钟,再往后随着linux架构的不断完善,出现的hrtimer,这是不管三七二十一,以硬件时钟作为定时的依据,而ticker只是其中的一种情况。

但是,hrtimer并不意味着依据此定时的应用就能真正的或者其精度(抛去中断不能及时接受而产生的偏移)。这是因为作为一个分时系统(基于linux内核的android系统完全遵循其规律),即使到了触发时刻如果对应的线程不能被及时调度,时钟到来没有意义。所以调度是另外一个最大的影响。

上面说的定时是基于硬件时钟的定时机制,其前提是系统正常工作,其特点是精度高,但是系统休眠时这个机制就没法工作。这时就是另外一个时钟RTC的任务了。

所谓RTC即实时时钟,在 ARM SOC硬件体系中有一块区域非常特殊,由单独的电路供电,而且有一个单独的晶振为其提供脉冲,及时休眠也不例外,这就是RTC时钟。

RTC不停地对晶振脉冲计数,用来长时间的定时,它有着唤醒处理器的作用,在Andorid系统中使用的定时通常是基于RTC工作的。


/****************************************原笔记*****************************************************/



android手机内核时钟整体架构分为上下两层,下层理解为物理时钟的操控,即按照上层时钟逻辑的要求产生物理时钟中断、编程产生下一次时钟中断、切换时钟中断产生的模式、初始、关闭物理时钟。而上层时钟的作用是根据内核运行时的要求对下层下发各种命令,并根据下层送上来的时钟事件驱动内核的high resolution timer,schedule tick,tickless sleep等机制的运行。


1 初始化

Linux内核用struct clock_event_device;来管理时钟设备。
举例来看:对于PXA系列SOC上,用于内核时钟的定时器,就用了一个如下的结构来管理:


static struct clock_event_device ckevt_pxa_osmr0 = {
.name = "osmr0",
.features = CLOCK_EVT_FEAT_ONESHOT,
.shift = 32,
.rating = 200,
.cpumask = CPU_MASK_CPU0,
.set_next_event= pxa_osmr0_set_next_event,// 对物理寄存器编程以产生下一次时钟中断
.set_mode = pxa_osmr0_set_mode,//根据内核抽象逻辑的时钟产生要求,实现物理芯片的操作
};


PXA 板级支持包在初始化的时候将这个内核时钟设备注册到内核。在内核源码树下$SRC_ROOT/kernel/time里的文件是在内核逻辑层面管理时钟的代码。PXA 板级支持包注册的ckevt_pxa_osmr0会在这里被近一步初始化:
当一个时钟设备被注册到内核后,内核通过notify机制激活时钟相关管理代码,对于注册新的时钟设备tick_check_new_device函数将初始化这个时钟


/*
 * Check, if the new registered device should be used.
 */
static int tick_check_new_device(struct clock_event_device *newdev)
{

tick_setup_device(td, newdev, cpu, cpumask);//初始化该设备
if (newdev->features & CLOCK_EVT_FEAT_ONESHOT)
tick_oneshot_notify();


spin_unlock_irqrestore(&tick_device_lock, flags);
return NOTIFY_STOP;



return ret;
}


static void tick_setup_device(struct tick_device *td,
     struct clock_event_device *newdev, int cpu,
     cpumask_t cpumask)
{





//注意在这里,内核根据该时钟的模式,将其设置为固定周期的内核时钟中断,还是非固定周期的内核时钟中断


if (td->mode == TICKDEV_MODE_PERIODIC)
tick_setup_periodic(newdev, 0);
else
tick_setup_oneshot(newdev, handler, next_event);

}


这里关键的操作是对于固定周期的内核时钟将其struct clock_event_device 的成员函数event_handler设置为tick_handle_periodic;


void tick_handle_periodic(struct clock_event_device *dev)
{

next = ktime_add(dev->next_event, tick_period);//确定下一次时钟中断的时间,注意这里是tick_period,即为传统unix操作系统下的tick时间。
for (;;) {
if (!clockevents_program_event(dev, next, ktime_get()))
return;
tick_periodic(cpu);
next = ktime_add(next, tick_period);
}
}


2 运行时分析
首先从内核中断体系来看,内核时钟中断机制是其一个常规的中断。PXA BSP包将
static struct irqaction pxa_ost0_irq = {
.name = "ost0",
.flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
.handler = pxa_ost0_interrupt,
.dev_id = &ckevt_pxa_osmr0,
};
中断处理单元,在初始化时通过static void __init pxa_timer_init(void)挂入内核中断处理链表,当硬件时钟中断到来时,内核找到pxa_ost0_irq的handler成员函数执行。而该中断处理函数只做了2件事:关了时钟中断;将中断时间送给struct clock_event_device的event_handler成员函数来执行。
static irqreturn_t
pxa_ost0_interrupt(int irq, void *dev_id)
{
struct clock_event_device *c = dev_id;


/* Disarm the compare/match, signal the event. */
OIER &= ~OIER_E0;
OSSR = OSSR_M0;
c->event_handler(c);


return IRQ_HANDLED;
}
这里的event_handle即为我们上文看到的tick_handle_periodic函数


3 切换


为什么要切换,其实如果linux内核没有加入这一部分,无论timer这部分代码改动多大,都只是表面的形式的变化,其内核工作机理和原先的是一样。


但是内核加入的这个新功能,使得linux向前进化了一大步。我们可以分析一下,既然时钟中断的周期是固定的,那么我们能够获得的最小定时粒度,就决定于这个系统时钟中断周期,这个值如果太小就会使系统中时钟中断过于密集,如果太大系统的最小定时粒度就会过大,严重影响实时性。所以对于大部分处理器架构这个值在1~10MS之间。但是对于一些实时应用来说1MS实在是太长的时间的。而现代处理器无论是基于X86-64、IA32、IA64的PC、服务器系统,还是基于的ARM低功耗嵌入式处理器,其硬件定时间隔的能力都远远小于1MS。另一方面,有些嵌入式芯片在sleep状态下,不希望被时钟中断频繁打断,因为如果中断到来这些芯片会切换到normal状态下,造成不必要的耗电。所以基于以上情况,内核社区大幅度修改了linux内核时钟架构,使得内核时候以我们需要的时间间隔到来。其具体做法如下:




首先要将固定周期的cpu时钟中断,改成非固定周期的cpu时钟中断。


$SRC_ROOT/kernel/time/tick-oneshot.c
int tick_switch_to_oneshot(void (*handler)(struct clock_event_device *))
{
struct tick_device *td = &__get_cpu_var(tick_cpu_device);
struct clock_event_device *dev = td->evtdev;


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;
}
//这个本来是0
td->mode = TICKDEV_MODE_ONESHOT;
dev->event_handler = handler;
clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT);
tick_broadcast_switch_to_oneshot();
return 0;
}






#ifdef CONFIG_HIGH_RES_TIMERS
/**
 * tick_init_highres - switch to high resolution mode
 *
 * Called with interrupts disabled.
 */
int tick_init_highres(void)
{
return tick_switch_to_oneshot(hrtimer_interrupt);
}
#endif



0 0
原创粉丝点击