diff --git a/text2.txt b/text2.txt new file mode 100644 index 00000000..9fe8d4d2 --- /dev/null +++ b/text2.txt @@ -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 #include +pid_t fork( void); (pid_t 是一个宏定义,其实质是int,被定义在#include中) 返回值:若成功调用一次则返回两个值,子进程返回0,父进程返回子进程PID;否则,出错返回-1 +fork使用示例1--创建子进程,并在父子进程中打印各自身份信息 +对上述代码进行编译、运行,结果如下: + +管理进程标识符 +Linux操作系统使用进程标识符来管理当前系统中的进程,进程的组标识符从父进程继承得到,用于区分进程是否同组。进程的标识符由系统分配,不能被修;组标识符可以通过相关系统调用进程修改。 +int getpid(); //取得当前进程的标识符(进程ID)。 +int getppid(); //取得当前进程的父进程ID。 +int getpgrp(); //取得当前进程的进程组标识符。 +int getpgid(int pid); //将当前进程的进程组标识符改为当前进程的进程ID,使其成为进程组首进程,并返回这一新的进程组标识符。 +fork使用示例2--创建子进程,并在父子进程中打印各自身份信息和进程标识符 + +运行结果如下: +编程要求 +根据上面fork的应用示例,在右侧编辑器补充代码,完成实验任务的功能要求,并点击测试按钮即可。 +测试说明 +开始你的任务吧,祝你成功! +#include +#include +#include +//-------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 1. int execl(const char *path, const char *arg, ...); execl 函数通过指定完整的文件路径 path 来执行新程序,后续参数以可变参数的形式传递给新程序,参数列表必须以 NULL 结尾。 例如: +1.#include +2.#include +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 +2.#include +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 +2.#include +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 +2.#include +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 +2.#include +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 +2.#include +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 +#include +#include +#include + +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 #include 函数原型: pid_t wait (int * status); 返回值: 如果执行成功则返回子进程识别码或标志符(PID),如果有错误发生则返回-1。 +wait()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用wait()时子进程已经结束,则wait()会立即返回子进程结束状态值。子进程的结束状态值会由参数status 返回,而子进程的进程识别码也会一起返回。如果不在意结束状态值,则参数status 可以设成NULL。 +wait()系统调用用于父进程等待子进程的结束,但并未指明是哪个子进程,当父进程有多个子进程时,可以使用waitpid()等待指定子进程结束。 +waitpid()系统调用原型如下: 头文件: #include #include 函数原型: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 +2. #include +3. #include +4. #include +5. #include +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 +#include +#include +#include +#include +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 +#include +#include +#include +#include +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;iname=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 +2.#include +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 +#include +#include +#include +#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;iname=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 +#include +#include +#include +#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;       +    }   +} +