【快速解决】实验一:模拟实现进程的创建《操作系统上机》实验报告

本文详细介绍了如何设计和实现进程的创建过程,包括进程控制块(PCB)的组织、状态管理、进程控制块的动态分配与回收,以及使用时间片轮转调度算法管理就绪队列。通过C语言代码展示了进程创建函数和主函数的操作流程。
摘要由CSDN通过智能技术生成

目录

实验要求

正文开始 

​编辑 难点讲解

 结语 


实验要求

实验一:进程的创建

一、实验项目类型:设计型

二、实验目的和要求

加深对进程概念的理解,熟悉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寄存器值设置为0
  pcb->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;
}

 对以上代码进行一下解释防止有人看不到创建过程:

  1. 首先定义了进程状态和最大进程数的宏,以及进程控制块(PCB)的数据结构,其中包含了进程的所有必要信息。

  2. 然后使用一个PCB池来预先分配PCB的内存,并定义两个指针readyQueueHead和readyQueueTail来维护就绪队列。

  3. createProcess函数实现了进程的创建和PCB的初始化: (1) 首先检查PCB池是否还有可用PCB,如果满了则返回错误 (2) 然后从池中分配一个PCB,并填写进程信息如PID (3) 初始化PCB中的寄存器状态值为0 (4) 最后将PCB插入到就绪队列的尾部

  4. main函数先做就绪队列的初始化,然后调用createProcess来创建两个进程。

  5. 通过遍历就绪队列并打印每个PCB的信息,可以验证进程创建和队列管理的正确性。

  6. 这样通过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主要修改了以下部分:

  1. 添加了ax、bx、cx、dx通用寄存器
  2. 添加了pc程序计数器
  3. 添加了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。

之所以要这么做初始化,主要有以下两个原因:

  1. 刚创建的新进程,它的寄存器内容应该是未定义的。在实际中,可能是随机数或其它值,但比较简单的方法是初始化为0。

  2. 为了后面打印输出时,这些寄存器有一个确定的值,便于显示,验证PCB结构和数据是否正确。

如果不进行初始化,打印输出时就是一些未定义的随机值,不方便调试。

所以这些代码就是设置新PCB的寄存器值为0,主要是为了简化处理和打印输出。

if(allocPCBCount >= MAX_PCB)这个什么意思 

这个if语句是用于检查当前PCB池中是否还有可用的PCB来分配给新进程。

具体逻辑是:

  1. 定义了最大PCB数量MAX_PCB,例如10

  2. allocPCBCount记录当前已经分配出去的PCB数量

  3. 当去申请新PCB时,先检查:

    if(allocPCBCount >= MAX_PCB)

    如果已分配数量大于等于最大数量,表示PCB池已满,没有可分配的PCB

  4. 这个时候就会打印输出 "No free PCB" 来标示分配失败

  5. 并且return直接退出,不再执行后续PCB分配逻辑

所以这个if语句就是控制整个PCB申请过程的关键逻辑,用来检查和处理PCB池为空时的情况。

在主函数中,可以多次调用createProcess来模拟申请PCB,当分配到最大数后,再调用就会失败,这就完成了PCB申请和管理的基本机制。

这样就结束了,感谢大家的观看,百分百成功,不会出现运行不了的问题。但一定要按照小光的代码复制,全部复制粘贴就行了。

 结语 

关注小光,小光帮你写实验报告(不是真的帮你写,就是我写好,你直接复制拿走的那种)也可以看看小光的其他文章。

