andre@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ andre@0: /* This Source Code Form is subject to the terms of the Mozilla Public andre@0: * License, v. 2.0. If a copy of the MPL was not distributed with this andre@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ andre@0: andre@0: #include "primpl.h" andre@0: andre@0: _PRCPU *_pr_primordialCPU = NULL; andre@0: andre@0: PRInt32 _pr_md_idle_cpus; /* number of idle cpus */ andre@0: /* andre@0: * The idle threads in MxN models increment/decrement _pr_md_idle_cpus. andre@0: * If _PR_HAVE_ATOMIC_OPS is not defined, they can't use the atomic andre@0: * increment/decrement routines (which are based on PR_Lock/PR_Unlock), andre@0: * because PR_Lock asserts that the calling thread is not an idle thread. andre@0: * So we use a _MDLock to protect _pr_md_idle_cpus. andre@0: */ andre@0: #if !defined(_PR_LOCAL_THREADS_ONLY) && !defined(_PR_GLOBAL_THREADS_ONLY) andre@0: #ifndef _PR_HAVE_ATOMIC_OPS andre@0: static _MDLock _pr_md_idle_cpus_lock; andre@0: #endif andre@0: #endif andre@0: PRUintn _pr_numCPU; andre@0: PRInt32 _pr_cpus_exit; andre@0: PRUint32 _pr_cpu_affinity_mask = 0; andre@0: andre@0: #if !defined (_PR_GLOBAL_THREADS_ONLY) andre@0: andre@0: static PRUintn _pr_cpuID; andre@0: andre@0: static void PR_CALLBACK _PR_CPU_Idle(void *); andre@0: andre@0: static _PRCPU *_PR_CreateCPU(void); andre@0: static PRStatus _PR_StartCPU(_PRCPU *cpu, PRThread *thread); andre@0: andre@0: #if !defined(_PR_LOCAL_THREADS_ONLY) andre@0: static void _PR_RunCPU(void *arg); andre@0: #endif andre@0: andre@0: void _PR_InitCPUs() andre@0: { andre@0: PRThread *me = _PR_MD_CURRENT_THREAD(); andre@0: andre@0: if (_native_threads_only) andre@0: return; andre@0: andre@0: _pr_cpuID = 0; andre@0: _MD_NEW_LOCK( &_pr_cpuLock); andre@0: #if !defined(_PR_LOCAL_THREADS_ONLY) && !defined(_PR_GLOBAL_THREADS_ONLY) andre@0: #ifndef _PR_HAVE_ATOMIC_OPS andre@0: _MD_NEW_LOCK(&_pr_md_idle_cpus_lock); andre@0: #endif andre@0: #endif andre@0: andre@0: #ifdef _PR_LOCAL_THREADS_ONLY andre@0: andre@0: #ifdef HAVE_CUSTOM_USER_THREADS andre@0: _PR_MD_CREATE_PRIMORDIAL_USER_THREAD(me); andre@0: #endif andre@0: andre@0: /* Now start the first CPU. */ andre@0: _pr_primordialCPU = _PR_CreateCPU(); andre@0: _pr_numCPU = 1; andre@0: _PR_StartCPU(_pr_primordialCPU, me); andre@0: andre@0: _PR_MD_SET_CURRENT_CPU(_pr_primordialCPU); andre@0: andre@0: /* Initialize cpu for current thread (could be different from me) */ andre@0: _PR_MD_CURRENT_THREAD()->cpu = _pr_primordialCPU; andre@0: andre@0: _PR_MD_SET_LAST_THREAD(me); andre@0: andre@0: #else /* Combined MxN model */ andre@0: andre@0: _pr_primordialCPU = _PR_CreateCPU(); andre@0: _pr_numCPU = 1; andre@0: _PR_CreateThread(PR_SYSTEM_THREAD, andre@0: _PR_RunCPU, andre@0: _pr_primordialCPU, andre@0: PR_PRIORITY_NORMAL, andre@0: PR_GLOBAL_THREAD, andre@0: PR_UNJOINABLE_THREAD, andre@0: 0, andre@0: _PR_IDLE_THREAD); andre@0: andre@0: #endif /* _PR_LOCAL_THREADS_ONLY */ andre@0: andre@0: _PR_MD_INIT_CPUS(); andre@0: } andre@0: andre@0: #ifdef WINNT andre@0: /* andre@0: * Right now this function merely stops the CPUs and does andre@0: * not do any other cleanup. andre@0: * andre@0: * It is only implemented for WINNT because bug 161998 only andre@0: * affects the WINNT version of NSPR, but it would be nice andre@0: * to implement this function for other platforms too. andre@0: */ andre@0: void _PR_CleanupCPUs(void) andre@0: { andre@0: PRUintn i; andre@0: PRCList *qp; andre@0: _PRCPU *cpu; andre@0: andre@0: _pr_cpus_exit = 1; andre@0: for (i = 0; i < _pr_numCPU; i++) { andre@0: _PR_MD_WAKEUP_WAITER(NULL); andre@0: } andre@0: for (qp = _PR_CPUQ().next; qp != &_PR_CPUQ(); qp = qp->next) { andre@0: cpu = _PR_CPU_PTR(qp); andre@0: _PR_MD_JOIN_THREAD(&cpu->thread->md); andre@0: } andre@0: } andre@0: #endif andre@0: andre@0: static _PRCPUQueue *_PR_CreateCPUQueue(void) andre@0: { andre@0: PRInt32 index; andre@0: _PRCPUQueue *cpuQueue; andre@0: cpuQueue = PR_NEWZAP(_PRCPUQueue); andre@0: andre@0: _MD_NEW_LOCK( &cpuQueue->runQLock ); andre@0: _MD_NEW_LOCK( &cpuQueue->sleepQLock ); andre@0: _MD_NEW_LOCK( &cpuQueue->miscQLock ); andre@0: andre@0: for (index = 0; index < PR_PRIORITY_LAST + 1; index++) andre@0: PR_INIT_CLIST( &(cpuQueue->runQ[index]) ); andre@0: PR_INIT_CLIST( &(cpuQueue->sleepQ) ); andre@0: PR_INIT_CLIST( &(cpuQueue->pauseQ) ); andre@0: PR_INIT_CLIST( &(cpuQueue->suspendQ) ); andre@0: PR_INIT_CLIST( &(cpuQueue->waitingToJoinQ) ); andre@0: andre@0: cpuQueue->numCPUs = 1; andre@0: andre@0: return cpuQueue; andre@0: } andre@0: andre@0: /* andre@0: * Create a new CPU. andre@0: * andre@0: * This function initializes enough of the _PRCPU structure so andre@0: * that it can be accessed safely by a global thread or another andre@0: * CPU. This function does not create the native thread that andre@0: * will run the CPU nor does it initialize the parts of _PRCPU andre@0: * that must be initialized by that native thread. andre@0: * andre@0: * The reason we cannot simply have the native thread create andre@0: * and fully initialize a new CPU is that we need to be able to andre@0: * create a usable _pr_primordialCPU in _PR_InitCPUs without andre@0: * assuming that the primordial CPU thread we created can run andre@0: * during NSPR initialization. For example, on Windows while andre@0: * new threads can be created by DllMain, they won't be able andre@0: * to run during DLL initialization. If NSPR is initialized andre@0: * by DllMain, the primordial CPU thread won't run until DLL andre@0: * initialization is finished. andre@0: */ andre@0: static _PRCPU *_PR_CreateCPU(void) andre@0: { andre@0: _PRCPU *cpu; andre@0: andre@0: cpu = PR_NEWZAP(_PRCPU); andre@0: if (cpu) { andre@0: cpu->queue = _PR_CreateCPUQueue(); andre@0: if (!cpu->queue) { andre@0: PR_DELETE(cpu); andre@0: return NULL; andre@0: } andre@0: } andre@0: return cpu; andre@0: } andre@0: andre@0: /* andre@0: * Start a new CPU. andre@0: * andre@0: * 'cpu' is a _PRCPU structure created by _PR_CreateCPU(). andre@0: * 'thread' is the native thread that will run the CPU. andre@0: * andre@0: * If this function fails, 'cpu' is destroyed. andre@0: */ andre@0: static PRStatus _PR_StartCPU(_PRCPU *cpu, PRThread *thread) andre@0: { andre@0: /* andre@0: ** Start a new cpu. The assumption this code makes is that the andre@0: ** underlying operating system creates a stack to go with the new andre@0: ** native thread. That stack will be used by the cpu when pausing. andre@0: */ andre@0: andre@0: PR_ASSERT(!_native_threads_only); andre@0: andre@0: cpu->last_clock = PR_IntervalNow(); andre@0: andre@0: /* Before we create any threads on this CPU we have to andre@0: * set the current CPU andre@0: */ andre@0: _PR_MD_SET_CURRENT_CPU(cpu); andre@0: _PR_MD_INIT_RUNNING_CPU(cpu); andre@0: thread->cpu = cpu; andre@0: andre@0: cpu->idle_thread = _PR_CreateThread(PR_SYSTEM_THREAD, andre@0: _PR_CPU_Idle, andre@0: (void *)cpu, andre@0: PR_PRIORITY_NORMAL, andre@0: PR_LOCAL_THREAD, andre@0: PR_UNJOINABLE_THREAD, andre@0: 0, andre@0: _PR_IDLE_THREAD); andre@0: andre@0: if (!cpu->idle_thread) { andre@0: /* didn't clean up CPU queue XXXMB */ andre@0: PR_DELETE(cpu); andre@0: return PR_FAILURE; andre@0: } andre@0: PR_ASSERT(cpu->idle_thread->cpu == cpu); andre@0: andre@0: cpu->idle_thread->no_sched = 0; andre@0: andre@0: cpu->thread = thread; andre@0: andre@0: if (_pr_cpu_affinity_mask) andre@0: PR_SetThreadAffinityMask(thread, _pr_cpu_affinity_mask); andre@0: andre@0: /* Created and started a new CPU */ andre@0: _PR_CPU_LIST_LOCK(); andre@0: cpu->id = _pr_cpuID++; andre@0: PR_APPEND_LINK(&cpu->links, &_PR_CPUQ()); andre@0: _PR_CPU_LIST_UNLOCK(); andre@0: andre@0: return PR_SUCCESS; andre@0: } andre@0: andre@0: #if !defined(_PR_GLOBAL_THREADS_ONLY) && !defined(_PR_LOCAL_THREADS_ONLY) andre@0: /* andre@0: ** This code is used during a cpu's initial creation. andre@0: */ andre@0: static void _PR_RunCPU(void *arg) andre@0: { andre@0: _PRCPU *cpu = (_PRCPU *)arg; andre@0: PRThread *me = _PR_MD_CURRENT_THREAD(); andre@0: andre@0: PR_ASSERT(NULL != me); andre@0: andre@0: /* andre@0: * _PR_StartCPU calls _PR_CreateThread to create the andre@0: * idle thread. Because _PR_CreateThread calls PR_Lock, andre@0: * the current thread has to remain a global thread andre@0: * during the _PR_StartCPU call so that it can wait for andre@0: * the lock if the lock is held by another thread. If andre@0: * we clear the _PR_GLOBAL_SCOPE flag in andre@0: * _PR_MD_CREATE_PRIMORDIAL_THREAD, the current thread andre@0: * will be treated as a local thread and have trouble andre@0: * waiting for the lock because the CPU is not fully andre@0: * constructed yet. andre@0: * andre@0: * After the CPU is started, it is safe to mark the andre@0: * current thread as a local thread. andre@0: */ andre@0: andre@0: #ifdef HAVE_CUSTOM_USER_THREADS andre@0: _PR_MD_CREATE_PRIMORDIAL_USER_THREAD(me); andre@0: #endif andre@0: andre@0: me->no_sched = 1; andre@0: _PR_StartCPU(cpu, me); andre@0: andre@0: #ifdef HAVE_CUSTOM_USER_THREADS andre@0: me->flags &= (~_PR_GLOBAL_SCOPE); andre@0: #endif andre@0: andre@0: _PR_MD_SET_CURRENT_CPU(cpu); andre@0: _PR_MD_SET_CURRENT_THREAD(cpu->thread); andre@0: me->cpu = cpu; andre@0: andre@0: while(1) { andre@0: PRInt32 is; andre@0: if (!_PR_IS_NATIVE_THREAD(me)) _PR_INTSOFF(is); andre@0: _PR_MD_START_INTERRUPTS(); andre@0: _PR_MD_SWITCH_CONTEXT(me); andre@0: } andre@0: } andre@0: #endif andre@0: andre@0: static void PR_CALLBACK _PR_CPU_Idle(void *_cpu) andre@0: { andre@0: _PRCPU *cpu = (_PRCPU *)_cpu; andre@0: PRThread *me = _PR_MD_CURRENT_THREAD(); andre@0: andre@0: PR_ASSERT(NULL != me); andre@0: andre@0: me->cpu = cpu; andre@0: cpu->idle_thread = me; andre@0: if (_MD_LAST_THREAD()) andre@0: _MD_LAST_THREAD()->no_sched = 0; andre@0: if (!_PR_IS_NATIVE_THREAD(me)) _PR_MD_SET_INTSOFF(0); andre@0: while(1) { andre@0: PRInt32 is; andre@0: PRIntervalTime timeout; andre@0: if (!_PR_IS_NATIVE_THREAD(me)) _PR_INTSOFF(is); andre@0: andre@0: _PR_RUNQ_LOCK(cpu); andre@0: #if !defined(_PR_LOCAL_THREADS_ONLY) && !defined(_PR_GLOBAL_THREADS_ONLY) andre@0: #ifdef _PR_HAVE_ATOMIC_OPS andre@0: _PR_MD_ATOMIC_INCREMENT(&_pr_md_idle_cpus); andre@0: #else andre@0: _PR_MD_LOCK(&_pr_md_idle_cpus_lock); andre@0: _pr_md_idle_cpus++; andre@0: _PR_MD_UNLOCK(&_pr_md_idle_cpus_lock); andre@0: #endif /* _PR_HAVE_ATOMIC_OPS */ andre@0: #endif andre@0: /* If someone on runq; do a nonblocking PAUSECPU */ andre@0: if (_PR_RUNQREADYMASK(me->cpu) != 0) { andre@0: _PR_RUNQ_UNLOCK(cpu); andre@0: timeout = PR_INTERVAL_NO_WAIT; andre@0: } else { andre@0: _PR_RUNQ_UNLOCK(cpu); andre@0: andre@0: _PR_SLEEPQ_LOCK(cpu); andre@0: if (PR_CLIST_IS_EMPTY(&_PR_SLEEPQ(me->cpu))) { andre@0: timeout = PR_INTERVAL_NO_TIMEOUT; andre@0: } else { andre@0: PRThread *wakeThread; andre@0: wakeThread = _PR_THREAD_PTR(_PR_SLEEPQ(me->cpu).next); andre@0: timeout = wakeThread->sleep; andre@0: } andre@0: _PR_SLEEPQ_UNLOCK(cpu); andre@0: } andre@0: andre@0: /* Wait for an IO to complete */ andre@0: (void)_PR_MD_PAUSE_CPU(timeout); andre@0: andre@0: #ifdef WINNT andre@0: if (_pr_cpus_exit) { andre@0: /* _PR_CleanupCPUs tells us to exit */ andre@0: _PR_MD_END_THREAD(); andre@0: } andre@0: #endif andre@0: andre@0: #if !defined(_PR_LOCAL_THREADS_ONLY) && !defined(_PR_GLOBAL_THREADS_ONLY) andre@0: #ifdef _PR_HAVE_ATOMIC_OPS andre@0: _PR_MD_ATOMIC_DECREMENT(&_pr_md_idle_cpus); andre@0: #else andre@0: _PR_MD_LOCK(&_pr_md_idle_cpus_lock); andre@0: _pr_md_idle_cpus--; andre@0: _PR_MD_UNLOCK(&_pr_md_idle_cpus_lock); andre@0: #endif /* _PR_HAVE_ATOMIC_OPS */ andre@0: #endif andre@0: andre@0: _PR_ClockInterrupt(); andre@0: andre@0: /* Now schedule any thread that is on the runq andre@0: * INTS must be OFF when calling PR_Schedule() andre@0: */ andre@0: me->state = _PR_RUNNABLE; andre@0: _PR_MD_SWITCH_CONTEXT(me); andre@0: if (!_PR_IS_NATIVE_THREAD(me)) _PR_FAST_INTSON(is); andre@0: } andre@0: } andre@0: #endif /* _PR_GLOBAL_THREADS_ONLY */ andre@0: andre@0: PR_IMPLEMENT(void) PR_SetConcurrency(PRUintn numCPUs) andre@0: { andre@0: #if defined(_PR_GLOBAL_THREADS_ONLY) || defined(_PR_LOCAL_THREADS_ONLY) andre@0: andre@0: /* do nothing */ andre@0: andre@0: #else /* combined, MxN thread model */ andre@0: andre@0: PRUintn newCPU; andre@0: _PRCPU *cpu; andre@0: PRThread *thr; andre@0: andre@0: andre@0: if (!_pr_initialized) _PR_ImplicitInitialization(); andre@0: andre@0: if (_native_threads_only) andre@0: return; andre@0: andre@0: _PR_CPU_LIST_LOCK(); andre@0: if (_pr_numCPU < numCPUs) { andre@0: newCPU = numCPUs - _pr_numCPU; andre@0: _pr_numCPU = numCPUs; andre@0: } else newCPU = 0; andre@0: _PR_CPU_LIST_UNLOCK(); andre@0: andre@0: for (; newCPU; newCPU--) { andre@0: cpu = _PR_CreateCPU(); andre@0: thr = _PR_CreateThread(PR_SYSTEM_THREAD, andre@0: _PR_RunCPU, andre@0: cpu, andre@0: PR_PRIORITY_NORMAL, andre@0: PR_GLOBAL_THREAD, andre@0: PR_UNJOINABLE_THREAD, andre@0: 0, andre@0: _PR_IDLE_THREAD); andre@0: } andre@0: #endif andre@0: } andre@0: andre@0: PR_IMPLEMENT(_PRCPU *) _PR_GetPrimordialCPU(void) andre@0: { andre@0: if (_pr_primordialCPU) andre@0: return _pr_primordialCPU; andre@0: else andre@0: return _PR_MD_CURRENT_CPU(); andre@0: }