目录
父子进程的关系
使用fork(或者其他能创建子进程的API)函数得到的子进程从父进程的继承了整个进程的地址空间,包括:进程上下文、进程堆栈、代码段、数据段、以及PCB和内存信息、打开的文件描述符、信号控制设置、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等。
getpid / getppid
getpid 获取当前进程id
getppid 获取当前进程的父进程的id
fork
pid_t fork()
- 为什么fork有两个返回值?
因为这两个返回值是由不同的进程return出来的,而不是由一个fork函数返回两个数。(fork后,进程由一个变成两个,两个进程分别有一个返回值)
返回值:
- 成功创建一个子进程,对于父进程来说返回子进程ID
- 成功创建一个子进程,对于子进程来说返回值为0
- 如果为-1表示创建失败 - 子进程创建成功后,代码的执行的开始位置?
fork代码段的位置 - 父子进程的执行顺序?
不一定谁先谁后(看谁抢到CPU资源)
示例代码1:子进程在fork之后,才开始执行
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
int main(){
for(int i=0;i<4;i++) //仅仅在父进程中执行一次
printf("---------%d\n",i);
pid_t pid=fork();
if(pid<0){
perror("fork fail");
}
if(pid>0){ //parent process
printf("parent process,pid=%d\n",getpid());
}
if(pid==0){ //child process
printf("child process,pid=%d\n",getpid());
}
for(int i=0;i<4;i++) //在父进程和子进程中各执行一次
printf("%d\n",i);
}
[gjw@localhost 1-fork]$ gcc fork.c -o fork --std=c99 #编译代码
[gjw@localhost 1-fork]$ ./fork #执行结果
---------0
---------1
---------2
---------3
parent process,pid=117802
0
1
2
3
child process,pid=117803
0
1
2
3
示例代码2:循环创建number个子进程
父进程
子进程1 子进程2 子进程3 ...... 子进程number
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
int main(){
int i;
int number=5;
pid_t pid;
for(i=0;i<number;i++){
pid=fork();
if(pid<0){
perror("fork fail");
exit(1);
}
if(pid==0)
break;
}
for(int j=0;j<number;j++){ //判断子进程是第几个孩子
if(j==i)
printf("%d process,pid=%d\n",j,getpid());
}
if(i==number) //判断父进程是哪一个:(父进程退出for循环时,i==number)
printf("parent process,pid=%d\n",getpid());
}
[gjw@localhost 1-fork]$ ./fork
0 process,pid=70113
1 process,pid=70114
2 process,pid=70115
parent process,pid=70112
4 process,pid=70117
3 process,pid=70116
示例代码3:循环创建process_num个子进程,每个进程循环打印rollnum次
int main(){
int process_num;
int rollnum;
scanf("%d",&process_num);
scanf("%d",&rollnum);
pid_t pid;
int i;
for(i=0;i<process_num;i++){
pid=fork();
if(pid<0){
perror("fork");
exit(1);
}
else if(pid==0){ //子进程
break;
}
}
for(int j=0;j<process_num;j++){ //判断是第几个子进程
if(i==j){
for(int k=0;k<rollnum;k++){ //每个进程打印rollnum次
usleep(500);
printf("%dth pid ,%d,____%d\n",j,getpid(),k);
}
}
}
if(i==process_num){ //父进程:释放子进程资源
int status;
pid_t ret;
while((ret=waitpid(-1,&status,WNOHANG))!=-1){
if(ret==0)
continue;
printf("wait %d pid",ret);
if(WIFEXITED(status))
printf("exit,status=%d\n",WEXITSTATUS(status));
else if(WIFSIGNALED(status))
printf("signal,status=%d\n",WTERMSIG(status));
}
}
return 0;
}
写时拷贝原则write-on-copy
- 读时共享
刚fork出来之后,两个地址空间用户区数据完全相同,父子进程都指向同一块共享区域,父子进程中都映射到共享区域中的变量(int num) - 写时拷贝
当后续父子进程对共享区域中的变量进行不同的操作时(父进程对num++,子进程对num–),—>发生写时拷贝原则,父子进程各自拷贝出来int大小的空间存放自己的num,因此父子进程中的num是相互独立,互不影响的====>因此父子进程之间不能够使用全局变量进行通信。
vfork
- 引出vfork的背景:
在fork还没实现copy on write之前。Unix设计者很关心fork之后立刻执行exec所造成的地址空间浪费,所以引入了vfork系统调用。 - vfork和fork的区别/联系:
vfork() 函数和 fork() 函数一样都是在已有的进程中创建一个新的进程,但它们创建的子进程是有区别的。
- 父子进程的执行顺序
fork(): 父子进程的执行次序不确定。
vfork():保证子进程先运行,在它调用 exec/exit之后,父进程才执行 - 是否拷贝父进程的地址空间
fork(): 子进程拷贝父进程的地址空间,子进程是父进程的一个复制品。
vfork():子进程共享父进程的地址空间 - 调用vfork函数,是为了执行exec函数;如果子进程没有调用 exec/exit, 程序会出错
vfork示例代码
验证1:vfork父子进程执行顺序
子进程先执行完exec或exit后
父进程才开始执行
int main(int argc, char *argv[]){
pid_t pid;
pid = vfork(); // 创建进程
if(pid < 0){ // 出错
perror("vfork");
}
if(0 == pid){
sleep(3); // 延时 3 秒
printf("i am son\n");
_exit(0); // 退出子进程,必须
}
else if(pid > 0){ // 父进程
printf("i am father\n");
}
}
执行结果:已经让子进程延时 3 s,结果还是子进程运行结束后,父进程才执行
验证2:vfork后,父子进程共享内存空间
int a = 10;
int main(int argc, char *argv[]){
pid_t pid;
int b = 20;
pid = vfork(); // 创建进程
if(pid < 0){ // 出错
perror("vfork");
}
if(0 == pid){ // 子进程
a = 100, b = 200;
printf("son: a = %d, b = %d\n", a, b);
_exit(0);
}
else if(pid > 0){
printf("father: a = %d, b = %d\n", a, b);
}
}
执行结果:子进程先执行,修改完a,b的值后,由于父子进程共享内存空间,因此会影响父进程
son: a = 100, b = 200
son: a = 100, b = 200