对Minix3中进程模型的简要分析

简单介绍

Minix(Mini UNIX)原来是荷兰阿姆斯特丹的Vrije大学计算机科学系的Andrew S. Tanenbaum教授所发展的一个类Unix操作系统。

目前的Minix版本为Minix 3,是一个免费、开源的操作系统,设计目标是实现高可靠性、灵活性及安全性。

其系统主要包括在核心模式下运作的微核心和在用户模式下作为一系列独立、受保护的进程运行的其余所有操作系统组件。

Minix3的整体认识

MINIX3本身就是一组进程的集合

第一层的主要功能是为上层驱动程序和服务器提供一组特权内核调用。进程都潜在具有一定特权,这是另外三层的不同之处。

第二层拥有最多特权。第二层内的称为设备驱动程序(device driver)。

第三层包含了服务器,有两个服务器不可少,进程管理器(Process Manager,PM)和文件系统(File System,FS),

还有信息服务器(information server,IS),再生服务器(reincarnation server,RS),

在一个网络操作系统上还可能有网络服务器(network server,inet)。

Minix3是怎么组织进程

进程的定义     

以下是来自《现代操作系统》中对进程的一些描述      
        进程是对正在运行程序的一个抽象
        进程本质是正在执行的一个程序
        一个进程就是一个正在执行程序的实例,包括程序计数器、寄存器和变量的当前值

进程的数据结构

 kernel/proc.h定义了内核进程表,进程表中的每一项被定义为一个proc进程:

 

struct proc {
struct stackframe_s p_reg;            /*process' registers saved in stack frame */
reg_t p_ldt_sel;                  /*selector in gdt with ldt base and limit */
struct segdesc_sp_ldt[2+NR_REMOTE_SEGS];    /* CS, DS and remote segments */
proc_n r_tp_nr;                   /* numberof this process (for fast access) */
struct priv *p_priv;                /*system privileges structure */
char p_rts_flags;                  /*SENDING, RECEIVING, etc. */
char p_priority;                  /*current scheduling priority */
char p_max_priority;                /*maximum scheduling priority */
char p_ticks_left;                  /*number of scheduling ticks left */
char p_quantum_size;                          /*quantum size in ticks */
struct mem_mapp_memmap[NR_LOCAL_SEGS];        /* memory map (T, D, S) */
clock_t p_user_time;                 /*user time in ticks */
clock_t p_sys_time;                   /*sys time in ticks */
struct proc *p_nextready;             /*pointer to next ready process */
struct proc *p_caller_q;              /*head of list of procs wishing to send */
struct proc *p_q_link;               /*link to next proc wishing to send */
message* p_messbuf;                 /*pointer to passed message buffer */
proc_n r_tp_getfrom;                /*from whom does process want to receive? */
proc_n r_tp_sendto;                 /* towhom does process want to send? */
sigset_t p_pending;                 /*bit map for pending kernel signals */
char p_name[P_NAME_LEN];              /*name of the process, including \0 */
};

 

每一项包括进程寄存器、栈指针、状态值、内存映射、栈限制、进程号、计数值、alarm时间以及消息信息。

 

进程表本身被定义为一个proc结构体的数组proc[NR_TASKS+NR_PROCS],

常量 NR_TASKS include/minix/com.h中被定义为4

常量 NR_PROCS include/minix/congfig.h中被定义为64

如果需要,NR_PROCS可以更改,以创建一个能够处理更多进程的系统(如在一个大型服务器上)。

 

进程状态如何转换

进程状态

 

一个进程从创建而产生至撤销而消亡的整个生命期间,
有时占有处理器执行,有时虽可运行但分不到处理器、有时虽有空闲处理器但因等待某个事件的发生而无法执行,
这一切都说明进程和程序不相同,它是活动的且有状态变化的,这可以用一组状态加以刻画。
为了便于管理进程,一般来说,按进程在执行过程中的不同情况至少要定义三种不同的进程状态:

 

  (1)运行(running)态:进程占有处理器正在运行。

 

  (2)就绪(ready)态:进程具备运行条件,等待系统分配处理器以便运行。

 

  (3)等待(wait)态:又称为阻塞(blocked)态或睡眠(sleep)态,指进程不具备运行条件,正在等待某个事件的完成。

 

 

进程状态转换

 

 

