diff options
Diffstat (limited to 'kernel/exit.c')
| -rw-r--r-- | kernel/exit.c | 141 | 
1 files changed, 103 insertions, 38 deletions
diff --git a/kernel/exit.c b/kernel/exit.c index 8dd874181542..f2b321bae440 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -561,29 +561,28 @@ void exit_files(struct task_struct *tsk)  #ifdef CONFIG_MM_OWNER  /* - * Task p is exiting and it owned mm, lets find a new owner for it + * A task is exiting.   If it owned this mm, find a new owner for the mm.   */ -static inline int -mm_need_new_owner(struct mm_struct *mm, struct task_struct *p) -{ -	/* -	 * If there are other users of the mm and the owner (us) is exiting -	 * we need to find a new owner to take on the responsibility. -	 */ -	if (atomic_read(&mm->mm_users) <= 1) -		return 0; -	if (mm->owner != p) -		return 0; -	return 1; -} -  void mm_update_next_owner(struct mm_struct *mm)  {  	struct task_struct *c, *g, *p = current;  retry: -	if (!mm_need_new_owner(mm, p)) +	/* +	 * If the exiting or execing task is not the owner, it's +	 * someone else's problem. +	 */ +	if (mm->owner != p)  		return; +	/* +	 * The current owner is exiting/execing and there are no other +	 * candidates.  Do not leave the mm pointing to a possibly +	 * freed task structure. +	 */ +	if (atomic_read(&mm->mm_users) <= 1) { +		mm->owner = NULL; +		return; +	}  	read_lock(&tasklist_lock);  	/* @@ -1377,11 +1376,23 @@ static int *task_stopped_code(struct task_struct *p, bool ptrace)  	return NULL;  } -/* - * Handle sys_wait4 work for one task in state TASK_STOPPED.  We hold - * read_lock(&tasklist_lock) on entry.  If we return zero, we still hold - * the lock and this task is uninteresting.  If we return nonzero, we have - * released the lock and the system call should return. +/** + * wait_task_stopped - Wait for %TASK_STOPPED or %TASK_TRACED + * @wo: wait options + * @ptrace: is the wait for ptrace + * @p: task to wait for + * + * Handle sys_wait4() work for %p in state %TASK_STOPPED or %TASK_TRACED. + * + * CONTEXT: + * read_lock(&tasklist_lock), which is released if return value is + * non-zero.  Also, grabs and releases @p->sighand->siglock. + * + * RETURNS: + * 0 if wait condition didn't exist and search for other wait conditions + * should continue.  Non-zero return, -errno on failure and @p's pid on + * success, implies that tasklist_lock is released and wait condition + * search should terminate.   */  static int wait_task_stopped(struct wait_opts *wo,  				int ptrace, struct task_struct *p) @@ -1397,6 +1408,9 @@ static int wait_task_stopped(struct wait_opts *wo,  	if (!ptrace && !(wo->wo_flags & WUNTRACED))  		return 0; +	if (!task_stopped_code(p, ptrace)) +		return 0; +  	exit_code = 0;  	spin_lock_irq(&p->sighand->siglock); @@ -1538,33 +1552,84 @@ static int wait_consider_task(struct wait_opts *wo, int ptrace,  		return 0;  	} -	if (likely(!ptrace) && unlikely(task_ptrace(p))) { +	/* dead body doesn't have much to contribute */ +	if (p->exit_state == EXIT_DEAD) +		return 0; + +	/* slay zombie? */ +	if (p->exit_state == EXIT_ZOMBIE) { +		/* +		 * A zombie ptracee is only visible to its ptracer. +		 * Notification and reaping will be cascaded to the real +		 * parent when the ptracer detaches. +		 */ +		if (likely(!ptrace) && unlikely(task_ptrace(p))) { +			/* it will become visible, clear notask_error */ +			wo->notask_error = 0; +			return 0; +		} + +		/* we don't reap group leaders with subthreads */ +		if (!delay_group_leader(p)) +			return wait_task_zombie(wo, p); +  		/* -		 * This child is hidden by ptrace. -		 * We aren't allowed to see it now, but eventually we will. +		 * Allow access to stopped/continued state via zombie by +		 * falling through.  Clearing of notask_error is complex. +		 * +		 * When !@ptrace: +		 * +		 * If WEXITED is set, notask_error should naturally be +		 * cleared.  If not, subset of WSTOPPED|WCONTINUED is set, +		 * so, if there are live subthreads, there are events to +		 * wait for.  If all subthreads are dead, it's still safe +		 * to clear - this function will be called again in finite +		 * amount time once all the subthreads are released and +		 * will then return without clearing. +		 * +		 * When @ptrace: +		 * +		 * Stopped state is per-task and thus can't change once the +		 * target task dies.  Only continued and exited can happen. +		 * Clear notask_error if WCONTINUED | WEXITED. +		 */ +		if (likely(!ptrace) || (wo->wo_flags & (WCONTINUED | WEXITED))) +			wo->notask_error = 0; +	} else { +		/* +		 * If @p is ptraced by a task in its real parent's group, +		 * hide group stop/continued state when looking at @p as +		 * the real parent; otherwise, a single stop can be +		 * reported twice as group and ptrace stops. +		 * +		 * If a ptracer wants to distinguish the two events for its +		 * own children, it should create a separate process which +		 * takes the role of real parent. +		 */ +		if (likely(!ptrace) && task_ptrace(p) && +		    same_thread_group(p->parent, p->real_parent)) +			return 0; + +		/* +		 * @p is alive and it's gonna stop, continue or exit, so +		 * there always is something to wait for.  		 */  		wo->notask_error = 0; -		return 0;  	} -	if (p->exit_state == EXIT_DEAD) -		return 0; -  	/* -	 * We don't reap group leaders with subthreads. +	 * Wait for stopped.  Depending on @ptrace, different stopped state +	 * is used and the two don't interact with each other.  	 */ -	if (p->exit_state == EXIT_ZOMBIE && !delay_group_leader(p)) -		return wait_task_zombie(wo, p); +	ret = wait_task_stopped(wo, ptrace, p); +	if (ret) +		return ret;  	/* -	 * It's stopped or running now, so it might -	 * later continue, exit, or stop again. +	 * Wait for continued.  There's only one continued state and the +	 * ptracer can consume it which can confuse the real parent.  Don't +	 * use WCONTINUED from ptracer.  You don't need or want it.  	 */ -	wo->notask_error = 0; - -	if (task_stopped_code(p, ptrace)) -		return wait_task_stopped(wo, ptrace, p); -  	return wait_task_continued(wo, p);  }  | 
