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 PROCenum ProcStatus{FREE=0,READY,SLEEP};typedef struct Process{struct  Process * next; //同优先级的下一个同状态进程int *saved_sp ;              //栈顶指针int pid;                //idint ppid;               //父进程int status;             //状态   Free|ready| etcint priority;           //优先级int event ;             //造成睡眠的事件idint exitCode ;          //进程退出值struct Process *child;     //first child PROC pointerstruct 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 = retPCpushl %eax             # save CPU registers on stackpushl %ebxpushl %ecxpushl %edxpushl %ebppushl %esipushl %edipushflmovl   running,%ebx    # ebx -> running PROCmovl   %esp,4(%ebx)    # running PROC.saved_sp = espFIND:	  call   scheduler       # pick a new running PROCRESUME:	  movl   running,%ebx    # ebx -> (new) running PROCmovl   4(%ebx),%esp    # esp =  (new) running PROC.saved_sppopfl                  # restore saved registers from stackpopl  %edipopl  %esipopl  %ebppopl  %edxpopl  %ecxpopl  %ebxpopl  %eaxret                    # 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 放进优先级指针数组中* @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 
#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 listenqueue(&freeList, running);     // enter running into freeListprintList("freeList", freeList); // show freeListtswitch();
}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 listprintf("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 P0p->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 treeenter_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 processrunning = 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 系统编程》


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部