引言—神奇的vfork
在说vfork()
函数之前我们不得不说说fork()
函数,它们两者都是用来创建子进程的函数,那么他们之间有什么不同,有什么相同的呢;
fork与vfork
定义
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
这是一个罕见的有两个返回值的函数,调用一次,返回两次,调用成功后,当前进程实际已经分裂为两个进程,一个是原来的父进程,另一个是刚刚创建的子进程;
父进程返回刚刚创建的子进程的ID
,子进程返回0
;
这一点上,vfork
函数与fork
函数是一致的;
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
区别
既然两个函数都是用来创建子进程的,那么他们创建的子进程有什么不同的;
fork创建子进程
使用fork
创建子进程的时候,子进程复制了父进程的资源,但是如果在创建的时候就进行复制,那么太耗费时间了,于是就采用写时复制,就是子进程共享父进程的内存,当子进程或者父进程要去写某块内存时,把父进程的数据拷贝一份,这样的子进程独立于父进程,具有良好的并发性;
写时复制不等于不复制,之前内存的页表等还是要在fork
的时候完成复制;
这样也要耗费一定的时间;
vfork创建子进程
对比上面的fork
创建子进程;vfork
做的更加彻底,写时复制都没有了,所有关于内存的东西都不复制了,父子进程之间共享内存;
那么问题来了?
父子进程共用着栈,这意味着,一个进程调用了函数或者对栈上的数据做了修改,另一个进程的栈也受到了影响;
所以,vfork
函数创建的子进程有一个限制,就是在子进程创建后,父进程在vfork
中被内核挂起,直到子进程有了自己的内存空间(调用exec
族函数)或者exit()
;
(这也是为什么在vfork
创建子进程以后,一直是先执行子进程)
在子进程没有exec
或者exit
之前子进程不能调用函数,或者修改栈上的数据,不能return
返回;
// 神奇的vfork,进程的D状态
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int add(int a, int b)
{
return a+b;
}
int main()
{
int pid;
int i = 0;
pid = vfork();
if (!pid) {
// 查看D状态
// sleep(100);
// i++;
// printf("子进程\n");
// printf("1 + 1 = %d\n", add(1, 1));
// printf("%d\n", ++i);
// vfork创建的子进程不具备写时复制能力所以不能修改栈上的数据上面三个实验都会在父进程返回时引发段错误;
// return 0; 子进程不能返回因为vfork与fork不同没有自己的内存空间;
exit(0);
}
return 0;
}
// fork对比
/* int main()
{
int pid = fork();
int i = 0;
if (!pid) {
printf("子进程\n");
printf("%d\n", ++i);
return 0;
}
return 0;
} */
进程状态D
D
表示:不可中断的睡眠状态;
进程处于睡眠状态,但是此刻进程是不可中断的。不可中断,指的并不是CPU不响应外部硬件的中断,而是指进程不响应异步信号。
这里我们可以对比一下,由fork
和vfork
创建的进程,他们的父进程是什么状态;
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
int pid = fork();
// int pid = vfork();
if (!pid) {
while (1) {
}
} else {
while (1) {
}
}
return 0;
}
我们将fork
编译为1
,vfork
编译为2
;
我们可以看到fork
后的子进程和父进程是一起执行的,而vfork
后父进程是不可中断的睡眠状态;