操作系统:fork进程

进程和进程通信

讲述系统进程之间的通信,fork的使用。以及printf的缓存问题.

1.进程的创建实验

(1)编译下述程序并解释现象

完整程序

1
#include <stdio.h>
2
#include <unistd.h>
3
4
int main(){
5
	int pid1 = fork();
6
	printf("**1**\n");
7
	int pid2 = fork();
8
	printf("**2**\n");
9
	if(pid1 == 0){
10
   		int pid3=fork();
11
   	    printf("**3**\n");
12
   	} else {
13
   		printf("**4**\n");
14
   	}
15
   	return 0;
16
}

输出

解释

首先,我把代码进行扩充。使用了getppid ()来获得当前进程的父进程,使用了getpid()来获得当前进程的进程,以帮助进行解释。

1
#include <stdio.h>
2
#include <unistd.h>
3
4
int main(){
5
	int pid1 = fork();
6
	printf("**1** pid: %d, parentpid: %d, pid1: %d \n", getpid(), getppid(), pid1);
7
	int pid2 = fork();
8
	printf("**2** pid: %d, parentpid: %d, pid2: %d \n", getpid(), getppid(), pid2);
9
	if(pid1 == 0){
10
   		int pid3=fork();
11
   	    printf("**3** pid: %d, parentpid: %d, pid3: %d \n", getpid(), getppid(), pid2);
12
   	} else {
13
   		printf("**4** pid: %d, parentpid: %d, pid3: %d \n", getpid(), getppid(), pid2);
14
   	}
15
   	return 0;
16
}

结果:

第一步:在父进程中执行到int pid1= fork() 指令,系统中创建了一个新的进程p8287。所以现在系统中有两个进程在执行,一个为p8287,一个为p8288(使用pxxxx来表示pid为xxxx的进程)。从pid1的值中可以看出子进程的pid(fork在父进程中会返回子进程的pid,子进程中返回0),并且这两个进程的代码从fork之后是相同的。p8230执行第一次printf输出**1**,此时关系为p8287->p8288(使用->来表示父子关系)。

第二步:父进程p8287先进行执行(查资料之后,发现父进程之间执行的顺序与操作系统的调度有关,不是完全确定的),p8287执行int pid2 = fork()指令,系统中创建了一个新的进程p8289。此时p8230执行printf输出**2**。此时关系为p8287->p8289

第三步:父进程p8287仍先进程执行,此时判断pid1的值,pid1=8288不等于0,所以进入else执行语句,输出**4**

第四步:p8288,作为p8287的子进程执行,继续int pid1之后的代码,输出**1**

第五步:p8289,作为p8287的子进程执行,继续int pid2之后的代码,输出**2**

第六步:p8289,继续执行,此时pid1不等于0,输出4,p8289进程结束

第七步:p8288继续执行,执行int pid2 = fork(),创建了子进程p8290,输出**2**,此时关系为p8288->p8290

第八步:p8288继续执行,由于p8288是int pid1=fork()创建出来的,所以在p8288中pid1=0,p8288执行int pid3 = fork() 得到子进程p8291输出3。(因为此时p8287已经死亡,所以p8288的父进程变成了p1)。

第九步:p8290,作为p8288在pid2之后的子进程继续执行,输出2

第十步:p8290,pid1=0,执行int pid3 = fork(), 创建了p8292,此时关系为p8290->p8292

第十一步:p8291作为p8288在pid3之后的子程序,输出3

第十二步:p8292作为p8290在pid之后的子程序,输出3

(2)编写程序并分析

题目要求

编写一段程序,使用系统调用fork()创建两个子进程。当此程序运行时,在系统中有一个父进程和两个子进程活动。让每一个进程在屏幕上显示一个字符;父进程显示字符“a”;子进程分别显示字符“b”和字符“c”。试观察记录屏幕上的显示结果,并分析原因。

程序

