自己的理解,不一定对,仅供参考,勿copy哦!雷同就一起挂了
实验1
【实验名称】:并发程序设计(实验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);
}
【实验要求】:每个同学必须独立完成本实验、提交实验报告、源程序和可执行程序。实验报告中必须包含预计的实验结果,关键代码的分析,调试记录,实际的实验结果,实验结果分析等内容。
一.关键代码分析
一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程(child process)。fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID。
子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。注意,子进程持有的是上述存储空间的“副本”,这意味着父子进程间不共享这些存储空间。
也就是说,当程序运行到if条件判断运行fork()函数后,父进程运行的是条件成立成立的代码,即
x+=30;
printf (“%d\n”,x);
子进程运行的是条件不成立的代码,即
printf(“%d\n”,x);
根据并发执行的工作方式及特征,我们预计可能的实验结果如下:
二.预计实验结果
猜测有6种情况(其实就是这4个数的全排列)
35 |
35 |
35 |
5 |
5 |
5 |
35 |
5 |
5 |
5 |
35 |
35 |
5 |
35 |
5 |
35 |
5 |
35 |
5 |
5 |
35 |
35 |
35 |
5 |
三.调试记录
1. 先建立一个.c为后缀的文档,如test.c
2. 编辑源程序:
#include<stdio.h>
#include<unistd.h>
void main (void)
{
int x=5;
if( fork( ) )
{
x+=30;
printf (“%d\n”,x);
}
else
printf(“%d\n”,x);
printf((“%d\n”,x);
}
3. 在命令行下编辑 gcc –o test.out test.c
然后编辑 ./test.out
运行程序
4. 实际实验结果:
35
35
(一串字符命令,省略)5
5
(PS:那段字符命令,父进程运行完就会出现,所以都是在输出两个35后出现)
重复步骤3,N次后发现还是这个结果。
四.实验结果分析
老师说其它结果的出现是小概率事件。我们不敢苟同,这都基本上可以当不可能事件处理了。
于是我们猜测,这个跟传说中的时间片有关。
因为这个程序运行所需的时间实在是太短,默认先运行的父进程完全可以在系统给定的时间片内运行完,如果没有出现什么其他情况而挂起,子进程就只能等到父进程运行完后才能运行。
所以呢,当然就只有一种情况了。
五.实验结果处理
不过,我们可以利用时间片的特性制造出6种结果。
主角登场了,sleep(1) 延时一秒函数
可以把延时函数理解为一段要运行1秒的代码。下面的提到的进程,都是因在这段延时代码中运行时进程时间片用完挂起(假设在这个延时代码中运行了0.6秒),再次调度回来时,只要再运行0.4秒就可以了跳出这个延时函数了。
第一种:
因为默认先运行的是父进程(我用的系统是这样),我们感觉对子进程太不公平了,所以我们制造的第一种情况的源码是:
#include<stdio.h>
#include<unistd.h>
void main (void)
{ int x=5;
if( fork( ) )
{
x+=30;
sleep(1);
printf (“%d\n”,x);
}
else
printf(“%d\n”,x);
printf((“%d\n”,x);
}
我们猜测时间片是0.6秒,不管对不对,重点是分析。
父进程运行0.6秒后,时间片用完挂起,调度运行子进程,子进程运行完后调度回父进程接着运行。所以我们猜测结果是
5
5
35
35
(一串字符命令,省略)
哇塞,运行完也是!
第二种:
这里我们突然想到如果在子进程前也加个延时函数会怎样,
#include<stdio.h>
#include<unistd.h>
void main (void)
{ int x=5;
if( fork( ) )
{
x+=30;
sleep(1);
printf (“%d\n”,x);
}
else {
sleep(1);
printf(“%d\n”,x);
}
printf((“%d\n”,x);
}
不过,这个只能调试完再根据答案去分析,因为我们也不知道真正的时间片是多少,只是猜测。运行结果为
35
35
(一串字符命令,省略)5
5
我们继续假设时间片为0.6秒,父进程运行0.6秒后挂起,轮到子进程运行0.6秒后也挂起,调度回父进程继续运行,因为之前已经运行了0.6秒,所以这次调度时间片就够用了。假设成立
所以,以下都是假设时间片为0.6秒,这个时间是很合理的。
第三种:
#include<stdio.h>
#include<unistd.h>
void main (void)
{
int x=5;
if( fork( ) )
{
x+=30;
sleep(1);
printf (“%d\n”,x);
}
else {
printf(“%d\n”,x);
sleep(1);
}
printf((“%d\n”,x);
}
与前面的分析类似,父进程运行0.6秒后挂起,调度子进程会先运行第一条指令输出x为5,然后进入延时函数运行0.6秒(前面运行1条指令的时间可以忽略不计)后挂起,调度回父进程运行完,再调度回子进程。所以输出为
5
35
35
(一串字符应该是提示输入的命令,省略)5
第四种:
#include<stdio.h>
#include<unistd.h>
void main (void)
{ int x=5;
if( fork( ) )
{
x+=30;
printf (“%d\n”,x);
sleep(1);
}
else {
printf(“%d\n”,x);
sleep(1);
}
printf((“%d\n”,x);
}
分析同上,输出结果为
35
5
35
(一串字符命令,省略)5
第五种:
#include<stdio.h>
#include<unistd.h>
void main (void)
{ int x=5;
if( fork( ) )
{
x+=30;
printf (“%d\n”,x);
sleep(1);
}
else {
printf(“%d\n”,x);
}
printf((“%d\n”,x);
}
运行结果为,
35
5
5
35
(一串字符命令,省略)
第六种:
仔细看下上面5种情况,会发现还有种比较奇葩的没有出现。
5
35
5
35
(一串字符命令,省略)
需要从父进程(延时挂起)->子进程(输出然后延时挂起)->父进程(延时然后输出然后再延时挂起)->子进程(输出结束进程)->父进程(延时然后输出结束进程),代码为
#include<stdio.h>
#include<unistd.h>
void main (void)
{ int x=5;
if( fork( ) )
{
x+=30;
sleep(1);
printf (“%d\n”,x);
sleep(1);
}
else {
printf(“%d\n”,x);
sleep(1);
}
printf((“%d\n”,x);
}
FZU操作系统课程实验 实验一,布布扣,bubuko.com
原文:http://blog.csdn.net/yl_freedom/article/details/24322621