1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "primpl.h"
7 
8 _PRCPU *_pr_primordialCPU = NULL;
9 
10 PRInt32 _pr_md_idle_cpus;       /* number of idle cpus */
11 /*
12  * The idle threads in MxN models increment/decrement _pr_md_idle_cpus.
13  * If _PR_HAVE_ATOMIC_OPS is not defined, they can't use the atomic
14  * increment/decrement routines (which are based on PR_Lock/PR_Unlock),
15  * because PR_Lock asserts that the calling thread is not an idle thread.
16  * So we use a _MDLock to protect _pr_md_idle_cpus.
17  */
18 #if !defined(_PR_LOCAL_THREADS_ONLY) && !defined(_PR_GLOBAL_THREADS_ONLY)
19 #ifndef _PR_HAVE_ATOMIC_OPS
20 static _MDLock _pr_md_idle_cpus_lock;
21 #endif
22 #endif
23 PRUintn _pr_numCPU;
24 PRInt32 _pr_cpus_exit;
25 PRUint32 _pr_cpu_affinity_mask = 0;
26 
27 #if !defined (_PR_GLOBAL_THREADS_ONLY)
28 
29 static PRUintn _pr_cpuID;
30 
31 static void PR_CALLBACK _PR_CPU_Idle(void *);
32 
33 static _PRCPU *_PR_CreateCPU(void);
34 static PRStatus _PR_StartCPU(_PRCPU *cpu, PRThread *thread);
35 
36 #if !defined(_PR_LOCAL_THREADS_ONLY)
37 static void _PR_RunCPU(void *arg);
38 #endif
39 
_PR_InitCPUs()40 void  _PR_InitCPUs()
41 {
42     PRThread *me = _PR_MD_CURRENT_THREAD();
43 
44     if (_native_threads_only) {
45         return;
46     }
47 
48     _pr_cpuID = 0;
49     _MD_NEW_LOCK( &_pr_cpuLock);
50 #if !defined(_PR_LOCAL_THREADS_ONLY) && !defined(_PR_GLOBAL_THREADS_ONLY)
51 #ifndef _PR_HAVE_ATOMIC_OPS
52     _MD_NEW_LOCK(&_pr_md_idle_cpus_lock);
53 #endif
54 #endif
55 
56 #ifdef _PR_LOCAL_THREADS_ONLY
57 
58 #ifdef HAVE_CUSTOM_USER_THREADS
59     _PR_MD_CREATE_PRIMORDIAL_USER_THREAD(me);
60 #endif
61 
62     /* Now start the first CPU. */
63     _pr_primordialCPU = _PR_CreateCPU();
64     _pr_numCPU = 1;
65     _PR_StartCPU(_pr_primordialCPU, me);
66 
67     _PR_MD_SET_CURRENT_CPU(_pr_primordialCPU);
68 
69     /* Initialize cpu for current thread (could be different from me) */
70     _PR_MD_CURRENT_THREAD()->cpu = _pr_primordialCPU;
71 
72     _PR_MD_SET_LAST_THREAD(me);
73 
74 #else /* Combined MxN model */
75 
76     _pr_primordialCPU = _PR_CreateCPU();
77     _pr_numCPU = 1;
78     _PR_CreateThread(PR_SYSTEM_THREAD,
79                      _PR_RunCPU,
80                      _pr_primordialCPU,
81                      PR_PRIORITY_NORMAL,
82                      PR_GLOBAL_THREAD,
83                      PR_UNJOINABLE_THREAD,
84                      0,
85                      _PR_IDLE_THREAD);
86 
87 #endif /* _PR_LOCAL_THREADS_ONLY */
88 
89     _PR_MD_INIT_CPUS();
90 }
91 
92 #ifdef WINNT
93 /*
94  * Right now this function merely stops the CPUs and does
95  * not do any other cleanup.
96  *
97  * It is only implemented for WINNT because bug 161998 only
98  * affects the WINNT version of NSPR, but it would be nice
99  * to implement this function for other platforms too.
100  */
_PR_CleanupCPUs(void)101 void _PR_CleanupCPUs(void)
102 {
103     PRUintn i;
104     PRCList *qp;
105     _PRCPU *cpu;
106 
107     _pr_cpus_exit = 1;
108     for (i = 0; i < _pr_numCPU; i++) {
109         _PR_MD_WAKEUP_WAITER(NULL);
110     }
111     for (qp = _PR_CPUQ().next; qp != &_PR_CPUQ(); qp = qp->next) {
112         cpu = _PR_CPU_PTR(qp);
113         _PR_MD_JOIN_THREAD(&cpu->thread->md);
114     }
115 }
116 #endif
117 
_PR_CreateCPUQueue(void)118 static _PRCPUQueue *_PR_CreateCPUQueue(void)
119 {
120     PRInt32 index;
121     _PRCPUQueue *cpuQueue;
122     cpuQueue = PR_NEWZAP(_PRCPUQueue);
123 
124     _MD_NEW_LOCK( &cpuQueue->runQLock );
125     _MD_NEW_LOCK( &cpuQueue->sleepQLock );
126     _MD_NEW_LOCK( &cpuQueue->miscQLock );
127 
128     for (index = 0; index < PR_ARRAY_SIZE(cpuQueue->runQ); index++) {
129         PR_INIT_CLIST( &(cpuQueue->runQ[index]) );
130     }
131     PR_INIT_CLIST( &(cpuQueue->sleepQ) );
132     PR_INIT_CLIST( &(cpuQueue->pauseQ) );
133     PR_INIT_CLIST( &(cpuQueue->suspendQ) );
134     PR_INIT_CLIST( &(cpuQueue->waitingToJoinQ) );
135 
136     cpuQueue->numCPUs = 1;
137 
138     return cpuQueue;
139 }
140 
141 /*
142  * Create a new CPU.
143  *
144  * This function initializes enough of the _PRCPU structure so
145  * that it can be accessed safely by a global thread or another
146  * CPU.  This function does not create the native thread that
147  * will run the CPU nor does it initialize the parts of _PRCPU
148  * that must be initialized by that native thread.
149  *
150  * The reason we cannot simply have the native thread create
151  * and fully initialize a new CPU is that we need to be able to
152  * create a usable _pr_primordialCPU in _PR_InitCPUs without
153  * assuming that the primordial CPU thread we created can run
154  * during NSPR initialization.  For example, on Windows while
155  * new threads can be created by DllMain, they won't be able
156  * to run during DLL initialization.  If NSPR is initialized
157  * by DllMain, the primordial CPU thread won't run until DLL
158  * initialization is finished.
159  */
_PR_CreateCPU(void)160 static _PRCPU *_PR_CreateCPU(void)
161 {
162     _PRCPU *cpu;
163 
164     cpu = PR_NEWZAP(_PRCPU);
165     if (cpu) {
166         cpu->queue = _PR_CreateCPUQueue();
167         if (!cpu->queue) {
168             PR_DELETE(cpu);
169             return NULL;
170         }
171     }
172     return cpu;
173 }
174 
175 /*
176  * Start a new CPU.
177  *
178  * 'cpu' is a _PRCPU structure created by _PR_CreateCPU().
179  * 'thread' is the native thread that will run the CPU.
180  *
181  * If this function fails, 'cpu' is destroyed.
182  */
_PR_StartCPU(_PRCPU * cpu,PRThread * thread)183 static PRStatus _PR_StartCPU(_PRCPU *cpu, PRThread *thread)
184 {
185     /*
186     ** Start a new cpu. The assumption this code makes is that the
187     ** underlying operating system creates a stack to go with the new
188     ** native thread. That stack will be used by the cpu when pausing.
189     */
190 
191     PR_ASSERT(!_native_threads_only);
192 
193     cpu->last_clock = PR_IntervalNow();
194 
195     /* Before we create any threads on this CPU we have to
196      * set the current CPU
197      */
198     _PR_MD_SET_CURRENT_CPU(cpu);
199     _PR_MD_INIT_RUNNING_CPU(cpu);
200     thread->cpu = cpu;
201 
202     cpu->idle_thread = _PR_CreateThread(PR_SYSTEM_THREAD,
203                                         _PR_CPU_Idle,
204                                         (void *)cpu,
205                                         PR_PRIORITY_NORMAL,
206                                         PR_LOCAL_THREAD,
207                                         PR_UNJOINABLE_THREAD,
208                                         0,
209                                         _PR_IDLE_THREAD);
210 
211     if (!cpu->idle_thread) {
212         /* didn't clean up CPU queue XXXMB */
213         PR_DELETE(cpu);
214         return PR_FAILURE;
215     }
216     PR_ASSERT(cpu->idle_thread->cpu == cpu);
217 
218     cpu->idle_thread->no_sched = 0;
219 
220     cpu->thread = thread;
221 
222     if (_pr_cpu_affinity_mask) {
223         PR_SetThreadAffinityMask(thread, _pr_cpu_affinity_mask);
224     }
225 
226     /* Created and started a new CPU */
227     _PR_CPU_LIST_LOCK();
228     cpu->id = _pr_cpuID++;
229     PR_APPEND_LINK(&cpu->links, &_PR_CPUQ());
230     _PR_CPU_LIST_UNLOCK();
231 
232     return PR_SUCCESS;
233 }
234 
235 #if !defined(_PR_GLOBAL_THREADS_ONLY) && !defined(_PR_LOCAL_THREADS_ONLY)
236 /*
237 ** This code is used during a cpu's initial creation.
238 */
_PR_RunCPU(void * arg)239 static void _PR_RunCPU(void *arg)
240 {
241     _PRCPU *cpu = (_PRCPU *)arg;
242     PRThread *me = _PR_MD_CURRENT_THREAD();
243 
244     PR_ASSERT(NULL != me);
245 
246     /*
247      * _PR_StartCPU calls _PR_CreateThread to create the
248      * idle thread.  Because _PR_CreateThread calls PR_Lock,
249      * the current thread has to remain a global thread
250      * during the _PR_StartCPU call so that it can wait for
251      * the lock if the lock is held by another thread.  If
252      * we clear the _PR_GLOBAL_SCOPE flag in
253      * _PR_MD_CREATE_PRIMORDIAL_THREAD, the current thread
254      * will be treated as a local thread and have trouble
255      * waiting for the lock because the CPU is not fully
256      * constructed yet.
257      *
258      * After the CPU is started, it is safe to mark the
259      * current thread as a local thread.
260      */
261 
262 #ifdef HAVE_CUSTOM_USER_THREADS
263     _PR_MD_CREATE_PRIMORDIAL_USER_THREAD(me);
264 #endif
265 
266     me->no_sched = 1;
267     _PR_StartCPU(cpu, me);
268 
269 #ifdef HAVE_CUSTOM_USER_THREADS
270     me->flags &= (~_PR_GLOBAL_SCOPE);
271 #endif
272 
273     _PR_MD_SET_CURRENT_CPU(cpu);
274     _PR_MD_SET_CURRENT_THREAD(cpu->thread);
275     me->cpu = cpu;
276 
277     while(1) {
278         PRInt32 is;
279         if (!_PR_IS_NATIVE_THREAD(me)) {
280             _PR_INTSOFF(is);
281         }
282         _PR_MD_START_INTERRUPTS();
283         _PR_MD_SWITCH_CONTEXT(me);
284     }
285 }
286 #endif
287 
_PR_CPU_Idle(void * _cpu)288 static void PR_CALLBACK _PR_CPU_Idle(void *_cpu)
289 {
290     _PRCPU *cpu = (_PRCPU *)_cpu;
291     PRThread *me = _PR_MD_CURRENT_THREAD();
292 
293     PR_ASSERT(NULL != me);
294 
295     me->cpu = cpu;
296     cpu->idle_thread = me;
297     if (_MD_LAST_THREAD()) {
298         _MD_LAST_THREAD()->no_sched = 0;
299     }
300     if (!_PR_IS_NATIVE_THREAD(me)) {
301         _PR_MD_SET_INTSOFF(0);
302     }
303     while(1) {
304         PRInt32 is;
305         PRIntervalTime timeout;
306         if (!_PR_IS_NATIVE_THREAD(me)) {
307             _PR_INTSOFF(is);
308         }
309 
310         _PR_RUNQ_LOCK(cpu);
311 #if !defined(_PR_LOCAL_THREADS_ONLY) && !defined(_PR_GLOBAL_THREADS_ONLY)
312 #ifdef _PR_HAVE_ATOMIC_OPS
313         _PR_MD_ATOMIC_INCREMENT(&_pr_md_idle_cpus);
314 #else
315         _PR_MD_LOCK(&_pr_md_idle_cpus_lock);
316         _pr_md_idle_cpus++;
317         _PR_MD_UNLOCK(&_pr_md_idle_cpus_lock);
318 #endif /* _PR_HAVE_ATOMIC_OPS */
319 #endif
320         /* If someone on runq; do a nonblocking PAUSECPU */
321         if (_PR_RUNQREADYMASK(me->cpu) != 0) {
322             _PR_RUNQ_UNLOCK(cpu);
323             timeout = PR_INTERVAL_NO_WAIT;
324         } else {
325             _PR_RUNQ_UNLOCK(cpu);
326 
327             _PR_SLEEPQ_LOCK(cpu);
328             if (PR_CLIST_IS_EMPTY(&_PR_SLEEPQ(me->cpu))) {
329                 timeout = PR_INTERVAL_NO_TIMEOUT;
330             } else {
331                 PRThread *wakeThread;
332                 wakeThread = _PR_THREAD_PTR(_PR_SLEEPQ(me->cpu).next);
333                 timeout = wakeThread->sleep;
334             }
335             _PR_SLEEPQ_UNLOCK(cpu);
336         }
337 
338         /* Wait for an IO to complete */
339         (void)_PR_MD_PAUSE_CPU(timeout);
340 
341 #ifdef WINNT
342         if (_pr_cpus_exit) {
343             /* _PR_CleanupCPUs tells us to exit */
344             _PR_MD_END_THREAD();
345         }
346 #endif
347 
348 #if !defined(_PR_LOCAL_THREADS_ONLY) && !defined(_PR_GLOBAL_THREADS_ONLY)
349 #ifdef _PR_HAVE_ATOMIC_OPS
350         _PR_MD_ATOMIC_DECREMENT(&_pr_md_idle_cpus);
351 #else
352         _PR_MD_LOCK(&_pr_md_idle_cpus_lock);
353         _pr_md_idle_cpus--;
354         _PR_MD_UNLOCK(&_pr_md_idle_cpus_lock);
355 #endif /* _PR_HAVE_ATOMIC_OPS */
356 #endif
357 
358         _PR_ClockInterrupt();
359 
360         /* Now schedule any thread that is on the runq
361          * INTS must be OFF when calling PR_Schedule()
362          */
363         me->state = _PR_RUNNABLE;
364         _PR_MD_SWITCH_CONTEXT(me);
365         if (!_PR_IS_NATIVE_THREAD(me)) {
366             _PR_FAST_INTSON(is);
367         }
368     }
369 }
370 #endif /* _PR_GLOBAL_THREADS_ONLY */
371 
PR_SetConcurrency(PRUintn numCPUs)372 PR_IMPLEMENT(void) PR_SetConcurrency(PRUintn numCPUs)
373 {
374 #if defined(_PR_GLOBAL_THREADS_ONLY) || defined(_PR_LOCAL_THREADS_ONLY)
375 
376     /* do nothing */
377 
378 #else /* combined, MxN thread model */
379 
380     PRUintn newCPU;
381     _PRCPU *cpu;
382     PRThread *thr;
383 
384 
385     if (!_pr_initialized) {
386         _PR_ImplicitInitialization();
387     }
388 
389     if (_native_threads_only) {
390         return;
391     }
392 
393     _PR_CPU_LIST_LOCK();
394     if (_pr_numCPU < numCPUs) {
395         newCPU = numCPUs - _pr_numCPU;
396         _pr_numCPU = numCPUs;
397     } else {
398         newCPU = 0;
399     }
400     _PR_CPU_LIST_UNLOCK();
401 
402     for (; newCPU; newCPU--) {
403         cpu = _PR_CreateCPU();
404         thr = _PR_CreateThread(PR_SYSTEM_THREAD,
405                                _PR_RunCPU,
406                                cpu,
407                                PR_PRIORITY_NORMAL,
408                                PR_GLOBAL_THREAD,
409                                PR_UNJOINABLE_THREAD,
410                                0,
411                                _PR_IDLE_THREAD);
412     }
413 #endif
414 }
415 
_PR_GetPrimordialCPU(void)416 PR_IMPLEMENT(_PRCPU *) _PR_GetPrimordialCPU(void)
417 {
418     if (_pr_primordialCPU) {
419         return _pr_primordialCPU;
420     }
421     else {
422         return _PR_MD_CURRENT_CPU();
423     }
424 }
425