〔重庆理工大学〕操作系统实验报告【实验五 进程控制】
本文介绍了Linux进程控制实验,通过修改process.c程序创建10个子进程,父进程使用wait()等待子进程退出。实验在Ubuntu和Linux 0.11平台运行成功,并生成/var/process.log日志文件。通过修改时间片参数(priority=1、15、10000),分析不同时间片对进程调度的影响:小时间片增加周转时间和等待时间,大时间片则接近默认值。实验结果表明,合理设置时间片对
实验五 进程控制(实验报告)
班级:123030201
学号:1230904XXXX
姓名:段 X X
| 课程目标 | 目标3得分(环境搭建和功能实现情况) | 目标4得分(自主学习和解决问题能力情况) |
|---|---|---|
| 自评分 | 95 | 95 |
| 批阅分 |
CQUT操作系统实验报告【实验五 进程控制】
一、实验目的
1、掌握 Linux 下的多进程编程技术,并能够按照实验要求进行软硬件的实现及验证。
2、能够通过自主学习学习实验相关知识,并解决实验中遇到的具体问题。
二、实验结果及分析
2.1 样本程序代码process.c(修改后):
修改原模板,实现用fork()建立若干个同时运行的子进程,父进程等待所有子进程退出后才退出,并且打印子进程的ID,每个子进程按照我们的意愿做不同或相同的cpuio_bound(),从而完成一个个性化的样本程序。子进程通过 cpuio_bound 函数实现占用CPU和I/O时间的操作。实现让父进程等待子进程的退出可以使用 wait() 系统调用。process.c完整代码如下:
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <sys/times.h>
#define HZ 100
void cpuio_bound(int last, int cpu_time, int io_time);
int main(int argc, char * argv[])
{
pid_t n_proc[10]; /* 数组存放10个子进程的 PID */
int i;
for(i=0;i<10;i++)
{
n_proc[i] = fork();
/* 子进程 */
if(n_proc[i] == 0)
{
cpuio_bound(20,2*i,20-2*i); /* 每个子进程都占用20s */
return 0; /* 执行完cpuio_bound 以后,结束该子进程 */
}
/* fork 失败 */
else if(n_proc[i] < 0 )
{
printf("Failed to fork child process %d!\n",i+1);
return -1;
}
/* 父进程继续fork */
}
/* 打印所有子进程PID */
for(i=0;i<10;i++)
printf("Child PID: %d\n",n_proc[i]);
/* 等待所有子进程完成 */
wait(&i); /* Linux 0.11 上 gcc要求必须有一个参数, gcc3.4+则不需要 */
return 0; /* 结束父进程 */
}
/*
* 此函数按照参数占用CPU和I/O时间
* last: 函数实际占用CPU和I/O的总时间,不含在就绪队列中的时间,>=0是必须的
* cpu_time: 一次连续占用CPU的时间,>=0是必须的
* io_time: 一次I/O消耗的时间,>=0是必须的
* 如果last > cpu_time + io_time,则往复多次占用CPU和I/O
* 所有时间的单位为秒
*/
void cpuio_bound(int last, int cpu_time, int io_time)
{
struct tms start_time, current_time;
clock_t utime, stime;
int sleep_time;
while (last > 0)
{
/* CPU Burst */
times(&start_time);
/* 其实只有t.tms_utime才是真正的CPU时间。但我们是在模拟一个
* 只在用户状态运行的CPU大户,就像“for(;;);”。所以把t.tms_stime
* 加上很合理。
*/
do
{
times(¤t_time);
utime = current_time.tms_utime - start_time.tms_utime;
stime = current_time.tms_stime - start_time.tms_stime;
} while ( ( (utime + stime) / HZ ) < cpu_time );
last -= cpu_time;
if (last <= 0 )
break;
/* IO Burst */
/* 用sleep(1)模拟1秒钟的I/O操作 */
sleep_time=0;
while (sleep_time < io_time)
{
sleep(1);
sleep_time++;
}
last -= sleep_time;
}
}
2.2运行结果展示
1.Ubuntu平台运行结果:在Ubuntu平台的oslab/test5/process.c目录下,执行gcc -o process process.c指令,进行编译,执行./process指令,运行样本程序代码process.c。编译时可能会出现一个warning,但不会影响后续运行。如果想要解决这个警告,在process.c中添加以下头文件即可:#include <sys/types.h>,#include <sys/wait.h>。编译运行后,终端输出10个子进程的PID,如图1所示。
图1 process.c在Ubuntu平台运行结果
2.Linux 0.11平台运行结果:在Linux 0.11终端中编译运行process.c,输出与 Ubuntu平台一致,成功创建10个子进程并打印PID。运行结束后,通过ls -l /var/ 可查看生成的process.log文件,文件大小随进程状态记录条数变化。如图2所示。
图2 process.c在Linux 0.11平台运行结果
3.日志文件展示:Linux 0.11中日志文件路径为 /var/process.log,文件权限为 -rw-rw-rw-(所有人可读可写)。日志文件按 pid\t状态\tjiffies 格式记录,包含所有进程的状态变化,其中状态字段含义:N(新建)、J(就绪)、R(运行)、W(阻塞)、E(退出),jiffies为系统滴答数(每10ms递增1)。如图3所示。
图3 日志文件内容部分展示(时间片为15)
2.3日志分析统计结果
1.原时间片(priority=15)统计结果:运行stat_log.py分析日志文件,统计10个子进程(PID 15~24)的调度数据。这10个进程的周转时间(Turnaround,指作业从提交到完成所用的总时间)、等待时间(Waiting)、CPU 使用时间(CPU Burst)、I/O 使用时间(I/O Burst)、所有进程平均周转时间、所有进程平均等待时间。如图4所示。
图4 原时间片(priority=15)统计结果
2.修改时间片后的统计结果
(1)时间片修改为 1(priority=1):由此生成日志文件内容,如图5所示。运行stat_log.py分析日志文件,统计10个子进程(PID 15~24)的调度数据。如图6所示。
图5 日志文件内容部分展示(修改后时间片为1)

