diff options
| author | Julian Elischer <julian@FreeBSD.org> | 2002-10-09 02:33:36 +0000 | 
|---|---|---|
| committer | Julian Elischer <julian@FreeBSD.org> | 2002-10-09 02:33:36 +0000 | 
| commit | 48bfcddd94bb965c037931abe94df44e96cf8340 (patch) | |
| tree | 93491b956e59fcfe0f873643838ff7944a49abe7 /sys/kern/kern_thread.c | |
| parent | 0ce3fbf1919e2061e757c99f8c924998d327b298 (diff) | |
Notes
Diffstat (limited to 'sys/kern/kern_thread.c')
| -rw-r--r-- | sys/kern/kern_thread.c | 433 | 
1 files changed, 264 insertions, 169 deletions
diff --git a/sys/kern/kern_thread.c b/sys/kern/kern_thread.c index 332611996937..407b7777b8f7 100644 --- a/sys/kern/kern_thread.c +++ b/sys/kern/kern_thread.c @@ -67,7 +67,7 @@ static int oiks_debug = 1;	/* 0 disable, 1 printf, 2 enter debugger */  SYSCTL_INT(_kern_threads, OID_AUTO, oiks, CTLFLAG_RW,  	&oiks_debug, 0, "OIKS thread debug"); -static int max_threads_per_proc = 6; +static int max_threads_per_proc = 10;  SYSCTL_INT(_kern_threads, OID_AUTO, max_per_proc, CTLFLAG_RW,  	&max_threads_per_proc, 0, "Limit on threads per proc"); @@ -470,6 +470,11 @@ thread_exit(void)  		thread_stash(ke->ke_tdspare);  		ke->ke_tdspare = NULL;  	} +	if (td->td_standin != NULL) { +		thread_stash(td->td_standin); +		td->td_standin = NULL; +	} +  	cpu_thread_exit(td);	/* XXXSMP */  	/* @@ -478,14 +483,6 @@ thread_exit(void)  	 * all this stuff.  	 */  	if (p->p_numthreads > 1) { -		/* Reassign this thread's KSE. */ -		ke->ke_thread = NULL; -		td->td_kse = NULL; -		ke->ke_state = KES_UNQUEUED; -		if (ke->ke_bound == td) -			ke->ke_bound = NULL; -		kse_reassign(ke); -  		/* Unlink this thread from its proc. and the kseg */  		TAILQ_REMOVE(&p->p_threads, td, td_plist);  		p->p_numthreads--; @@ -501,12 +498,41 @@ thread_exit(void)  				thread_unsuspend_one(p->p_singlethread);  			}  		} + +		/* Reassign this thread's KSE. */ +		ke->ke_thread = NULL; +		td->td_kse = NULL; +		ke->ke_state = KES_UNQUEUED; +		if (ke->ke_bound == td) { +			printf("thread_exit: entered with ke_bound set\n"); +			ke->ke_bound = NULL; /* should never happen */ +		} + +		kse_reassign(ke);  		PROC_UNLOCK(p);  		td->td_state	= TDS_INACTIVE;  		td->td_proc	= NULL;  		td->td_ksegrp	= NULL;  		td->td_last_kse	= NULL; -		ke->ke_tdspare = td; +		/*  +		 * For now stash this here, however +		 * it's not a permanent solution. +		 *  When we want to make KSEs exit as well +		 * we'll have to face this one again. +		 * Where will we hide it then? +		 * +		 * In borrower threads, stash it in the lender +		 * Where it won't be needed until +		 * this thread is long gone. +		 */ +		if (ke->ke_bound) { +			if (ke->ke_bound->td_standin) { +				thread_stash(ke->ke_bound->td_standin); +			} +			ke->ke_bound->td_standin = td; +		} else { +			ke->ke_tdspare = td; +		}  	} else {  		PROC_UNLOCK(p);  	} @@ -555,40 +581,85 @@ struct thread *  thread_schedule_upcall(struct thread *td, struct kse *ke)  {  	struct thread *td2; +	int newkse;  	mtx_assert(&sched_lock, MA_OWNED); -	if (ke->ke_tdspare != NULL) { -		td2 = ke->ke_tdspare; -		ke->ke_tdspare = NULL; +	newkse = (ke != td->td_kse); + +	/*  +	 * If the kse is already owned by another thread then we can't +	 * schedule an upcall because the other thread must be BOUND +	 * which means it is not in a position to take an upcall. +	 * We must be borrowing the KSE to allow us to complete some in-kernel +	 * work. When we complete, the Bound thread will have teh chance to  +	 * complete. This thread will sleep as planned. Hopefully there will +	 * eventually be un unbound thread that can be converted to an +	 * upcall to report the completion of this thread. +	 */ +	if (ke->ke_bound && ((ke->ke_bound->td_flags & TDF_UNBOUND) == 0)) { +		return (NULL); +	} +	KASSERT((ke->ke_bound == NULL), ("kse already bound")); + +	if ((td2 = td->td_standin) != NULL) { +		td->td_standin = NULL;  	} else { -		mtx_unlock_spin(&sched_lock); -		td2 = thread_alloc(); -		mtx_lock_spin(&sched_lock); +		if (newkse) +			panic("no reserve thread when called with a new kse"); +		/* +		 * If called from (e.g.) sleep and we do not have +		 * a reserve thread, then we've used it, so do not +		 * create an upcall. +		 */ +		return(NULL);  	}  	CTR3(KTR_PROC, "thread_schedule_upcall: thread %p (pid %d, %s)", -	     td, td->td_proc->p_pid, td->td_proc->p_comm); +	     td2, td->td_proc->p_pid, td->td_proc->p_comm);  	bzero(&td2->td_startzero,  	    (unsigned)RANGEOF(struct thread, td_startzero, td_endzero));  	bcopy(&td->td_startcopy, &td2->td_startcopy,  	    (unsigned) RANGEOF(struct thread, td_startcopy, td_endcopy));  	thread_link(td2, ke->ke_ksegrp);  	cpu_set_upcall(td2, td->td_pcb); + +	/* +	 * XXXKSE do we really need this? (default values for the +	 * frame). +	 */  	bcopy(td->td_frame, td2->td_frame, sizeof(struct trapframe)); +  	/* -	 * The user context for this thread is selected when we choose -	 * a KSE and return to userland on it. All we need do here is -	 * note that the thread exists in order to perform an upcall. -	 * -	 * Since selecting a KSE to perform the upcall involves locking -	 * that KSE's context to our upcall, its best to wait until the -	 * last possible moment before grabbing a KSE. We do this in -	 * userret(). +	 * Bind the new thread to the KSE, +	 * and if it's our KSE, lend it back to ourself +	 * so we can continue running.  	 */  	td2->td_ucred = crhold(td->td_ucred); -	td2->td_flags = TDF_UNBOUND|TDF_UPCALLING; -	TD_SET_CAN_RUN(td2); -	setrunqueue(td2); -	return (td2); +	td2->td_flags = TDF_UPCALLING; /* note: BOUND */ +	td2->td_kse = ke; +	td2->td_state = TDS_CAN_RUN; +	td2->td_inhibitors = 0; +	/* +	 * If called from msleep(), we are working on the current +	 * KSE so fake that we borrowed it. If called from +	 * kse_create(), don't, as we have a new kse too. +	 */ +	if (!newkse) { +		/* +		 * This thread will be scheduled when the current thread +		 * blocks, exits or tries to enter userspace, (which ever +		 * happens first). When that happens the KSe will "revert" +		 * to this thread in a BOUND manner. Since we are called +		 * from msleep() this is going to be "very soon" in nearly +		 * all cases. +		 */ +		ke->ke_bound = td2; +		TD_SET_LOAN(td2); +	} else { +		ke->ke_bound = NULL; +		ke->ke_thread = td2; +		setrunqueue(td2); +	} +	return (td2);	/* bogus.. should be a void function */  }  /* @@ -605,6 +676,7 @@ signal_upcall(struct proc *p, int sig)  	int error;  	PROC_LOCK_ASSERT(p, MA_OWNED); +return (NULL);  	td = FIRST_THREAD_IN_PROC(p);  	ke = td->td_kse; @@ -619,94 +691,15 @@ signal_upcall(struct proc *p, int sig)  	PROC_LOCK(p);  	if (error)  		return (NULL); +	if (td->td_standin == NULL) +		td->td_standin = thread_alloc();  	mtx_lock_spin(&sched_lock); -	td2 = thread_schedule_upcall(td, ke); +	td2 = thread_schedule_upcall(td, ke); /* Bogus JRE */  	mtx_unlock_spin(&sched_lock);  	return (td2);  }  /* - * Consider whether or not an upcall should be made, and update the - * TDF_UPCALLING flag appropriately. - * - * This function is called when the current thread had been bound to a user - * thread that performed a syscall that blocked, and is now returning. - * Got that? syscall -> msleep -> wakeup -> syscall_return -> us. - * - * This thread will be returned to the UTS in its mailbox as a completed - * thread.  We need to decide whether or not to perform an upcall now, - * or simply queue the thread for later. - * - * XXXKSE Future enhancement: We could also return back to - * the thread if we haven't had to do an upcall since then. - * If the KSE's copy is == the thread's copy, and there are - * no other completed threads. - */ -static int -thread_consider_upcalling(struct thread *td) -{ -	struct proc *p; -	struct ksegrp *kg; -	int error; - -	/* -	 * Save the thread's context, and link it -	 * into the KSEGRP's list of completed threads. -	 */ -	error = thread_export_context(td); -	td->td_flags &= ~TDF_UNBOUND; -	td->td_mailbox = NULL; -	if (error) -		/* -		 * Failing to do the KSE operation just defaults -		 * back to synchonous operation, so just return from -		 * the syscall. -		 */ -		return (error); - -	/* -	 * Decide whether to perform an upcall now. -	 */ -	/* Make sure there are no other threads waiting to run. */ -	p = td->td_proc; -	kg = td->td_ksegrp; -	PROC_LOCK(p); -	mtx_lock_spin(&sched_lock); -	/* bogus test, ok for testing though */ -	if (TAILQ_FIRST(&kg->kg_runq) &&  -	    (TAILQ_LAST(&kg->kg_runq, threadqueue)  -		!= kg->kg_last_assigned)) { -		/* -		 * Another thread in this KSEG needs to run. -		 * Switch to it instead of performing an upcall, -		 * abondoning this thread.  Perform the upcall -		 * later; discard this thread for now. -		 * -		 * XXXKSE - As for the other threads to run; -		 * we COULD rush through all the threads -		 * in this KSEG at this priority, or we -		 * could throw the ball back into the court -		 * and just run the highest prio kse available. -		 * What is OUR priority?  The priority of the highest -		 * sycall waiting to be returned? -		 * For now, just let another KSE run (easiest). -		 */ -		thread_exit(); /* Abandon current thread. */ -		/* NOTREACHED */ -	}  -	/* -	 * Perform an upcall now. -	 * -	 * XXXKSE - Assumes we are going to userland, and not -	 * nested in the kernel. -	 */ -	td->td_flags |= TDF_UPCALLING; -	mtx_unlock_spin(&sched_lock); -	PROC_UNLOCK(p); -	return (0); -} - -/*   * The extra work we go through if we are a threaded process when we   * return to userland.   * @@ -724,86 +717,188 @@ thread_userret(struct thread *td, struct trapframe *frame)  	int error;  	int unbound;  	struct kse *ke; +	struct ksegrp *kg; +	struct thread *td2; +	struct proc *p; -	if (td->td_kse->ke_bound) { -		thread_export_context(td); -		PROC_LOCK(td->td_proc); -		mtx_lock_spin(&sched_lock); -		thread_exit(); -	} +	error = 0; -	/* Make the thread bound from now on, but remember what it was. */  	unbound = td->td_flags & TDF_UNBOUND; -	td->td_flags &= ~TDF_UNBOUND; -	/* -	 * Ensure that we have a spare thread available. -	 */ -	ke = td->td_kse; -	if (ke->ke_tdspare == NULL) { -		mtx_lock(&Giant); -		ke->ke_tdspare = thread_alloc(); -		mtx_unlock(&Giant); -	} -	/* -	 * Originally bound threads need no additional work. -	 */ -	if (unbound == 0) -		return (0); -	error = 0; + +	kg = td->td_ksegrp; +	p = td->td_proc; +  	/* -	 * Decide whether or not we should perform an upcall now. +	 * Originally bound threads never upcall but they may  +	 * loan out their KSE at this point. +	 * Upcalls imply bound.. They also may want to do some Philantropy. +	 * Unbound threads on the other hand either yield to other work +	 * or transform into an upcall. +	 * (having saved their context to user space in both cases)  	 */ -	if (((td->td_flags & TDF_UPCALLING) == 0) && unbound) { -		/* if we have other threads to run we will not return */ -		if ((error = thread_consider_upcalling(td))) -			return (error); /* coundn't go async , just go sync. */ -	} -	if (td->td_flags & TDF_UPCALLING) { +	if (unbound ) {  		/* -		 * There is no more work to do and we are going to ride -		 * this thead/KSE up to userland as an upcall. +		 * We are an unbound thread, looking to return to  +		 * user space. +		 * THere are several possibilities: +		 * 1) we are using a borrowed KSE. save state and exit. +		 *    kse_reassign() will recycle the kse as needed, +		 * 2) we are not.. save state, and then convert ourself +		 *    to be an upcall, bound to the KSE. +		 *    if there are others that need the kse, +		 *    give them a chance by doing an mi_switch(). +		 *    Because we are bound, control will eventually return +		 *    to us here. +		 * *** +		 * Save the thread's context, and link it +		 * into the KSEGRP's list of completed threads.  		 */ -		CTR3(KTR_PROC, "userret: upcall thread %p (pid %d, %s)", -		    td, td->td_proc->p_pid, td->td_proc->p_comm); +		error = thread_export_context(td); +		td->td_mailbox = NULL; +		if (error) { +			/* +			 * If we are not running on a borrowed KSE, then +			 * failing to do the KSE operation just defaults +			 * back to synchonous operation, so just return from +			 * the syscall. If it IS borrowed, there is nothing +			 * we can do. We just lose that context. We +			 * probably should note this somewhere and send +			 * the process a signal. +			 */ +			PROC_LOCK(td->td_proc); +			psignal(td->td_proc, SIGSEGV); +			mtx_lock_spin(&sched_lock); +			if (td->td_kse->ke_bound == NULL) { +				td->td_flags &= ~TDF_UNBOUND; +				PROC_UNLOCK(td->td_proc); +				mtx_unlock_spin(&sched_lock); +				return (error);	/* go sync */ +			} +			thread_exit(); +		}  		/* -		 * Set user context to the UTS. +		 * if the KSE is owned and we are borrowing it, +		 * don't make an upcall, just exit so that the owner +		 * can get its KSE if it wants it. +		 * Our context is already safely stored for later +		 * use by the UTS.  		 */ -		cpu_set_upcall_kse(td, ke); - +		PROC_LOCK(p); +		mtx_lock_spin(&sched_lock); +		if (td->td_kse->ke_bound) { +			thread_exit(); +		} +		PROC_UNLOCK(p); +				  		/* -		 * Put any completed mailboxes on this KSE's list. +		 * Turn ourself into a bound upcall. +		 * We will rely on kse_reassign() +		 * to make us run at a later time. +		 * We should look just like a sheduled upcall +		 * from msleep() or cv_wait().  		 */ -		error = thread_link_mboxes(td->td_ksegrp, ke); -		if (error) -			goto bad; +		td->td_flags &= ~TDF_UNBOUND; +		td->td_flags |= TDF_UPCALLING; +		/* Only get here if we have become an upcall */ -		/* -		 * Set state and mailbox. +	} else { +		mtx_lock_spin(&sched_lock); +	} +	/*  +	 * We ARE going back to userland with this KSE. +	 * Check for threads that need to borrow it. +	 * Optimisation: don't call mi_switch if no-one wants the KSE. +	 * Any other thread that comes ready after this missed the boat. +	 */ +	ke = td->td_kse; +	if ((td2 = kg->kg_last_assigned))  +		td2 = TAILQ_NEXT(td2, td_runq); +	else +		td2 = TAILQ_FIRST(&kg->kg_runq); +	if (td2)  { +		/*  +		 * force a switch to more urgent 'in kernel' +		 * work. Control will return to this thread +		 * when there is no more work to do. +		 * kse_reassign() will do tha for us.  		 */ -		td->td_flags &= ~TDF_UPCALLING; -#if 0 -		error = suword((caddr_t)ke->ke_mailbox + -		    offsetof(struct kse_mailbox, km_curthread), -		    0); -#else	/* if user pointer arithmetic is ok in the kernel */ -		error = suword((caddr_t)&ke->ke_mailbox->km_curthread, 0); -#endif -		if (error) -			goto bad; +		TD_SET_LOAN(td); +		ke->ke_bound = td; +		ke->ke_thread = NULL; +		mi_switch(); /* kse_reassign() will (re)find td2 */ +	} +	mtx_unlock_spin(&sched_lock); + +	/* +	 * Optimisation: +	 * Ensure that we have a spare thread available, +	 * for when we re-enter the kernel. +	 */ +	if (td->td_standin == NULL) { +		if (ke->ke_tdspare) { +			td->td_standin = ke->ke_tdspare; +			ke->ke_tdspare = NULL; +		} else { +			td->td_standin = thread_alloc(); +		}  	} + +	/*  +	 * To get here, we know there is no other need for our +	 * KSE so we can proceed. If not upcalling, go back to  +	 * userspace. If we are, get the upcall set up. +	 */ +	if ((td->td_flags & TDF_UPCALLING) == 0) +		return (0); + +	/*  +	 * We must be an upcall to get this far. +	 * There is no more work to do and we are going to ride +	 * this thead/KSE up to userland as an upcall. +	 * Do the last parts of the setup needed for the upcall. +	 */ +	CTR3(KTR_PROC, "userret: upcall thread %p (pid %d, %s)", +	    td, td->td_proc->p_pid, td->td_proc->p_comm); + +	/* +	 * Set user context to the UTS. +	 */ +	cpu_set_upcall_kse(td, ke); +  	/* -	 * Stop any chance that we may be separated from -	 * the KSE we are currently on. This is "biting the bullet", -	 * we are committing to go to user space as as this KSE here. +	 * Put any completed mailboxes on this KSE's list.  	 */ -	return (error); +	error = thread_link_mboxes(kg, ke); +	if (error) +		goto bad; + +	/* +	 * Set state and mailbox. +	 * From now on we are just a bound outgoing process. +	 * **Problem** userret is often called several times. +	 * it would be nice if this all happenned only on the first time  +	 * through. (the scan for extra work etc.) +	 */ +	td->td_flags &= ~TDF_UPCALLING; +#if 0 +	error = suword((caddr_t)ke->ke_mailbox + +	    offsetof(struct kse_mailbox, km_curthread), 0); +#else	/* if user pointer arithmetic is ok in the kernel */ +	error = suword((caddr_t)&ke->ke_mailbox->km_curthread, 0); +#endif +	if (!error) +		return (0); +  bad:  	/*  	 * Things are going to be so screwed we should just kill the process.   	 * how do we do that?  	 */ -	 panic ("thread_userret.. need to kill proc..... how?"); +	PROC_LOCK(td->td_proc); +	psignal(td->td_proc, SIGSEGV); +	PROC_UNLOCK(td->td_proc); +	return (error);	/* go sync */  }  /*  | 
