71v5 发表于 2014-06-24 21:06

freebsd9.2-APIC TIMER时钟中断的处理

本帖最后由 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相比,时钟这块的改动个人觉的还是挺大的:



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次。



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. */
    98static 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;
   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        }
页: [1]
查看完整版本: freebsd9.2-APIC TIMER时钟中断的处理