Linux C : 进程管理实验:创建进程、上下文切换

       进程可以看成程序的执行过程,可以展示在当前时刻的执行状态。它是程序在一个数据集合上的一次动态执行的过程。这个数据集合通常包含存放可执行代码的代码段,存放初始化全局变量和初始化静态局部变量的数据段、用于调试的符号表、未初始化的全局变量和未初始化的静态局部变量的bss段,存放对象数据和临时变量以及数据层次结构的堆栈,系统资源等
       进程有多种状态,例如创建,就绪,执行,等待,终止 等。

       为了实验简单,暂时只设置如下的进程状态:空闲状态,就绪,睡眠,僵尸状态。其中,僵尸状态值的是进程已经执行结束,但是还没释放资源的进程。而对于单CPU的系统来说,获得CPU使用权的进程状态就是执行态,进程执行结束就应该释放资源。

目录

一、结构体定义

二、上下文切换

三、优先级队列

四、进程操作

五、编译和执行


一、结构体定义

在 type.h中 定义进程的结构体:

为了尽量和实际操作系统中保持一致,要把栈 kstack 放在结构体的最后面,栈底到栈顶,就是从高地址到低地址,从kstack[SSIZE -1 ] 到 kstack [0] 。而saved_sp就是这个栈的栈顶指针。

//
// Created by Administrator on 2021/6/7.
//

#ifndef PROCESSMANAGEMENT_TYPE_H
#define PROCESSMANAGEMENT_TYPE_H

#define NPROC 9
#define SSIZE 1024
#define proc PROC
     enum ProcStatus{
        FREE=0,
        READY,
        SLEEP
    };
    typedef struct Process{
        struct  Process * next; //同优先级的下一个同状态进程
        int *saved_sp ;              //栈顶指针
        int pid;                //id
        int ppid;               //父进程
        int status;             //状态   Free|ready| etc
        int priority;           //优先级
        int event ;             //造成睡眠的事件id
        int exitCode ;          //进程退出值
        struct Process *child;     //first child PROC pointer
        struct Process *slbling;   //兄弟节点
        struct Process *perent;    //父节点
        int kstack[SSIZE];      //栈空间
    }PROC;
 #endif //PROCESSMANAGEMENT_TYPE_H

二、上下文切换

       上下文切换,指的是正在执行的进程因为某些原因需要让出CPU使用权,需要保留现有的状态,等到重新获得CPU使用权的时候,恢复刚刚暂停执行时的状态继续执行。scheduler 方法在后续中会讲到,它的功能是把当前执行的进程running弄到就绪队列中,再从就绪队列中搞一个进程来执行。

       在tswitch.s 汇编文件中写入下述代码。其中 

          movl   running,%ebx    
          movl   %esp,4(%ebx)

      这段代码把 running指针的值放入%ebx,而 4(%ebx)则是running地址再往高地址偏移4个字节,对应再程序中的变量是 Process 中的 *saved_sp变量。所以这两句代码就是为了 saved_sp= %esp

      当running换成新进程后,再 Resume 代码块,先 %esp = saved_sp,然后再按顺序出栈,把之前的状态进行恢复。执行到ret时,弹出PC寄存器的代码地址继续执行。

          .globl  running,scheduler,tswitch
tswitch:
SAVE:                            # on entry: stack top = retPC
	  pushl %eax             # save CPU registers on stack
	  pushl %ebx
	  pushl %ecx
	  pushl %edx
	  pushl %ebp
	  pushl %esi
	  pushl %edi
          pushfl

          movl   running,%ebx    # ebx -> running PROC
          movl   %esp,4(%ebx)    # running PROC.saved_sp = esp

FIND:	  call   scheduler       # pick a new running PROC

RESUME:	  movl   running,%ebx    # ebx -> (new) running PROC
          movl   4(%ebx),%esp    # esp =  (new) running PROC.saved_sp

          popfl                  # restore saved registers from stack
	  popl  %edi
          popl  %esi
          popl  %ebp
          popl  %edx
          popl  %ecx
          popl  %ebx
          popl  %eax
	
          ret                    # return by retPC on top of stack 

# stack contents = |retPC|eax|ebx|ecx|edx|ebp|esi|edi|eflag|
#                    -1   -2  -3  -4  -5  -6  -7  -8   -9
	

三、优先级队列

      队列采用链表形式进行组织,对进程列表按照优先级进行入队、出队、打印操作


#include "queue.h"

/**
 * <p> 把进程指针P 放进优先级指针数组中
 * @param queue
 * @param p
 * @return
 */
static int enqueue(PROC **queue ,PROC * p){
    PROC  *q = *queue;
    if (q==NULL || p->priority > q->priority){
        //如果队列为空或者P的优先级要比队列头来得高,那么直接放进队列头
        *queue = p;
        p->next = q;
    }else{
        //否则定位到对应的优先级序列
        while(q->next!=NULL && p->priority <= q->next->priority){
            q = q->next;
        }
        p->next = q->next;
        q->next = p;
    }
}

static PROC* dequeue(PROC ** queue){
    PROC  *p= *queue;
    if(p!=NULL){
        *queue = (*queue)->next;
    }
    return p;
}

static int printList (const char * name ,PROC * p){
    printf("%s =",name);
    while(p){
        printf("[%d %d ]->",p->pid,p->priority);
        p=p->next;
    }
    printf("NULL\n");
    return  0;
}

四、进程操作

1)定义执行、空闲、就绪、睡眠状态进程队列 ,本次示例最多有 NPROC=9 个进程 

