MINIX3 内核时钟分析 

4.1 内核时钟概要 

先想想为什么 OS 需要时钟?时钟是异步的一个非常重要的标志,设想一下,如 果我们的应用程序需要在多少秒后将触发某个程序或者进程,我们该怎么做到? 就需要一个时钟的鼎力相助才能完成这个项工作。我的意思非常明确,就是说内 核时钟就是一个非常重要的部件,它可以完成分时系统的调度功能,同时它也能 为应用程序提供一个非常方便的异步处理问题的功能 

现在我们简要的来看下时钟芯片的构造基本原理,如果需要了解详细的时钟芯 片,可以参考 intel 时钟芯片的详细编程资料。 

一般时钟芯片就是晶体振荡器,其主要功能就是计时,我们设定一个寄存器,芯 片每振荡一次,寄存器内容自动减一,知道寄存器减到 0,它就会触发时钟芯片 其他逻辑部件自动产生一个时钟中断,CPU 识别之后就会自动进入时间中断处 理程序完成应该完成的  。上面的操作过程其实就是 Intel 时钟芯片的一个简单工 作流程。我们可以用图表示: 

55 

我们现在来分析时钟任务能够给我们带来什么潜在的功能: 

第一,既然是时钟,也就是有一个最基本的功能,可以计时,也就是说,可以计
算现在是哪年哪月哪日哪个时辰。 

第二,上面那个功能对于 CPU 而言是一个非常简单的功能,时钟能够给 CPU 在
精确到多少节拍时产生一个硬件中断,这个非常的重要 

第三,如理用户应用进程的类似于 alarm 的系统调用。这个系统调用是一个比较
复杂的过程,它涉及到 PM 机制,我们会在后面章节有详细的说明。
分析完潜在的功能,我们现在又来看下这个问题, 

如果现在我指定若干个时间点产生中断,我们知道 Intel 时钟芯片只有 4 个,但 是怎么来处理若干个呢?MINIX3 是采用一种队列的做法,也就是把一系列时间 点组成一个队列,时钟芯片以一个固定的时间来产生中断,每产生一个时间中断, (这个时间中断还是固定的),检查时钟队列队头有没有超时的,如果有,则应 该马上发送一个通知(注意这里是通知)给相应的目标进程,发送完之后,就把 对头时间点删除掉,换上后面一个,以此类推,往后面就是一样的动作。时钟任 务处理用户进程需要 alarm 调用就是这样一个过程。 

时钟整体任务基本如上面所描述的,现在用一个图来阐明上面的动作过程。 

56 

4.2 MINIX3 内核时钟源码导读 

我们现在深入内部源码分析: 

我们来先从另外一个角度来分析:我们知道时钟硬件会经过一段时间产生中断, 内核会根据这种中断进入向量处理地址,也就是时钟硬件处理地址。接下来就是 来执行这个时钟硬件处理程序。另外一方面,时钟任务一直处在阻塞状态,现在 时钟硬件处理程序就会发一个信息给时钟任务,时钟任务就会进入调度队列投入 运行,之后就进行完成一次时钟处理任务。 

我们先从这个角度来分析程序: 

57 

/*=================================================================== ========* 

* clock_handler *

*==================================================================== =======*/ 

PRIVATE int clock_handler(hook)
irq_hook_t  *hook; 

/* This executes on each clock tick  (i.e., every time the timer chip
generates    an interrupt). It does a little bit of work so the clock task
does not have to be called on every tick.    The clock task is called when: 

*这个例程在每一个时钟节拍用完是都用使用,每一个时钟芯片将产生一个中断. 事实上它只做很少一部分的事情也就是没有必要时钟任务都需要唤醒,时钟任务 主要在下面种情况下被调用: 

* (1) the scheduling quantum of the running process has expired, or

* (1) 正在运行的进程时间片已经耗尽

* (2) a timer has expired and the watchdog function should be run.

* (2)一个时钟器已经耗尽并且看门狗函数应该运行起来

* Many global global and static variables are accessed here.    The safety of this must be justified. All scheduling and message passing code
acquires a    lock by temporarily disabling interrupts, so no conflicts
with calls from the task level can occur. Furthermore, interrupts are not reentrant, the interrupt handler cannot be bothered by other interrupts. * 许多全名变量和静态变量能够在这里获取。所以这里的安全应该得到保证.
所有调度过程和消息传送代码时通过关闭临时的中断来获取一个锁,所以没有从 任务层面上不会发出调动冲突.并且中断不能嵌套,也就是中断处理器不用担心
其他中断器会干扰中断过程 

* Variables that are updated in the clock's interrupt handler: *  lost_ticks: 

*  Clock ticks counted outside the clock task. This for example *  is used when the boot monitor processes a real mode interrupt. * 时钟tick记录外部的时钟任务 

*   realtime: 

*   The current uptime is incremented with all outstanding ticks. * 当前更新时间是所有未加偿付的时钟节拍和 

*  proc_ptr, bill_ptr: 

* These are used for accounting.    It does not matter if proc.c

* is changing them, provided they are always valid pointers,

* since at worst the previous process would be billed.

这个被用来计数,它不管proc.c文件是否正在改变这个值,提供给他们的 总是无效指针,因为前一个指针将会付账 

*/ 

