#include
#include
#include
int main()
{
pid_t pid1, pid2;
while ((pid1 = fork()) == -1);
if (pid1 == 0)
{ printf("b");}
else
{
while ((pid2 = fork()) == -1);
if (pid2 == 0) printf("c");
else printf("a");
}
return 0;
}
编译后执行:
分析:
程序运行多次,发现结果一致,并未有改变。
原因可能是操作系统总是按照如上的顺序进行运行。
修改程序,将每个进程输出一个字符改为每个进程输出一句话,观察分析显示结果;
#include
#include
#include
int main()
{
pid_t pid1, pid2;
while ((pid1 = fork()) == -1);
if (pid1 == 0) printf("pid1 here : b\n");
else
{
while ((pid2 = fork()) == -1);
if (pid2 == 0) printf("pid2 here : c\n");
else printf("father progress here : a\n");
}
return 0;
}
结果和之前没有区别,但偶有输出 b c a 可能和操作系统执行顺序有关。
再试试:
如果在父进程fork之前,输出一句话,这句话后面不加“\n”或加“\n”,结果有什么不同,为什么?
#include
#include
#include
int main()
{
pid_t pid1, pid2;
printf("This is a sentence.");
// printf("This is a sentence.\n");
while ((pid1 = fork()) == -1);
if (pid1 == 0) printf("pid1 here : b\n");
else
{
while ((pid2 = fork()) == -1);
if (pid2 == 0) printf("pid2 here : c\n");
else printf("father progress here : a\n");
}
return 0;
}
结果分析:
查阅相关资料后进行分析:
(1)不加’\n’:
由于输出时,我们要将内存中的数据输出到磁盘中,系统会先将要printf的内容存到磁盘缓冲区buffer,也就是将This is a sentence.这句话写入buffer。而子进程会继承父进程的buffer内容,于是在子进程中也会打印这句话,第1个子进程会输出,第2个子进程也会输出。
(2)加’\n’:
系统检测到“\n”会自动清空缓冲区,于是子进程继承父进程时,缓冲区里面没有要打印这句话的信息,于是在2个子进程中只分别输出c和b。
修改已经编好的程序(使用fork()创建进程),将每个进程输出一个字符改为每个进程用for循环重复输出一句话,再观察程序执行时屏幕上出现的现象,并分析原因。
#include
#include
#include
int main()
{
pid_t pid1, pid2;
while ((pid1 = fork()) == -1);
if (pid1 == 0) {
for(int i=0;i<5;i++){
printf("pid1 here : b\n");
}
}
else
{
while ((pid2 = fork()) == -1);
if (pid2 == 0) {
for(int i=0;i<5;i++){
printf("pid2 here : c\n");
}
}else{
for(int i=0;i<5;i++){
printf("father progress here : a\n");
}
}
}
return 0;
}
结果分析:
如果在程序中使用系统调用lockf()来给每一个进程加锁,可以实现进程之间的互斥,观察并分析出现的现象。
如果在程序中使用系统调用lockf来给临界资源加锁,可以实现临界资源的互斥访问。将lockf加在输出语句前后运行试试;将一条输出语句变成多条输出语句,将lockf语句放在循环语句外部或内部试试,观察显示结果并分析原因。
(一)直接对stdout加锁,发现和之前的输出没有区别,因为进行互斥约束后仍然可以访问stdout中的资源:
#include
#include
#include
int main()
{
pid_t pid1, pid2;
while ((pid1 = fork()) == -1);
if (pid1 == 0)
{
lockf(1, 1, 0);
printf("pid1 here : b\n");
lockf(1, 0, 0);
}
else
{
while ((pid2 = fork()) == -1);
if (pid2 == 0)
{
lockf(1, 1, 0);
printf("pid2 here : c\n");
lockf(1, 0, 0);
}
else
{
lockf(1, 1, 0);
printf("father progress here : a\n");
lockf(1, 0, 0);
}
}
return 0;
}
(二)进行循环访问并在内部加锁,发现子进程pid1先执行4次输出,然后parent输出,最后是pid2:
#include
#include
#include
int main()
{
pid_t pid1, pid2;
while ((pid1 = fork()) == -1);
if (pid1 == 0)
{
for (int i = 0; i < 5; ++i)
{
lockf(1, 1, 0);
printf("pid1 here : %d\n", i);
lockf(1, 0, 0);
}
}
else
{
while ((pid2 = fork()) == -1);
if (pid2 == 0)
{
for (int i = 0; i < 5; ++i)
{
lockf(1, 1, 0);
printf("pid2 here : %d\n", i);
lockf(1, 0, 0);
}
}
else printf("father progress here : a\n");
}
return 0;
}
(三)进行循环访问并在外部加锁,发现输出情况一样:
#include
#include
#include
int main()
{
pid_t pid1, pid2;
while ((pid1 = fork()) == -1);
if (pid1 == 0)
{
lockf(1, 1, 0);
for (int i = 0; i < 5; ++i)
printf("pid1 here : %d\n", i);
lockf(1, 0, 0);
}
else
{
while ((pid2 = fork()) == -1);
if (pid2 == 0)
{
lockf(1, 1, 0);
for (int i = 0; i < 5; ++i)
printf("pid2 here : %d\n", i);
lockf(1, 0, 0);
}
else printf("father progress here : a\n");
}
return 0;
}
(四)上锁后令进程进行短暂休眠,发现pid1和pid2交替输出,并且父进程不是最后输出:
#include
#include
#include
int main()
{
pid_t pid1, pid2;
while ((pid1 = fork()) == -1);
if (pid1 == 0)
{
for (int i = 0; i < 5; ++i)
{
lockf(1, 1, 0);
printf("pid1 here : %d\n", i);
lockf(1, 0, 0);
sleep(1);
}
}
else
{
while ((pid2 = fork()) == -1);
if (pid2 == 0)
{
for (int i = 0; i < 5; ++i)
{
lockf(1, 1, 0);
printf("pid2 here : %d\n", i);
lockf(1, 0, 0);
sleep(1);
}
}
else printf("father progress here : a\n");
}
return 0;
}
分析:
思考:
系统是如何创建进程?当父进程fork子进程后,父进程和子进程从程序什么位置开始执行?为什么?
(1)首先创建父进程,再根据父进程创建子进程。具体而言,是首先通过内核调用sys_clone来完成,先对父进程的数据和环境进行复制,然后修改新进程的一些属性,之后通过do_fork函数和ret_from_fork函数完成对新进程的创建以及运行。
(2)经过资料查阅,个人认为,fork创建的父子进程并不保证任何有规律的执行顺序。不同linux,不同的系统配置环境,不同的系统负载下很可能结果不一样。
(3)从实验的结果来推测,子进程和父进程都从调用fork函数的下一条语句开始执行。因为程序的顺序执行,也很难想象会从之前的语句重新执行。