1
#include <stdio.h>
2
#include <unistd.h>
3
4
int main(){
5
	int pid1 = fork();
6
	if (pid1 == 0) {
7
		printf("b\n");
8
	} else {
9
		printf("a\n");
10
		int pid2 = fork();
11
		if (pid2 == 0) {
12
			printf("c\n");
13
		}
14
	}
15
16
	return 0;
17
}

结果

分析

通过补充代码进行分析

1
#include <stdio.h>
2
#include <unistd.h>
3
4
int main(){
5
	int pid1 = fork();
6
	if (pid1 == 0) {
7
		printf("b pid: %d parentpid: %d\n", getpid(), getppid());
8
	} else {
9
		printf("a pid: %d parentpid: %d\n", getpid(), getppid());
10
		int pid2 = fork();
11
		if (pid2 == 0) {
12
			printf("c pid: %d parentpid: %d\n", getpid(), getppid());
13
		}
14
	}
15
16
	return 0;
17
}

第一步:p8924执行int pid1 = fork(); 创建了一个子进程p8925,pid1在父程序中返回子程序pid8295,所以执行else语句输出a。此时关系为p8924->p8925

第二步:在a中执行了int pid2 = fork(); 创建了一个子进程p8926,在pid2中子进程不为0不执行if。此时关系为p8924->p8926

第三步:在p8925中,pid1=0,执行了if语句输出b

第四步,在p8926中,pid20,执行了if语句输出c

(3)

题目

下面程序将在屏幕上输出的字符‘X’、数字“1” 和“0”各多少个?为什么?

1
#include <stdio.h>  
2
#include <sys/types.h>  
3
#include <unistd.h>  
4
int main(void)  
5
{  
6
   int i, a=0;  
7
   pid_t pid; // 同int
8
if((pid=fork()))a=1;
9
   for(i=0; i<2; i++){  
10
      printf("X");  
11
   }  
12
   if(pid==0)printf("%d\n",a);
13
   return 0;  
14
}

将输出4个X一个0

分析

假设main函数为p1000,执行完pid=fork()之后,创建了一个子进程p1001,pid=1001执行if语句a=1。通过for循环输出两个X

创建的子进程p1001,pid=0,不执行if语句a仍为0,执行for输出两个X,执行最后一个if输出0

验证

(4)printf缓存

问题

如果将上面main函数修改如下,则屏幕上输出的字符‘X’、数字“1”和“0”各多少个?为什么?

1
int main(void)  
2
{  
3
   int i, a=0;  
4
   pid_t pid[2];
5
   for(i=0; i<2; i++){ 
6
   if((pid[i]=fork()))a=1;
7
      printf("X");  
8
   }  
9
   if(pid[0]==0)printf("%d\n",a);
10
   if(pid[1]==0)printf("%d\n",a);
11
   return 0;  
12
}

输出了8个X,1个1,3个0

分析

同样根据上述的分析方法

fork的重要特性:

  • fork()系统调用是Unix下以自身进程创建子进程的系统调用,一次调用,两次返回,如果返回是0,则是子进程,如果返回值>0,则是父进程(返回值是子进程的pid)
  • 在fork()的调用处,整个父进程空间会原模原样地复制到子进程中,包括指令,变量值,程序调用栈,环境变量,缓冲区等

一开始分析的时候觉得只有6个X但是,经过试验之后发现有8个X。原来是printf的缓存的原因:printf(“-\n”);这里程序遇到”\n”,或是EOF,或是缓中区满,或是文件描述符关闭,或是主动flush,或是程序退出,就会把数据刷出缓冲区。需要注意的是,标准输出是行缓冲,所以遇到”n”的时候会刷出缓冲区,但对于磁盘这个块设备来说,“n”并不会引起缓冲区刷出的动作,那是全缓冲,你可以使用setvbuf来设置缓冲区大小,或是用fflush刷缓存。

效果

下图为无\n和有\n的输出不同

参考

腾讯校招题:fork进程与缓存

linux中fork()函数详解(原创!!实例讲解)

您的支持将鼓励我继续创作!