进程和进程通信
讲述系统进程之间的通信,fork的使用。以及printf的缓存问题.
1.进程的创建实验
(1)编译下述程序并解释现象
完整程序
1 |
|
2 |
|
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 |
|
2 |
|
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 |
|
2 |
|
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 |
|
2 |
|
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 |
|
2 |
|
3 |
|
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的输出不同