PM 时钟机制

PM 时钟机制

10.1 Minix3 PM 时钟机制概述在 MINIX3 中,除了前面所讲到的 CLOCK 时钟,在 pm 中也是维持了一个时钟, 我们暂且不分析为啥要这么做,我就分析是怎么实现这个 PM 时钟监视器。我可 以这么肯定的说,这个时钟监视器只是一个虚幻的时钟监视器,最终还是得内核 时钟来完成这个工作,但是这个时钟监视器还是很有用的。我分析下 PM 时钟监 视器的工作过程:

如果用户需要用到时钟的相关功能,用户进程就会告诉 pm 我需要设定一个时钟
警告器来告诉我在未来的多长时间给我响一次警报,我想完成相关的内容,pm
就会维持一个时钟队列,就像内核时钟任务一样,每当一个 pm 时钟对头快要消
耗完成时,把下个即将成为队头得时钟警报器发送给内核时钟,内核时钟队列同
时也维持一个时钟队列,这个时钟队列是内核的时钟队列,在前面有详细介绍,
如果内核时钟监视器发现这个时钟警报器是 PM 发送并且已经耗尽时间时,它将
会发送一个消息给 PM,PM 先更新新的时钟队列,之后将相关信息通知这个需
要通知的进程。以上过程基本上整个 PM 时钟所完成的任务。下面用图详细表示
出来

10.2 MINIX3PM时钟机制源码导读:

155

先看在servers/pm/timers.c,这个几个函数主要是为了PM机制提供一种服务。我们所以先看 这几个函数:这里的几个函数都是被其他函数而调用的。

/* PM watchdog timer management. These functions in this file provide
* a convenient interface to the timers library that manages a list of
* watchdog timers. All details of scheduling an alarm at the CLOCK task

* are hidden behind this interface.

* Only system processes are allowed to set an alarm timer at the kernel. * Therefore, the PM maintains a local list of timers for user processes * that requested an alarm signal.

* PM看门狗计时器。在这个文件的这些函数提供给时钟库一个便利的接口,这
个时钟警报器 主要是管理一系列看门狗计时器。所有的调度内核时钟警报器任
务的细节都隐藏在这个接口上。只有系统进程被允许在内核上设定时钟警报器,
因此,PM维持一个本地用户时钟计时器用来想请求一个时钟信号的进程

* The entry points into this file are:

*      pm_set_timer: reset and existing or set a new watchdog timer

重新设置一个或者设置一个新的看门狗计时器

*      pm_expire_timers:    check for expired timers and run Watchdog Functions。 检查一个一个消耗计时器或者运行看门狗函数
*      pm_cancel_timer:      remove a time from the list of timers
从一个时间计时器中去除一个时钟

*

*/

#include "pm.h"

#include  <timers.h>

#include  <minix/syslib.h>
#include  <minix/com.h>

//这个是计时器时钟指针,是这个文件的全局变量

PRIVATE timer_t  *pm_timers  = NULL; 先看这个函数的执行图:

156

PM 时钟机制_第1张图片

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

* pm_set_timer 重新设置一个或者设置一个新的看门狗计

时器 *

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

PUBLIC void pm_set_timer(timer_t *tp, int ticks, tmr_func_t watchdog, int
arg)

{

int r;

clock_t now, prev_time  =  0, next_time;

if  ((r  = getuptime(&now))  != OK)

157

panic(__FILE__, "PM couldn't get uptime", NO_NUM);

/* Set timer argument and add timer to the list.  */

tmr_arg(tp)->ta_int  = arg;

prev_time  =

tmrs_settimer(&pm_timers,tp,now+ticks,watchdog,&next_time);

//这个函数就是设置一个看门狗计时器。将pm_timers时钟队列中安装一个tp时 钟队列,将其消耗时间设置成为now+ticks,看门狗程序设置成为watchdog函数 首地址,next_time是返回变量。这个变量就是耗时时间(绝对时间)

/* Reschedule our synchronous alarm if necessary.  */ if  (! prev_time  || prev_time  > next_time)  {
if  (sys_setalarm(next_time,  1)  != OK)

panic(__FILE__, "PM set timer couldn't set alarm.", NO_NUM);

}

return;

}