图6 修改后时间片(priority=1)统计结果
(2)时间片修改为 10000(priority=10000):由此生成日志文件内容,如图7所示。运行stat_log.py分析日志文件,统计10个子进程(PID 15~24)的调度数据。如图8所示。
图7 日志文件内容部分展示(修改后时间片为10000)

图8 修改后时间片(priority=10000)统计结果
2.4修改时间片后的结果分析
1.时间片对周转时间的影响:
(1)时间片过小时(priority=1):进程切换频繁,上下文切换开销增大,导致平均周转时间显著增加。
(2)时间片过大时(priority=10000):进程切换次数减少,上下文切换开销降低,平均周转时间与默认值(priority=15)接近,但个别 I/O 密集型进程可能等待时间略有增加。
2.时间片对等待时间的影响:
(1)时间片越小,进程等待 CPU 的时间越长,因为 CPU 被频繁分配给不同进程,单个进程难以持续执行。
(2)时间片增大后,进程等待时间略有上升但变化不大,因为 CPU 密集型进程能获得更长连续执行时间,减少等待次数。
3.时间片对吞吐量的影响:
(1)时间片过小会降低吞吐量,因为大量 CPU 时间消耗在上下文切换上,有效执行时间占比下降。
(2)时间片过大时,吞吐量与默认值基本一致,但若时间片远超进程平均 CPU 占用时间,会导致 I/O 密集型进程等待 CPU 时间过长,吞吐量可能略有下降。
三、实验思考
(请回答实验思考题中的问题)
3.1你编写的样本程序中创建了几个子进程?父进程是如何等待所有的子进程都结束后才退出的?
样本程序中创建了10个子进程,通过循环调用 fork() 函数实现,循环变量从0到9,共执行10次创建操作。
父进程等待所有子进程退出的实现方式:原程序中使用 wait(&i) 进行等待,但该函数一次调用仅能等待一个子进程结束,若要确保等待所有子进程,更严谨的实现是通过循环调用 wait() 或 waitpid() 直至所有子进程退出。例如可使用 while(waitpid(-1, NULL, 0) != -1);,其中 waitpid(-1, NULL, 0) 表示等待任意一个子进程结束,循环执行直至函数返回-1(即无未结束的子进程),从而保证父进程在所有子进程退出后再退出。
3.2你是如何修改 0.11 的时间片的?
Linux 0.11 中进程的时间片由进程控制块(PCB)中的 counter 字段控制,而 counter 的初始值与进程优先级 priority 相关,核心修改步骤如下:
1.定位配置文件:找到 Linux 0.11 内核源码中的 include/linux/sched.h 文件,该文件中定义了 INIT_TASK 宏,此宏用于初始化进程 0 的 PCB 信息。
2.修改优先级参数:INIT_TASK 宏中包含 priority 成员(默认值为15),时间片大小与该优先级正相关。通过修改该参数值实现时间片调整,本次实验中分别将其改为1(小时间片)和10000(大时间片)。
3.生效修改:修改完成后,在 Linux 0.11 源码根目录执行 make all 重新编译内核,生成新的内核镜像,启动 Bochs 模拟器加载新内核后,时间片修改即可生效。
3.3结合自己的体会,谈谈从程序设计者的角度看,单进程编程和多进程编程最大的区别是什么?
1.资源占用与调度方式不同:单进程编程中,程序仅有一个执行流,独占进程的地址空间、CPU 资源等,无需考虑资源竞争问题,CPU 调度仅针对该进程自身的指令流;多进程编程中,多个进程拥有独立的地址空间,CPU 需在多个进程间切换调度,每个进程的执行相对独立,程序设计者需关注进程间的资源隔离与共享问题。
2.并发能力与复杂度不同:单进程编程无法实现真正的并发,若程序中存在 I/O 等耗时操作,会导致整个程序阻塞,CPU 资源利用率低;多进程编程可通过进程切换实现并发,当一个进程因 I/O 阻塞时,CPU 可调度其他进程执行,提升资源利用率,但需额外处理进程创建、进程间通信(若有需求)、子进程回收等问题,编程复杂度更高。
3.容错性与维护性不同:单进程编程中,程序的任意一处异常若导致进程崩溃,整个程序会直接终止;多进程编程中,各进程独立运行,一个子进程的崩溃通常不会影响其他进程及父进程的运行,容错性更强,但同时也增加了程序的调试与维护难度,需针对每个进程的运行状态进行监控。
3.4进程状态转换的触发条件是什么?
1.新建态(N):调用 fork() 系统调用,内核执行 copy_process() 函数创建新进程 PCB 后触发。
2.就绪态(J):进程创建完成、从阻塞态被唤醒(wake_up())、时间片用完被调度器切换出 CPU 时触发。
3.运行态(R):调度器 schedule() 选中就绪队列中 counter 最大的进程,执行 switch_to() 切换上下文后触发。
4.阻塞态(W):进程调用 sleep_on()、sys_pause()、sys_waitpid() 等函数,主动放弃 CPU 等待资源时触发。
5.退出态(E):进程执行完毕调用 exit() 系统调用,内核执行 do_exit() 函数释放资源前触发。
3.5为什么日志文件需要在进程 0 中提前打开?
进程 0 是系统启动后的第一个进程,所有后续进程(包括进程 1 及用户进程)都是其子孙进程。在进程 0 中打开日志文件后,后续进程可通过继承文件描述符 3 访问该文件,确保系统启动后所有进程的状态变化都能被记录,避免遗漏早期进程(如 init 进程)的轨迹。
3.6时间片轮转调度算法的核心是什么?Linux 0.11 中如何实现动态调整
1.核心:按时间片分配 CPU 资源,进程每次占用 CPU 的时间不超过时间片大小,时间片用完后切换至就绪态,确保多个进程公平竞争 CPU。
2.Linux 0.11 动态调整机制:当所有就绪进程的 counter 为 0 时,内核执行 (*p)->counter = ((*p)->counter >> 1) + (*p)->priority,对所有进程的 counter 进行衰减并累加优先级,使阻塞时间越长的进程优先级越高,下次获得的时间片越大,兼顾公平性与 I/O 密集型进程的响应速度。
四、问题及解决方法
(给出在实验过程中遇到的问题及解决方法)
4.1问题1:编译 process.c 时出现警告
1.问题描述:编译 process.c 时出现 “implicit declaration of function‘wait’” 警告。
2.原因分析:wait() 函数属于系统调用,其函数声明定义在 <sys/types.h> 和 <sys/wait.h> 头文件中。样本程序初始未包含这两个头文件,编译器无法识别该函数,因此抛出隐式声明的警告。
3.解决方法:在 process.c 代码的头文件包含部分,添加 #include <sys/types.h> 和 #include <sys/wait.h> 语句,明确 wait() 函数的声明,重新编译后警告消失。
4.2问题2:编译失败提示 fprintk 函数未定义
1.问题描述:内核修改后编译失败,提示 fprintk 函数未定义。
2.原因分析:编译失败的核心原因是链接阶段无法找到 fprintk()函数的实现代码,可能存在两种情况:一是该函数未添加到指定的内核文件中;二是添加了函数实现,但遗漏了函数声明所需的相关头文件,导致编译器无法识别函数原型。
3.解决方法:首先确认 fprintk() 函数的源码已完整添加至 kernel/printk.c 文件中,函数实现逻辑无误;其次检查该文件是否包含 #include <linux/sched.h> 和 #include <sys/stat.h> 等必要头文件(用于支持文件操作相关的结构体和宏定义);最后重新执行 make all 编译内核,确保函数实现被正确链接。
4.3问题3:未生成 process.log 日志文件
1.问题描述:Linux 0.11 中未生成 process.log 日志文件。
2.原因分析:日志文件未生成通常与文件创建时机、路径配置或缓存刷新有关:一是日志文件创建代码的位置错误,未在进程 0 的 fork() 调用前添加,导致后续进程无法继承文件描述符;二是文件路径填写错误,未指向 /var/ 目录(该目录在 Linux 0.11 中为可写目录);三是程序运行后未刷新缓存,日志内容未写入磁盘。
3.解决方法:
检查 init/main.c 中日志文件创建代码,确保其位于 move_to_user_mode() 之后、fork() 之前;确认文件创建路径为 /var/process.log,避免路径错误;在 Linux 0.11 中运行 process 程序结束后,执行 sync 命令强制刷新文件缓存,确保日志内容写入磁盘。
4.4问题4:统计脚本运行时出现提示
1.问题描述:统计脚本运行时提示 “No such file or directory”。
2.原因分析:该错误表明脚本无法找到指定的日志文件或参数中指定的 PID 对应的进程信息不存在,可能原因:一是日志文件路径填写错误,未将 process.log 拷贝至 Ubuntu 实验目录,或脚本参数中日志文件路径与实际路径不一致;二是脚本参数中指定的 PID 范围(如13~22)与日志文件中实际记录的子进程 PID 不匹配。
3.解决方法:通过 ls 命令查看 Ubuntu 实验目录,确认 process.log 已存在,若未存在则重新从 Linux 0.11 虚拟磁盘的 /var/ 目录拷贝;打开日志文件查看实际的子进程PID范围,修改脚本运行命令中的PID参数,确保与日志内容一致。
4.5问题5:修改时间片后统计结果无变化
1.问题描述:修改时间片后统计结果无变化。
2.原因分析:统计结果无变化说明时间片修改未生效,核心原因包括:一是未正确修改 priority 参数,或修改后未保存文件;二是未重新编译内核,导致修改后的源码未生成新的内核镜像;三是未重启 Bochs 模拟器加载新内核,仍使用修改前的内核运行程序。
3.解决方法:重新打开 include/linux/sched.h 文件,确认 INIT_TASK 宏的 priority 参数已修改为目标值(1或10000);在 Linux 0.11 源码目录执行 make all 重新编译内核,生成新的 Image 镜像文件;关闭现有 Bochs 模拟器进程,重新启动模拟器加载新内核,再次运行 process 程序生成新日志后进行统计。
五、实验步骤
(列出实验步骤)
5.1实验准备
1.环境搭建:确保 Ubuntu 宿主机(或实验楼环境)已安装 gcc 编译器、Python 解释器,且已配置 Linux 0.11 开发环境(含 oslab 工具集、内核源码)。
2.资源下载:根据实验要求,将原样本程序模板 process.c 和统计脚本 stat_log.py,保存至实验目录(如 oslab/test5/)。
5.2编写并调试样本程序 process.c
1.理解模板功能:分析 cpuio_bound 函数的作用,该函数可按参数指定占用 CPU 时间和 I/O 时间,支持循环交替占用资源。
2.修改 process.c:基于模板实现多进程功能,创建 10 个子进程,每个子进程调用 cpuio_bound(20, 2i, 20-2i)(总运行时间 20 秒,CPU 与 I/O 时间按序号递增/递减);父进程打印所有子进程 PID,并通过 wait() 等待所有子进程退出后再终止。
3.编译运行验证:在 Ubuntu 环境中编译 process.c(gcc -o process process.c),解决编译警告(添加 <sys/types.h> 和 <sys/wait.h> 头文件),运行程序验证子进程创建及 PID 打印功能。
5.3修改 Linux 0.11 内核,实现进程轨迹跟踪
本次实验严格遵循 Linux 0.11 系统调用添加标准流程,共分为以下的核心步骤,大部分操作基于 oslab/linux-0.11 源码目录开展:
1.内核入口修改(init/main.c)
(1)文件路径:oslab/linux-0.11/init/main.c
(2)操作内容:在 move_to_user_mode() 后、fork() 前添加代码,提前加载文件系统、关联标准输入输出描述符,并创建 /var/process.log 日志文件,确保系统启动即开始日志记录。添加代码位置,如图9所示。
图9 内核入口修改(init/main.c)
(3)添加内容:
/**modify**/
setup((void *) &drive_info); /*加载文件系统*/
(void) open("/dev/tty0",O_RDWR,0);/*建立文件描述符0和/dev/tty0的关联*/
(void) dup(0); /*文件描述符1也和/dev/tty0关联*/
(void) dup(0); /*文件描述符2也和/dev/tty0关联*/
(void) open("/var/process.log",O_CREAT|O_TRUNC|O_WRONLY,0666); /*打开log文件*/
/**modify**/
2.添加内核日志写入函数
(1)文件路径:oslab/linux-0.11/kernel/printk.c
(2)操作内容:在 kernel/printk.c 中,在原printk() 函数的末尾,添加 fprintk() 函数,支持在内核态向指定文件描述符写入日志,适配 stdout/stderr 及自定义日志文件。添加代码位置,如图10所示。
图10 添加内核日志写入函数
(3)添加内容:
/**modify**/
#include <linux/sched.h>
#include <sys/stat.h>
static char logbuf[1024];
int fprintk(int fd, const char *fmt, ...)
{
va_list args;
int count;
struct file * file;
struct m_inode * inode;
va_start(args, fmt);
count=vsprintf(logbuf, fmt, args);
va_end(args);
if (fd < 3) /* 如果输出到stdout或stderr,直接调用sys_write即可 */
{
__asm__("push %%fs\n\t"
"push %%ds\n\t"
"pop %%fs\n\t"
"pushl %0\n\t"
"pushl $logbuf\n\t" /* 注意对于Windows环境来说,是_logbuf,下同 */
"pushl %1\n\t"
"call sys_write\n\t" /* 注意对于Windows环境来说,是_sys_write,下同 */
"addl $8,%%esp\n\t"
"popl %0\n\t"
"pop %%fs"
::"r" (count),"r" (fd):"ax","cx","dx");
}
else /* 假定>=3的描述符都与文件关联。事实上,还存在很多其它情况,这里并没有考虑。*/
{
if (!(file=task[0]->filp[fd])) /* 从进程0的文件描述符表中得到文件句柄 */
return 0;
inode=file->f_inode;
__asm__("push %%fs\n\t"
"push %%ds\n\t"
"pop %%fs\n\t"
"pushl %0\n\t"
"pushl $logbuf\n\t"
"pushl %1\n\t"
"pushl %2\n\t"
"call file_write\n\t"
"addl $12,%%esp\n\t"
"popl %0\n\t"
"pop %%fs"
::"r" (count),"r" (file),"r" (inode):"ax","cx","dx");
}
return count;
}
3.定位进程状态切换点
分析 fork.c、sched.c、exit.c 中涉及进程状态变化的核心函数(如 copy_process()、schedule()、sleep_on()、do_exit() 等)。Linux 0.11 支持四种进程状态的转换(此外还有新建和退出两种情况):就绪到运行;运行到就绪;运行到睡眠;睡眠到就绪。其中就绪到运行通过 schedule() 完成( schedule 亦是调度算法所在);运行到睡眠通过 sleep_on() 和 interruptible_sleep_on() 完成,还有进程主动睡觉的系统调用 sys_pause() 和 sys_waitpid();睡眠到就绪通过 wake_up() 完成。
4.添加日志记录代码
在本次实验中,进程状态包括以下五种:新建态(N),就绪态(J),运行态®,阻塞态(W),退出(E) 。在各状态切换点插入 fprintk() 调用,按格式 pid\t状态\tjiffies 记录状态变化,包括:新建态(N):copy_process() 中进程 PCB 初始化后。就绪态(J):copy_process() 中设置 state=TASK_RUNNING 后、schedule() 中进程被唤醒时、wake_up() 函数中。运行态(R):schedule() 中进程切换执行前。阻塞态(W):sleep_on()、interruptible_sleep_on()、sys_pause()、sys_waitpid() 函数中。退出态(E):do_exit() 函数中进程资源释放前。如表1所示。
| 状态 | 对应函数 |
|---|---|
| 新建 - N | copy_process() |
| 就绪 - J | copy_process()、schedule()、sleep_on()、interruptible_sleep_on()、wake_up() |
| 运行 - R | schedule() |
| 等待 - W | sys_pause()、sleep_on()、interruptible_sleep_on()、sys_waitpid() |
| 退出 - E | do_exit() |
表1 Linux 0.11 中实现状态切换的对应函数
5.修改fork.c文件
(1)文件路径:oslab/linux-0.11/kernel/fork.c
(2)操作内容:修改 fork.c 中的 copy_process 函数,添加情况如下:
【第一处】添加代码位置,如图11所示。添加内容如下:
/**modify**/
fprintk(3,"%d\tN\t%d\n",p->pid,jiffies); /*新增修改,新建进程*/
/**modify**/

