重庆大学
学生实验报告
实验课程名称 操作系统原理
开课实验室 DS1501
学 院软件学院年级 20## 专业班软件工程2 班
学生姓名 胡其友 学 号 20131802
开课时间 20## 至 2016 学年第 一 学期
软件学院制
《操作系统原理》实验报告
开课实验室: 年 月 日
第二篇:操作系统--实验一实验报告
操作系统课程实验指导书
【实验名称】:并发程序设计(实验1)
【实验目的】:掌握在程序中创建新进程的方法, 观察并理解多道程序并发执行的现象。
【实验原理】:fork():建立子进程。子进程得到父进程地址空间的一个复制。
返回值:成功时,该函数被调用一次,但返回两次,fork()对子进程返回0,对父进程返回子进程标识符(非0值)。不成功时对父进程返回-1,没有子进程。
【实验内容】:首先分析一下程序运行时其输出结果有哪几种可能性,然后实际调试该程序观察其实际输出情况,比较两者的差异,分析其中的原因。
void main (void)
{ int x=5;
if( fork( ) )
{
x+=30;
printf (“%d\n”,x);
}
else
printf(“%d\n”,x);
printf((“%d\n”,x);
}
【实验要求】:每个同学必须独立完成本实验、提交实验报告、源程序和可执行程序。实验报告中必须包含预计的实验结果,关键代码的分析,调试记录,实际的实验结果,实验结果分析等内容。
【预计的实验结果】
35
5
35
5
【关键代码分析】
1,这个实验代码只有几行,看起来很简单。可以看到,重点就是fork()函数和一个if else 条件分支语句。
2,关于fork()函数,它是Linux的系统调用。
函数定义:
int fork( void );
返回值:
子进程中返回0,父进程中返回子进程ID,出错返回-1
函数说明:
一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程(child process)。fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID。
子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。注意,子进程持有的是上述存储空间的“副本”,这意味着父子进程间不共享这些存储空间,它们之间共享的存储空间只有代码段。
3,如果不知道fork()函数的作用的话,一定会对为什么会有两组输出结果感到疑惑。我们知道,英文单词fork有分岔,叉子的意思,这就很形象地描述了fork()的作用。简单地说,程序段调用了fork()之后,程序出现了分岔,系统派生出一个跟主程序一模一样的子进程。可以这样想象,2个进程一直同时运行,而且步调一致,在fork之后,他们分别作不同的工作,也就是分岔了。
4,一个进程表示的,就是一个可执行程序的一次执行过程中的一个状态。当这个程序执行if(fork()) 时,操作系统创建一个新的进程(子进程),并且在进程表中相应为它建立一个新的表项。新进程和原有进程的可执行程序是同一个程序;上下文和数据,绝大部分就是原 进程(父进程)的copy,但它们是两个相互独立的进程!
在父进程中,程序继续执行,根据Linux操作系统对fork的实现(假定这里fork()执行成功了),这个函数会返回刚刚创建的子进程的pid(肯定不为零)。所以在if中的判断语句为true,程序执行<x+=30; printf (“%d\n”,x);>
这些代码,因此父进程的x变量被赋值为35,并输出。
子进程在之后的某个时候得到调度,它的上下文被换入,占据 CPU,操作系统对fork的实现,使得子进程中fork函数返回整数0。所以在这个子进程if的判断语句为false。因而它就会进入else语句执行<printf(“%d\n”,x);>。
因为子进程从父进程复制过来的x的值是5,也没有被重新赋值,所以这里子进程会输出5。
再提一下,fork前父进程的东西子进程可以继承,而在fork后子进程没有任何和父进程的继承关系了。在子进程里创建的东西是子进程的,在父进程创建的东西是父进程的。可以完全看成普通的两个进程。所以在执行printf((“%d\n”,x);
的时候,父子进程当然都会执行它,因为他们本身程序段里都有这句代码。而x的值的则分别看父进程和子进程此时x的值,父进程输出35,子进程输出5。其实这里的情况已经和明了了,这里就是两个没有牵连的程序,他们各自该怎么执行就怎么执行,跟以前我们接触的普通程序一样。一句话,执行自己的进程,甬管别的进程。
【调试记录】
这段代码基本上没有什么运行的问题,在这里我就用gdb来跟踪查看一下这个代码的运行情况。
(shell下) $gdb //进入gdb调试器
在 < if(fork()) { > 这行加个断点,执行run命令,就可以在fork执行之前停下来,此时变量x的值为5。
执行next命令,终端立即打印出5\n5\n,我们知道这里是父进程创建的子进程在执行,而且已经执行完了。所以我们可以很清楚的看到,fork()之后,子进程被创建,并与父进程在宏观上并行执行。因为父进程正在被调试中,所以子进程自己先执行完了。
此时断点在< x+=30; >这行代码,执行完剩下的代码后终端打印35\n35,调试结束。
【实际的实验结果】
35
35
5
5
【实验结果分析】
从实际的实验结果来看,父进程比子进程更先执行完,但我们应该记住,对于fork()来说,父子进程的优先级是一样的,至于父进程和子进程谁先执行,
则与操作系统的调度算法等等诸多因素。因此不能保证这个实验结果就是唯一的。至于其中的运行细节分析,已在关键代码分析中叙述,这里不再赘述。
【附加分析】
这里讨论一个有趣的代码段:
void main(void) {
printf("string");
if(fork()) {
printf("\n", x);
} else {
printf("\n", x);
}
}
执行结果是string\nstring\n。
如果我们在把printf(“string”);改为printf(“string\n”);,也就是价格换行符,
执行结果是string\n\n\n
这是为什么呢?
其实这跟printf函数的缓冲机制有关,在printf某些内容时,操作系统仅仅是把该内容放到了stdout(标准输出)的缓冲队列里了,并没有实际的写到屏幕(终端)上。但是,只要看到有 \n 则会立即刷新stdout,因此就马上能够打印在屏幕上了. 在运行了printf("string") 后, string 仅仅是被放到了缓冲里而已,再运行到fork时,缓冲里面的 string 被子进程继承了。因此在子进程度stdout缓冲里面就也有了 string。所以,最终结果是 string 被printf了2次。
而运行 printf("string\n")后, string 被立即打印到了屏幕上,之后fork到的子进程里的stdout缓冲里不会有 string 内容。因此结果会是 string 被printf了1次。