ADD file via upload

main
pgq6zmujf 2 months ago
parent 70b9decbca
commit ab06125f7e

@ -0,0 +1,808 @@
第1关进程的创建
100
学习内容
记录
评论
任务描述
相关知识
进程的创建
管理进程标识符
编程要求
测试说明
进程管理:进程管理又称进程控制,主要完成进程各状态之间的转换,由具有特定功能的原语完成。进程管理包括进程创建,进程阻塞,进程终止和进程挂起等。
任务描述
编写一C语言程序实现在程序运行时通过系统调用fork( )创建两个子进程,使父亲、女儿、儿子三进程并发执行: 1在父进程中输出当前进程为父进程的提示“I am father”、当前进程的PID 和两个子进程的PID。 2在儿子进程中输出当前进程为儿子进程的提示“I am son”、当前进程的PID 和父进程的PID。 3在女儿进程中输出当前进程为女儿进程的提示“I am daughter”、当前进程的PID 和父进程的PID。
相关知识
为了完成本关任务你需要掌握1.如何创建进程2.如何管理以及获取进程及其父进程标识符。
进程的创建
1.
引起进程创建的事件:
2.
用户登录 作业调度 提供服务 应用请求
3.
4.
进程创建过程: 申请空白PCB 分配所需资源; 初始化PCB 插入就绪队列。
5.
创建进程的进程称为父进程,被创建的进程称为子进程。
1.进程创建新进程后有两种执行可能:
父进程与子进程并发执行
父进程等待,直到其某个或全部进程执行完毕
新进程的地址空间也有两种可能:
子进程是父进程的复制品(子进程具有和父进程相同的程序和数据)
子进程加载另一个程序
1.进程创建的控制函数
fork ()函数通过系统调用创建一个与原来进程几乎完全相同的进程,两个进程可以做完全相同的事,根据初始参数或者传入的变量不同,两个进程也可以做不同的事。 一个进程调用fork ()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
fork()通过复制调用进程来建立新的进程,是最基本的进程建立过程
为子进程分配一个空闲的进程控制块
分配给子进程唯一标识pid
以一次一页的方式复制父进程地址空间
从父进程处继承共享资源,如打开的文件和当前工作目录等
将子进程的状态设置为就绪,插入到就绪队列
对子进程返回标识符0
向父进程返回子进程的pid
函数原型: 头文件: #include<unistd.h> #include<sys/types.h>
pid_t fork( void); pid_t 是一个宏定义其实质是int被定义在#include<sys/types.h>中) 返回值若成功调用一次则返回两个值子进程返回0父进程返回子进程PID否则出错返回-1
fork使用示例1--创建子进程,并在父子进程中打印各自身份信息
对上述代码进行编译、运行,结果如下:
管理进程标识符
Linux操作系统使用进程标识符来管理当前系统中的进程进程的组标识符从父进程继承得到用于区分进程是否同组。进程的标识符由系统分配不能被修组标识符可以通过相关系统调用进程修改。
int getpid(); //取得当前进程的标识符进程ID
int getppid(); //取得当前进程的父进程ID。
int getpgrp(); //取得当前进程的进程组标识符。
int getpgid(int pid); //将当前进程的进程组标识符改为当前进程的进程ID使其成为进程组首进程并返回这一新的进程组标识符。
fork使用示例2--创建子进程,并在父子进程中打印各自身份信息和进程标识符
运行结果如下:
编程要求
根据上面fork的应用示例在右侧编辑器补充代码完成实验任务的功能要求并点击测试按钮即可。
测试说明
开始你的任务吧,祝你成功!
#include<stdlib.h>
#include<unistd.h>
#include<stdio.h>
//-------begin---------
int main()
{
    int pid_son = fork();
    if(pid_son == 0){
        printf("I am son,%d,%d\n",getpid(),getppid());
        exit(0);
    }else if(pid_son > 0){
        int pid_daughter = fork();
        if(pid_daughter == 0)
        {
            printf("I am daughter,%d,%d\n",getpid(),getppid());
            exit(0);
        }else if(pid_daughter > 0)
        {
            printf("I am father,%d,%d,%d\n",getpid(),pid_son,pid_daughter);
            wait(NULL);
            wait(NULL);
        }
    }
    return 0;
}
第2关加载新的进程映像
100
学习内容
记录
评论
任务描述
相关知识
加载新的进程映像
编程要求
任务描述
修改第一关的代码在儿子进程中调用exec 函数族以执行系统命令ls在女儿进程中调用exec函数族以执行系统命令pwd。
相关知识
为了完成本关任务,你需要掌握:如何加载新的进程映像。
加载新的进程映像
使用fork创建新进程后往往希望它能执行新的程序在Linux中进程创建和加载新进程映像是分离操作的。 在Linux中当创建一个进程后通常使用exec系列函数将子进程替换成新的进程映像。 exec()用新程序来重写当前进程。
1.exec()示例代码
2.int main()
3.{…
4. int pid = fork(); // 创建子进程
5. if (pid == 0) { // 子进程在这里继续
6. exec_status = execlp(“calc”, argc, argv0, argv1, …);
7. printf(“Why would I execute?”);
8. } else { // 父进程在这里继续
9. printf(“Whose your daddy?”);
10. …
11.}
exec()包括一系列系统调用,它们都是通过用一段新的程序代码覆盖原来的地址空间,实现进程执行代码的转换。
允许进程“加载”一个完全不同的程序并从main开始执行(即_start)。
允许进程加载时指定启动参数(argc, argv)。
exec调用成功时它是相同的进程PID没有改变但是运行了不同的程序代码段、堆栈和堆(heap)等完全重写。 Linux中并不存在exec()函数exec函数族指的是一组函数一共6个分别是
#include <unistd.h> 1. int execl(const char *path, const char *arg, ...); execl 函数通过指定完整的文件路径 path 来执行新程序,后续参数以可变参数的形式传递给新程序,参数列表必须以 NULL 结尾。 例如:
1.#include <stdio.h>
2.#include <unistd.h>
3.
4.int main() {
5. // 执行 ls 命令,列出当前目录下文件信息
6. execl("/bin/ls", "ls", "-l", NULL);
7. perror("execl"); // 如果执行失败,输出错误信息
8. return 1;
9.}
2. int execlp(const char *file, const char *arg, ...); execlp 函数与 execl 类似,但它不需要指定完整的文件路径,会在环境变量 PATH 指定的目录中搜索 file 对应的可执行文件。参数同样以可变参数形式传递,以 NULL 结尾。 例:
1.#include <stdio.h>
2.#include <unistd.h>
3.
4.int main() {
5. // 执行 ls 命令,由于 PATH 包含 /bin 等目录,可直接用文件名
6. execlp("ls", "ls", "-l", NULL);
7. perror("execlp"); // 如果执行失败,输出错误信息
8. return 1;
9.}
3. int execle(const char *path, const char *arg, ..., char *const envp[]); execle 函数通过指定完整的文件路径 path 执行新程序,参数以可变参数形式传递,以 NULL 结尾。最后一个参数 envp 是一个指向环境变量数组的指针,用于为新程序设置环境变量。 例:
1.#include <stdio.h>
2.#include <unistd.h>
3.
4.int main() {
5. char *envp[] = {"MY_VAR=value", NULL}; // 自定义环境变量
6. // 执行 echo 命令,输出环境变量 MY_VAR 的值
7. execle("/bin/echo", "echo", "$MY_VAR", NULL, envp);
8. perror("execle"); // 如果执行失败,输出错误信息
9. return 1;
10.}
4. int execv(const char *path, char *const argv[]); execv 函数通过指定完整的文件路径 path 执行新程序,参数以字符串数组 argv 的形式传递,数组最后一个元素必须为 NULL。 例:
1.#include <stdio.h>
2.#include <unistd.h>
3.
4.int main() {
5. char *argv[] = {"ls", "-l", NULL}; // 命令参数数组
6. // 执行 ls 命令
7. execv("/bin/ls", argv);
8. perror("execv"); // 如果执行失败,输出错误信息
9. return 1;
10.}
5. int execvp(const char *file, char *const argv[]); execvp 函数与 execv 类似,但它不需要指定完整的文件路径,会在环境变量 PATH 指定的目录中搜索 file 对应的可执行文件。参数以字符串数组 argv 的形式传递,数组最后一个元素必须为 NULL。 例:
1.#include <stdio.h>
2.#include <unistd.h>
3.
4.int main() {
5. char *argv[] = {"ls", "-l", NULL}; // 命令参数数组
6. // 执行 ls 命令
7. execvp("ls", argv);
8. perror("execvp"); // 如果执行失败,输出错误信息
9. return 1;
10.}
6. int execve(const char *path, char *const argv[], char *const envp[]);
execve 函数是 exec 函数族的底层实现,其他 exec 系列函数最终都会调用 execve。它通过指定完整的文件路径 path 执行新程序,参数以字符串数组 argv 的形式传递,数组最后一个元素必须为 NULL。最后一个参数 envp 是一个指向环境变量数组的指针,用于为新程序设置环境变量。 例:
1.#include <stdio.h>
2.#include <unistd.h>
3.
4.int main() {
5. char *argv[] = {"echo", "$MY_VAR", NULL}; // 命令参数数组
6. char *envp[] = {"MY_VAR=value", NULL}; // 自定义环境变量
7. // 执行 echo 命令,输出环境变量 MY_VAR 的值
8. execve("/bin/echo", argv, envp);
9. perror("execve"); // 如果执行失败,输出错误信息
10. return 1;
11.}
注意事项 这些函数如果执行成功,不会返回原程序;如果执行失败,会返回 -1并设置 errno 以指示错误原因。 在使用可变参数时,记得以 NULL 结尾;在使用数组形式传递参数时,数组最后一个元素必须为 NULL。 除 execvp 和 execlp 外,其他函数都需要指定完整的文件路径。以上函数在使用时,任选其一即可。
例如父进程创建子进程子进程在其代码中调用exec 函数族以执行系统命令date.其代码如下:
编译运行后结果如下:
编程要求
请在右侧编辑器中补全代码,然后点击测试按钮即可。 注意由于测评需求请如上例所示在每个printf语句后面添加fflush(stdout)以强制刷新缓冲区。
开始你的任务吧,祝你成功!
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
    pid_t son_pid, daughter_pid;
  //begin
    son_pid = fork();
    if(son_pid == 0){
     
      printf("I am son,%d,%d\n",getpid(),getppid());
      fflush(stdout);
      execlp("ls", "ls", NULL);
      // exit(0);
    }else if(son_pid > 0){
      daughter_pid = fork();
      if(daughter_pid == 0)
      {
       
        printf("I am daughter,%d,%d\n",getpid(),getppid());
        fflush(stdout);
        execlp("pwd", "pwd", NULL);
       
        // exit(0);
      }else if(daughter_pid > 0)
      {
        printf("I am father,%d,%d,%d\n",getpid(),son_pid,daughter_pid);
        fflush(stdout);
        wait(NULL);
        wait(NULL);
      }
    }
  //end
    return 0;
}
第1关进程的阻塞与退出
100
学习内容
记录
评论
任务描述
相关知识
进程的阻塞及其原语的使用
进程的撤销及其原语的使用
编程要求
任务描述
在实验二第一关代码的基础上修改程序在父进程中调用wait()儿子和女儿进程中调用exit()系统调用“实现”进程同步推进。父进程中获取子进程的PID号及结束状态值,儿子进程退出运行时返回状态值4女儿进程结束运行时返回状态值5。多次反复运行改进后的程序观察并记录运行结果。
相关知识
进程管理:进程管理又称进程控制,主要完成进程各状态之间的转换,由具有特定功能的原语完成。 除了实验二中介绍的进程创建原语fork还包括进程撤销原语exit()以及进程阻塞原语wait ()/waitpid()。
为了完成本关任务你需要掌握1.进程的阻塞及其原语的使用2.进程的退出及其原语的使用。
进程的阻塞及其原语的使用
处于运行状态的进程在其运行过程中期待某一事件的发生如等待键盘输入、等待磁盘数据传输完成、等待其它进程发送消息当被等待的事件未发生时由进程自己调用阻塞原语使自己由运行态转变为阻塞态。其使用的原语或者系统调用函数为Wait()。 wait()系统调用用于父进程等待子进程的结束。
子进程结束时通过exit()向父进程返回一个值
父进程通过wait()接受并处理返回值
wait()系统调用的功能
有子进程存活时,父进程进入等待状态,等待子进程的返回结果
当某子进程调用exit()时,唤醒父进程将exit()返回值作为父进程中wait的返回值
有僵尸子进程等待时wait()立即返回其中一个值
无子进程存活时wait()立刻返回
wait系统调用的原型
头文件: #include<sys/types.h> #include<sys/wait.h> 函数原型: pid_t wait (int * status) 返回值: 如果执行成功则返回子进程识别码或标志符PID如果有错误发生则返回-1。
wait()会暂时停止目前进程的执行直到有信号来到或子进程结束。如果在调用wait()时子进程已经结束则wait()会立即返回子进程结束状态值。子进程的结束状态值会由参数status 返回而子进程的进程识别码也会一起返回。如果不在意结束状态值则参数status 可以设成NULL。
wait()系统调用用于父进程等待子进程的结束但并未指明是哪个子进程当父进程有多个子进程时可以使用waitpid()等待指定子进程结束。
waitpid系统调用原型如下 头文件: #include<sys/types.h> #include<sys/wait.h> 函数原型pid_t waitpid(pid_t pid,int * status,int options) 返回值如果执行成功则返回子进程识别码PID如果有错误发生则返回-1。 pid参数说明
• pid > 0表示等待进程ID为pid的子进程 • pid = 0表示等待与调用进程同一进程组的任意子进程 • pid = -1表示等待任意子进程 • pid < -1表示等待所有子进程中进程组ID与pid绝对值相等的所有子进程 参数options是一个位掩码可以同时存在多个标志。其值可以为0或下面的组合。
WNOHANG 如果没有任何已经结束的子进程则马上返回,不予以等待。
WUNTRACED 如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会。
以上两个原语中都有status参数该参数用于存放子进程的结束状态下面有几个宏可判别结束情况
WIFEXITEDstatus如果子进程正常结束则为非0 值。
WEXITSTATUSstatus取得子进程exit()返回的结束代码,一般会先用 WIFEXITED 来判断是否正常结束才能使用此宏。
WIFSIGNALEDstatus如果子进程是因为信号而结束则此宏值为真。
WTERMSIGstatus取得子进程因信号而中止的信号代码一般会先用 WIFSIGNALED 来判断后才使用此宏。
WIFSTOPPEDstatus如果子进程处于暂停执行情况则此宏值为真。一般只有使用WUNTRACED 时才会有此情况。
WSTOPSIGstatus取得引发子进程暂停的信号代码一般会先用WIFSTOPPED 来判断后才使用此宏。
进程的撤销及其原语的使用
进程的撤销意味着结束进程收回进程所占用的资源关闭打开的文件、断开网络连接、回收分配的内存以及撤销该进程的PCB。 进程结束执行时调用exit(),完成进程资源回收。 exit()系统调用的功能:
将调用参数作为进程的“结果”
关闭所有打开的文件等占用资源
释放内存
释放大部分进程相关的内核数据结构
检查是否父进程是存活着的
如存活保留结果的值直到父进程需要它进入僵尸zombie/defunct状态 如果没有,它释放所有的数据结构,进程结束
清理所有等待的僵尸进程 进程终止是最终的垃圾收集(资源回收)
例如 在下例中子进程运行结束调用exit并把参数5作为结果返回给父进程父进程调用wait()等待子进程的结束若父进程调用wait时子进程未结束则父进程进入阻塞状态直到子进程调用exit将其唤醒并把结束的值5传递给它若子进程先于父进程结束则会保留结果值5直到父进程调用wait(),并让自己进入僵尸状态。
1. #include<stdlib.h>
2. #include <stdio.h>
3. #include<unistd.h>
4. #include<sys/types.h>
5. #include<sys/wait.h>
6. int main(int argc, char ** argv)
7. {
8. pid_t pid;
9. int status,i;
10. if(fork()== 0)
11. {
12. printf("This is the child process .pid =%d\n",getpid());
13. exit(5);
14. }
15. else
16. {
17. sleep(1);
18. printf("This is the parent process ,wait for child...\n");
19. pid=wait(&status);
20. if(WIFEXITEDstatus)
21. i=WEXITSTATUS(status);
22. printf("childs pid =%d .exit status=%d\n",pid,i);
23. }
24. }
将上述代码通过gcc编译运行其结果如下图所示
编程要求
根据提示,在右侧编辑器补充代码,并点击评测按钮评测即可。
开始你的任务吧,祝你成功!
代码文件
实验环境1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdio.h>
int main()
{  
  int fpid,fpid2,cpid1,cpid2;
  int status,i,j;
  fpid=fork();
  if(fpid<0)
  {
  perror("fork failed\n");
  exit(0);
  }
  else if(fpid==0)
  {
    printf("I am son, my PID is 
%d, parent PID is %d\n",getpid(),
getppid());  
    printf("I am working\n");
    sleep(1);
测试结果
1/1全部通过
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdio.h>
int main()
{  
  int fpid,fpid2,cpid1,cpid2;
  int status,i,j;
  fpid=fork();
  if(fpid<0)
  {
  perror("fork failed\n");
  exit(0);
  }
  else if(fpid==0)
  {
    printf("I am son, my PID is %d, parent PID is %d\n",getpid(),getppid());  
    printf("I am working\n");
    sleep(1);
    printf("I am done\n");
    exit(4);
  }
  else if (fpid>0)
    {
    /* 创建第二个子进程(女儿) */
        fpid2=fork();
        if (fpid2==0)
        {
        // 女儿进程执行代码
            printf("I am daughter, my PID is %d, parent PID is %d\n",getpid(),getppid());
            printf("I am working\n");
            sleep(1);
            printf("I am finished\n");
            exit(5);
        }
        else if (fpid2<0)
        {
            perror("Second fork failed");
            return 1;
        }
        else if(fpid2>0)
        {/* 父进程执行代码 */
            printf("I am father, my PID is %d, son PID is %d, daughter PID is %d\n",
            getpid(),fpid,fpid2);  
            fpid=wait(&status);
            i=WEXITSTATUS(status);  
            fpid2=wait(&status);                
            j=WEXITSTATUS(status);
            printf("Child %d exited with status %d\n",fpid,i);
            printf("Child %d exited with status %d\n",fpid2,j);
        }
   
    }  
}
第1关进程调度--时间片轮转调度算法
300
学习内容
记录
评论
任务描述
相关知识
进程控制块PCB的定义
就绪队列和运行队列的创建
随机数的产生
时间片轮转调度算法的原理及其流程
编程要求
任务描述
本关任务编写程序模拟实现单处理机系统中的进程调度算法实现对多个进程的模拟调度。具体为定义合理的PCB数据结构建立含有8个进程结点的就绪队列PCB组织成单链表形式每个进程的要求运行时间随机产生但不小于1不大于15。设置时间片大小3~6使用时间片轮转调度算法实现对8个进程的模拟调度。
相关知识
进程控制块通过链表队列的方式组织起来,系统中存在运行队列和就绪队列(为简单起见,不设阻塞队列),进程的调度就是进程控制块在运行队列和就绪队列之间的切换。当需要调度时,从就绪队列中挑选一个进程占用处理机,即从就绪队列中删除一个进程,插入到运行队列中,当占用处理机的进程运行的时间片完成后,放弃处理机,即在运行队列中的进程控制块经过一段时间(时间片)后,从该队列上删除,如果该进程运行完毕,则删除该进程(节点);否则,则插入到就绪队列中。
为了完成本关任务你需要掌握1.进程控制块PCB的定义 2.就绪队列和运行队列的创建 3.随机数的产生 4.时间片轮转调度算法的原理
进程控制块PCB的定义
进程控制块可以根据具体的调度算法来确定自身所包含的信息如进程名、优先数、到达时间、需要运行时间、已用CPU时间、进程状态等。进程控制块用C语言中的结构体来表示。本实验中定义进程控制块内容包括参数①进程名name②要求运行时间 runtime③已运行时间runedtime④本轮运行时间killtime。
struct PCB { int name; int runtime; int runedtime; int killtime; struct PCB *next; }; typedef struct PCB PCB;
就绪队列和运行队列的创建
就绪队列和运行队列的创建实际就是单链表的创建单链表中的结点就是进程的PCB。就绪队列中的结点是没有运行完且处于就绪状态的进程PCB运行队列中的结点是处于运行状态的进程PCB初始为空。关于单链表的创建这是数据结构的范畴这里不再赘述。
由于运行队列初始为空,这里只给出就绪队列的创建代码。
1.#define LEN sizeof(PCB)
2.PCB *runqueue;//运行队列指针
3.PCB *top,*tail,*temp;//就绪队列指针
4.int i;
5.srand((int)time(0));
6.for(i=0;i<NUM;i++)//NUM为就绪队列中进程的个数
7.{
8. temp=(PCB*)malloc(LEN);//申请空白PCB
9. temp->name=i;//初始化进程名
10. temp->runtime=rand()%15;//初始化进程需要运行的时间
11. temp->runedtime=0;//初始化进程已经运行的时间
12. temp->next=NULL;//初始化该进程PCB所指向的下一个进程PCB;
13. temp->killtime=0;//初始化该进程本轮运行的时间;
14. if(i==0) //如果是创建的第一个进程PCB
15. {
16. top=temp; //头尾指针均指向它;
17. tail=temp;
18. }
19. else//如果不是链表中的第一个节点则将当前进程PCB插入到链表尾部
20. {
21. tail->next=temp;
22. tail=temp;
23. }
24. printf("process name %d, runtime=%d, runedtime=%d,killtime=%d\n", tail->name,tail->runtime,tail->runedtime,tail->killtime); //队列创建好后,打印就绪队列信息;
25.}
随机数的产生
本实验中要求每个进程的运行时间随机产生库函数中系统提供了两个函数用于产生随机数srand()和rand()。 函数一int rand(void); 从srand (seed)中指定的seed开始返回一个[0,RAND_MAX0x7fff]间的随机整数。
函数二void srand(unsigned seed) 参数seed是rand()的种子用来初始化rand()的起始值。函数rand()是真正的随机 数生成器而srand()会设置供rand()使用的随机数种子。 如果你在第一次调用rand()之前没有调用srand()那么系统会为你自动调用srand()。而使用同种子相同的数调用 srand() 会导致相同的随机数序列被生成。通常用srand( (unsigned) time(0) )或者srand((unsigned)time(NULL))来产生种子. 关于time_t time(0)time_t被定义为长整型它返回从1970年1月1日零时零分零秒到目前为止所经过的时间单位为秒。
例如:
1.#include<time.h>
2.#include<stdio.h>
3.void main()
4.{
5. int i,j;
6. srand(10); //srand((int)time(0));
7. for (i=0; i<10; i++)
8. {
9. j = (int) (rand())%20;
10. printf(" %d\n ", j);
11. }
12.}
时间片轮转调度算法的原理及其流程
第1步取就绪队列的队首结点作为被调度的进程将其从就绪队列中删除修改就绪队列队首指针后移涉及到单链表中节点的删除并将其插入到运行队列中模拟调度运行涉及到单链表中节点的插入 第2步调度运行队列结点即修改运行队列中进程PCB的要求运行时间将原本要求运行时间减去本轮时间片时间 第3步a若修改后要求运行时间<=0则表示该进程结点运行完毕修改进程结点的PCB信息记录runtimerunedtimekilltime等信息。并将结点信息输出。b否则表示该进程结点未完成记录runtimerunedtimekilltime等信息将结点信息输出。并将该运行进程的PCB所对应的节点置于就绪队列的队尾等待下次调度同时修改就绪队列中的队尾指针。 第4步若就绪队列非空则继续执行第1步直至就绪队列为空。
编程要求
在右侧编辑器补充代码完成使用时间片轮转调度算法实现对8个进程就绪队列的模拟调度。
开始你的任务吧,祝你成功!
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <time.h>
#define NUM 8
#define LEN sizeof(PCB)
struct PCB
{
  int name;
  int runtime;
    int runedtime;
  int killtime;
   struct PCB *next;
};
typedef struct PCB PCB;
void main()
{
   int timeslice=5;
   PCB *runqueue;
   PCB *top,*tail,*temp;
   int i;
   srand((int)time(0));
   for(i=0;i<NUM;i++)
   {
     temp=(PCB*)malloc(LEN);
     temp->name=i;
     temp->runtime=rand()%15+1;
     temp->runedtime=0;
     temp->killtime=0;
     temp->next=NULL;
     if(i==0)
     {
       top=temp;
       tail=temp;
     }
     else
     {
       tail->next=temp;
       tail=temp;
     }
     printf("START:process name %d, runtime=%d, runedtime=%d,killtime=%d\n",tail->name,tail->runtime,tail->runedtime,tail->killtime);
   }
   printf("******************************\n");
   while(top!=NULL)
   {
      runqueue=top;
      top=top->next;
      runqueue->next=NULL;
      runqueue->runtime=runqueue->runtime-timeslice;
      if(runqueue->runtime<=0)
      {
        runqueue->killtime=runqueue->killtime+timeslice;
        runqueue->runedtime=runqueue->runedtime+runqueue->killtime;
        runqueue->runtime=0;
        printf("process name %d, runtime=%d, runedtime=%d,killtime=%d...END...\n",runqueue->name,runqueue->runtime,runqueue->runedtime,runqueue->killtime);
      }
     else
      {
        runqueue->runedtime += timeslice;
        runqueue->runtime -= timeslice;
        printf("process name %d, runtime=%d, runedtime=%d,killtime=%d\n",runqueue->name,runqueue->runtime,runqueue->runedtime,runqueue->killtime);
        if (tail != NULL)
        {
          tail->next = runqueue;
        } else {
          top = runqueue;
        }
        tail = runqueue;
     }
   }
}
第1关存储管理
200
学习内容
记录
评论
任务描述
相关知识
实验原理
如何定义内存块
分配内存流程
回收内存流程
编程要求
测试说明
任务描述
本关任务根据流程图和参考程序完成模拟内存分配和回收过程。内存空间大小为100进程数为5每个进程所需空间为随机产生大小为1~20编制程序首先对5个进程进行内存分配然后回收指定的进程空间并进行适当的空闲分区合并操作要求每次操作结束后都能显示当前的内存分配情况。
相关知识
为了完成本关任务,你需要掌握:
1.实验原理
2.如何定义内存块;
3.分配内存流程;
4.回收内存流程。
实验原理
使用一个链表来模拟内存存储空间,建立内存块来记录内存分配使用情况,通过随机产生进程及其所需要的内存来模拟真实的进程。通过给进程分配内存及回收来实现动态分区分配存储管理方法。 初始化链表中只有一个节点代表整个内存空间大小为100起始地址为0使用标志为0表示空闲。 分配阶段对于进程5个及其随机产生的需求空间每个进程需求空间120从链表中第一个节点所指示的可用内存中进行分配即进行节点的创建和插入以及相关参数的修改。 回收阶段:要求用户输入指定回收的进程空间,根据用户指定的进程名,进行单链表查找,定位到指定进程所代表的节点后,再判断本次要回收的进程内存空间其左右节点是否为空闲块,如若是,则需要进行空闲块合并,在程序里则需要进行节点删除和相关参数的修改。如果不是,则直接修改对应节点参数即可。
图示说明: 假设内存空间初始阶段为100进程数为5每个进程随机产生的内存需求空间为1514171812。则表示内存中各内存块的链表变化过程为 分配阶段 初始状态:
进程1申请大小为15的内存空间为其分配后
进程2又申请大小为14的内存空间为其分配后
进程3又申请大小为17的内存空间为其分配后
进程4又申请大小为18的内存空间为其分配后
进程5又申请大小为12的内存空间为其分配后
回收阶段:
假设回收进程内存空间的顺序32415。则内存中链表的变化过程为
        
如何定义内存块
如上面图示所示,对于内存块数据结构的定义,其包括参数 ①进程名name ②起始地址 address ③长度 length ④标志 flag表示该块是否被分配。 于是,内存块被定义为结构体:
struct MEMORY_BLOCK { int name; int address; int length; int flag; struct MEMORY_BLOCK *next; };
分配内存流程
系统利用某种分配算法(本实验中默认使用的是首次适应),从空闲分区链(表)中找到所需大小的分区。 设请求的分区大小为u.size表中每个空闲分区的大小可表示为m.size。 若m.size-u.size<=size(事先规定的不可再切割的剩余分区的大小,碎片),说明多余部分太小,可不再切割,将整个分区分配给请求者。 否则,从该分区中按请求大小划分出一块内存空间分配出去,余下的留在空闲分区链(表)中,将分配区首地址返回给调用者。
回收内存流程
回收内存:当进程运行完毕释放内存时,需要对该内存进行回收。 根据回收区的首址,从空闲区链(表)中找到相应的插入点,此时可能出现以下四种情况之一。
1.回收区与插入点的前一个空闲分区相邻接
直接修改链表中前一个空闲分区对应的结点将其大小修改为F1+回收区之和,并删除回收区所对应的链表中的结点如上图中回收阶段的回收4号进程。
1.回收区与插入点的后一个空闲分区相邻接 
直接修改链表中回收区所对应的空闲分区结点,将其大小修改为回收区+F2之和名称改为-1并同步删除F2分区所对应的链表中的结点如上图中回收阶段的回收2号进程。
1.回收区同时与插入点的前、后两个分区相邻接
修改链表中F1分区所对应的空闲分区结点将其大小修改为F1+回收区+F2之和并同步删除回收区以及F2分区所对应的链表中的结点如上图中回收阶段的回收5号进程。
1.回收区不与任何空闲区邻接
直接修改回收区所对应的结点中的标志位和名称,将其名称改为-1标志位改为0即可。
其流程图如下:
编程要求
根据提示在右侧编辑器补充代码完成模拟内存分配和回收过程。内存空间大小为100进程数为5每个进程所需空间为随机产生大小为1~20编制程序首先对5个进程进行内存分配然后回收指定的进程空间并进行适当的空闲分区合并操作要求每次操作结束后都能显示当前的内存分配情况。
测试说明
平台会对你编写的代码进行测试:
开始你的任务吧,祝你成功!
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <time.h>
#define LEN sizeof(MEMORY_BLOCK)
#define NUM 5
struct MEMORY_BLOCK
{
    int name;
    int address;
    int length;
    int flag;
    struct MEMORY_BLOCK *next;
};
typedef struct MEMORY_BLOCK MEMORY_BLOCK;
void allocation(MEMORY_BLOCK *Header,int name,int length_p);
void reclaim(int processname, MEMORY_BLOCK *Header);
void main()
{
    int length_p,i;
    int processname;
    MEMORY_BLOCK *Header,*t;
    Header=(MEMORY_BLOCK*)malloc(LEN);//初始化存储空间
    Header->name=-1;
    Header->address=0;
    Header->length=100;
    Header->flag=0;
    Header->next=NULL;
    srand(0);
    for(i=1;i<=NUM;i++)
    {
        length_p=rand()%20+1;   //随机产生进程所需存储空间至少为1;
        allocation(Header,i,length_p);
    }
    printf("内存分配情况:  \n");
    t=Header;
    while(t!=NULL)
    {
        printf("process name %d, address=%d, length=%d,flag=%d\n",t->name,t->address,t->length,t->flag);
        t=t->next;
    }
    printf("请输入回收的进程号(输入0结束):\n");
    scanf("%d",&processname);  
    while(processname!=0)
    {
        printf("回收 process name %d\n",processname);
        reclaim(processname,Header);
        t=Header;
        while(t!=0)
        {
            printf("process name %d, address=%d, length=%d,flag=%d\n",t->name,t->address,t->length,t->flag);
            t=t->next;
        }
        if (Header->next==NULL) break;
        printf("请输入回收的进程号(输入0结束):\n");
        scanf("%d",&processname);
    }
   
   
    printf("当前内存分配情况:\n");
    t=Header;
    while(t!=0)
    {
        printf("process name %d, address=%d, length=%d,flag=%d\n",t->name,t->address,t->length,t->flag);
        t=t->next;
    }
}
void reclaim(int processname, MEMORY_BLOCK *Header)
{
    MEMORY_BLOCK *temp,*t,*tt;
    t=Header;
    temp=t;
    while(t->name!=processname)
    {
        temp=t;
        t=t->next;
    }
    if(t->next!=NULL)//t非尾结点
        if(temp->flag==0&&t->next->flag==0)//左右为空
        {
            temp->name=-1;
            temp->length=temp->length+t->length+t->next->length;
            tt=t->next;
            temp->next=tt->next;
        }
        else if(temp->flag==0) //左为空
        {
            temp->name=-1;
            temp->length=temp->length+t->length;
            temp->next=t->next;
        }
            else if(t->next->flag==0) //右为空
            {
                t->name = -1;
                t->flag = 0;
                t->length=t->length + t->next->length;
                tt = t->next;
                t->next = tt->next;
            }
                else // 左右不为空
                {
                    t->name = -1;
                    t->flag = 0;
                }
    else//t是尾结点
    {
        if(temp->flag==0) //左为空
        {
            temp->length = temp->length + t->length;
            temp->next = NULL;
        }
        else // 左不为空
        {
            t->name = -1;
            t->flag = 0;
        }
    }
}
void allocation(MEMORY_BLOCK *Header,int name,int length_p)
{
    MEMORY_BLOCK *temp,*t,*tt;
    int minsize=2; //不可切割的分区阈值
    t=Header;
    while(t!=0)
    {
        if(t->length>length_p&&t->flag==0) break;
        t=t->next;
    }
    if(t->length-length_p>minsize) //分割
    {
        temp=(MEMORY_BLOCK*)malloc(LEN);//temp=new MEMORY_BLOCK;
        temp->name=-1;
        temp->flag=0;
        temp->length=t->length-length_p;
        temp->address=t->address+length_p;
        t->name=name;
        t->flag=1;
        t->length=length_p;
        temp->next=t->next;
        t->next=temp;
    }
    else  //直接分配
    {
        t->name = name;
        t->flag = 1;      
    }  
}
Loading…
Cancel
Save