图11 修改fork.c的 copy_process 函数【第一处】
【第二处】添加代码位置,如图12所示。添加内容如下:
/**modify**/
fprintk(3,"%d\tJ\t%d\n",p->pid,jiffies); /*新增修改,进程就绪*/
/**modify**/

图12 修改fork.c的 copy_process 函数【第二处】
6.修改sched.c文件
(1)文件路径:oslab/linux-0.11/kernel/sched.c
(2)操作内容:修改 sched.c 中的 schedule 函数,添加情况如下:
【第一处】添加代码位置,如图13所示。添加内容如下:
/**modify**/ /*可中断睡眠=>就绪*/
fprintk(3,"%d\tJ\t%d\n",(*p)->pid,jiffies);
/**modify**/

图13 修改sched.c的 schedule 函数【第一处】
【第二处】添加代码位置,如图14所示。添加内容如下:
/**modify**/
/*切换到相同的进程不输出*/
if(current->pid != task[next]->pid)
{
/*时间片到时程序=>就绪*/
if(current->state == TASK_RUNNING)
fprintk(3,"%d\tJ\t%d\n",current->pid,jiffies);
fprintk(3,"%d\tR\t%d\n",task[next]->pid,jiffies);
}
/**modify**/

图14 修改sched.c的 schedule 函数【第二处】
(3)操作内容:修改 sched.c 中的 sys_pause 函数,添加情况如下:
【第一处】添加代码位置,如图15所示。添加内容如下:
/**modify**/
/*当前进程运行 => 可中断睡眠*/
if(current->pid != 0)
fprintk(3,"%d\tW\t%d\n",current->pid,jiffies);
/**modify**/

