diff options
Diffstat (limited to 'kernel/fork.c')
| -rw-r--r-- | kernel/fork.c | 60 | 
1 files changed, 39 insertions, 21 deletions
| diff --git a/kernel/fork.c b/kernel/fork.c index e2cd3e2a5ae8..26a7a6707fa7 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -668,6 +668,38 @@ struct mm_struct *mm_access(struct task_struct *task, unsigned int mode)  	return mm;  } +static void complete_vfork_done(struct task_struct *tsk) +{ +	struct completion *vfork; + +	task_lock(tsk); +	vfork = tsk->vfork_done; +	if (likely(vfork)) { +		tsk->vfork_done = NULL; +		complete(vfork); +	} +	task_unlock(tsk); +} + +static int wait_for_vfork_done(struct task_struct *child, +				struct completion *vfork) +{ +	int killed; + +	freezer_do_not_count(); +	killed = wait_for_completion_killable(vfork); +	freezer_count(); + +	if (killed) { +		task_lock(child); +		child->vfork_done = NULL; +		task_unlock(child); +	} + +	put_task_struct(child); +	return killed; +} +  /* Please note the differences between mmput and mm_release.   * mmput is called whenever we stop holding onto a mm_struct,   * error success whatever. @@ -683,8 +715,6 @@ struct mm_struct *mm_access(struct task_struct *task, unsigned int mode)   */  void mm_release(struct task_struct *tsk, struct mm_struct *mm)  { -	struct completion *vfork_done = tsk->vfork_done; -  	/* Get rid of any futexes when releasing the mm */  #ifdef CONFIG_FUTEX  	if (unlikely(tsk->robust_list)) { @@ -704,17 +734,15 @@ void mm_release(struct task_struct *tsk, struct mm_struct *mm)  	/* Get rid of any cached register state */  	deactivate_mm(tsk, mm); -	/* notify parent sleeping on vfork() */ -	if (vfork_done) { -		tsk->vfork_done = NULL; -		complete(vfork_done); -	} +	if (tsk->vfork_done) +		complete_vfork_done(tsk);  	/*  	 * If we're exiting normally, clear a user-space tid field if  	 * requested.  We leave this alone when dying by signal, to leave  	 * the value intact in a core dump, and to save the unnecessary -	 * trouble otherwise.  Userland only wants this done for a sys_exit. +	 * trouble, say, a killed vfork parent shouldn't touch this mm. +	 * Userland only wants this done for a sys_exit.  	 */  	if (tsk->clear_child_tid) {  		if (!(tsk->flags & PF_SIGNALED) && @@ -1018,7 +1046,6 @@ static void copy_flags(unsigned long clone_flags, struct task_struct *p)  	new_flags &= ~(PF_SUPERPRIV | PF_WQ_WORKER);  	new_flags |= PF_FORKNOEXEC; -	new_flags |= PF_STARTING;  	p->flags = new_flags;  } @@ -1548,16 +1575,9 @@ long do_fork(unsigned long clone_flags,  		if (clone_flags & CLONE_VFORK) {  			p->vfork_done = &vfork;  			init_completion(&vfork); +			get_task_struct(p);  		} -		/* -		 * We set PF_STARTING at creation in case tracing wants to -		 * use this to distinguish a fully live task from one that -		 * hasn't finished SIGSTOP raising yet.  Now we clear it -		 * and set the child going. -		 */ -		p->flags &= ~PF_STARTING; -  		wake_up_new_task(p);  		/* forking complete and child started to run, tell ptracer */ @@ -1565,10 +1585,8 @@ long do_fork(unsigned long clone_flags,  			ptrace_event(trace, nr);  		if (clone_flags & CLONE_VFORK) { -			freezer_do_not_count(); -			wait_for_completion(&vfork); -			freezer_count(); -			ptrace_event(PTRACE_EVENT_VFORK_DONE, nr); +			if (!wait_for_vfork_done(p, &vfork)) +				ptrace_event(PTRACE_EVENT_VFORK_DONE, nr);  		}  	} else {  		nr = PTR_ERR(p); | 
