xref: /openbsd-src/sys/kern/kern_sched.c (revision e9e52ee3261aced75061b593a900c7b645509a31)
1 /*	$OpenBSD: kern_sched.c,v 1.5 2008/06/11 12:35:46 deraadt Exp $	*/
2 /*
3  * Copyright (c) 2007 Artur Grabowski <art@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <sys/param.h>
19 
20 #include <sys/sched.h>
21 #include <sys/proc.h>
22 #include <sys/kthread.h>
23 #include <sys/systm.h>
24 #include <sys/resourcevar.h>
25 #include <sys/signalvar.h>
26 #include <sys/mutex.h>
27 
28 #include <uvm/uvm_extern.h>
29 
30 void sched_kthreads_create(void *);
31 void sched_idle(void *);
32 
33 /*
34  * A few notes about cpu_switchto that is implemented in MD code.
35  *
36  * cpu_switchto takes two arguments, the old proc and the proc
37  * it should switch to. The new proc will never be NULL, so we always have
38  * a saved state that we need to switch to. The old proc however can
39  * be NULL if the process is exiting. NULL for the old proc simply
40  * means "don't bother saving old state".
41  *
42  * cpu_switchto is supposed to atomically load the new state of the process
43  * including the pcb, pmap and setting curproc, the p_cpu pointer in the
44  * proc and p_stat to SONPROC. Atomically with respect to interrupts, other
45  * cpus in the system must not depend on this state being consistent.
46  * Therefore no locking is necessary in cpu_switchto other than blocking
47  * interrupts during the context switch.
48  */
49 
50 /*
51  * sched_init_cpu is called from main() for the boot cpu, then it's the
52  * responsibility of the MD code to call it for all other cpus.
53  */
54 void
55 sched_init_cpu(struct cpu_info *ci)
56 {
57 	struct schedstate_percpu *spc = &ci->ci_schedstate;
58 
59 	spc->spc_idleproc = NULL;
60 
61 	kthread_create_deferred(sched_kthreads_create, ci);
62 
63 	LIST_INIT(&spc->spc_deadproc);
64 }
65 
66 void
67 sched_kthreads_create(void *v)
68 {
69 	struct cpu_info *ci = v;
70 	struct schedstate_percpu *spc = &ci->ci_schedstate;
71 	static int num;
72 
73 	if (kthread_create(sched_idle, ci, &spc->spc_idleproc, "idle%d", num))
74 		panic("fork idle");
75 
76 	num++;
77 }
78 
79 void
80 sched_idle(void *v)
81 {
82 	struct proc *p = curproc;
83 	struct cpu_info *ci = v;
84 	int s;
85 
86 	KERNEL_PROC_UNLOCK(p);
87 
88 	/*
89 	 * First time we enter here, we're not supposed to idle,
90 	 * just go away for a while.
91 	 */
92 	SCHED_LOCK(s);
93 	p->p_stat = SSLEEP;
94 	mi_switch();
95 	SCHED_UNLOCK(s);
96 
97 	while (1) {
98 		KASSERT(ci == curcpu());
99 		KASSERT(curproc == ci->ci_schedstate.spc_idleproc);
100 
101 		while (!sched_is_idle()) {
102 			struct schedstate_percpu *spc = &ci->ci_schedstate;
103 			struct proc *dead;
104 
105 			SCHED_LOCK(s);
106 			p->p_stat = SSLEEP;
107 			mi_switch();
108 			SCHED_UNLOCK(s);
109 
110 			while ((dead = LIST_FIRST(&spc->spc_deadproc))) {
111 				LIST_REMOVE(dead, p_hash);
112 				exit2(dead);
113 			}
114 		}
115 
116 		cpu_idle_enter();
117 		while (sched_is_idle())
118 			cpu_idle_cycle();
119 		cpu_idle_leave();
120 	}
121 }
122 
123 /*
124  * To free our address space we have to jump through a few hoops.
125  * The freeing is done by the reaper, but until we have one reaper
126  * per cpu, we have no way of putting this proc on the deadproc list
127  * and waking up the reaper without risking having our address space and
128  * stack torn from under us before we manage to switch to another proc.
129  * Therefore we have a per-cpu list of dead processes where we put this
130  * proc and have idle clean up that list and move it to the reaper list.
131  * All this will be unnecessary once we can bind the reaper this cpu
132  * and not risk having it switch to another in case it sleeps.
133  */
134 void
135 sched_exit(struct proc *p)
136 {
137 	struct schedstate_percpu *spc = &curcpu()->ci_schedstate;
138 	struct timeval tv;
139 	struct proc *idle;
140 	int s;
141 
142 	microuptime(&tv);
143 	timersub(&tv, &spc->spc_runtime, &tv);
144 	timeradd(&p->p_rtime, &tv, &p->p_rtime);
145 
146 	LIST_INSERT_HEAD(&spc->spc_deadproc, p, p_hash);
147 
148 #ifdef MULTIPROCESSOR
149 	KASSERT(__mp_lock_held(&kernel_lock) == 0);
150 #endif
151 
152 	SCHED_LOCK(s);
153 	idle = spc->spc_idleproc;
154 	idle->p_stat = SRUN;
155 	cpu_switchto(NULL, idle);
156 }
157 
158 /*
159  * Run queue management.
160  *
161  * The run queue management is just like before, except that it's with
162  * a bit more modern queue handling.
163  */
164 
165 TAILQ_HEAD(prochead, proc) sched_qs[NQS];
166 volatile int sched_whichqs;
167 
168 void
169 sched_init_runqueues(void)
170 {
171 	int i;
172 
173 	for (i = 0; i < NQS; i++)
174 		TAILQ_INIT(&sched_qs[i]);
175 
176 #ifdef MULTIPROCESSOR
177 	__mp_lock_init(&sched_lock);
178 #endif
179 }
180 
181 void
182 setrunqueue(struct proc *p)
183 {
184 	int queue = p->p_priority >> 2;
185 
186 	SCHED_ASSERT_LOCKED();
187 
188 	TAILQ_INSERT_TAIL(&sched_qs[queue], p, p_runq);
189 	sched_whichqs |= (1 << queue);
190 }
191 
192 void
193 remrunqueue(struct proc *p)
194 {
195 	int queue = p->p_priority >> 2;
196 
197 	SCHED_ASSERT_LOCKED();
198 
199 	TAILQ_REMOVE(&sched_qs[queue], p, p_runq);
200 	if (TAILQ_EMPTY(&sched_qs[queue]))
201 		sched_whichqs &= ~(1 << queue);
202 }
203 
204 struct proc *
205 sched_chooseproc(void)
206 {
207 	struct proc *p;
208 	int queue;
209 
210 	SCHED_ASSERT_LOCKED();
211 
212 again:
213 	if (sched_is_idle()) {
214 		p = curcpu()->ci_schedstate.spc_idleproc;
215 		if (p == NULL) {
216                         int s;
217 			/*
218 			 * We get here if someone decides to switch during
219 			 * boot before forking kthreads, bleh.
220 			 * This is kind of like a stupid idle loop.
221 			 */
222 #ifdef MULTIPROCESSOR
223 			__mp_unlock(&sched_lock);
224 #endif
225 			spl0();
226 			delay(10);
227 			SCHED_LOCK(s);
228 			goto again;
229                 }
230 		KASSERT(p);
231 		p->p_stat = SRUN;
232 	} else {
233 		queue = ffs(sched_whichqs) - 1;
234 		p = TAILQ_FIRST(&sched_qs[queue]);
235 		TAILQ_REMOVE(&sched_qs[queue], p, p_runq);
236 		if (TAILQ_EMPTY(&sched_qs[queue]))
237 			sched_whichqs &= ~(1 << queue);
238 	}
239 
240 	return (p);
241 }
242