图15 修改sched.c的 sys_pause 函数【第一处】
(4)操作内容:修改 sched.c 中的 sleep_on 函数,添加情况如下:
【第一处】添加代码位置,如图16所示。添加内容如下:
/**modify**/ /*当前进程=>不可中断睡眠*/
fprintk(3,"%d\tW\t%d\n",current->pid,jiffies);
/**modify**/

图16 修改sched.c的 sleep_on 函数【第一处】
【第二处】添加代码位置,如图17所示。添加内容如下:
/**modify**/
/*原等待队列 第一个进程 => 唤醒(就绪)*/
fprintk(3,"%d\tJ\t%d\n",tmp->pid,jiffies);
/**modify**/

图17 修改sched.c的 sleep_on 函数【第二处】
(5)操作内容:修改 sched.c 中的 interruptible_sleep_on 函数,添加情况如下:
【第一处】添加代码位置,如图18所示。添加内容如下:
/**modify**/ /*唤醒队列中间进程,过程中使用Wait*/
fprintk(3,"%d\tW\t%d\n",current->pid,jiffies);
/**modify**/

图18 修改sched.c的 interruptible_sleep_on 函数【第一处】
【第二处】添加代码位置,如图19所示。添加内容如下:
/**modify**/ /*修改--当前进程 => 可中断睡眠*/
fprintk(3,"%d\tJ\t%d\n",(*p)->pid,jiffies);
/**modify**/

