进程创建fork()
fork()函数以父进程为蓝本复制一个进程,其ID号与父进程ID号不同,内存等与父进程不同,其他与父进程同享,只有在父进程或者子进程进行了修改后,才重新生成一份。
成功时,fork返回是进程ID,失败返回-1;
#include<sys/types.h>
#include<unistd.h>
pid_t fork(void)
for k()的特点是执行一次,返回两次。在父进程和子进程中返回的是不同的值,父进程返回的是子进程的ID号,而在子进程中返回0。fork函数创建一个新的进程,并从内核中为此进程得到一个新的可用进程ID,之后为这个新进程分配进程空间,并将父进程的进程空间中的内容复制到子进程的进程空间中,包括父进程的数据段+堆栈段,并与其父进程共享代码段。
在Linux中,通常把调用fork系统调用的进程称为父进程,把新产生的进程称为子进程。
fork()子父进程变量共享情况
子进程会拷贝父进程的所有资源,变量
注意:子进程拷贝了父进程数据空间、堆、栈等资源的副本,
父子进程间不共享这些存储空间,共享的空间只有代码段,
子进程修改一个全局变量,父进程的这个全局变量不会改变,因为是一个副本。
子进程从父进程拷贝的内容主要有以下:
用户号UIDS和用户组号GIDS
环境Environment
堆栈
共享内存
打开文件的描述符
执行时关闭标志
信号控制设定
进程组号
当前工作目录
根目录
文件方式创建屏蔽字
资源限制
控制终端
子进程独有
进程号PID
不同的父进程号
自己的文件描述符和目录流的拷贝
子进程不继承父进程的进程正文,数据和其他锁定内存
不继承异步输入和输出
父进程和子进程拥有独立的地址空间和PID参数
案例:
执行结果:
Vfork()函数:
Vfork()函数和fork()函数一样都是在已有进程中创建一个新的进程,但他们创建的子进程是有区别的。
所需头文件:
#include<sys/types.h>
#include<unistd.h>
pid_t vfork(void);
参数:无
返回值:
成功:子进程中返回0,父进程中返回子进程ID。pid_t,为无符号整型。
失败返回-1
fork() 与 vfock() 都是创建一个进程,那它们有什么区别呢?
- fork():父子进程的执行次序不确定
vfork ():保证子进程先运行,在它调用exec(进程替换)或exit(退出进程)之后父进程才可能被调度运行。
2)fork():子进程拷贝父进程的地址空间,子进程是父进程的一个复制品
vfork ():子进程共享父进程的地址空间(准确来说,在调用exec(进程替换)或exit(退出进程)之前与父进程数据是共享的)
案例:
/*
通过 vfork() 创建的子进程会执行完后,才到父进程执行
子进程共享父进程的地址空间
*/
执行结果:可看到vfork创建的父子进程的空间地址是一样的,即共享一份地址空间。
案例:
/*
vfork()保证子进程先运行,在它调用exec(进程替换)或exit(退出进程)之后父进程才可能被调度运行
如果子进程没有调用exec,exit,程序则会导致死锁,程序是有问题的程序,没有意义。
*/
结果如下:
如果子进程使用return返回也会问题
输出结果如下:
原因分析:从上面我们知道,结束子进程的调用是exit()而不是return,如果你在vfork中return了,那么,这就意味着main()函数return 了,而因为函数栈父子进程共享,所以整个程序栈就出问题了。
若子进程中return,那么基本是下面的过程:
- 子进程的main()函数return了,于是程序的函数栈发生了变化。
- 而main()函数return后,通常会调用exit()或相似的函数(如:_exit(),exitgroup())
- 这时,父进程收到子进程exit(),开始从vfork返回,但是父进程栈都被子进程给return了,父进程无法执行。(注:栈会返回一个诡异的一个栈地址,对于某些内核版本的实现,直接报“栈错误”,然而,对于某些内核版本的实现,于是有可能会再次调用main(),于是进入了一个无限循环的结果,直到vfork调用返回error)
现在回到return和exit,return会释放局部变量,并弹栈,回到上级函数执行。exit直接退掉。return会调用局部对象的析构函数,exit不会。
可见,子进程调用exit()没有修改函数栈,所以,父进程得以顺利执行。