/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
* The contents of this file are subject to the Netscape Public License
* Version 1.0 (the "NPL"); you may not use this file except in
* compliance with the NPL. You may obtain a copy of the NPL at
* http://www.mozilla.org/NPL/
*
* Software distributed under the NPL is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
* for the specific language governing rights and limitations under the
* NPL.
*
* The Initial Developer of this code under the NPL is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
* Reserved.
*/
#include "primpl.h"
_PRCPU *_pr_primordialCPU = NULL;
PRInt32 _pr_md_idle_cpus; /* number of idle cpus */
/*
* The idle threads in MxN models increment/decrement _pr_md_idle_cpus.
* If _PR_HAVE_ATOMIC_OPS is not defined, they can't use the atomic
* increment/decrement routines (which are based on PR_Lock/PR_Unlock),
* because PR_Lock asserts that the calling thread is not an idle thread.
* So we use a _MDLock to protect _pr_md_idle_cpus.
*/
#if !defined(_PR_LOCAL_THREADS_ONLY) && !defined(_PR_GLOBAL_THREADS_ONLY)
#ifndef _PR_HAVE_ATOMIC_OPS
static _MDLock _pr_md_idle_cpus_lock;
#endif
#endif
PRUintn _pr_numCPU;
PRInt32 _pr_cpus_exit;
PRInt32 _pr_cpu_affinity_mask = 0;
#if !defined (_PR_GLOBAL_THREADS_ONLY)
static PRUintn _pr_cpuID;
static void PR_CALLBACK _PR_CPU_Idle(void *);
static _PRCPU *_PR_CreateCPU(PRThread *thread, PRBool needQueue);
void _PR_InitCPUs()
{
PRThread *me = _PR_MD_CURRENT_THREAD();
_pr_cpuID = 0;
_MD_NEW_LOCK( &_pr_cpuLock);
#if !defined(_PR_LOCAL_THREADS_ONLY) && !defined(_PR_GLOBAL_THREADS_ONLY)
#ifndef _PR_HAVE_ATOMIC_OPS
_MD_NEW_LOCK(&_pr_md_idle_cpus_lock);
#endif
#endif
#ifdef HAVE_CUSTOM_USER_THREADS
_PR_MD_CREATE_PRIMORDIAL_USER_THREAD(me);
#endif
/* Now start the first CPU. */
_pr_primordialCPU = _PR_CreateCPU(me, PR_TRUE);
_pr_numCPU = 1;
_PR_MD_SET_CURRENT_CPU(_pr_primordialCPU);
/* Initialize cpu for current thread (could be different from me) */
_PR_MD_CURRENT_THREAD()->cpu = _pr_primordialCPU;
_PR_MD_SET_LAST_THREAD(me);
_PR_MD_INIT_CPUS();
}
static _PRCPUQueue *_PR_CreateCPUQueue(void)
{
PRInt32 index;
_PRCPUQueue *cpuQueue;
cpuQueue = PR_NEWZAP(_PRCPUQueue);
_MD_NEW_LOCK( &cpuQueue->runQLock );
_MD_NEW_LOCK( &cpuQueue->sleepQLock );
_MD_NEW_LOCK( &cpuQueue->miscQLock );
for (index = 0; index < PR_PRIORITY_LAST + 1; index++)
PR_INIT_CLIST( &(cpuQueue->runQ[index]) );
PR_INIT_CLIST( &(cpuQueue->sleepQ) );
PR_INIT_CLIST( &(cpuQueue->pauseQ) );
PR_INIT_CLIST( &(cpuQueue->suspendQ) );
PR_INIT_CLIST( &(cpuQueue->waitingToJoinQ) );
cpuQueue->numCPUs = 1;
return cpuQueue;
}
/*
* Create a new CPU.
*/
static _PRCPU *_PR_CreateCPU(PRThread *thread, PRBool needQueue)
{
_PRCPU *cpu;
/*
** Create a new cpu. The assumption this code makes is that the
** underlying operating system creates a stack to go with the new
** native thread. That stack will be used by the cpu when pausing.
*/
cpu = PR_NEWZAP(_PRCPU);
if (cpu) {
cpu->last_clock = PR_IntervalNow();
if (needQueue == PR_TRUE)
cpu->queue = _PR_CreateCPUQueue();
else
cpu->queue = _PR_MD_CURRENT_CPU()->queue;
if (!cpu->queue) {
PR_DELETE(cpu);
return NULL;
}
/* Before we create any threads on this CPU we have to
* set the current CPU
*/
_PR_MD_SET_CURRENT_CPU(cpu);
_PR_MD_INIT_RUNNING_CPU(cpu);
thread->cpu = cpu;
cpu->idle_thread = _PR_CreateThread(PR_SYSTEM_THREAD,
_PR_CPU_Idle,
(void *)cpu,
PR_PRIORITY_NORMAL,
PR_LOCAL_THREAD,
PR_UNJOINABLE_THREAD,
0,
_PR_IDLE_THREAD);
if (!cpu->idle_thread) {
/* didn't clean up CPU queue XXXMB */
PR_DELETE(cpu);
return NULL;
}
cpu->idle_thread->cpu = cpu;
cpu->idle_thread->no_sched = 0;
cpu->thread = thread;
if (_pr_cpu_affinity_mask)
PR_SetThreadAffinityMask(thread, _pr_cpu_affinity_mask);
/* Created a new CPU */
_PR_CPU_LIST_LOCK();
cpu->id = _pr_cpuID++;
PR_APPEND_LINK(&cpu->links, &_PR_CPUQ());
_PR_CPU_LIST_UNLOCK();
}
return cpu;
}
#if !defined(_PR_GLOBAL_THREADS_ONLY) && !defined(_PR_LOCAL_THREADS_ONLY)
/*
** This code is used during a cpu's initial creation.
*/
static void _PR_RunCPU(void *unused)
{
#if defined(XP_MAC)
#pragma unused (unused)
#endif
_PRCPU *cpu;
PRThread *me = _PR_MD_CURRENT_THREAD();
PR_ASSERT(NULL != me);
#ifdef HAVE_CUSTOM_USER_THREADS
_PR_MD_CREATE_PRIMORDIAL_USER_THREAD(me);
#endif
me->no_sched = 1;
cpu = _PR_CreateCPU(me, PR_TRUE);
_PR_MD_SET_CURRENT_CPU(cpu);
_PR_MD_SET_CURRENT_THREAD(cpu->thread);
me->cpu = cpu;
while(1) {
PRInt32 is;
if (!_PR_IS_NATIVE_THREAD(me)) _PR_INTSOFF(is);
_PR_MD_START_INTERRUPTS();
_PR_MD_SWITCH_CONTEXT(me);
}
}
#endif
static void PR_CALLBACK _PR_CPU_Idle(void *_cpu)
{
_PRCPU *cpu = (_PRCPU *)_cpu;
PRThread *me = _PR_MD_CURRENT_THREAD();
PR_ASSERT(NULL != me);
me->cpu = cpu;
cpu->idle_thread = me;
if (_MD_LAST_THREAD())
_MD_LAST_THREAD()->no_sched = 0;
if (!_PR_IS_NATIVE_THREAD(me)) _PR_MD_SET_INTSOFF(0);
while(1) {
PRInt32 is;
PRIntervalTime timeout;
if (!_PR_IS_NATIVE_THREAD(me)) _PR_INTSOFF(is);
_PR_RUNQ_LOCK(cpu);
#if !defined(_PR_LOCAL_THREADS_ONLY) && !defined(_PR_GLOBAL_THREADS_ONLY)
#ifdef _PR_HAVE_ATOMIC_OPS
_PR_MD_ATOMIC_INCREMENT(&_pr_md_idle_cpus);
#else
_PR_MD_LOCK(&_pr_md_idle_cpus_lock);
_pr_md_idle_cpus++;
_PR_MD_UNLOCK(&_pr_md_idle_cpus_lock);
#endif /* _PR_HAVE_ATOMIC_OPS */
#endif
/* If someone on runq; do a nonblocking PAUSECPU */
if (_PR_RUNQREADYMASK(me->cpu) != 0) {
_PR_RUNQ_UNLOCK(cpu);
timeout = PR_INTERVAL_NO_WAIT;
} else {
_PR_RUNQ_UNLOCK(cpu);
_PR_SLEEPQ_LOCK(cpu);
if (PR_CLIST_IS_EMPTY(&_PR_SLEEPQ(me->cpu))) {
timeout = PR_INTERVAL_NO_TIMEOUT;
} else {
PRThread *wakeThread;
wakeThread = _PR_THREAD_PTR(_PR_SLEEPQ(me->cpu).next);
timeout = wakeThread->sleep;
}
_PR_SLEEPQ_UNLOCK(cpu);
}
/* Wait for an IO to complete */
(void)_PR_MD_PAUSE_CPU(timeout);
#if !defined(_PR_LOCAL_THREADS_ONLY) && !defined(_PR_GLOBAL_THREADS_ONLY)
#ifdef _PR_HAVE_ATOMIC_OPS
_PR_MD_ATOMIC_DECREMENT(&_pr_md_idle_cpus);
#else
_PR_MD_LOCK(&_pr_md_idle_cpus_lock);
_pr_md_idle_cpus--;
_PR_MD_UNLOCK(&_pr_md_idle_cpus_lock);
#endif /* _PR_HAVE_ATOMIC_OPS */
#endif
_PR_ClockInterrupt();
/* Now schedule any thread that is on the runq
* INTS must be OFF when calling PR_Schedule()
*/
me->state = _PR_RUNNABLE;
_PR_MD_SWITCH_CONTEXT(me);
if (!_PR_IS_NATIVE_THREAD(me)) _PR_FAST_INTSON(is);
}
}
#endif /* _PR_GLOBAL_THREADS_ONLY */
PR_IMPLEMENT(void) PR_SetConcurrency(PRUintn numCPUs)
{
#if !defined(_PR_GLOBAL_THREADS_ONLY) && !defined(_PR_LOCAL_THREADS_ONLY)
PRUintn newCPU;
PRThread *cpu;
if (!_pr_initialized) _PR_ImplicitInitialization();
_PR_CPU_LIST_LOCK();
if (_pr_numCPU < numCPUs) {
newCPU = numCPUs - _pr_numCPU;
_pr_numCPU = numCPUs;
} else newCPU = 0;
_PR_CPU_LIST_UNLOCK();
for (; newCPU; newCPU--) {
cpu = _PR_CreateThread(PR_SYSTEM_THREAD,
_PR_RunCPU,
NULL,
PR_PRIORITY_NORMAL,
PR_GLOBAL_THREAD,
PR_UNJOINABLE_THREAD,
0,
_PR_IDLE_THREAD);
}
#endif
}
PR_IMPLEMENT(_PRCPU *) _PR_GetPrimordialCPU(void)
{
if (_pr_primordialCPU)
return _pr_primordialCPU;
else
return _PR_MD_CURRENT_CPU();
}
syntax highlighted by Code2HTML, v. 0.9.1