图19修改sched.c的 interruptible_sleep_on 函数【第二处】
【第三处】添加代码位置,如图20所示。添加内容如下:
/**modify**/
/*原等待队列 第一个进程 => 唤醒(就绪)*/
fprintk(3,"%d\tJ\t%d\n",tmp->pid,jiffies);
/**modify**/

图20修改sched.c的 interruptible_sleep_on 函数【第三处】
(6)操作内容:修改 sched.c 中的 wake_up 函数,添加情况如下:
【第一处】添加代码位置,如图21所示。添加内容如下:
/**modify**/ /*唤醒 最后进入等待序列的进程*/
fprintk(3,"%d\tJ\t%d\n",(*p)->pid,jiffies);
/**modify**/

图21修改sched.c的 wake_up 函数【第一处】
7.修改exit.c文件
(1)文件路径:oslab/linux-0.11/kernel/exit.c
(2)操作内容:修改 exit.c 中的 do_exit 函数,添加情况如下:
【第一处】添加代码位置,如图22所示。添加内容如下:
/**modify**/ /*退出一个进程*/
fprintk(3,"%d\tE\t%d\n",current->pid,jiffies);
/**modify**/

图22修改exit.c的 do_exit 函数【第一处】
(3)操作内容:修改 exit.c 中的 sys_waitpid 函数,添加情况如下:
【第一处】添加代码位置,如图23所示。添加内容如下:
/**modify**/ /*当前进程 => 等待*/
fprintk(3,"%d\tW\t%d\n",current->pid,jiffies);
/**modify**/