2)CPU使用权切换 scheduler,把一个正在执行的换进就绪队列中,再从就绪队列换出一个进程。

3)上下文切换 do_switch, 其中,tswitch的定义在 tswitch.s文件中。该方法实现,保存寄存器状态值,恢复一个就绪态的进程,并恢复该进程暂停之前的状态

4)创建进程 do_kfork ,从空闲队列中抽出一个进程并初始化信息,加入到就绪队列中。

在初始化过程中,kstack[SSIZE-1] 赋成和 body的函数地址,save_sp指针指向kstack[SSIZE -9]。意味着,如果在tswtich.s 执行 movl .save_sp ,%esp 后再pop  8个寄存器后,esp寄存器对应的内存单元就是kstack[SSIZE-1] 即body的函数地址,再ret一下,就会执行body函数。

5)进程退出 do_exit , 把进程重新加入到空闲队列中。

6)系统初始化  init ,假设在该系统中 proc [0]  即 P0 的进程优先级最低,在空闲列表中的进程都是P0进程的子进程 ,子进程的优先级都为1 ,P0进程的优先级为0。

7) main 函数:初始化进程P0,并创建一个子进程。调用tswitch 切换到子进程中,如果子进程都结束退出,那么P0才有机会重新获取CPU执行权。P0结束,程序结束 

#include <stdio.h>
#include "type.h"

int kfork(), kexit(), tswitch(), printList(), enqueue() ;

PROC proc[NPROC], *running, *freeList, *readyQueue, *sleepList;

int do_switch()
{
   printf("proc %d switching task\n", running->pid);
   tswitch();
   printf("proc %d resuming\n", running->pid);
}

int do_kfork()
{
   int child = kfork();
   if (child < 0)
      printf("kfork failed\n");
   else{
      printf("proc %d kforked a child = %d\n", running->pid, child); 
      printList("readyQueue", readyQueue);
   }
   return child;
}



int kexit(){
 
 
   running->status = FREE;
   running->priority = 0;

// ASSIGNMENT 3: add YOUR CODE to delete running PROC from parent's child list

   enqueue(&freeList, running);     // enter running into freeList
   printList("freeList", freeList); // show freeList

   tswitch();
}

int do_exit()
{
  if (running->pid==1){
    printf("P1 never dies\n");
    return -1;
  }
  kexit();    // journey of no return 
}

int body()
{
  int c, CR;
  printf("proc %d starts from body()\n", running->pid);
  while(1){
    printf("***************************************\n");
    printf("proc %d running: Parent = %d\n", running->pid, running->ppid);

 // ASSIGNMENT 3: add YOUR CODE to show child list

    printf("input a char [f|s|q] : ");
    c = getchar(); CR=getchar(); 
    switch(c){
      case 'f': do_kfork();     break;
      case 's': do_switch();    break;
      case 'q': do_exit();      break;
    }
  }
}
     
/*******************************************************
  kfork() creates a child porc; returns child pid.
  When scheduled to run, child PROC resumes to body();
********************************************************/
int kfork()
{
  PROC *p;
  int  i;
  /*** get a proc from freeList for child proc: ***/
  p = dequeue(&freeList);
  if (!p){
     printf("no more proc\n");
     return(-1);
  }

  /* initialize the new proc and its stack */
  p->status = READY;
  p->priority = 1;         // for ALL PROCs except P0
  p->ppid = running->pid;

  //                    -1   -2  -3  -4  -5  -6  -7  -8   -9
  // kstack contains: |retPC|eax|ebx|ecx|edx|ebp|esi|edi|eflag|
  for (i=1; i<10; i++)
    p->kstack[SSIZE - i] = 0;

  p->kstack[SSIZE-1] = (int)body;
  p->saved_sp = &(p->kstack[SSIZE - 9]); 
  
/**************** ASSIGNMENT 3  ********************
  add YOUR code to implement the PROC tree as a BINARY tree
  enter_child(running, p);
****************************************************/

  enqueue(&readyQueue, p);
  return p->pid;
}

int init()
{
  int i;
  for (i = 0; i < NPROC; i++){
    proc[i].pid = i; 
    proc[i].status = FREE;
    proc[i].priority = 0;
    proc[i].next = (PROC *)&proc[(i+1)];
  }
  proc[NPROC-1].next = 0;
 
  freeList = &proc[0];        
  readyQueue = 0;
  sleepList = 0;

  // create P0 as the initial running process
  running = dequeue(&freeList);
  running->status = READY;
  running->priority = 0;  

  running->child = 0;  
  running->sibling = 0;  
  running->parent = running;

  printf("init complete: P0 running\n"); 
  printList("freeList", freeList);
}

/*************** main() ***************/
int main()
{
   printf("\nWelcome to 360 Multitasking System\n");
   init();
   kfork();  
   printf("P0: switch task\n");
     tswitch();
   printf("All dead. Happy ending\n");
}

/*********** scheduler *************/
int scheduler()
{ 
  printf("proc %d in scheduler()\n", running->pid);
  if (running->status == READY)
      enqueue(&readyQueue, running);
  printList("readyQueue", readyQueue);
  running = dequeue(&readyQueue);
  printf("next running = %d\n", running->pid);  
}


五、编译和执行

编译上述文件:命令为

gcc -m32  t.c  tswtich.s queue.c 

再执行 ./a.out 如下结果:

参考书本《Systems Programming in Unix/Linux》 或 译文《Unix/Linux 系统编程》

猜你喜欢

转载自blog.csdn.net/superSmart_Dong/article/details/118230347