vfork()

vfork():sys_vfork()

经过系统调用sys_vfork()进入do_fork()时,
因其clone_flags为  VFORK | CLONE_VM | SIGHLD,
所以只执行了copy_files()、copy_fs()以及copy_sighand
而copy_mm(),则因标志位CLONE_VM为1,只是通过指针共享其父进程的存储空间,
包括用户空间堆栈在内

这里的共享是真的共享,不会发生写时复制,
实际上vfork()都搭配着exec()系列函数来产生新的进程

特点一:也就是说,vfork()产生的子进程与父进程共享数据段上的数据

我们来看一linux内核中关于vfork()函数的部分实现:
见注释:

asmlinkage int sys_vfork(struct pt_regs *regs)
{
	return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD,
				  regs->ARM_sp, 
				  regs, 0, NULL, NULL);
}

和sys_fork()一样,同样调用的是do_fork():

long do_fork(unsigned long clone_flags,
	      unsigned long stack_start,
	      struct pt_regs *regs,
	      unsigned long stack_size,
	      int __user *parent_tidptr,
	      int __user *child_tidptr)
{
	struct task_struct *p;
	int trace = 0;
	......
}

对于sys_vfork()我们只关心这部分的内容做了什么:

.........
if (!IS_ERR(p))  //如果调用的是vfork()函数
{
		struct completion vfork;

		nr = task_pid_vnr(p);

		if (clone_flags & CLONE_PARENT_SETTID)
			put_user(nr, parent_tidptr);

		if (clone_flags & CLONE_VFORK) {
			p->vfork_done = &vfork;
			init_completion(&vfork);
		}

		tracehook_report_clone(trace, regs, clone_flags, nr, p);

		p->flags &= ~PF_STARTING;

		//如果设置了 CLONE_STOPPED标志,则将子进程设置为TASK_STOP状态
		//发送SIGSTOP信号将子进程挂起    这部分对我们研究vfork机制没有太大帮助
		if (unlikely(clone_flags & CLONE_STOPPED)) {
		
			sigaddset(&p->pending.signal, SIGSTOP);
			set_tsk_thread_flag(p, TIF_SIGPENDING);
			__set_task_state(p, TASK_STOPPED);
		} else {
			wake_up_new_task(p, clone_flags);
			//否则调用wake_up_new_task()
		}

		tracehook_report_clone_complete(trace, regs,
						clone_flags, nr, p);


//这一部分是关键
//很明显我们设置了CLONE_VFORK标志位
//系统将会把父进程插入到等待队列,并挂起父进程直到子进程结束(exit)或者执行了exec系列函数
		if (clone_flags & CLONE_VFORK) {
			freezer_do_not_count();
			wait_for_completion(&vfork);
			freezer_count();
			tracehook_report_vfork_done(p, nr);
		}
	}
	.........

特点二:由vfork产生的子进程一定会先于父进程运行,期间会将父进程挂起,直到子进程运行结束或者调用了exec()系列函数才会重新调度父进程。

接下来我们在用程序验证一下vfork()函数特点一:

#include
#include
#include
#include

int main()
{
	pid_t pid;
	int val = 0;
	pid = vfork();  //产生一个子进程,和父进程共享数据段内存
	
	if(pid == -1)
	{
		perror("fork erro\n");
		exit(1);
	}

	if(pid == 0)
	{
		val+=99;   //对数据val增加99后退出exit,实际上修改的是父进程的数据段数据
		printf("child id = %d,val = %d\n",getpid(),val);
		exit(0);
	}
	else
	{
		val +=1;//等到子进程执行结束后,父进程对自己数据段的数据val+1操作
		printf("parent id = %d,val = %d\n",getpid(),val);
	}
	return 0;
}

执行结果:
child id = 16410,val = 99
parent id = 16409,val = 100

不难看出,子进程修改了父进程的数据内容,父子进程数据共享与不言而喻
没有发送写时复制,真正的共享,而不是读共享

fork()和vfork()的区别联系:

1.
fork() 子进程拷贝父进程的前3G的地址空间的内容. 
vfork() 子进程与父进程共享数据段,读写都共享.|
2.
fork() 父子进程的执行次序不确定,最终由调度进程决定. 
vfork():保证子进程先运行,父进程会被挂起,直到子进程调用exit()或者exec()系列函数
如果在 调用这两个函数之前子进程有依赖于父进程的进一步动作,则会导致死锁,因为父进程已经被挂起,父进程的运行需要子进程的退出,而子进程的执行又依赖父进程。
3.
vfork用于创建一个新进程,而该新进程的目的是exec一个新进程,通常vfork()和exec搭配使用
vfork和fork一样都创建一个子进程,但是它并不将父进程的地址空间完全复制到子进程中,不会复制页表。因为子进程会立即调用exec,于是也就不会存放该地址空间。不过在子进程中调用exec或exit之前,他在父进程的空间中运行。

vfork这个系统调用是用来启动一个新的应用程序。
其次,子进程在vfork()返回后直接运行在父进程的栈空间,并使用父进程的内存和数据,这是很危险的操作。

子进程还必须避免改变全局数据结构或全局变量中的任何信息,
因为这些改变都有可能使父进程不能继续。

通常,如果应用程序不是在fork()之后立即调用exec(),
就有必要在fork()被替换成vfork()之前做仔细的检查。

fork()和vfork()的应用场景:

1.fork()真正的产生子进程,我们多进程并发应该使用的是它

2.vfork()一般搭配着exec来产生一个新的进程

fork()和vfork()的渊源:

正如前面所讲,vfork一般和exec搭配产生新的进程,如果我们都使用fork,
fork会将父进程前3G的地址空间以及PCB中的部分内容都拷贝到自己空间
这样开销太大了

exec最近要把子进程中的数据和指令替换,那干嘛还要费老大劲儿将父进程的东西拷贝进去呢
这样做不是做无用功吗

就这样vfork应运而生。

vfork产生的子进程会有自己的独立地址空间,只不过数据和父进程共享,所以不要和线程混淆,线程没有独立的地址空间。

你可能感兴趣的:(linux,linux进程)