- 论坛徽章:
- 0
|
本帖最后由 71v5 于 2014-07-02 01:03 编辑
为什么要选取APIC TIMER来描述,因为APIC TIMER包含在intel cpu中,intel芯片手册中对其有详尽的描述,并且与芯片的其它部分联系比较紧密,这样的话,就会对其有一个整体的把握。
不然的话,就需要看看8254,ACPI-HPET等相关的知识,估计会影响学习兴趣。总之就是用最简单且适用的方法说明一些复杂的问题。
关于如何对APIC TIMER编程,大家可以参考一下"Intel 64 and IA-32 Architectures Software Developer’s Manual Volume 3A: System Programming Guide, Part 1"
中的第十章,从中摘取了一些对APIC TIMER的部分描述,这样就可以大概知道APIC TIMER中断的处理。
The local APIC unit contains a 32-bit programmable timer that is available to software
to time events or operations. This timer is set up by programming four registers:
the divide configuration register , the Initial-Count register,current-count register,
and the LVT timer register.
The time base for the timer is derived from the processor’s bus clock, divided by the
value specified in the divide configuration register。
The timer can be configured through the timer LVT entry for one-shot or periodic
operation. In one-shot mode, the timer is started by programming its initial-count
register. The initial count value is then copied into the current-count register and
count-down begins. After the timer reaches zero, an timer interrupt is generated and
the timer remains at its 0 value until reprogrammed.
In periodic mode, the current-count register is automatically reloaded from the
initial-count register when the count reaches 0 and a timer interrupt is generated,
and the count-down is repeated. If during the count-down process the initial-count
register is set, counting will restart, using the new initial-count value. The initialcount
register is a read-write register; the current-count register is read only.
A write of 0 to the initial-count register effectively stops the local APIC timer, in both
one-shot and periodic mode.
这里做以下假设:
APIC TIMER为periodic mode。
APIC TIMER来跟踪hardclock,statclock,profclock。
描述APIC TIMER的struct eventtimer对象设置了ET_FLAGS_PERCPU标志。
如果没有设置ET_FLAGS_PERCPU标志,在SMP系统中,只有BSP激活了APIC TIMER,而AP上的时钟处理由
BSP向AP发送一个处理期间中断来实现。
和freebsd8.3相比,时钟这块的改动个人觉的还是挺大的:
[freebsd8.3时钟频率]:
APIC TIMER频率为2*hz,即每秒2000次,周期为0.5毫秒,每秒发出2000次中断,每次中断发生时,
相应的中断服务例程都要检查是否运行下面三个时钟:
hardclock:频率为hz,每秒1000次,当发生两次APIC TIMER时钟中断时,才运行一次hardclock。
statclock:频率为128,周期为1/128秒,即1/128秒运行一次statclock。
profclock:频率为2*hz,即每秒2000次,周期为0.5毫秒,每秒运行2000次。
[freebsd9.2时钟频率如下所示,这里假设和freebsd8.3保持一致]:
hardclock:频率为hz,每秒1000次,当发生两次APIC TIMER时钟中断时,才运行一次hardclock。
statclock:频率为128,周期为1/128秒,即1/128秒运行一次statclock。
profclock:频率为2*hz,即每秒2000次,周期为0.5毫秒,每秒运行2000次。- /**********************************************************************
- * timerperiod:当APIC-TIMER处于periodic mode时,APIC-TIMER的周期。
- hardperiod:hardclock的周期。
- statperiod:statclock的周期。
- profperiod:profclock的周期。
- 这里不用搞清楚这些时钟的频率和周期到底是多少,只要知道这些数据对象
- 代表什么就行了,这里就假设跟freebsd8.3中的值保持一致,这样不影响
- 下面的分析。
- ***************************************/
- 97 static struct bintime timerperiod; /* Timer period for periodic mode. */
- 98 static struct bintime hardperiod; /* hardclock() events period. */
- 99 static struct bintime statperiod; /* statclock() events period. */
- 100 static struct bintime profperiod; /* profclock() events period. */
- /***********************************************************************
- * 只有当描述event timer的struct eventtimer对象没有设置ET_FLAGS_PERCPU
- 标志时,才使用下面两个变量。
- *****************************************/
- 101 static struct bintime nexttick; /* Next global timer tick time. */
- 102 static struct bintime nexthard; /* Next global hardlock() event. */
复制代码 下面简单分析一下当APIC TIMER中断发生时以及statclock被触发后所做的处理,因为statclock检查thread是否用完其时间片,需要的时候被重新调度,
statclock触发后将调用statclock_cnt函数。
当APIC TIMER中断发生时,内核会调用其相应的中断服务历程,该汇编例程会进一步调用高级C函数lapic_handle_timer,下面大概分析一下lapic_handle_timer函数的
处理过程:- 783 void
- 784 lapic_handle_timer(struct trapframe *frame)
- 785 {
- /**************************************************************
- * 786:
- la指向描述local APIC的struct lapic对象。
- freebsd定义了一个类型为struct lapic的数组lapics
- 来描述local APIC。
- 数组大小为MAX_APIC_ID + 1,数组lapics的索引为当前
- local apic id。
- 787:可以通过oldframe很方便的访问保存在线程内核栈
- 上的硬件上下文。
- 788:指向中断发生时正在运行线程对应的struct thread对象,
- 即curthread。
- **************************************/
- 786 struct lapic *la;
- 787 struct trapframe *oldframe;
- 788 struct thread *td;
- 789
- 790
- /*****************************************************************
- * Send EOI first thing.
- 根据intel芯片手册上的描述,此时要向EOI Register发出一个写
- 操作,对这个写操作,cpu做出下面的回应:
- "Upon receiving an EOI, the APIC clears the highest priority
- bit in the ISR and dispatches the next highest priority
- interrupt to the processor。"
-
- ****************************/
- 791 lapic_eoi();
- 792
- 793 #if defined(SMP) && !defined(SCHED_ULE)
- 794 /*********************************************************************
- 795 * Don't do any accounting for the disabled HTT cores, since it
- 796 * will provide misleading numbers for the userland.
- 797 *
- 798 * No locking is necessary here, since even if we loose the race
- 799 * when hlt_cpus_mask changes it is not a big deal, really.
- 800 *
- 801 * Don't do that for ULE, since ULE doesn't consider hlt_cpus_mask
- 802 * and unlike other schedulers it actually schedules threads to
- 803 * those CPUs.
- 支持SMP但是使用的不是ULE调度程序时,此时检查当前cpu是否被hlt了,
- 如果是,就直接返回。
- 804 */
- 805 if (CPU_ISSET(PCPU_GET(cpuid), &hlt_cpus_mask))
- 806 return;
- 807 #endif
- 808
- 809
- /**********************************************************
- * Look up our local APIC structure for the tick counters.
- 810:la指向描述当前cpu的local APIC的struct lapic对象。
- 811:递增la_timer_count成员,用来标识APIC TIMER发生中断
- 的次数。
- *****************************/
- 810 la = &lapics[PCPU_GET(apic_id)];
- 811 (*la->la_timer_count)++;
- 812 critical_enter();
- /*********************************
- * 这里只关心818行的函数调用。
- * 818:这里将调用timercb函数。
- ************************/
- 813 if (lapic_et.et_active) {
- 814 td = curthread;
- 815 td->td_intr_nesting_level++;
- 816 oldframe = td->td_intr_frame;
- 817 td->td_intr_frame = frame;
- 818 lapic_et.et_event_cb(&lapic_et, lapic_et.et_arg);
- 819 td->td_intr_frame = oldframe;
- 820 td->td_intr_nesting_level--;
- 821 }
- 822 critical_exit();
- 823 }
复制代码 [函数timercb]:- /***************************************************************
- * Hardware timer callback function.
- 参数描述:
- et:指向lapic_et,即&lapic_et。
- arg:NULL。
- *****************************/
- 338 static void
- 339 timercb(struct eventtimer *et, void *arg)
- 340 {
- 341 struct bintime now;
- 342 struct bintime *next;
- 343 struct pcpu_state *state;
- 344 #ifdef SMP
- 345 int cpu, bcast;
- 346 #endif
- 347
- 348
- /************************************************************************
- * Do not touch anything if somebody reconfiguring timers.
- static u_int busy = 0; Reconfiguration is in progress.
-
- 如果要重新配置timers,此时就要以参数0调用configtimer函数,之后紧接着
- 以参数1调用configtimer函数。在以参数0调用configtimer函数的过程中,
- 会将busy变量设置为1。
- ****************************/
- 349 if (busy)
- 350 return;
- 351
- /*********************************************************************************
- * Update present and next tick times.
- 这里的state指向的数据类型为struct pcpu_state,这里的struct pcpu_state
- 类似于linux中的每cpu变量,系统中每个cpu都有其对应的struct pcpu_state
- 对象,用来描述该cpu上是否有相应的调度事件,hardlock event,statclock event等等。
-
- timerperiod:当APIC-TIMER处于periodic mode时,APIC-TIMER的周期。
-
- 353-363:执行完后,struct pcpu_state对象的nexttick成员已经被更新,保存了下一次
- APIC TIMER中断发生时的时间戳。
- *******************************************/
- 352 state = DPCPU_PTR(timerstate);
- 353 if (et->et_flags & ET_FLAGS_PERCPU) {
- 354 next = &state->nexttick;
- 355 } else
- 356 next = &nexttick;
- /*********************************************
- * 357-360:APIC TIMER为periodic mode。
- *************************/
- 357 if (periodic) {
- 358 now = *next; /* Ex-next tick time becomes present time. */
- 359 bintime_add(next, &timerperiod); /* Next tick in 1 period. */
- 360 } else {
- 361 binuptime(&now); /* Get present time from hardware. */
- 362 next->sec = -1; /* Next tick is not scheduled yet. */
- 363 }
- /* 364:更新struct pcpu_state对象的now成员 */
- 364 state->now = now;
- 365 CTR4(KTR_SPARE2, "intr at %d: now %d.%08x%08x",
- 366 curcpu, now.sec, (unsigned int)(now.frac >> 32),
- 367 (unsigned int)(now.frac & 0xffffffff));
- 368
- /*******************************************************************************
- * 369-386:
- 在SMP中,仅在描述APIC TIMER的struct eventtimer对象没有ET_FLAGS_PERCPU标志时
- 进行相应的处理。这段代码仅在BSP上执行,BSP查看AP是否有相应的时钟事件发生,
- 如果有,就将其对应的struct pcpu_state对象的ipi成员设置为1,表示需要给相应
- 的AP发送一个处理器间中断。
- *****************************************/
- 369 #ifdef SMP
- 370 /* Prepare broadcasting to other CPUs for non-per-CPU timers. */
- 371 bcast = 0;
- 372 if ((et->et_flags & ET_FLAGS_PERCPU) == 0 && smp_started) {
- 373 CPU_FOREACH(cpu) {
- 374 state = DPCPU_ID_PTR(cpu, timerstate);
- 375 ET_HW_LOCK(state);
- 376 state->now = now;
- 377 if (bintime_cmp(&now, &state->nextevent, >=)) {
- 378 state->nextevent.sec++;
- 379 if (curcpu != cpu) {
- 380 state->ipi = 1;
- 381 bcast = 1;
- 382 }
- 383 }
- 384 ET_HW_UNLOCK(state);
- 385 }
- 386 }
- 387 #endif
- 388
- 389
- /***************************************************************
- * Handle events for this time on this CPU.
- 390:调用函数handleevents处理相应的时钟事件。
- ***********************/
- 390 handleevents(&now, 0);
- 391
- /**************************************************************
- * 392-405:
- 根据上面369-387之间的结果,来决定是否向AP发送一个类型为
- IPI_HARDCLOCK的处理器间中断。
- *****************************/
- 392 #ifdef SMP
- 393 /* Broadcast interrupt to other CPUs for non-per-CPU timers. */
- 394 if (bcast) {
- 395 CPU_FOREACH(cpu) {
- 396 if (curcpu == cpu)
- 397 continue;
- 398 state = DPCPU_ID_PTR(cpu, timerstate);
- 399 if (state->ipi) {
- 400 state->ipi = 0;
- 401 ipi_cpu(cpu, IPI_HARDCLOCK);
- 402 }
- 403 }
- 404 }
- 405 #endif
- 406 }
复制代码 [函数handleevents]:- /*************************************************************
- * Handle all events for specified time on this CPU
- now:当前的时间戳。
- fake:正常情况下调用handleevents函数时,fake参数都为0.
- 不过,在SMP系统中,当AP被启动后并初始化自己的时钟
- 时,会以参数2调用handleevents函数,这里就忽略这种
- 情况。
- *************************/
- 180 static int
- 181 handleevents(struct bintime *now, int fake)
- 182 {
- 183 struct bintime t;
- 184 struct trapframe *frame;
- 185 struct pcpu_state *state;
- 186 uintfptr_t pc;
- 187 int usermode;
- 188 int done, runs;
- 189
- 190 CTR4(KTR_SPARE2, "handle at %d: now %d.%08x%08x",
- 191 curcpu, now->sec, (unsigned int)(now->frac >> 32),
- 192 (unsigned int)(now->frac & 0xffffffff));
- /**********************************************************************************************************
- * 194-202:
- * 此时将执行else条件语句。
- #define TRAPF_USERMODE(framep) ((ISPL((framep)->tf_cs) == SEL_UPL) || ((framep)->tf_eflags & PSL_VM))
- #define TRAPF_PC(framep) ((framep)->tf_eip)
- 200:如果发生APIC TIMER中断时cpu处于用户态或者处于vm86模式,usermode的值为非零,这里只关心
- cpu是否处于用户态。
- pc:保存在线程内核栈上的eip(中断发生时eip的值)
- *******************************************/
- 193 done = 0;
- 194 if (fake) {
- 195 frame = NULL;
- 196 usermode = 0;
- 197 pc = 0;
- 198 } else {
- 199 frame = curthread->td_intr_frame;
- 200 usermode = TRAPF_USERMODE(frame);
- 201 pc = TRAPF_PC(frame);
- 202 }
- 203 /* 204:state指向当前cpu对应的struct pcpu_state对象 */
- 204 state = DPCPU_PTR(timerstate);
- 205
- /**********************************************************
- * 206-217: runs记录相应clock被触发的次数。
- 处理hardclock,并更新nexthard成员。
- ********************************/
- 206 runs = 0;
- 207 while (bintime_cmp(now, &state->nexthard, >=)) {
- 208 bintime_add(&state->nexthard, &hardperiod);
- 209 runs++;
- 210 }
- 211 if ((timer->et_flags & ET_FLAGS_PERCPU) == 0 &&
- 212 bintime_cmp(&state->nexthard, &nexthard, >))
- 213 nexthard = state->nexthard;
- 214 if (runs && fake < 2) {
- 215 hardclock_cnt(runs, usermode);
- 216 done = 1;
- 217 }
- /**********************************************************
- * 218-226: runs记录相应clock被触发的次数。
- 处理statclock,并更新nextstat成员,statclock检查线程的
- 时间片是否用完。
- ********************************/
- 218 runs = 0;
- 219 while (bintime_cmp(now, &state->nextstat, >=)) {
- 220 bintime_add(&state->nextstat, &statperiod);
- 221 runs++;
- 222 }
- 223 if (runs && fake < 2) {
- 224 statclock_cnt(runs, usermode);
- 225 done = 1;
- 226 }
- /***********************************************************
- * 227-238:
- 只有当线程执行了profil系统调用时,这里才会检查profclock。
- static int profiling = 0; Profiling events enabled.
- ******************************/
- 227 if (profiling) {
- 228 runs = 0;
- 229 while (bintime_cmp(now, &state->nextprof, >=)) {
- 230 bintime_add(&state->nextprof, &profperiod);
- 231 runs++;
- 232 }
- 233 if (runs && !fake) {
- 234 profclock_cnt(runs, usermode, pc);
- 235 done = 1;
- 236 }
- 237 } else
- 238 state->nextprof = state->nextstat;
- 239
- 240 #ifdef KDTRACE_HOOKS
- 241 if (fake == 0 && cyclic_clock_func != NULL &&
- 242 state->nextcyc.sec != -1 &&
- 243 bintime_cmp(now, &state->nextcyc, >=)) {
- 244 state->nextcyc.sec = -1;
- 245 (*cyclic_clock_func)(frame);
- 246 }
- 247 #endif
- 248 /* 249-261:忽略 */
- 249 getnextcpuevent(&t, 0);
- 250 if (fake == 2) {
- 251 state->nextevent = t;
- 252 return (done);
- 253 }
- 254 ET_HW_LOCK(state);
- 255 if (!busy) {
- 256 state->idle = 0;
- 257 state->nextevent = t;
- 258 loadtimer(now, 0);
- 259 }
- 260 ET_HW_UNLOCK(state);
- 261 return (done);
- 262 }
复制代码 |
|