目录
实验要求
正文开始
编辑 难点讲解
结语
实验要求
实验一:进程的创建
一、实验项目类型:设计型
二、实验目的和要求
加深对进程概念的理解,熟悉PCB的组织,深入了解创建进程的一般过程,掌握用队列组织进程的方法。
三、实验内容
编程实现创建原语,形成就绪队列,模拟实现进程的创建过程。
具体内容包括:
1、确定进程控制块的内容,用链表组织进程控制块;
2、完成进程创建原语;
四、实验主要仪器设备
个人计算机、C程序编译器
五、实验具体内容和步骤的说明
这个实验主要考虑二个问题:如何组织进程、如何创建进程。
1、进程的组织:
首先就要设定进程控制块的内容。进程控制块PCB记录各个进程执行时的所有信息,不同的操作系统,进程控制块所记录的信息内容不一样。操作系统功能越强,软件也越庞大,进程控制块所记录的内容也就越多。本次实验只使用必不可少的信息。一般操作系统中,无论进程控制块中信息量多少,信息都可以大致分为以下四类:
① 标识信息
每个进程都要有一个惟一的标识符,用来标识进程的存在和区别于其他进程。这个标识符是必不可少的,可以用符号或编号实现,它必须是操作系统分配的。本实验中要求,采用编号方式,也就是为每个进程依次分配一个不相同的正整数。
② 说明信息
用于记录进程的基本情况,例如进程的状态、等待原因、进程程序存放位置、进程数据存放位置等等。本模拟实验中,因为进程没有数据和程序,仅使用进程控制块模拟进程,所以这部分内容仅包括进程状态。
③ 现场信息
现场信息记录各个寄存器的内容。当进程由于某种原因让出处理器时,需要将现场信息记录在进程控制块中,当进行进程调度时,从选中进程的进程控制块中读取现场信息进行现场恢复。现场信息就是处理器的相关寄存器内容,包括通用寄存器、程序计数器和程序状态字寄存器等。在实验中,可选取几个寄存器作为代表。用大写的全局变量AX、BX、CX、DX模拟通用寄存器、大写的全局变量PC模拟程序计数器、大写的全局变量PSW模拟程序状态字寄存器。本实验要求读取一个寄存器的值,予以输出
④ 管理信息
管理信息记录进程管理和调度的信息。例如进程优先数、进程队列指针等。实验中,仅包括队列指针。
综合上面内容,建议进程控制块结构定义如下:
struct pcb
{int name; //进程标识符
int status; //进程状态
int ax, bx, cx, dx; //进程现场信息,通用寄存器内容
int pc; //进程现场信息,程序计数器内容
int psw; //进程现场信息,程序状态字寄存器内容
int next; //下一个进程控制块的位置
}
进程控制块定义好后,考虑如何组织进程控制块。多道程序设计系统中,往往同时创建多个进程。在单处理器的情况下,每次只能有一个进程处于运行态,其他的进程处于就绪状态或阻塞状态。为了便于管理,通常把处于相同状态的进程的进程控制块链接在一起。
单处理器系统中,正在运行的进程只有一个。因此,单处理器系统中的进程控制块分成三个队列:
①、一个正在运行进程的进程控制块;
②、就绪进程的进程控制块组成的就绪队列;
③、阻塞进程的进程控制块组成的阻塞队列。
由于实验模拟的是进程调度,没有对阻塞队列的操作,所以实验中只有一个指向正在运行进程的进程控制块指针和一个就绪进程的进程控制块队列指针。操作系统的实现中,系统往往在主存中划分出一个连续的专门区域存放系统的进程控制块,实验中应该用数组模拟这个专门的进程控制块区域,定义如下:
#define n 10 //假定系统允许进程个数为10
struct pcb pcbarea[n]; //模拟进程控制块区域的数组
这样,进程控制块的链表实际上是数据结构中使用的静态链表。进程控制块的链接方式可以采用单向和双向链表,实验中,进程控制块队列采用单向静态链表。为了管理空闲进程控制块,还应该将空闲控制块链接成一个队列。
实验中将采用时间片轮转调度算法,这种算法是将进程控制块按照进入就绪队列的先后次序排成队列。对就绪队列的操作就是从队头摘下一个进程控制块和从队尾挂入一个进程控制块。因此为就绪队列定义两个指针,一个头指针,指向就绪队列的第一个进程控制块;一个尾指针,指向就绪队列的最后一个进程控制块。
实验中指向运行进程的进程控制块指针、就绪队列指针和空闲进程控制块队列指针定义如下:
int run; //定义指向正在运行进程的进程控制块的指针
struct
{int head;
int tail;
}ready; //定义指向就绪队列的头指针head和尾指针tail
int pfree; //定义指向空闲进程控制块队列的指针
2、进程创建。
进程创建是一个原语,因此在实验中应该用一个函数实现,进程创建的过程应该包括:
①申请进程控制块:进程控制块的数量是有限的,如果没有空闲进程控制块,则进程不能创建,如果申请成功才可以执行第②步;
②申请资源:除了进程控制块外,还需要有必要的资源才能创建进程,如果申请资源不成功,则不能创建进程,并且归还已申请的进程控制块;如果申请成功,则执行第三步,实验无法申请资源,所以模拟程序忽略了申请资源这一步;
③填写进程控制块:将该进程信息写入进程控制块内,实验中只有进程标识符、进程状态可以填写,每个进程现场信息中的寄存器内容由于没有具体数据而使用进程(模拟进程创建时,需输入进程标识符字,进程标识符本应系统建立,并且是惟一的,输入时注意不要冲突),刚刚创建的进程应该为就绪态,然后转去执行第四步;
④挂入就绪队列:如果原来就绪队列不为空,则将该进程控制块挂入就绪队列尾部,并修改就绪队列尾部指针;如果原来就绪队列为空,则将就绪队列的头指针、尾指针均指向该进程控制块,进程创建完成。
3、主程序:
完成上述功能后,编写主函数进行测试:首先建立一个就绪队列,手工输入信息建立几个进程;然后输出每个进程的进程id,察看结果。
注意:
这篇文章中小光会带你们将老师布置的实验内容这部分,详细的讲解一下,大家可以直接复制代码,也可以学习一下怎么写,在这个基础上加以拓展,这样就不会查重了。
正文开始
让我们先来看一下实验要求中的代码(代码如下)
#include <stdio.h>
#include <stdlib.h>
#define READY 1
#define MAX_PCB 10 struct PCB {int pid; int state;int ax;int bx;int cx;int dx;int pc;int psw;struct PCB* next;
};struct PCB pcbPool[MAX_PCB];
int allocPCBCount = 0; struct PCB* readyQueueHead;
struct PCB* readyQueueTail;void createProcess() {struct PCB* pcb;if(allocPCBCount >= MAX_PCB) {printf("No free PCB\n");return;} else {pcb = &pcbPool[allocPCBCount++]; allocPCBCount++;}printf("Enter pid: ");scanf("%d", &pcb->pid);pcb->state = READY; // 初始化寄存器值pcb->ax = 0; //将该PCB的ax寄存器值设置为0pcb->bx = 0;pcb->cx = 0;pcb->dx = 0;pcb->pc = 0;pcb->psw = 0;if(readyQueueHead == NULL) {readyQueueHead = readyQueueTail = pcb; } else {readyQueueTail->next = pcb;readyQueueTail = pcb;}
}int main() {readyQueueHead = readyQueueTail = NULL;createProcess();createProcess();struct PCB* p;for(p = readyQueueHead; p != NULL; p = p->next) {printf("pid: %d state: %d ax: %d bx: %d cx: %d dx: %d pc: %d psw: %d\n",p->pid, p->state, p->ax, p->bx, p->cx, p->dx, p->pc, p->psw);}return 0;
}
对以上代码进行一下解释防止有人看不到创建过程:
首先定义了进程状态和最大进程数的宏,以及进程控制块(PCB)的数据结构,其中包含了进程的所有必要信息。
然后使用一个PCB池来预先分配PCB的内存,并定义两个指针readyQueueHead和readyQueueTail来维护就绪队列。
createProcess函数实现了进程的创建和PCB的初始化: (1) 首先检查PCB池是否还有可用PCB,如果满了则返回错误 (2) 然后从池中分配一个PCB,并填写进程信息如PID (3) 初始化PCB中的寄存器状态值为0 (4) 最后将PCB插入到就绪队列的尾部
main函数先做就绪队列的初始化,然后调用createProcess来创建两个进程。
通过遍历就绪队列并打印每个PCB的信息,可以验证进程创建和队列管理的正确性。
这样通过PCB池、就绪队列和创建函数的配合,实现了进程控制块的动态分配与回收,以及进程状态的维护和管理。
展示一下运行效果图(如果大家的运行效果是这样就说明完成了)
我使用是编译环境是DevC++大家也可以使用其他的编译环境。
难点讲解
pcb->state = READY;这个是什么意思
pcb->state = READY;
这行代码的作用是设置PCB的状态为就绪(READY)状态。
这里我使用了一个宏定义:
#define READY 1
将就绪状态定义为1。
所以pcb->state = READY;
就会将pcb的state字段设置为1,表示该PCB对应的进程现在是就绪(ready)状态。
之所以要设置状态,是为了后续在调度算法选择新进程运行时,可以根据进程状态来判断,只有就绪状态的进程才能被调度运行。
此外还可以定义其它状态,如:
#define RUNNING 2
#define BLOCKED 3
设置状态主要是为了在程序中表示和跟踪一个进程的当前情况。
createProcess(); createProcess();这个为什么要写两个
createProcess();
函数是用来创建一个新的进程并将其添加到就绪队列中的。
之所以示例代码中调用了两个 createProcess();
,主要是为了创建两个进程做测试:
createProcess(); // 创建第一个进程
createProcess(); // 创建第二个进程
创建第一个进程后,就绪队列中只有一个进程。
再创建第二个进程后,就绪队列中就已经有两个进程了。
这样当我们后面遍历就绪队列并打印每个进程的信息时,可以看到两个进程的输出,验证创建和就绪队列管理是否正确。
如果只调用了一次 createProcess();
,那么就绪队列中只会有一个进程,输出也就只有一个进程的信息,不太容易观察到链表和过程是否正确。
所以调用两次主要是为了创建多个进程,方便后续的打印调试与验证。
PCB的组成部分和需要体现的信息。
struct PCB {int pid; // 进程标识符int state; // 进程状态 int ax; // 通用寄存器int bx;int cx;int dx;int pc; // 程序计数器int psw; // 程序状态字struct PCB* next; // 指向下一个PCB的指针
};
以上PCB主要修改了以下部分:
- 添加了ax、bx、cx、dx通用寄存器
- 添加了pc程序计数器
- 添加了psw程序状态字寄存器
这样PCB中就包含了所需的标识信息、说明信息、现场信息和管理信息。
后续在创建进程时,可以要求输入或随机生成这些寄存器的值;在调度运行过程中,需要保存和恢复这些寄存器的现场。
初始化寄存器值 pcb->ax = 0; pcb->bx = 0; pcb->cx = 0; pcb->dx = 0; pcb->pc = 0; pcb->psw = 0;是什么意思
这几行代码是用于初始化新创建的PCB中的寄存器的值,将它们都设置为0。
具体来说:
pcb->ax = 0; 意思是将该PCB的ax寄存器值设置为0。
pcb->bx = 0; 意思是将该PCB的bx寄存器值设置为0。
以此类推,将cx、dx、pc和psw寄存器都设置为了0。
之所以要这么做初始化,主要有以下两个原因:
-
刚创建的新进程,它的寄存器内容应该是未定义的。在实际中,可能是随机数或其它值,但比较简单的方法是初始化为0。
-
为了后面打印输出时,这些寄存器有一个确定的值,便于显示,验证PCB结构和数据是否正确。
如果不进行初始化,打印输出时就是一些未定义的随机值,不方便调试。
所以这些代码就是设置新PCB的寄存器值为0,主要是为了简化处理和打印输出。
if(allocPCBCount >= MAX_PCB)这个什么意思
这个if语句是用于检查当前PCB池中是否还有可用的PCB来分配给新进程。
具体逻辑是:
-
定义了最大PCB数量MAX_PCB,例如10
-
allocPCBCount记录当前已经分配出去的PCB数量
-
当去申请新PCB时,先检查:
if(allocPCBCount >= MAX_PCB)
如果已分配数量大于等于最大数量,表示PCB池已满,没有可分配的PCB
-
这个时候就会打印输出 "No free PCB" 来标示分配失败
-
并且return直接退出,不再执行后续PCB分配逻辑
所以这个if语句就是控制整个PCB申请过程的关键逻辑,用来检查和处理PCB池为空时的情况。
在主函数中,可以多次调用createProcess来模拟申请PCB,当分配到最大数后,再调用就会失败,这就完成了PCB申请和管理的基本机制。
这样就结束了,感谢大家的观看,百分百成功,不会出现运行不了的问题。但一定要按照小光的代码复制,全部复制粘贴就行了。
结语
关注小光,小光帮你写实验报告(不是真的帮你写,就是我写好,你直接复制拿走的那种)也可以看看小光的其他文章。
🌌点击下方个人名片,交流会更方便哦~(欢迎到博主主页加入我们的 CodeCrafters联盟一起交流学习)↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