🌌点击下方个人名片,交流会更方便哦~(欢迎到博主主页加入我们的 CodeCrafters联盟一起交流学习↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓    

  • 36
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
操作系统 进程创建实验报告 调用fork( )创建进程 实验原理: 一) 进程 UNIX中,进程既是一个独立拥有资源的基本单位,又是一个独立调度的基本单位。一个进程实体由若干个区(段)组成,包括程序区、数据区、栈区、共享存储区等。每个区又分为若干页,每个进程配置有唯一的进程控制块PCB,用于控制和管理进程。 PCB的数据结构如下: 1、进程表项(Process Table Entry)。包括一些最常用的核心数据: 进程标识符PID、用户标识符UID、进程状态、事件描述符、进程和U区在内存或外存的地址、软中断信号、计时域、进程的大小、偏置值nice、指向就绪队列中下一个PCB的指针P_Link、指向U区进程正文、数据及栈在内存区域的指针。 2、U区(U Area)。用于存放进程表项的一些扩充信息。 每一个进程都有一个私用的U区,其中含有:进程表项指针、真正用户标识符u-ruid(read user ID)、有效用户标识符u-euid(effective user ID)、用户文件描述符表、计时器、内部I/O参数、限制字段、差错字段、返回值、信号处理数组。 由于UNIX系统采用段页式存储管理,为了把段的起始虚地址变换为段在系统中的物理地址,便于实现区的共享,所以还有: 3、系统区表项。以存放各个段在物理存储器中的位置等信息。 系统把一个进程的虚地址间划分为若干个连续的逻辑区,有正文区、数据区、栈区等。这些区是可被共享和保护的独立实体,多个进程可共享一个区。为了对区进行管理,核心中设置一个系统区表,各表项中记录了以下有关描述活动区的信息: 区的类型和大小、区的状态、区在物理存储器中的位置、引用计数、指向文件索引结点的指针。 4、进程区表 系统为每个进程配置了一张进程区表。表中,每一项记录一个区的起始虚地址及指向系统区表中对应的区表项。核心通过查找进程区表和系统区表,便可将区的逻辑地址变换为物理地址。 二) 进程映像 UNIX系统中,进程进程映像的执行过程,也就是正在执行的进程实体。它由三部分组成: 1、用户级上、下文。主要成分是用户程序; 2、寄存器上、下文。由CPU中的一些寄存器的内容组成,如PC,PSW,SP及通用寄存器等; 3、系统级上、下文。包括OS为管理进程所用的信息,有静态和动态之分。 三) 所涉及的系统调用 1、fork( ) 创建一个进程。 系统调用格式: pid = fork( ) 参数定义: int fork( ) fork( )返回值意义如下: 0:在子进程中,pid变量保存的fork( )返回值为0,表示当前进程是子进程。 >0:在父进程中,pid变量保存的fork( )返回值为子进程的id值(进程唯一标识符)。 -1:创建失败。 如果fork( )调用成功,它向父进程返回子进程的PID,并向子进程返回0,即fork( )被调用了一次,但返回了两次。此时OS在内存中一个进程,所进程是调用fork( )父进程(parent process)的副本,称为子进程(child process)。子进程继承了父进程的许多特性,并具有与父进程完全相同的用户级上下文。父进程与子进程并发执行。 核心为fork( )完成以下操作: (1)为进程分配一进程表项和进程标识符 进入fork( )后,核心检查系统是否有足够的资源来一个进程。若资源不足,则fork( )系统调用失败;否则,核心为进程分配一进程表项和唯一的进程标识符。 (2)检查同时运行的进程数目 超过预先规定的最大数目时,fork( )系统调用失败。 (3)拷贝进程表项中的数据 将父进程的当前目录和所有已打开的数据拷贝到子进程表项中,并置进程的状态为“创建”状态。 (4)子进程继承父进程的所有文件 对父进程当前目录和所有已打开的文件表项中的引用计数加1。 (5)为子进程创建进程上、下文 进程创建结束,设子进程状态为“内存中就绪”并返回子进程的标识符。 (6)子进程执行 虽然父进程与子进程程序完全相同,但每个进程都有自己的程序计数器PC(注意子进程的PC开始位置),然后根据pid变量保存的fork( )返回值的不同,执行了不同的分支语句。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

命运之光

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值