mirror of
https://github.com/signalwire/freeswitch.git
synced 2025-06-23 20:25:47 +00:00
git-svn-id: http://svn.freeswitch.org/svn/freeswitch/trunk@3693 d0543943-73ff-0310-b7d9-9358b9ac24b2
441 lines
13 KiB
C
441 lines
13 KiB
C
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is the Netscape Portable Runtime (NSPR).
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Netscape Communications Corporation.
|
|
* Portions created by the Initial Developer are Copyright (C) 1998-2000
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
#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(void);
|
|
static PRStatus _PR_StartCPU(_PRCPU *cpu, PRThread *thread);
|
|
|
|
#if !defined(_PR_LOCAL_THREADS_ONLY)
|
|
static void _PR_RunCPU(void *arg);
|
|
#endif
|
|
|
|
void _PR_InitCPUs()
|
|
{
|
|
PRThread *me = _PR_MD_CURRENT_THREAD();
|
|
|
|
if (_native_threads_only)
|
|
return;
|
|
|
|
_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 _PR_LOCAL_THREADS_ONLY
|
|
|
|
#ifdef HAVE_CUSTOM_USER_THREADS
|
|
_PR_MD_CREATE_PRIMORDIAL_USER_THREAD(me);
|
|
#endif
|
|
|
|
/* Now start the first CPU. */
|
|
_pr_primordialCPU = _PR_CreateCPU();
|
|
_pr_numCPU = 1;
|
|
_PR_StartCPU(_pr_primordialCPU, me);
|
|
|
|
_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);
|
|
|
|
#else /* Combined MxN model */
|
|
|
|
_pr_primordialCPU = _PR_CreateCPU();
|
|
_pr_numCPU = 1;
|
|
_PR_CreateThread(PR_SYSTEM_THREAD,
|
|
_PR_RunCPU,
|
|
_pr_primordialCPU,
|
|
PR_PRIORITY_NORMAL,
|
|
PR_GLOBAL_THREAD,
|
|
PR_UNJOINABLE_THREAD,
|
|
0,
|
|
_PR_IDLE_THREAD);
|
|
|
|
#endif /* _PR_LOCAL_THREADS_ONLY */
|
|
|
|
_PR_MD_INIT_CPUS();
|
|
}
|
|
|
|
#ifdef WINNT
|
|
/*
|
|
* Right now this function merely stops the CPUs and does
|
|
* not do any other cleanup.
|
|
*
|
|
* It is only implemented for WINNT because bug 161998 only
|
|
* affects the WINNT version of NSPR, but it would be nice
|
|
* to implement this function for other platforms too.
|
|
*/
|
|
void _PR_CleanupCPUs(void)
|
|
{
|
|
PRUintn i;
|
|
PRCList *qp;
|
|
_PRCPU *cpu;
|
|
|
|
_pr_cpus_exit = 1;
|
|
for (i = 0; i < _pr_numCPU; i++) {
|
|
_PR_MD_WAKEUP_WAITER(NULL);
|
|
}
|
|
for (qp = _PR_CPUQ().next; qp != &_PR_CPUQ(); qp = qp->next) {
|
|
cpu = _PR_CPU_PTR(qp);
|
|
_PR_MD_JOIN_THREAD(&cpu->thread->md);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
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.
|
|
*
|
|
* This function initializes enough of the _PRCPU structure so
|
|
* that it can be accessed safely by a global thread or another
|
|
* CPU. This function does not create the native thread that
|
|
* will run the CPU nor does it initialize the parts of _PRCPU
|
|
* that must be initialized by that native thread.
|
|
*
|
|
* The reason we cannot simply have the native thread create
|
|
* and fully initialize a new CPU is that we need to be able to
|
|
* create a usable _pr_primordialCPU in _PR_InitCPUs without
|
|
* assuming that the primordial CPU thread we created can run
|
|
* during NSPR initialization. For example, on Windows while
|
|
* new threads can be created by DllMain, they won't be able
|
|
* to run during DLL initialization. If NSPR is initialized
|
|
* by DllMain, the primordial CPU thread won't run until DLL
|
|
* initialization is finished.
|
|
*/
|
|
static _PRCPU *_PR_CreateCPU(void)
|
|
{
|
|
_PRCPU *cpu;
|
|
|
|
cpu = PR_NEWZAP(_PRCPU);
|
|
if (cpu) {
|
|
cpu->queue = _PR_CreateCPUQueue();
|
|
if (!cpu->queue) {
|
|
PR_DELETE(cpu);
|
|
return NULL;
|
|
}
|
|
}
|
|
return cpu;
|
|
}
|
|
|
|
/*
|
|
* Start a new CPU.
|
|
*
|
|
* 'cpu' is a _PRCPU structure created by _PR_CreateCPU().
|
|
* 'thread' is the native thread that will run the CPU.
|
|
*
|
|
* If this function fails, 'cpu' is destroyed.
|
|
*/
|
|
static PRStatus _PR_StartCPU(_PRCPU *cpu, PRThread *thread)
|
|
{
|
|
/*
|
|
** Start 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.
|
|
*/
|
|
|
|
PR_ASSERT(!_native_threads_only);
|
|
|
|
cpu->last_clock = PR_IntervalNow();
|
|
|
|
/* 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 PR_FAILURE;
|
|
}
|
|
PR_ASSERT(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 and started a new CPU */
|
|
_PR_CPU_LIST_LOCK();
|
|
cpu->id = _pr_cpuID++;
|
|
PR_APPEND_LINK(&cpu->links, &_PR_CPUQ());
|
|
_PR_CPU_LIST_UNLOCK();
|
|
|
|
return PR_SUCCESS;
|
|
}
|
|
|
|
#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 *arg)
|
|
{
|
|
_PRCPU *cpu = (_PRCPU *)arg;
|
|
PRThread *me = _PR_MD_CURRENT_THREAD();
|
|
|
|
PR_ASSERT(NULL != me);
|
|
|
|
/*
|
|
* _PR_StartCPU calls _PR_CreateThread to create the
|
|
* idle thread. Because _PR_CreateThread calls PR_Lock,
|
|
* the current thread has to remain a global thread
|
|
* during the _PR_StartCPU call so that it can wait for
|
|
* the lock if the lock is held by another thread. If
|
|
* we clear the _PR_GLOBAL_SCOPE flag in
|
|
* _PR_MD_CREATE_PRIMORDIAL_THREAD, the current thread
|
|
* will be treated as a local thread and have trouble
|
|
* waiting for the lock because the CPU is not fully
|
|
* constructed yet.
|
|
*
|
|
* After the CPU is started, it is safe to mark the
|
|
* current thread as a local thread.
|
|
*/
|
|
|
|
#ifdef HAVE_CUSTOM_USER_THREADS
|
|
_PR_MD_CREATE_PRIMORDIAL_USER_THREAD(me);
|
|
#endif
|
|
|
|
me->no_sched = 1;
|
|
_PR_StartCPU(cpu, me);
|
|
|
|
#ifdef HAVE_CUSTOM_USER_THREADS
|
|
me->flags &= (~_PR_GLOBAL_SCOPE);
|
|
#endif
|
|
|
|
_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);
|
|
|
|
#ifdef WINNT
|
|
if (_pr_cpus_exit) {
|
|
/* _PR_CleanupCPUs tells us to exit */
|
|
_PR_MD_END_THREAD();
|
|
}
|
|
#endif
|
|
|
|
#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)
|
|
#ifdef XP_MAC
|
|
#pragma unused(numCPUs)
|
|
#endif
|
|
|
|
/* do nothing */
|
|
|
|
#else /* combined, MxN thread model */
|
|
|
|
PRUintn newCPU;
|
|
_PRCPU *cpu;
|
|
PRThread *thr;
|
|
|
|
|
|
if (!_pr_initialized) _PR_ImplicitInitialization();
|
|
|
|
if (_native_threads_only)
|
|
return;
|
|
|
|
_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_CreateCPU();
|
|
thr = _PR_CreateThread(PR_SYSTEM_THREAD,
|
|
_PR_RunCPU,
|
|
cpu,
|
|
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();
|
|
}
|