图23修改exit.c的 sys_waitpid 函数【第一处】
8.重新编译内核
在 Linux 0.11 源码目录执行 make all,生成修改后的内核镜像。
// oslab/linux-0.11目录下运行
make all
5.4跨平台运行与日志生成
1.文件拷贝:在oslab 目录下,通过 mount-hdc 命令挂载 Linux 0.11 虚拟磁盘,将 Ubuntu 中调试通过的 process.c 拷贝至虚拟磁盘的 /usr/root/ 目录,卸载磁盘。process.c拷贝后的所在位置,如图24所示。执行指令如下:
// oslab 目录下运行
// 先挂载,才能实现宿主机和虚拟机之间的文件交换
sudo ./mount-hdc
// 将 process.c 拷贝到bochs虚拟机的root目录下
cp ./test5/process.c ./hdc/usr/root/
// 拷贝完记得卸载
sudo umount hdc

图24 process.c已拷贝到 Linux 0.11 系统
2.运行 Linux 0.11:启动 Bochs 模拟器,进入 Linux 0.11 系统,编译 process.c(gcc -o process process.c)并运行(./process)。运行结果,如图25所示。执行指令如下:
// 在Linux 0.11 系统中下编译运行
gcc -o process process.c
./process

图25 process.c于 Linux 0.11 系统中的编译运行
3.日志保存:程序运行结束后,执行 sync 命令刷新缓存,确保日志文件写入磁盘;关闭模拟器后,重新挂载虚拟磁盘,将 /var/process.log 拷贝至 Ubuntu 实验目录oslab/test5下,卸载磁盘。process.log拷贝后的所在位置,如图26所示。执行指令如下:
// oslab 目录下
//还是要先挂载
sudo ./mount-hdc
//拷贝到 exp_04 目录下
cp ./hdc/var/process.log ./test5/
//卸载
sudo umount hdc


