进程可以看成程序的执行过程,可以展示在当前时刻的执行状态。它是程序在一个数据集合上的一次动态执行的过程。这个数据集合通常包含存放可执行代码的代码段,存放初始化全局变量和初始化静态局部变量的数据段、用于调试的符号表、未初始化的全局变量和未初始化的静态局部变量的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 系统编程》