1. 进程执行位置
在父进程中成功创建了子进程,子进程就拥有父进程代码区的所有代码,那么子进程中的代码是在什么位置开始运行的呢?
父进程是从main()函数开始运行的,子进程是在父进程中调用fork()函数之后被创建, 子进程就从fork()之后开始向下执行代码。
上图中演示了父子进程中代码的执行流程,可以看到如果在程序中对fork()的返回值做了判断,就可以控制父子进程的行为,如果没有做任何判断这个代码块父子进程都可以执行。 在编写多进程程序的时候,一定要将代码想象成多份进行分析,因为直观上看代码就一份,但实际上数据都是多份,且多份数据中变量名都相同,但是他们的值却不一定相同。
2. 循环创建子进程
掌握了进程创建函数之后,实现一个简单的功能,在一个父进程中循环创建3个子进程,也就是最后需要得到4个进程,1个父进程,3个子进程 为了方便验证程序的正确性,要求在程序中打印出每个进程的进程ID。
// process_loop.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> int main() { for(int i=0; i<3; ++i) { pid_t pid = fork(); printf("当前进程pid: %d\n", getpid()); } return 0; }
编译并执行上面的代码,得到了如下结果:
# 编译 $ gcc process_loop.c # 执行 $ ./a.out # 最终得到了 8个进程 当前进程pid: 18774 ------ 1 当前进程pid: 18774 ------ 1 当前进程pid: 18774 ------ 1 当前进程pid: 18777 ------ 2 当前进程pid: 18776 ------ 3 当前进程pid: 18776 ------ 3 当前进程pid: 18775 ------ 4 当前进程pid: 18775 ------ 4 当前进程pid: 18775 ------ 4 当前进程pid: 18778 ------ 5 当前进程pid: 18780 ------ 6 当前进程pid: 18779 ------ 7 当前进程pid: 18779 ------ 7 当前进程pid: 18781 ------ 8
通过程序打印的信息发现程序循环了三次,最终得到了8个进程,也就是创建出了7个子进程,没有在程序中加条件控制,所有的代码父子进程都是有资格执行的。
上图中的树状结构,蓝色节点代表父进程:
- 循环第一次 i = 0,创建出一个子进程,即红色节点,子进程变量值来自父进程拷贝,因此 i=0
- 循环第二次 i = 1,蓝色父进程和红色子进程都去创建子进程,得到两个紫色进程,子进程变量值来自父进程拷贝,因此 i=1
- 循环第三次 i = 2,蓝色父进程和红色、紫色子进程都去创建子进程,因此得到4个绿色子进程,子进程变量值来自父进程拷贝,因此 i=2
- 循环第四次 i = 3,所有进程都不满足条件
for(int i=0; i<3; ++i)
因此不进入循环,退出了。
解决方案:可以只让父进程创建子进程,如果是子进程不让其继续创建子进程,只需在程序中添加关于父子进程的判断即可。
// 需要在上边的程序中控制不让子进程, 再创建子进程即可 // process_loop.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> int main() { pid_t pid; // 在循环中创建子进程 for(int i=0; i<3; ++i) { pid = fork(); if(pid == 0) { // 不让子进程执行循环, 直接跳出 break; } } printf("当前进程pid: %d\n", getpid()); return 0; }
最后编译并执行程序,查看最终结果,可以看到最后确实得到了4个不同的进程 pid最小的为父进程,其余为子进程:
# 编译 $ gcc process_loop.c # 执行 $ ./a.out 当前进程pid: 2727 当前进程pid: 2730 当前进程pid: 2729 当前进程pid: 2728
在多进程序中,进程的执行顺序是没有规律的,因为所有的进程都需要在就绪态争抢CPU时间片,抢到了就执行,抢不到就不执行 默认进程的优先级是相同的,操作系统不会让某一个进程一直抢不到CPU时间片。
3. 终端显示问题
在执行多进程程序的时候,经常会遇到下图中的问题 看似进程还没有执行完成,貌似因为什么被阻塞了,实际上终端是正常的,通过键盘输入一些命令,终端也能接受输入并输出相关信息,那为什么终端会显示成这样呢?
- a.out 进程启动之后,共创建了3个子进程,其实 a.out 也是有父进程的就是当前的终端
- 终端只能检测到 a.out 进程的状态,a.out执行期间终端切换到后台,a.out执行完毕之后终端切换回前台
- 当终端切换到前之后,a.out的子进程还没有执行完毕,当子进程输出的信息就显示到终端命令提示符的后边了,导致终端显示有问题,但是此时终端是可以接收键盘输入的,只是看起来不美观而已。
- 想要解决这个问题,需要让所有子进程退出之后再退出父进程,比如:在父进程代码中调用 sleep()
pid_t pid = fork(); if(pid > 0) { sleep(3); // 让父进程睡一会儿 } else if(pid == 0) { // 子进程 }
4. 进程数数
当父进程创建一个子进程,那么父子进程之间可以通过全局变量互动,实现交替数数的功能吗?
// number.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> // 定义全局变量 int number = 10; int main() { printf("创建子进程之前 number = %d\n", number); pid_t pid = fork(); // 父子进程都会执行这一行 printf("当前进程fork()的返回值: %d\n", pid); //如果是父进程 if(pid > 0) { printf("我是父进程, pid = %d, number = %d\n", getpid(), ++number); printf("父进程的父进程(终端进程), pid = %d\n", getppid()); sleep(1); } else if(pid == 0) { // 子进程 number += 100; printf("我是子进程, pid = %d, number = %d\n", getpid(), number); printf("子进程的父进程, pid = %d\n", getppid()); } return 0; }
编译程序并测试:
$ gcc number.c $ ./a.out 创建子进程之前 number = 10 当前进程fork()的返回值: 3513 当前进程fork()的返回值: 0 我是子进程, pid = 3513, number = 110 子进程的父进程, pid = 3512 我是父进程, pid = 3512, number = 11 #没有接着子进程的110继续数,父子进程各玩各的,测试失败 父进程的父进程(终端进程), pid = 2175
通过验证得到结论:两个进程中是不能通过全局变量实现数据交互的 因为每个进程都有自己的地址空间,两个同名全局变量存储在不同的虚拟地址空间中,二者没有任何关联性。 如果要进行进程间通信需要使用:管道,共享内存,本地套接字,内存映射区,消息队列等方式。