进程是如何调度的

 当进程被中断(被输入输出设备或时钟等),或进程执行软中断指令,或进程结束时,系统将决定接下来运行哪个进程。

队列优先级

Minix的进程调度使用多级队列,每个队列的优先级不同。

见 kernel/proc.h 中:

/* Scheduling priorities for p_priority. Values must start at zero (highest
 * priority) and increment.  Priorities of the processes in the boot image
 * can be set in table.c. IDLE must have a queue for itself, to prevent low
 * priority user processes to run round-robin with IDLE.
 */
#define NR_SCHED_QUEUES   16        /* MUST equal minimum priority + 1 */
#define TASK_Q             0        /* highest, used for kernel tasks */
#define MAX_USER_Q         0        /* highest priority for user processes */  
#define USER_Q             7        /* default (should correspond to nice 0) */  
#define MIN_USER_Q        14        /* minimum priority for user processes */
#define IDLE_Q            15        /* lowest, only IDLE process goes here */
EXTERN struct proc *rdy_head[NR_SCHED_QUEUES]; /* ptrs to ready list headers */ EXTERN struct proc *rdy_tail[NR_SCHED_QUEUES]; /* ptrs to ready list tails */

服务进程所用的队列通常比用户进程所用的队列优先级更高;而驱动进程所用的队列通常比服务进程所用的队列优先级更高;

而时钟和系统任务使用的队列,是所有队列中优先级最高的。

时间片

用户进程的时间片通常相对较小;驱动进程和服务进程通常应该运行直至阻塞,但实际上被分配了大却有限的时间片。在每一个时钟节拍,都将检查当前正在运行的进程是否用完了它的时间片,如果是,则它将被放到队尾,然后选择下一个进程运行。

见 /kernel/clock.c 中:

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 ......
 */
  ......
  /* Check if do_clocktick() must be called. Done for alarms and scheduling.
   ......
   */
  if (  ...... || (proc_ptr->p_ticks_left <= 0)) {
      prev_ptr = proc_ptr;                        /* store running process */
      lock_notify(HARDWARE, CLOCK);               /* send notification */
  }
  ......
}

上面函数clock_handler()中的lock_notify()将导致下面的函数do_clocktick()被调用。
        见 /kernel/clock.c 中:

PRIVATE int do_clocktick(m_ptr)
message *m_ptr;                                /* pointer to request message */
{
   ......
  /* 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.
   */
  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 */
  }
  ......
}

上面函数do_clocktick()中的lock_enqueue()实际调用了下面的函数enqueue(),从而选择下一个进程运行。
  见 /kernel/proc.c 中:

PRIVATE void enqueue(rp)
register struct proc *rp; /* this process is now runnable */
{
/* Add 'rp' to one of the queues of runnable processes.  This function is
 * responsible for inserting a process into one of the scheduling queues.
 * The mechanism is implemented here.   The actual scheduling policy is
 * defined in sched() and pick_proc().
 */
  int q;      /* scheduling queue to use */
  int front;     /* add to front or back */

  /* Determine where to insert to process. */
  sched(rp, &q, &front);

  /* Now add the process to the queue. */
  if (rdy_head[q] == NIL_PROC) {        /* add to empty queue */
      rdy_head[q] = rdy_tail[q] = rp;   /* create a new queue */
      rp->p_nextready = NIL_PROC;       /* mark new end */
  }
  else if (front) {              /* add to head of queue */
      rp->p_nextready = rdy_head[q];    /* chain head of queue */
      rdy_head[q] = rp;                 /* set new queue head */
  }
  else {                                /* add to tail of queue */
      rdy_tail[q]->p_nextready = rp;    /* chain tail of queue */ 
      rdy_tail[q] = rp;                 /* set new queue tail */
      rp->p_nextready = NIL_PROC;       /* mark new end */
  }

  /* Now select the next process to run. */
  pick_proc();   

}

对Minix3进程模型的看法

本文基于Minix3源代码简要的分析了Minix3的进程模型,其中主要包括:操作系统是怎么组织进程的、 进程状态如何转换以及进程是如何调度的;

MINIX3 的进程调度还是非常简单的,调度算法是非常短小的,其目的就是体现 了一个简单和高效的设计原则,当然简单和高效其实很难并存,

整体而言,就是 一个多队列调度算法,根据优先级来放到相应的位置。

posted @ 2018-05-01 15:34  小鱼发风  阅读(381)  评论(0编辑  收藏  举报