图26 process.log已拷贝到 Ubuntu 宿主机的oslab 目录下
5.5日志分析与数据统计
1.下载 python 解释器:在自己的 Ubuntu 上实验,需要先下载 python 解释器。执行指令如下:
sudo apt-get install python
2.脚本准备:给 stat_log.py 添加执行权限(chmod +x stat_log.py),确保 Python 环境正常。执行指令如下:
// stat_log.py 所在目录下执行
sudo chmod +x ./stat_log.py
3.统计分析:运行脚本分析日志文件(./stat_log.py ./process.log 15 14 … 24),统计 10 个子进程的周转时间、等待时间、CPU/I/O 占用时间,计算平均周转时间、平均等待时间和吞吐量。统计分析结果,如图27所示。执行指令如下:
// !注意文件路径,我的py文件和log文件都在test5目录下 !
// 以下命令也在test5目录下执行
./stat_log.py ./process.log 15 16 17 18 19 20 21 22 23 24

图27 原时间片(priority=15)统计分析结果
5.6修改时间片并对比测试
1.定位时间片配置:修改 oslab/linux-0.11/include/linux/sched.h 中 INIT_TASK 宏的 priority 参数(默认 15),分别改为 1 和 10000。时间片修改为 1(priority=1)的所在位置,如图28所示。时间片修改为 10000(priority=10000)的所在位置,如图29所示。
图28 时间片修改为 1(priority=1)的所在位置

