|
|
@ -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参数,该参数用于存放子进程的结束状态,下面有几个宏可判别结束情况:
|
|
|
|
|
|
|
|
WIFEXITED(status)如果子进程正常结束则为非0 值。
|
|
|
|
|
|
|
|
WEXITSTATUS(status)取得子进程exit()返回的结束代码,一般会先用 WIFEXITED 来判断是否正常结束才能使用此宏。
|
|
|
|
|
|
|
|
WIFSIGNALED(status)如果子进程是因为信号而结束则此宏值为真。
|
|
|
|
|
|
|
|
WTERMSIG(status)取得子进程因信号而中止的信号代码,一般会先用 WIFSIGNALED 来判断后才使用此宏。
|
|
|
|
|
|
|
|
WIFSTOPPED(status)如果子进程处于暂停执行情况则此宏值为真。一般只有使用WUNTRACED 时才会有此情况。
|
|
|
|
|
|
|
|
WSTOPSIG(status)取得引发子进程暂停的信号代码,一般会先用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(WIFEXITED(status))
|
|
|
|
|
|
|
|
21. i=WEXITSTATUS(status);
|
|
|
|
|
|
|
|
22. printf("child’s 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_MAX(0x7fff)]间的随机整数。
|
|
|
|
|
|
|
|
函数二: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信息,记录runtime,runedtime,killtime等信息。并将结点信息输出。b.否则,表示该进程结点未完成,记录runtime,runedtime,killtime等信息,将结点信息输出。并将该运行进程的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个)及其随机产生的需求空间(每个进程需求空间1~20),从链表中第一个节点所指示的可用内存中进行分配,即进行节点的创建和插入以及相关参数的修改。 回收阶段:要求用户输入指定回收的进程空间,根据用户指定的进程名,进行单链表查找,定位到指定进程所代表的节点后,再判断本次要回收的进程内存空间其左右节点是否为空闲块,如若是,则需要进行空闲块合并,在程序里则需要进行节点删除和相关参数的修改。如果不是,则直接修改对应节点参数即可。
|
|
|
|
|
|
|
|
图示说明: 假设内存空间初始阶段为100,进程数为5,每个进程随机产生的内存需求空间为15,14,17,18,12。则表示内存中各内存块的链表变化过程为: 分配阶段 初始状态:
|
|
|
|
|
|
|
|
进程1申请大小为15的内存空间,为其分配后:
|
|
|
|
|
|
|
|
进程2又申请大小为14的内存空间,为其分配后:
|
|
|
|
|
|
|
|
进程3又申请大小为17的内存空间,为其分配后:
|
|
|
|
|
|
|
|
进程4又申请大小为18的内存空间,为其分配后:
|
|
|
|
|
|
|
|
进程5又申请大小为12的内存空间,为其分配后:
|
|
|
|
|
|
|
|
回收阶段:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
假设回收进程内存空间的顺序3,2,4,1,5。则内存中链表的变化过程为:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
如何定义内存块
|
|
|
|
|
|
|
|
如上面图示所示,对于内存块数据结构的定义,其包括参数 ①进程名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;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|