register unsigned ticks; 

58 

/* Acknowledge the PS/2 clock interrupt.  */ 

if  (machine.ps_mca) outb(PORT_B, inb(PORT_B)  | CLOCK_ACK_BIT); 

/* Get number of ticks and update realtime.  */ 

//得到ticks数量并且更新实时时间 

ticks  = lost_ticks  +  1;//lost_ticks记录了丢失的时钟记录,是一个全局
变量 

lost_ticks  =  0;//清 

realtime  += ticks;//realtime需要及时更新 

/* Update user and system accounting times. Charge the current process
for user time. If the current process is not billable, that is, if a
non-user process is running, charge the billable process for system time
as well. Thus the unbillable process' user time is the billable user's
system time. 

更新用户和系统计时。为当前的用户进程更新用户时间。如果当前进程是不可 支付的也就是说,如果一个非用户进程正在运行,为系统时间掌控可以支付的进 程。不能支付的进程的用户时间是可以支付用户的系统时间 

*/ 

proc_ptr->p_user_time  += ticks; 

//如果进程是可抢占的,则p_ticks-_left值应该减掉ticks值,这个ticks在前 面有介绍 

if  (priv(proc_ptr)->s_flags & PREEMPTIBLE)  {
proc_ptr->p_ticks_left  -= ticks; 

//如果是不可支付的则更新bill_ptr进程 

if  (!  (priv(proc_ptr)->s_flags & BILLABLE))  {
bill_ptr->p_sys_time  += ticks; 

bill_ptr->p_ticks_left  -= ticks;

}

/* Check if do_clocktick() must be called. Done for alarms and 

scheduling. Some processes, such as the kernel tasks, cannot be preempted.
检查时候do_clocktick()一定要被调用。为警报器和调度器做一系列动作。
一些进程,如内核任务,是不不能被抢占。 

*/ 

//next_timeout指向一下个计时器,如果这个计时器绝对值已经小realtime, 或者当前运行的进程时间片已经耗尽 则需用通知时钟任务进行处理相关程序 //这里表明了一个问题:时钟任务只在种结果下才会执行,第一看门狗警报器发 生了第当前进程的时间已经用完了。 

if  ((next_timeout  <= realtime)  ||  (proc_ptr->p_ticks_left  <=  0))  { //这里将pre_ptr变成proc_ptr,主要保持当前正在运行的进程 

prev_ptr  = proc_ptr; /* store running process  */

59 

//给CLOCK任务发送一个通知,这个通知效率最快,CLOCK此时一定是处于应该是 处于阻塞状态,我们发送通知后,Clock任务就会在调度任务执行。 

lock_notify(HARDWARE, CLOCK); /* send notification  */

return(1); /* reenable interrupts  */

我们想下这个问题:时钟任务应该是以一个进程的形式存在,既然是以进程的形
式存在,我们就应该想到这个问题,进程应该拥有自己的硬件和软件资源,进程
应该拥有各种状态,包括阻塞态,运行态。现在我们继续往下面分析时钟任务 

这个函数是时钟进程的主体部分: 

我们在这里先看下执行示意图: 

/*=================================================================== ========* 

* clock_task

*这是时钟任务整体的一个入口 

*==================================================================== 

60 

=======*/ 

PUBLIC void clock_task()

/* Main program of clock task. If the call is not HARD_INT it is an error.
*/ 

// 时钟任务只接受硬件消息,这个消息已经定义好了,任何进程发送消息都是 用这个m来//储存 

message m; /* message buffer for both input and output  */

int result; /* result returned by the handler  */

//初始化时钟任务,这个函数在下个函数中有介绍 

init_clock(); /* initialize clock task  */

//这个任务的最重要的循环入口.用于接受任何地方发送过来的消息 

/* Main loop of the clock task.    Get work, process it. Never reply. */ while  (TRUE)  { 

/* Go get a message.  */
//接受任何消息 

receive(ANY, &m); 

//处理发送请求,当然这个消息只接受硬件硬件请求,也就是时钟计数器已经 耗尽,这里 

//会调用do_clocktick(&m)运行 

/* Handle the request. Only clock ticks are expected.  */ switch  (m.m_type)  { 

case HARD_INT: 

result  = do_clocktick(&m); /* handle clock tick  */

break;

default: /* illegal request type  */

kprintf("CLOCK: illegal request  %d from  %d.\n",

m.m_type,m.m_source);

}

}

}

Doclocktick(&m)是一个非常核心的函数,主要用于处理时间中断,事实上前面 的clock_handler是一个入口函数,只是改变一些值,而这个函数是真正意义上 的处理核心信息。这样的编排和设计主要也是为了高效起见,并不是每一次时间 中断都应该进行时间处理,所以编排一个判断接口函数是很有作用的。现在我们 继续往do_clocktick(&m)里分析: 

/*=================================================================== ========* 

* do_clocktick *

时间处理函数,主要用于处理真正需要时间中断处理的函数,函数的设计主要是
为了很高效的时间而存在,所以对于整个函数而言,很少看到复杂的计算
*====================================================================
=======*/ 

61 

PRIVATE int do_clocktick(m_ptr) 

message  *m_ptr; /* pointer to request message  */

/* Despite its name, this routine is not called on every clock tick. It
* is called on those clock ticks when a lot of work needs to be done.
*/ 

//取决与它的名字,这个例程不是每次时间中断都会调用,它是当有许多事情要
做时菜需要被调用,(这里的事情要理解好,事情主要是点:看门狗警报器触发)
/* A process used up a full quantum. The interrupt handler stored this
* process in 'prev_ptr'.    First make sure that the process is not on
the scheduling queues.    Then announce the process ready again. Since it
has    no more time left, it gets a new quantum and is inserted at the right
* place in the queues.    As a side-effect a new process will be
scheduled. 

*/ 

//如果pre_ptr其实就是前面已经更改了的,其实就是当前进程。如果这个进程
//的时钟剩余时间小于或者等于0并且这个进程是可以抢占的,则我们认为就应
//该重新调用调度算法,计算出优先级最大的进程作为下一次的调度对象
//下面2个函数在进程调度那个章节有详细的介绍,这里就默认为是一个调度算
//法即可。 

if  (prev_ptr->p_ticks_left  <=  0 && priv(prev_ptr)->s_flags & PREEMPTIBLE)  { 

lock_dequeue(prev_ptr); /* take it off the queues  */

lock_enqueue(prev_ptr); /* and reinsert it again  */

/* Check if a clock timer expired and run its watchdog function.  */
//这段程序是处理看门狗程序,如果时钟警报器的时间next_timeout时间小于
这里的绝对值,我们认为警报器应该响起,因为警报器的时间过去了,我们就调 用tmrs_exptimers()这个函数,这个函数本来是在库函数中,现在我们就跟着 trmrs_exptimers()看看是什么情况。 

if  (next_timeout  <= realtime)  { 

tmrs_exptimers(&clock_timers, realtime, NULL); 

//我们从tmrs_exptimers()函数回来之后,我们将clock_timers(是时间队列表) //next_timeout是一个公共变量,是下一个内核在什么时候时钟警报器应该响起 //这里的含义应该就是将next_timeout设定成下一个时间警报器。 

next_timeout  = clock_timers  == NULL  ? 

TMR_NEVER  : clock_timers->tmr_exp_time;

}

/* Inhibit sending a reply.  */
return(EDONTREPLY); 

/*-------------------------------------------------------------------------* 

62 

* *

*      下面这个函数是从系统库添加进来的 但是这个韩式还是非常重要,所以拿来分析 #include "timers.h" 

/*=================================================================== ========* 

* tmrs_exptimers从前面的入口我可以继续进行往里面分析

这个函数主要的功能检测expired时钟警报器并且运行看门狗程
序    上面函数调用的入口参数 clock_timers,realtime,NULL
这里只要好好注意clock_timers,这是一个私有变量,含义是指向时钟队列表表
头。* 

*==================================================================== =======*/ 

void tmrs_exptimers(tmrs, now, new_head)

timer_t  **tmrs;

//指向时钟队列

clock_t now;

//当前时钟

clock_t  *new_head;
{

/* pointer to timers queue  */

/* current time  */ 

/* Use the current time to check the timers queue list for expired timers. * Run the watchdog functions for all expired timers and deactivate them. * The caller is responsible for scheduling a new alarm if needed.
使用当前时钟为expired时钟器来检查当前时钟队列列表.运行当前看门狗程序 为所有的expired时钟器并且使这些时钟变得无效。调用者有这种指责:如果有 必要的话就调用一个新的警报器 

*/ 

timer_t  *tp; 

//对时钟队列进行扫描,如果时钟的耗尽时间已经小于当前的时间,我们就将 //执行时钟警报函数tp,并且将所指向的tmr结构的tmr_exp_time设定为
//TMR_NEVER。 

while  ((tp  =  *tmrs)  != NULL && tp->tmr_exp_time  <= now)  { *tmrs  = tp->tmr_next; 

tp->tmr_exp_time  = TMR_NEVER;
(*tp->tmr_func)(tp); 

//如果new_head!=NULL,则将时钟队列的新头设定为tmr所含的警报时间,否则 //就将头设定为O。 

if(new_head)  {
if(*tmrs) 

*new_head  =  (*tmrs)->tmr_exp_time; 

else 

*new_head  =  0;

}

63 

至此,我们对整个时钟的主体程序分析完。我们现在来分析简要分析该文件的其 他函数,目的是能够让读者有一个整体的感觉. 

/* This file contains the clock task, which handles time related functions. 

* Important events that are handled by the CLOCK include setting and
* monitoring alarm timers and deciding when to (re)schedule processes.
* The CLOCK offers a direct interface to kernel processes. System
services 

* can access its services through system calls, such as sys_setalarm().
The 

* CLOCK task thus is hidden from the outside world. 

*这个文件包括了时钟任务,这个任务处理与时间相关的函数。CLOCK处理的重 要的事情包括设定 

*设定和监管警报器和决定什么时候来安排进程 

*CLOCK提供一个与内核的接口,系统服务能够通过系统调用获取他的服务,例 如sys_setalarm(). 

*因而这个CLOCK任务从外面的世界隐藏起来。 * Changes: 

*      Oct  08,  2005      reordering and comment editing  (A. S. Woodhull)
*      Mar  18,  2004      clock interface moved to SYSTEM task  (Jorrit N.
Herder) 

*      Sep  30,  2004      source code documentation updated (Jorrit N.

Herder)

*      Sep  24,  2004      redesigned alarm timers (Jorrit N. Herder)

*

* The function do_clocktick() is triggered by the clock's interrupt * handler when a watchdog timer has expired or a process must be
scheduled. 

*当一个看门狗时钟已经用完或者一个进程必须被调度时,do_clocktick()函数
被这个时钟中断处理器给触发。 

* In addition to the main clock_task() entry point, which starts the main * loop, there are several other minor entry points: 

*      clock_stop:  called just before MINIX shutdown 

*      get_uptime:  get realtime since boot in clock ticks 

*      set_timer: set a watchdog timer  (+)

*      reset_timer: reset a watchdog timer  (+) 

*      read_clock:  read the counter of channel  0 of the  8253A timer

*  (+) The CLOCK task keeps tracks of watchdog timers for the entire kernel. 

* The watchdog functions of expired timers are executed in 

64 

do_clocktick(). 

* It is crucial that watchdog functions not block, or the CLOCK task may
* be blocked. Do not send() a message when the receiver is not expecting
it. 

* Instead, notify(), which always returns, should be used. 

* CLOCK任务为整个内核保持看门狗时钟。至关重要的是看门狗函数不阻塞,或 者说是CLOCK任务 

也许会阻塞,不要send()一个信息当接受者不希望它这样做的话。取而代之的 是notify(),这个函数 

总是返回。所以notfiy()应该用来使用。
*/ 

#include "kernel.h" 

#include "proc.h" 

#include  <signal.h> 

#include  <minix/com.h> 

/* Function prototype for PRIVATE functions.  */ 

FORWARD  _PROTOTYPE( void init_clock,  (void)  ); 

FORWARD  _PROTOTYPE( int clock_handler,  (irq_hook_t  *hook)  );
FORWARD  _PROTOTYPE( int do_clocktick,  (message  *m_ptr)  ); 

/* Clock parameters.  */ 

#define COUNTER_FREQ  (2*TIMER_FREQ)  /* counter frequency using square wave  */ 

#define LATCH_COUNT 0x00 /* cc00xxxx, c  = channel, x  = any  */

#define SQUARE_WAVE 0x36 /* ccaammmb, a  = access, m  = mode, b  =

BCD  */ 

/* 11x11,  11  = LSB then MSB, x11  = sq wave  */

#define TIMER_COUNT  ((unsigned)  (TIMER_FREQ/HZ))  /* initial value for

counter*/

#define TIMER_FREQ 1193182L /* clock frequency for timer in PC and

AT  */

#define CLOCK_ACK_BIT 0x80 /* PS/2 clock interrupt acknowledge bit

*/

//下面个变量是整个文件的公共变量 

/* The CLOCK's timers queue. The functions in <timers.h> operate on this. * Each system process possesses a single synchronous alarm timer. If other    kernel parts want to use additional timers, they must declare their own    persistent (static) timer structure, which can be passed to the clock via  (re)set_timer(). When a timer expires its watchdog function is run by the CLOCK task. 

*/ 

65 

PRIVATE timer_t  *clock_timers; /* queue of CLOCK timers  */

PRIVATE clock_t next_timeout; /* realtime that next timer expires

*/ 

/* The time is incremented by the interrupt handler on each clock tick.
*/ 

PRIVATE clock_t realtime; /* real time clock  */

//这个私有变量是中断处理器的钩子。这个钩子主要用于中断处理程序注册 PRIVATE irq_hook_t clock_hook; /* interrupt handler hook  */ 

/*=================================================================== ========* 

* init_clock 初始化整个INTEL时钟芯片. 具体参数参考

INTEL时钟芯片。并且这里还起到了一个注册时钟的作用。将时钟中断处理程序 

注册到相相依的位置 *

*==================================================================== =======*/ 

PRIVATE void init_clock()

/* Initialize the CLOCK's interrupt hook.  */ 

clock_hook.proc_nr  = CLOCK; 

//初始化操作,clock_hook为时钟中断钩子,下面是对A进行初始化编程 

/* Initialize channel  0 of the  8253A timer to, e.g.,  60 Hz.  */ 

//对INTEL时钟芯片进行初始化操作。具体参数参考硬件信息也就是芯片8253A 

outb(TIMER_MODE, SQUARE_WAVE); /* set timer to run continuously */

outb(TIMER0, TIMER_COUNT); /* load timer low byte  */

outb(TIMER0, TIMER_COUNT  >>  8); /* load timer high byte  */

//注册时钟中断处理器,前面有讲到put_irq_handler()函数。我们看看 

//这个函数各个参数的具体含义:colock_hook,时钟中断钩子.CLOCK_IRQ 时 //中断向量号,clock_handler中断处理程序 

put_irq_handler(&clock_hook, CLOCK_IRQ, clock_handler);/* register handler  */ 

//调用enable_irq(),使得函数能够正常运行 

enable_irq(&clock_hook); /* ready for clock interrupts  */

}

/*=================================================================== ========* 

* clock_stop

这个函数的功能就是关闭INTEL时钟芯片的功能。  * 

*==================================================================== =======*/ 

PUBLIC void clock_stop()

66 

/* Reset the clock to the BIOS rate.  (For rebooting)  */ 

outb(TIMER_MODE,  0x36); 

outb(TIMER0,  0); 

outb(TIMER0,  0);

}

/*=================================================================== ========* 

* get_uptime   功能就是返回一个实时时间 *

*==================================================================== =======*/ 

PUBLIC clock_t get_uptime()

/* Get and return the current clock uptime in ticks.  */
return(realtime); 

/*=================================================================== ========* 

* set_timer  这个函数就是设置一个时钟,将tp指向的时间警报

器注册到内核时钟中。也就是clock_timers队列中。 *

*====================================================================

=======*/

PUBLIC void set_timer(tp, exp_time, watchdog)

struct timer  *tp; /* pointer to timer structure  */

clock_t exp_time; /* expiration realtime  */

tmr_func_t watchdog; /* watchdog to be called  */

/* Insert the new timer in the active timers list. Always update the * next timeout time by setting it to the front of the active list. 在活动时钟器链表中插入一个新的时钟器。通过设置时钟警报器链表的头部来总 是更新下一个时钟即将完成时间timeout。 

*/ 

//这个函数在后面有详细介绍,这个函数的主要作用就是设置注册一个时钟警报 器,这个时钟警报器是注册在内核时钟clock_timers链表中 

tmrs_settimer(&clock_timers, tp, exp_time, watchdog, NULL); next_timeout  = clock_timers->tmr_exp_time; 

67 

/*=================================================================== ========* 

* reset_timer    这个函数的功能就是重新设置时钟警报器

准确的说是将tp指向的时钟警报器从这个链表中去除掉。* 

*==================================================================== =======*/ 

PUBLIC void reset_timer(tp) 

struct timer  *tp; /* pointer to timer structure  */

{

/* The timer pointed to by 'tp' is no longer needed. Remove it from both the active and expired lists. Always update the next timeout time by setting it to the front of the active list. 

*/ 

//tmr_clrtimer()函数的意义就是将tp指向的时钟警报器从clock_timers这个 //队列中去除掉 

tmrs_clrtimer(&clock_timers, tp, NULL); 

//next_timeout重新设置,放置去掉的这个时钟警报器是队列的对头。所以需要 //重新设置 

next_timeout  =  (clock_timers  == NULL)  ?
TMR_NEVER  : clock_timers->tmr_exp_time;

/*=================================================================== ========* 

* read_clock 这个函数就是实现读时钟的最底层命令

读的过程涉及到时钟芯片,这里就不做详细的分析 *

*==================================================================== =======*/ 

PUBLIC unsigned long read_clock()

/* Read the counter of channel 0 of the 8253A timer.    This counter counts
* down at a rate of TIMER_FREQ and restarts at TIMER_COUNT-1 when it
* reaches zero. A hardware interrupt (clock tick) occurs when the counter 

* gets to zero and restarts its cycle. 

*/ 

unsigned count; 

outb(TIMER_MODE, LATCH_COUNT);
count  = inb(TIMER0); 

count  |=  (inb(TIMER0)  <<  8); 

68 

return count;

}

/*-------------------------------------------------------------------
------* 

*      下面这个函数是从系统库添加进来的但是这个韩式还是非常重要,所以拿 来分析 

*/ 

#include "timers.h" 

/*=================================================================== ========* 

* tmrs_settimer 这个函数就是将tp安装到tmrs队列的时钟里

*==================================================================== =======*/ 

clock_t tmrs_settimer(tmrs, tp, exp_time, watchdog, new_head)

timer_t  **tmrs;

//指向时钟队列

timer_t  *tp;

//被添加的时钟警报器

clock_t exp_time;

/* pointer to timers queue  */

/* the timer to be added  */

/* its expiration time  */ 

//这个时钟警报器的消耗时间 

tmr_func_t watchdog; /* watchdog function to be run  */

//这个时钟警报器的看门狗函数的首地址 

clock_t  *new_head; /* new earliest timer, if non NULL  */

//新的最近的时钟警报器, 

/* Activate a timer to run function 'fp' at time 'exp_time'. If the timer
is already in use it is first removed from the timers queue. Then, it is
put in the list of active timers with the first to expire in front. 

* The caller responsible for scheduling a new alarm for the timer if needed. 

*/ 

timer_t  **atp; 

clock_t old_head  =  0; 

//判断这个时钟队列是否存在
if(*tmrs) 

old_head  =  (*tmrs)->tmr_exp_time; 

//存在就将队头得时钟储存在old_head变量中 

69 

/* Set the timer's variables.  */ 

(void) tmrs_clrtimer(tmrs, tp, NULL); 

//设置tp的相关参数,tmr_exp_time和tmr_func
tp->tmr_exp_time  = exp_time; 

tp->tmr_func  = watchdog; 

/* Add the timer to the active timers. The next timer due is in front.
*/ 

//进行检查,找到这个链表尾,将这个新的时钟插到链表的表尾。
for  (atp  = tmrs;  *atp  != NULL; atp  = &(*atp)->tmr_next)  {
if  (exp_time  <  (*atp)->tmr_exp_time) break;

tp->tmr_next  =  *atp;
*atp  = tp; 

if(new_head) 

(*new_head)  =  (*tmrs)->tmr_exp_time; return old_head; 

4.3 与内核时钟有关的系统任务 

我们现在额外分析几个时钟调用,是和外界有软件形式的一种借口,比如和PM 进程,在后面PM进程的时钟机制可以看到,它们之间的交互就是通过几个函数完 成,所以这几个函数的功能非常的重要。这几个函数主要是在SYSTM文件夹里。 我先把它们附录出来: 

/* The kernel call implemented in this file: *      m_type:   SYS_SETALARM 

消息类型:SYS_SETALARM

* The parameters for this kernel call are: 

* m2_l1:   ALRM_EXP_TIME (alarm's expiration time)

ALRM_EXP_TIME 时钟警报时间 

* m2_i2:   ALRM_ABS_TIME (expiration time is absolute?)

ALRM_ABS_TIME 消耗时间是绝对还是相对

* m2_l1:   ALRM_TIME_LEFT

ALRM_TIME_LEFT:返回还有多少秒剩余
*/

#include "../system.h"

#if USE_SETALARM

(return seconds left of previous) 

FORWARD  _PROTOTYPE( void cause_alarm,  (timer_t  *tp)  ); 

70 

/*=================================================================== ========* 

* do_setalarm    这里主要是设置一个时钟警报器

主要是为了给服务器进程使用,是内核和外核服务器的一个接口函数* 

*==================================================================== =======*/ 

PUBLIC int do_setalarm(m_ptr) 

message  *m_ptr; /* pointer to request message  */

/* A process requests a synchronous alarm, or wants to cancel its alarm.
*/ 

//对时钟操作的最终需要调用的任务 

register struct proc  *rp;  /* pointer to requesting process  */

int proc_nr;

long exp_time; int use_abs_time; timer_t  *tp;

/* which process wants the alarm  */

/* expiration time for this alarm  */
/* use absolute or relative time  */
/* the process' timer structure  */

71 

clock_t uptime; /* placeholder for current uptime  */

/* Extract shared parameters from the request message.  */ 

exp_time  = m_ptr->ALRM_EXP_TIME;  /* alarm's expiration time  */ 

use_abs_time  = m_ptr->ALRM_ABS_TIME; /* flag for absolute time  */ 

proc_nr  = m_ptr->m_source; /* process to interrupt later  */

rp  = proc_addr(proc_nr); 

if  (!  (priv(rp)->s_flags & SYS_PROC)) return(EPERM); 

/* Get the timer structure and set the parameters for this alarm.  */
//在这里设置tp所指向的timer_t的各个属性值,其实这个结构体是在进程里
定义的 

tp  = &(priv(rp)->s_alarm_timer); 

//ta_int的属性值存入proc_nr 

tmr_arg(tp)->ta_int  = proc_nr; 

//tp的tmr_func代表的是当时间耗尽该指向哪个函数 

tp->tmr_func  = cause_alarm; 

/* Return the ticks left on the previous alarm.  */
// 

uptime  = get_uptime(); 

if  ((tp->tmr_exp_time  != TMR_NEVER) && (uptime  < tp->tmr_exp_time) )

{

m_ptr->ALRM_TIME_LEFT  =  (tp->tmr_exp_time  - uptime);

} else  {

m_ptr->ALRM_TIME_LEFT  =  0;

}

/* Finally,  (re)set the timer depending on the expiration time.  */ if  (exp_time  ==  0)  { 

reset_timer(tp);
} else  { 

tp->tmr_exp_time  =  (use_abs_time)  ? exp_time  : exp_time  + get_uptime(); 

set_timer(tp, tp->tmr_exp_time, tp->tmr_func);

}

return(OK);

}

Cause_alarm函数的大体执行过程: 

72 

/*=================================================================== ========* 

* cause_alarm    这个函数在PM时钟机制中被当做用户的看门狗

处理函数,这个函数主要的功能就是发送一个通知,通知的来源是CLOCK,通知
的目的进程是proc_nr。一般就是内核时钟用完之后,触发这个内核时钟看门狗
函数。当然在服务器进程会进一步做出相关的处理,在后面的PM时钟机制和MINIX 

信号量机制中会做出详细的分析。 *

*==================================================================== =======*/ 

PRIVATE void cause_alarm(tp)
timer_t  *tp; 

/* Routine called if a timer goes off and the process requested a 

synchronous alarm. The process number is stored in timer argument 'ta_int'. Notify that process with a notification message from CLOCK.  */
//如果时间计时器已经耗尽,并且进程需要一个异步警告器。进程数被存储在 timer结构的ta_int里, 

//通知内核有一个时钟消息这个消息来源于proc_nr进程 

int proc_nr  = tmr_arg(tp)->ta_int; /* get process number  */

lock_notify(CLOCK, proc_nr); /* notify process  */

73 

#endif  /* USE_SETALARM  */ 

这就是我们内核时钟机制的全部分析,在后面的PM时钟机制里,还是多次涉及到 这个内核时钟。所以在后面的分析过程中,对内核时钟机制做出进一步的分析。 会对内核时钟与外核服务器时钟区别有进一步的理解。

MINIX3 内核时钟分析的更多相关文章

  1. MINIX3 内核整体架构回顾及内核定 性分析

    MINIX3  内核整体架构回顾及内核定 性分析 12.1 注意事项 由于本文档不对 I/O 文件系统做出分析,所以在此不对 MINIX3 整体做出一个分 析,本章主要是针对内核进程分析.并且这里的模 ...

  2. MINIX3 进程通信分析

    MINIX3 进程通信分析 6.1MINIX3 进程通信概要 MINIX3 的进程通信是 MINIX3 内核部分最重要的一个部件,我个人认为其实这 是内核中的“内核”,怎么来理解这个概念呢?其实 MI ...

  3. Linux内核启动分析

    张超<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 我的代码可见https://www.shiyanlo ...

  4. 第3阶段——内核启动分析之start_kernel初始化函数(5)

    内核启动分析之start_kernel初始化函数(init/main.c) stext函数启动内核后,就开始进入start_kernel初始化各个函数, 下面只是浅尝辄止的描述一下函数的功能,很多函数 ...

  5. stm32时钟分析

    转载自http://blog.chinaunix.net/uid-21658993-id-3129667.html   在STM32中,有五个时钟源,为HSI.HSE.LSI.LSE.PLL. 其实是 ...

  6. mkimage工具 加载地址和入口地址 内核启动分析

    第三章第二节 mkimage工具制作Linux内核的压缩镜像文件,需要使用到mkimage工具.mkimage这个工具位于u-boot-2013. 04中的tools目录下,它可以用来制作不压缩或者压 ...

  7. 几个常用内核函数(《Windows内核情景分析》)

    参考:<Windows内核情景分析> 0x01  ObReferenceObjectByHandle 这个函数从句柄得到对应的内核对象,并递增其引用计数. NTSTATUS ObRefer ...

  8. [1]windows 内核情景分析---说明

    本文说明:这一系列文章(笔记)是在看雪里面下载word文档,现转帖出来,希望更多的人能看到并分享,感谢原作者的分享精神. 说明 本文结合<Windows内核情景分析>(毛德操著).< ...

  9. windows内核情景分析之—— KeRaiseIrql函数与KeLowerIrql()函数

    windows内核情景分析之—— KeRaiseIrql函数与KeLowerIrql()函数 1.KeRaiseIrql函数 这个 KeRaiseIrql() 只是简单地调用 hal 模块的 KfRa ...

随机推荐

  1. lucene底层数据结构——底层filter bitset原理,时间序列数据压缩将同一时间数据压缩为一行

    如何联合索引查询? 所以给定查询过滤条件 age=18 的过程就是先从term index找到18在term dictionary的大概位置,然后再从term dictionary里精确地找到18这个 ...

  2. Storm(3) - Calculating Term Importance with Trident

    Creating a URL stream using a Twitter filter Start by creating the project directory and standard Ma ...

  3. String.resize()

    void resize (size_t n); void resize (size_t n, char c); 测试代码: // resizing string #include <iostre ...

  4. RewriteRule参数

    RewriteCond指令格式 [说明]定义重写发生的条件 [语法]RewriteCond TestString CondPattern [flags] RewriteCond指令定义一条规则条件.在 ...

  5. 使用AlarmManager设置闹钟----之二

    import android.media.MediaPlayer;import android.os.Bundle;import android.app.Activity;import android ...

  6. 摄像机导致的粒子效果混乱出错变成贴图sprite显示在镜头前

    只要把出错的摄像机记的标签改成maincamera问题就消失了!! 我之前一直以为是烘培导致的问题!

  7. EasyMock的原理及使用方法

    就不费劲转过来了https://www.ibm.com/developerworks/cn/opensource/os-cn-easymock/这上面介绍的比较全.

  8. BindingNavigator操作DatagridView的数据

    参考 http://wenku.baidu.com/link?url=NWfEfArPZvDO_aI-xEKBHVGoZY9wQO_Oty_GCsGLiPspheCzFYLf_dytuWAqN2_0A ...

  9. 使用ASP.Net WebAPI构建REST服务(六)——Self-Host

    Asp.Net WebAPI生成的是一个程序集,并不是独立的进程,因此,要运行的时候必须将其承载在相应的宿主上,一般比较常见的是IIS承载.很多时候,我们为了简化部署或者功能集成,需要将其承载到独立的 ...

  10. android 录音的断点续传

    系统没有暂停的功能  只能把每次的录音进行拼接... package com.example.zrecord; import java.io.File;import java.io.FileInput ...