现在我们来看下一个函数,同样,这个函数还是非常的重要。先给出示意图:

158

PM 时钟机制_第2张图片

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

* pm_expire_timers 检查一个一个消耗计时器或者运行看门

狗函数 *

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

PUBLIC void pm_expire_timers(clock_t now)
{

clock_t next_time;

/* Check for expired timers and possibly reschedule an alarm.  */ //检查时钟警报器并且可能重新调度一个时钟警报

//事实这个函数在前面的内核时钟已经讲到了,我们现在在此做一个简要的参数
分析:pm_timers是一个时钟队列,我们将pm_timers中的队头消耗时间和now相
比较,如果发现这个消耗时间小于或者等于现在这个时间now.就执行队列队头的

159

看门狗函数。并且重新设置队列对头,并且将新的队头消耗时间传递给Next_time
变量。留给下面重新设置一个系统警报。当然如果消耗时间大于now,则什么都不
会做。

tmrs_exptimers(&pm_timers, now, &next_time); if  (next_time  >  0)  {

if  (sys_setalarm(next_time,  1)  != OK)

panic(__FILE__, "PM expire timer couldn't set alarm.",

NO_NUM);

}
}

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

* pm_cancel_timer 这个函数的主要功能就是取消时钟警报

器 *

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

PUBLIC void pm_cancel_timer(timer_t  *tp)
{

//函数实现去除tp指向的时钟警报器。函数主体就是调用一个tmrs_clrtimer() 函数,这个函数就是把从pm_timers时钟队列中去除掉tp所指向的函数。
clock_t next_time, prev_time;

prev_time  = tmrs_clrtimer(&pm_timers, tp, &next_time);

/* If the earliest timer has been removed, we have to set the alarm

to

* the next timer, or cancel the alarm altogether if the last timer

has

* been cancelled  (next_time will be  0 then).
*/

if  (prev_time  < next_time  ||  ! next_time)  {
if  (sys_setalarm(next_time,  1)  != OK)

panic(__FILE__, "PM expire timer couldn't set alarm.",

NO_NUM);

}
}

160

PM 时钟机制_第3张图片

MINIX3PM时钟和内核时钟的交互

这种交互其实就是一种信号量的实现,信号量的具体实现参考MINIX3信号量实现 机制,在这这里我们主要看是怎么实现相关的交互,执行到信号量问题时,我们 默认信号量原来的定义,原来的作用。这里选取的几个函数都是处理PM时钟机制 准备工作。这几个函数是来自于/servers/pm/sigal.c里。在讨论这几个函数之 前,我想讨论的工作就是一种大体的宏观执行过程:

先看用户进程是怎么注册一个时钟警报器:

我们现在来看看PM时钟看门狗响起时的大体执行过程

161

PM 时钟机制_第4张图片

从上面2个图中可以看到,pm_exipre_timers和pm_set_timers函数都调用了,这
就是为什么刚才先分析那2个函数。在看这2副图注意和前面的2个函数执行图想
联系

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

*do_alarm做时钟警报工作。主体其实就是设定set_alarm(who,m_in.seconds)

函数这个函数在下面会有详细的介绍 *

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

PUBLIC int do_alarm()
{

/* Perform the alarm(seconds) system call.  */
return(set_alarm(who, m_in.seconds));
}

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

* set_alarm *

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

162

=======*/

PUBLIC int set_alarm(proc_nr, sec)

int proc_nr; //想要警报器的进程号 /* process that wants the alarm

*/

int sec; //在信号产生之前,许多多少秒的延迟/* how many seconds delay

before the signal  */