图29 时间片修改为 10000(priority=10000)的所在位置
2.重复实验流程:在 oslab/Linux 0.11 源码目录再次执行 make all,重新编译内核;在 Linux 0.11 中运行 process.c,生成新的日志文件 process.log;重新挂载虚拟磁盘,将 /var/process.log 拷贝至 Ubuntu 实验目录oslab/test5下,卸载磁盘;最后,使用 stat_log.py 统计分析新的 log 文件数据。
3.结果对比:整理不同时间片(1、15、10000)下的统计结果,分析时间片大小对系统性能的影响。
六、实验收获
(简要列出自己在该实验中学到的相关知识点及能力)
1.技术知识点:
(1)掌握了 Linux 多进程编程核心技术,理解 fork() 进程创建原理、wait() 进程等待机制及 cpuio_bound 函数的资源占用模拟方法。
(2)深入理解了 Linux 0.11 内核结构,学会修改内核源码(main.c、printk.c、fork.c 等),添加自定义日志功能,掌握内核态文件操作的特殊方法(fprintk() 函数)。
(3)明确了进程五态模型及状态转换机制,掌握了进程调度算法的量化评价方法(通过日志统计周转时间、等待时间、吞吐量)。
(4)理解了时间片轮转调度算法的实现原理,掌握了时间片调整方法及对系统性能的影响规律。
2.能力提升:
(1)养了自主学习能力,通过查阅内核源码、技术博客,解决了内核修改、跨平台运行等复杂问题。
(2)提升了代码调试能力,学会通过日志排查内核态与用户态程序的错误,定位状态切换点的代码问题。
(3)增强了系统思维,理解了操作系统底层工作机制,能够从进程调度、资源管理的角度分析系统性能优化方向。
七、请了解熟悉以余祖胜烈士为代表的校友校史校情,结合我们的课程或专业,谈谈如何在我们目前的课程学习生活中发扬传承重庆理工大学“抗战文化、红岩精神、兵工基因”的校本文化?
重庆理工大学的“抗战文化、红岩精神、兵工基因”是学校百年办学历史的核心积淀,蕴含着爱国奉献、攻坚克难、严谨求实、开拓创新的精神内核。结合本次操作系统实验,我认为可以从以下方面传承发扬校本文化:
1.以“抗战文化”的爱国奉献精神为引领,树立专业责任感。操作系统是计算机系统的核心基础,其稳定性、高效性直接影响国家信息安全和科技发展。在实验中,我深刻认识到每一行内核代码的重要性,今后将以严谨的态度对待专业学习,立志为我国操作系统自主研发贡献力量,践行爱国奉献的使命。
2.以“红岩精神”的攻坚克难意志为支撑,突破学习难点。本次实验涉及内核修改、状态跟踪、日志分析等多个复杂环节,多次遇到编译失败、日志丢失、统计异常等问题。面对困难,我以红岩烈士“坚韧不拔、百折不挠”的精神为动力,通过反复查阅源码、调试代码、咨询资料,最终解决所有问题。今后在课程学习中,将继续发扬这种攻坚克难的精神,勇于挑战复杂技术难题。
3.以“兵工基因”的严谨求实作风为准则,锤炼专业技能。兵工产业对精度和可靠性的要求极高,操作系统的设计与实现同样如此。在修改内核代码时,一个微小的错误(如日志格式错误、状态切换点遗漏)都会导致实验失败。实验中,我严格按照规范编写代码、测试验证,确保每一步操作都准确无误。这种严谨求实的作风将贯穿今后的学习和工作,培养精益求精的专业素养。
4.以“开拓创新”的时代精神为动力,深化知识应用。本次实验通过修改时间片探索调度算法优化,是一次创新尝试。今后将继续发扬开拓创新精神,结合专业知识探索更多技术应用场景,如基于进程调度算法优化嵌入式系统性能、开发更高效的日志分析工具等,将校本文化转化为推动专业发展的实际动力。
八、参考资料
(给出在实验过程中参考的资料及网络资源等)
[1]罗伯特・洛夫《Linux 内核设计与实现》(第三版):进程管理与调度算法核心机制解析章节
[2] OSLab 官方文档:《Linux 0.11 实验指导手册》—— 进程轨迹跟踪与内核日志开发模块
[3] CSDN 博客:《Linux 0.11 进程状态跟踪与日志文件实现》
[4] 李治军《操作系统实验教程》:多进程编程与时间片轮转调度优化实践章节
[5] 简书:《操作系统之 fork () 函数详解》—— 进程创建与父子进程关系解析
九、实验报告(电子版Word文档)
1.操作系统实验报告【实验五 进程控制】
十、实验源代码
1.操作系统实验源代码【实验五 进程控制】
更多推荐



所有评论(0)