时钟在Linux中拥有广泛的应用,比如基于时间片的进程切换,文件的时间戳,定时器等。这些时钟可以分为两种,我们可以以此加以区别:
1,保存当前的时间和日期,以便能够通过time(),ftime(),和gettimeofday()系统调用把它们返回给用户程序,也可以由内核本身把当前时间作为文件和网络包的事件戳。
2,维持定时器,这种机制能够告诉内核或用户程序某一实际间隔已经过去了。
上面的两种时钟机制在Linux内核里面的实现都是及其复杂的,但是这两种时钟机制的原理及实现并不是本文关心的内容,因此阅读本文无法让你了解Linux的时钟机制的原理及实现。本文要讲的内容是Linux上述两种时钟机制的基础,它的主要作用是为上述两种时钟机制提供时钟源,所以它是基于时钟硬件的。
本文主要包括两方面的内容:
1,T2平台在硬件上提供了哪些时钟资源以及从硬件层面来看这些硬件时钟的工作机制。
2,Linux是如何驱动T2平台提供的硬件时钟资源以为Linux的两种时钟机制提供支持的。
计算机上除了本文所讲的时钟源外还存在一种叫做RTC的时钟硬件,它在系统关闭时仍然维护着机器的时间,当Linux启动后会从RTC里面取得当前时间,然后利用这个时间和本文所讲的时钟源计算系统的当前时间,当关闭系统时会把当前时间写入RTC,本文并不打算讲解RTC的相关内容,因为它取决于所用的RTC芯片,体系结构相关性不是很大。
T2时钟硬件:
在T2的每个虚拟处理器上都有两个成对的寄存器:System Tick (STICK) Register 和System Tick Compare (STICK_CMPRP) Register
,这两个寄存器相互协作就可以为系统提供时钟源,它们就是T2提供的时钟硬件资源。
STICK寄存器可以看作一个CPU的时钟节拍计数器,虚拟器的每个时钟节拍,该寄存器都会自加1。STICK寄存器的各个位域如下示:
npt ounter
63 62 0
从图中可以看出STICK寄存器分为两个域,其中63位是npt域,62到0是counter域,各域描述如下:
npt域:该域控制STICK寄存器在nonprivilged模式下的是否是可访问的,当npt=1,该寄存器在nonprivilged模式下是既不可读也不可写的,当npt=0时,该寄存器在nonprivilged模式下同样是不可写的,但是却是可读的。
conter域:时钟节拍计数器。
STICK_CMPR寄存器是与STICK寄存器配对的比较寄存器,当STICK_CMPR寄存器的stick_cmpr域与STICK寄存器counter域匹配时,系统就可以产生trap。STICK_CMPR
寄存器寄存器在nonprivilged模式下是不可访问的。STICK_CMPR寄存器的各个位域如下图所示:
int_dis stick_cmpr
63 62 0
从图中可以看出STICK_CMPR寄存器同样也分为两个域,其中位63是int_dis域,位62到0是 stick_cmpr域。各域描述如下:
int_dis域:中断允许位,当int_dis=1时,即使STICK_CMPR寄存器的stick_cmpr域与STICK寄存器counter域匹配,相应的trap也不会产生。
stick_cmpr域:比较域,如果该域与STICK寄存器counter域匹配,且int_dis=0 ,SOFTINT.sm=1其系统中其他中断控制位允许时,则会在相应的虚拟处理器上产生 interrupt_level_14 trap。
这两个寄存器相互协作,则会在相应的虚拟处理器上产生时钟中断,当然时钟中断的间隔是可编程的。这两个寄存器可以看作一个时钟源。
Linux T2 时钟软件:
对于Linux中的维护系统时间和日期的时钟机制(即上文的第一种时钟机制),Linux为了统一不同的时钟硬件,内核把每一个可以用来计时的时钟抽象为clocksource结构。因此驱动T2时钟硬件支持该时钟机制的软件最主要的工作就是实现clocksource对象并注册进系统。相应的,对于实现系统中定时器的时钟机制(上文中的第二种时钟机制),Linux把时钟源抽象为clock_event_device结构,因此驱动T2时钟硬件支持该时钟机制的软件最主要的工作就是实现clock_event_device对象并注册进系统。
Clocksource结构定义如下:
struct clocksource {
char *name;
struct list_head list;
int rating;
cycle_t (*read)(struct clocksource *cs);
int (*enable)(struct clocksource *cs);
void (*disable)(struct clocksource *cs);
cycle_t mask;
u32 mult;
u32 shift;
u64 max_idle_ns;
unsigned long flags;
cycle_t (*vread)(void);
void (*resume)(void);
#ifdef CONFIG_IA64
void *fsys_mmio; /* used by fsyscall asm code */
#define CLKSRC_FSYS_MMIO_SET(mmio, addr) ((mmio) = (addr))
#else
#define CLKSRC_FSYS_MMIO_SET(mmio, addr) do { } while (0)
#endif
cycle_t cycle_last ____cacheline_aligned_in_smp;
#ifdef CONFIG_CLOCKSOURCE_WATCHDOG
/* Watchdog related data, used by the framework */
struct list_head wd_list;
cycle_t wd_last;
#endif
};
该结构各成员的含义解释如下:
name:该clocksource对象的名称
rating:优先级
read:函数指针,该函数的作用是读取时钟源所记录的时钟节拍数
enable:使能该clocksource对象
disable:禁止该clocksource对象
mask:位掩码
mult:乘数系数
shift:移位系数
利用mult和shift可在时钟节拍数和这些时钟节拍经过多少纳秒之间进行转换。
clock_event_device结构定义如下:
struct clock_event_device {
const char *name;
unsigned int features;
unsigned long max_delta_ns;
unsigned long min_delta_ns;
unsigned long mult;
int shift;
int rating;
int irq;
const struct cpumask *cpumask;
int (*set_next_event)(unsigned long evt,
struct clock_event_device *);
void (*set_mode)(enum clock_event_mode mode,
struct clock_event_device *);
void (*event_handler)(struct clock_event_device *);
void (*broadcast)(const struct cpumask *mask);
struct list_head list;
enum clock_event_mode mode;
ktime_t next_event;
};
该结构各成员解释如下:
name:名称
features:表示该对象功能,CLOCK_EVT_FEAT_PERIODIC表示周期性中断,CLOCK_EVT_FEAT_ONESHOT表示单次中断
max_delta_ns:表示最大中断周期,单位是纳秒
min_delta_ns:表示最小中断周期,单位是纳秒
mult,shift成员的含义跟clocksource对象一样
rate:跟clocksource对象一样表示优先级
irq:中断号
cpumask:cpu掩码,表示该对象在哪个cpu上工作
set_next_event:该函数作用是设置下一次时钟中断的到期时间
set_mode:设置模式,可以是周期中断模式和单中断模式
event_hander:时钟中断到达时执行的中断处理函数
next_event:表示下一次该设备发出的中断时间,单位是纳秒
从这两个结构的定义可以看出,clocksource对象是不需要时钟中断的。看完这两个结构以后,我们看看Linux是怎么基于T2地时钟硬件实现这两个对象的。该实现位于arch/sparc/kernel/time_64.c文件中。
为了操作时钟源硬件,我们定义了一个结构struct sparc64_tick_ops,它里面保存了操作时钟硬件的函数,我们看该结构的定义:
struct sparc64_tick_ops {
unsigned long long (*get_tick)(void);
int (*add_compare)(unsigned long);
unsigned long softint_mask;
void (*disable_irq)(void);
void (*init_tick)(void);
unsigned long (*add_tick)(unsigned long);
char *name;
};
对于我们tlb_type==hypervisor而言,该结构即为stick_operations(后面会看到),stick_operations是 struct sparc64_tick_ops类型变量,它它定义如下:
static struct sparc64_tick_ops stick_operations __read_mostly = {
.name = "stick",
.init_tick = stick_init_tick,
.disable_irq = stick_disable_irq,
.get_tick = stick_get_tick,
.add_tick = stick_add_tick,
.add_compare = stick_add_compare,
.softint_mask = 1UL << 16,
};
我们来分析stick_operations的各成员。
stick_operations名称为”stick”。
init_tick成员是函数stick_init_tick函数,它是用来初始化时钟硬件的,我们看这个函数:
188 static void stick_init_tick(void)
189 {
190 /* Writes to the %tick and %stick register are not
191 * allowed on sun4v. The Hypervisor controls that
192 * bit, per-strand.
193 */
194 if (tlb_type != hypervisor) {
195 tick_disable_protection();
196 tick_disable_irq();
197
198 /* Let the user get at STICK too. */
199 __asm__ __volatile__(
200 " rd %%asr24, %%g2\n"
201 " andn %%g2, %0, %%g2\n"
202 " wr %%g2, 0, %%asr24"
203 : /* no outputs */
204 : "r" (TICK_PRIV_BIT)
205 : "g1", "g2");
206 }
207
208 stick_disable_irq();
209 }
第194到206行显然不是我们需要关心的。
第208行, stick_disable_irq函数禁止当STICK_CMPR寄存器的stick_cmpr域与STICK寄存器counter域匹配时所发生的trap,从前面的时钟硬件的描述来看,它是把STICK_CMPR寄存器的int_dis域设为1,我们看看这个函数:
180 static void stick_disable_irq(void)
181 {
182 __asm__ __volatile__(
183 "wr %0, 0x0, %%asr25"
184 : /* no outputs */
185 : "r" (TICKCMP_IRQ_BIT));
186 }
这是段内嵌汇编,它是使用wr指令把 TICKCMP_IRQ_BIT写入asr25寄存器中,asr25寄存器即是STICK_CMPR寄存器。TICKCMP_IRQ_BIT是1<<63。这样STICK_CMPR寄存器的位63就置为1了。
stick_operations的disable_irq成员初始化为函数stick_disable_irq,stick_disable_irq函数我们上面刚刚讲过
stick_operations的get_tick成员初始化为函数stick_get_tick,该函数的作用是读取STICK寄存器counter域得到所记录的节拍数,这个函数也使用了内嵌汇编,不过很简单,读者可自行分析。
stick_operations的add_tick成员初始化为函数stick_add_tick,该函数的作用是跟新STICK寄存器的counter域,把counter域增加某个指定的值,我们看这个函数:
221 static unsigned long stick_add_tick(unsigned long adj)
222 {
223 unsigned long new_tick;
224
225 __asm__ __volatile__("rd %%asr24, %0\n\t"
226 "add %0, %1, %0\n\t"
227 "wr %0, 0, %%asr24\n\t"
228 : "=&r" (new_tick)
229 : "r" (adj));
230
231 return new_tick;
232 }
这个函数也是一段内嵌汇编
第225行,读取STICK寄存器的值放在new_tick中
第226行,new_tick的值加上adj的值放在new_tick中
第227行,把新的new_tick写会STICK寄存器
stick_operations的add_compare成员初始化为函数stick_add_compare,该函数的作用把STICK_CMPR寄存器的stick_cmpr域设置为STICK寄存器的counter域加上n个节拍数,同时允许匹配成功时的trap发生,这样做是为经过n个时钟节拍后产生时钟中断,我们看该函数的实现:
234 static int stick_add_compare(unsigned long adj)
235 {
236 unsigned long orig_tick, new_tick;
237
238 __asm__ __volatile__("rd %%asr24, %0"
239 : "=r" (orig_tick));
240 orig_tick &= ~TICKCMP_IRQ_BIT;
241
242 __asm__ __volatile__("wr %0, 0, %%asr25"
243 : /* no outputs */
244 : "r" (orig_tick + adj));
245
246 __asm__ __volatile__("rd %%asr24, %0"
247 : "=r" (new_tick));
248 new_tick &= ~TICKCMP_IRQ_BIT;
249
250 return ((long)(new_tick - (orig_tick+adj))) > 0L;
251 }
第238行,读取STICK寄存器的值放到 orig_tick变量中。
第240行,清0 orig_tick变量的位63。
第242行,把orig_tick加上adj后写入STICK_CMPR寄存器,因为 orig_tick变量的位63已经被清0,所以当匹配时会发生trap
第246到250行,判断操作是否成功,即是否会在未来的某个时候产生时钟中断,判断的方法是再次读取STICK寄存器的counter域,判断其是否超过orig_tick+adj,如果超过则操作失败。
stick_operations的softint_mask是时钟中断在SOFTINT寄存器中的状态位,如果发生时钟中断,则该位会置位。
看完stick_operations 后,我们来看Linux T2的时钟硬件初始化函数,很显然,该函数会使用stick_operations实现clocksource对象和clock_event_device对象。初始化函数是time_init函数,会在start_kernel函数执行,我们看看这个函数:
830 void __init time_init(void)
831 {
832 unsigned long freq = sparc64_init_timers();
833
834 tb_ticks_per_usec = freq / USEC_PER_SEC;
835
836 timer_ticks_per_nsec_quotient =
837 clocksource_hz2mult(freq, SPARC64_NSEC_PER_CYC_SHIFT);
838
839 clocksource_tick.name = tick_ops->name;
840 clocksource_tick.mult =
841 clocksource_hz2mult(freq,
842 clocksource_tick.shift);
843 clocksource_tick.read = clocksource_tick_read;
844
845 printk("clocksource: mult[%x] shift[%d]\n",
846 clocksource_tick.mult, clocksource_tick.shift);
847
848 clocksource_register(&clocksource_tick);
849
850 sparc64_clockevent.name = tick_ops->name;
851
852 setup_clockevent_multiplier(freq);
853
854 sparc64_clockevent.max_delta_ns =
855
856 sparc64_clockevent.min_delta_ns =
857 clockevent_delta2ns(0xF, &sparc64_clockevent);
858
859 printk("clockevent: mult[%lx] shift[%d]\n",
860 sparc64_clockevent.mult, sparc64_clockevent.shift);
861
862 setup_sparc64_timer();
863 }
第832行,sparc64_init_timers函数的作用得到时钟硬件的时钟频率,我们这里其实就是CPU的频率,我们看看这个函数:
607 /* This is gets the master TICK_INT timer going. */
608 static unsigned long sparc64_init_timers(void)
609 {
610 struct device_node *dp;
611 unsigned long freq;
612
613 dp = of_find_node_by_path("/");
614 if (tlb_type == spitfire) {
615 unsigned long ver, manuf, impl;
616
617 __asm__ __volatile__ ("rdpr %%ver, %0"
618 : "=&r" (ver));
619 manuf = ((ver >> 48) & 0xffff);
620 impl = ((ver >> 32) & 0xffff);
621 if (manuf == 0x17 && impl == 0x13) {
622 /* Hummingbird, aka Ultra-IIe */
623 tick_ops = &hbtick_operations;
624 freq = of_getintprop_default(dp, "stick-frequency", 0);
625 } else {
626 tick_ops = &tick_operations;
627 freq = local_cpu_data().clock_tick;
628 }
629 } else {
630 tick_ops = &stick_operations;
631 freq = of_getintprop_default(dp, "stick-frequency", 0);
632 }
633
634 return freq;
635 }
第613行,得到设备节点树跟节点的节点号
第614到628行,这段分支语句我们不用关心,我们tlb_type == hypervisor
第630行,初始化tick_ops变量,该全局变量就是前面提到的 struct sparc64_tick_ops结构类型的变量,这里把它初始化为stick_operations。
第631行,得到跟节点的stick-frequency属性,该属性中即存有CPU频率
可以看出 sparc64_init_timers函数就是取得设备节点树根节点的stick-frequency属性,从而得到时钟硬件的频率的,
我们回到time_init函数中来,
第834行,tb_ticks_per_usec变量里面存放的是每微妙的时钟节拍数。
第836行,clocksource_hz2mult函数是Linux提供的标准函数,这个函数就不分析了,它的作用就是根据指定的shift得到与之对应的mult从而得到一个mult/shift对,mult/shift对在前面说过,通过他们可以在时钟节拍数和纳秒时间之间进行转换,转换的公式如下:
ns = (ticks*mult)>>shift
这里的mult保存在timer_ticks_per_nsec_quotient变量中,shift就是宏SPARC64_NSEC_PER_CYC_SHIFT。
第839行,初始化clocksource_tick的name成员,clocksource_tick就是我们 这里实现的clocksource对象,它的定义如下:
static struct clocksource clocksource_tick = {
.rating = 100,
.mask = CLOCKSOURCE_MASK(64),
.shift = 16,
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
};
它的name成员初始化为tick_ops结构的name成员,tick_ops即是前面分析过的stick_operations。
第840行,利用clocksource_hz2mult通过clocksource对象的shift成员计算出mult成员。
843行,初始化clocksource对象的read成员为clocksource_tick_read函数,该函数实际上就是调用stick_operations的read成员。
第848行,初始化好clocksource对象后这里把它注册进系统,这样Linux的维护系统时间的时钟机制就会利用该对象维护系统时间了。
第850行,从这里开始就是初始化clock_event_device对象了。这里的sparc64_clockevent变量就是clock_event_device类型的变量,这条语句把该clock_event对象的name设置为stick_operations的name成员
sparc64_clockevent成员定义如下:
static struct clock_event_device sparc64_clockevent = {
.features = CLOCK_EVT_FEAT_ONESHOT,
.set_mode = sparc64_timer_setup,
.set_next_event = sparc64_next_event,
.rating = 100,
.shift = 30,
.irq = -1,
};
上面的定义初始化了features成员为 CLOCK_EVT_FEAT_ONESHOT,即单次中断模式。
set_mode成员初始化为函数 sparc64_timer_setup,我们看该函数实现:
700 static void sparc64_timer_setup(enum clock_event_mode mode,
701 struct clock_event_device *evt)
702 {
703 switch (mode) {
704 case CLOCK_EVT_MODE_ONESHOT:
705 case CLOCK_EVT_MODE_RESUME:
706 break;
707
708 case CLOCK_EVT_MODE_SHUTDOWN:
709 tick_ops->disable_irq();
710 break;
711
712 case CLOCK_EVT_MODE_PERIODIC:
713 case CLOCK_EVT_MODE_UNUSED:
714 WARN_ON(1);
715 break;
716 };
717 }
从这个函数可以看出我们的clock_event_device对象只能设置程单中断模式和 CLOCK_EVT_MODE_SHUTDOWN模式,即禁止时钟,实际上就是禁止时钟中断。
set_next_event成员初始化为函数 sparc64_next_event,这个函数就不分析了,它实际上就是调用stick_operations的add_compare成员。
回到time_init函数中来
第852行,setup_clockevent_multiplier函数就不分析了,它实际上是根据shift成员初始化clock_event_device的mult成员。
第854行和856行,clockevent_delta2ns也是Linux的标准函数,就不分析了,这两行初始化clock_event_device的max_delta_ns成员和min_delta_ns
第862行,调用函数setup_sparc64_timer,我们来看这个函数:
753 void __devinit setup_sparc64_timer(void)
754 {
755 struct clock_event_device *sevt;
756 unsigned long pstate;
757
758 /* Guarantee that the following sequences execute
759 * uninterrupted.
760 */
761 __asm__ __volatile__("rdpr %%pstate, %0\n\t"
762 "wrpr %0, %1, %%pstate"
763 : "=r" (pstate)
764 : "i" (PSTATE_IE));
765
766 tick_ops->init_tick();
767
768 /* Restore PSTATE_IE. */
769 __asm__ __volatile__("wrpr %0, 0x0, %%pstate"
770 : /* no outputs */
771 : "r" (pstate));
772
773 sevt = &__get_cpu_var(sparc64_events);
774
775 memcpy(sevt, &sparc64_clockevent, sizeof(*sevt));
776 sevt->cpumask = cpumask_of(smp_processor_id());
777
778 clockevents_register_device(sevt);
779 }
解释这个函数之前,先讲解一点背景知识,在T2 里面存在着多个虚拟处理器,相应的每个虚拟处理器上都有硬件时钟源,这样在硬件上系统中就存在着多个时钟源。虽然如此,系统中却只存在一个clocksource对象,因为clocksource对象是用来维护系统时间的,而系统时间只能有一个,从时钟硬件上来看,clocksource对象操作的是STICK寄存器的counter域,而该域是用来保存CPU节拍的,因此在所有的虚拟处理器上,该域的值应该是相等的。stick_operations虽然提供了add_tick函数用来跟新STICK寄存器,但该函数主要使用来保持所有的虚拟处理器上STICK寄存器的counter域的同步的。因此系统中所有的时钟硬件只对应一个clocksource对象。与clocksouce对象不同,clock_event_device对象是使用时钟中断来维持系统中的定时器的,而每个虚拟处理器上都有各自不同的定时器实体,因此每个虚拟处理器上都有一个单独的clock_envent_device对象,系统中的每个时钟硬件都对应一个clock_evnet_device对象,这些clock_evnet_device对象可以看作是 sparc64_clockevent的一个副本,因此系统中真正起作用的clock_evnet_device对象是这些与每个虚拟处理器相关的副本,而不是sparc64_clockevent。这些副本定义为一个per_cpu变量,定义如下:
static DEFINE_PER_CPU(struct clock_event_device, sparc64_events);
现在来正式分析这个函数。
第761到764行,禁止系统中的中断。
第766行,调用stick_operations的init_tick成员,该成员前面分析过,它实际上就是禁止时钟中断,实际上这里是禁止了当前虚拟处理器的时钟中断
第769行,允许系统中的中断
第773行,取得当前虚拟处理器的clock_event_device对象副本
第775行,把之前初始化过的sparc64_clockevent复制为当前虚拟处理器的clock_event_device对象,这样当前cpu的clock_event_device就被初始化过了。
第776行设置clock_event_device对象的cpumask成员
第778行,把初始化号的clock_event_device对象注册如系统中,之后linux的定时器时钟机制就会使用该对象产生时钟中断了。
这样T2的时钟硬件的初始化就完成了,在linux运行过程中,除了会使用初始化过的clocksource对象和clock_event_device对象中提供的函数驱动时钟硬件。在这个过程中会产生时钟中断,我们来看一看时钟中断是怎么执行的。
时钟中断实际上是interrupt_level_14 trap,在Linux的trap table中,interrupt_level_14 trap hander是:
TRAP_IRQ(timer_interrupt, 14)
这里TRAP_IRQ宏会调用函数timer_interrupt,这个函数是在time_64.c定义的,我们来看这个函数:
729 void timer_interrupt(int irq, struct pt_regs *regs)
730 {
731 struct pt_regs *old_regs = set_irq_regs(regs);
732 unsigned long tick_mask = tick_ops->softint_mask;
733 int cpu = smp_processor_id();
734 struct clock_event_device *evt = &per_cpu(sparc64_events, cpu);
735
736 clear_softint(tick_mask);
737
738 irq_enter();
739
740 kstat_incr_irqs_this_cpu(0, irq_to_desc(0));
741
742 if (unlikely(!evt->event_handler)) {
743 printk(KERN_WARNING
744 "Spurious SPARC64 timer interrupt on cpu %d\n", cpu);
745 } else
746 evt->event_handler(evt);
747
748 irq_exit();
749
750 set_irq_regs(old_regs);
751 }
第736行,清除时钟中断状态位以接收下一次时钟中断。
第738行,进入中断执行环境。
第740行,在Linux T2的中断机制的讲解中我们知道中断请求号0默认分配给了时钟中断,因此利用中断请求号0的中断描述符中的中断状态域更新中断次数,中断描述符中的中断状态域为每个虚拟处理器都准备了一个子域,它们实际上是个计数器,记录了相应虚拟处理器上该中断发生的次数, kstat_incr_irqs_this_cpu函数是Linux标准函数,就不分析了。
第746行,执行clock_event_device对象中保存的时钟中断处理函数,在我们之前clock_event_device对象的初始化时,实际上并没有初始化clock_event_device的event_handler成员,实际上该成员的初始化是在Linux体系无关的代码中初始化的,当Linux初始化标准的Linux时钟机制时会为该成员赋值,赋的值也是Linux标准的时钟中断处理函数,由于我们不打算涉及体系结构无关代码,就不分析了。
第748行到750行,推出中断执行环境。
Linux有些与时钟节拍相关的操作并没有使用clocksource对象或者clock_event_device对象,它们是直接提供一些标准的API,体系结构相关代码为了支持它们所要做的工作就是实现这些标准的API。在T2平台上,实际上就是调用stick_operations的成员函数来实现这些API的,这些API包括:
void __delay(unsigned long loops);
void udelay(unsigned long usecs);
unsigned long long sched_clock(void)
int __devinit read_current_timer(unsigned long *timer_val)
我们只分析其中的__delay和udelay延时函数,其他的读者可自行分析。
808 void __delay(unsigned long loops)
809 {
810 unsigned long bclock, now;
811
812 bclock = tick_ops->get_tick();
813 do {
814 now = tick_ops->get_tick();
815 } while ((now-bclock) < loops);
816 }
817 EXPORT_SYMBOL(__delay);
__delay函数的功能是演示loops个时钟节拍,这个函数很简单,它就是一个while循环,先通过 tick_ops->get_tick读出时钟节拍的初始值,然后在while循环中等待,直到HSTICK寄存器中保存的时钟节拍计数器更新过loops,这时 tick_ops->get_tick读出的新的时钟节拍数与初始值的差应该会大于loops
819 void udelay(unsigned long usecs)
820 {
821 __delay(tb_ticks_per_usec * usecs);
822 }
823 EXPORT_SYMBOL(udelay);
udelay函数就是__delay函数延时一定的时钟节拍数,这个时钟
节拍数是通过要延时的微妙数计算出来的。 tb_ticks_per_usec变量前面的分析中已经初始化过,它保存的是每微妙有多少个时钟节拍。
这样Linux T2 时钟机制就分析完了。