{

/* This routine is used by do_alarm() to set the alarm timer.    It is also
used

* to turn the timer off when a process exits with the timer still on. 这个例程被do_alarm()来调用,目的是为了设置时间警报器。当一个进程存在 且时钟仍然在上面,

它仍然被用来关闭一个时钟
*/

clock_t ticks; /* number of ticks for alarm  */

//警报器的时钟节拍数

clock_t exptime; /* needed for remaining time on previous alarm  */

//前一个时钟警报器遗留的时间

clock_t uptime; /* current system time  */

//当前系统时间

int remaining; /* previous time left in seconds  */

//前一个时钟警报器还有多少秒

int s;

/* First determine remaining time of previous alarm, if set.  */

//首先查看进程表是否是标志ALARM_ON标志,如果是ALARM_ON标志,就表明 //该进程的警报器是打开的

if  (mproc[proc_nr].mp_flags & ALARM_ON)  { if  (  (s=getuptime(&uptime))  != OK)

panic(__FILE__,"set_alarm couldn't get uptime", s); //这个函数前面分析了,主要是看看警报器还有多少时间剩余
exptime  =  *tmr_exp_time(&mproc[proc_nr].mp_timer);
//将remaining转变成秒做单位

remaining  =  (int)  ((exptime  - uptime  +  (HZ-1))/HZ); //计算剩余时间,如果剩余时间小于把其置

if  (remaining  <  0) remaining  =  0; } else  {

remaining  =  0;

}

/* Tell the clock task to provide a signal message when the time comes.
* 当时钟器来临的时候告诉这个时钟任务来提供一个信号消息
* Large delays cause a lot of problems.    First, the alarm system call
* takes an unsigned seconds count and the library has cast it to an

163

int. That probably works, but on return the library will convert

"negative" unsigneds to errors.    Presumably no one checks for these

errors, so force this call through.    Second, If unsigned and long have the same size, converting from seconds to ticks can easily overflow. Finally, the kernel has similar overflow bugs adding ticks.
大的延迟可能会引起一些问题,首先就是时钟系统调用

* Fixing this requires a lot of ugly casts to fit the wrong interface
* types and to avoid overflow traps.    ALRM_EXP_TIME has the right type
* (clock_t) although it is declared as long.    How can variables like
* this be declared properly without combinatorial explosion of message
* types?

*/

ticks  =  (clock_t)  (HZ  *  (unsigned long)  (unsigned) sec); //算成节拍数

if  (  (unsigned long) ticks  / HZ  !=  (unsigned) sec)
ticks  = LONG_MAX; /* eternity  (really TMR_NEVER)  */

if  (ticks  !=  0)  {

//如果节拍不是为0的话,就表明时钟警报器有效,pm_set_timer()函数在前面
有讲过,这里简要分析执行完这个函数之后,将mp_timer时钟警报器安装在
pm_timers时钟队列中,应该是安装到队尾,并且设置mp_timer时钟警报器的各
个参数,这里需要注意的一点就是将看门狗函数设置成为cause_sigalrm。
pm_set_timer(&mproc[proc_nr].mp_timer, ticks, cause_sigalrm,
proc_nr);

//设置完成之后,将进程的标志位的ALARM_ON置位
mproc[proc_nr].mp_flags  |=    ALARM_ON;

}  //如果ticks计算出来结果还是0并且其警报器标志位ALARM_ON还是开启状 态,则应该取消这个函数的时钟警报器,并且将ALARM_ON位关闭,也就是复位 else if  (mproc[proc_nr].mp_flags & ALARM_ON)  {

pm_cancel_timer(&mproc[proc_nr].mp_timer);
mproc[proc_nr].mp_flags &=  ~ALARM_ON;
}

return(remaining);

}

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

* cause_sigalrm 这个函数由前面的分析我们可以看出来这

个函数其实就是一个看门狗函数,我们不想对这个过深入的分析,这个函数的分

析事实上涉及到信号量机制,我们暂且不做相关的分析 *

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

PRIVATE void cause_sigalrm(tp)

164

struct timer  *tp;
{

int proc_nr;

register struct mproc  *rmp;

proc_nr  = tmr_arg(tp)->ta_int; /* get process from timer  */

//计算是哪个进程号

rmp  = &mproc[proc_nr];

//对本进程号做出一些基本检测

if  ((rmp->mp_flags &  (IN_USE  | ZOMBIE))  != IN_USE) return; //检测本进程的标志位是否开启ALARM_ON,如果没有开启,就返回
if  ((rmp->mp_flags & ALARM_ON)  ==  0) return;

//上面2种检测完成之后,我们首先就将ALARM_ON位关闭,之后执行check_sig() 函数,这个函数主要目的就是执行相关的看门狗函数。这里不做深入分析,因为 后面信号量机制会详细的分析这个函数的用途。

rmp->mp_flags &=  ~ALARM_ON;

check_sig(rmp->mp_pid, SIGALRM);

}

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

========*

* sys_setalarm 主要是在内核态设置一

个警报器。这点不做详细分析 *

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

PUBLIC int sys_setalarm(exp_time, abs_time)

clock_t exp_time; /* expiration time for the alarm  */

//这个警报器的消耗时间,从上面一个函数传来的也就是next_time

int abs_time;  /* use absolute or relative expiration time  */

//是使用绝对时间还是相对时间

{

/* Ask the SYSTEM schedule a synchronous alarm for the caller. The process
* number can be SELF if the caller doesn't know its process number.
*/

//设置消息内容。准备进入内核来完成这个SYS_SETALARM任务。

message m;

m.ALRM_EXP_TIME  = exp_time; /* the expiration time  */

m.ALRM_ABS_TIME  = abs_time; /* time is absolute?  */

return  _kernel_call(SYS_SETALARM, &m);

}

165

PM时钟的最后一个部分是PM时钟库。这里拿出来做出简要的分析: 下面是这个函数源码:

/* This file takes care of those system calls that deal with time. *这个文件注意了那些与时钟相关的系统调用

* The entry points into this file are 指向这个文件的进口有:

*      do_time:

执行TIME调用

*      do_stime: 执行STME调用

*      do_times:
执行TIMES系统调用
*/

#include "pm.h"

perform the TIME system call

perform the STIME system call

perform the TIMES system call

#include  <minix/callnr.h>
#include  <minix/com.h>
#include  <signal.h>
#include "mproc.h"
#include "param.h"

PRIVATE time_t boottime;

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

* do_time 获得当前的实时时钟和运行时间

*

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

PUBLIC int do_time()
{

/* Perform the time(tp) system call. This returns the time in seconds since
* 1.1.1970.    MINIX is an astrophysically naive system that assumes the
earth

* rotates at a constant rate and that such things as leap seconds do not * exist.

*/

clock_t uptime;
int s;

if  (  (s=getuptime(&uptime))  != OK)

panic(__FILE__,"do_time couldn't get uptime", s);

166

//上面的getuptime(&uptime)函数调用,将结果存在uptime中。存储之后将mp? //的相关域设置好。

mp->mp_reply.reply_time  =  (time_t)  (boottime  +  (uptime/HZ)); mp->mp_reply.reply_utime  =  (uptime%HZ)*1000000/HZ;
return(OK);

}

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

* do_stime   设置实时时钟 *

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

PUBLIC int do_stime()
{

/* Perform the stime(tp) system call. Retrieve the system's uptime (ticks * since boot) and store the time in seconds at system boot in the global * variable 'boottime'.

*/

clock_t uptime;
int s;

if  (mp->mp_effuid  != SUPER_USER)  {
return(EPERM);

}

if  (  (s=getuptime(&uptime))  != OK)

panic(__FILE__,"do_stime couldn't get uptime", s); //主要是更改boottime这个时间

boottime  =  (long) m_in.stime  -  (uptime/HZ);

/* Also inform FS about the new system time.  */

tell_fs(STIME, boottime,  0,  0);

return(OK);

}

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

* do_times 获得进程的统计时间 *

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

PUBLIC int do_times()
{

167

/* Perform the times(buffer) system call.  */
register struct mproc  *rmp  = mp;

clock_t t[5];

int s;

if  (OK  !=  (s=sys_times(who, t)))

panic(__FILE__,"do_times couldn't get times", s);

//这个调用主要是获取系统时间,通过前面的sys_times(who,t),将需要的参数 //全部存储在t[5]数组中。之后将rmp进程表项相关域设置好。

rmp->mp_reply.reply_t1  = t[0]; /* user time  */

rmp->mp_reply.reply_t2  = t[1]; /* system time  */

rmp->mp_reply.reply_t3  = rmp->mp_child_utime; /* child user time */

rmp->mp_reply.reply_t4  = rmp->mp_child_stime; /* child system time

*/

rmp->mp_reply.reply_t5  = t[4]; /* uptime since boot  */

return(OK);

}

PM时钟机制基本分析完毕。跟信号量相关的处理在MINIX3信号量分析那个章节里 有详细的分析!

你可能感兴趣的:(PM 时钟机制)