| /* RTco.cc provides minimal access to thread primitives. | |
| Copyright (C) 2019-2022 Free Software Foundation, Inc. | |
| Contributed by Gaius Mulley <gaius.mulley@southwales.ac.uk>. | |
| This file is part of GNU Modula-2. | |
| GNU Modula-2 is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 3, or (at your option) | |
| any later version. | |
| GNU Modula-2 is distributed in the hope that it will be useful, but | |
| WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| General Public License for more details. | |
| Under Section 7 of GPL version 3, you are granted additional | |
| permissions described in the GCC Runtime Library Exception, version | |
| 3.1, as published by the Free Software Foundation. | |
| You should have received a copy of the GNU General Public License and | |
| a copy of the GCC Runtime Library Exception along with this program; | |
| see the files COPYING3 and COPYING.RUNTIME respectively. If not, see | |
| <http://www.gnu.org/licenses/>. */ | |
| #include "config.h" | |
| #include <unistd.h> | |
| #include <pthread.h> | |
| #include <sys/select.h> | |
| #include <stdlib.h> | |
| #include <m2rts.h> | |
| #include <cstdio> | |
| #define EXPORT(FUNC) m2iso ## _RTco_ ## FUNC | |
| #define M2EXPORT(FUNC) m2iso ## _M2_RTco_ ## FUNC | |
| #define M2LIBNAME "m2iso" | |
| /* This implementation of RTco.cc uses a single lock for mutex across | |
| the whole module. It also forces context switching between threads | |
| in transfer by combining an implementation of wait and signal. | |
| All semaphores are implemented using the same mutex lock and | |
| separate condition variables. */ | |
| #undef TRACEON | |
| #define POOL | |
| #define SEM_POOL 10000 | |
| #define THREAD_POOL 10000 | |
| #define _GTHREAD_USE_COND_INIT_FUNC | |
| #include "gthr.h" | |
| /* Ensure that ANSI conform stdio is used. This needs to be set | |
| before any system header file is included. */ | |
| #if defined __MINGW32__ | |
| #define _POSIX 1 | |
| #define gm2_printf gnu_printf | |
| #else | |
| #define gm2_printf __printf__ | |
| #endif | |
| #if defined(TRACEON) | |
| #define tprintf printf | |
| #else | |
| #define tprintf(...) | |
| #endif | |
| typedef struct threadCB_s | |
| { | |
| void (*proc) (void); | |
| pthread_t p; | |
| int tid; /* The thread id. */ | |
| unsigned int interruptLevel; | |
| __gthread_cond_t run_counter; /* Used to block the thread and force | |
| a context switch. */ | |
| int value; /* Count 0 or 1. */ | |
| bool waiting; /* Is this thread waiting on the run_counter? */ | |
| } threadCB; | |
| typedef struct threadSem_s | |
| { | |
| __gthread_cond_t counter; | |
| bool waiting; | |
| int sem_value; | |
| } threadSem; | |
| static unsigned int nThreads = 0; | |
| static threadCB *threadArray = NULL; | |
| static unsigned int nSemaphores = 0; | |
| static threadSem **semArray = NULL; | |
| /* These are used to lock the above module data structures. */ | |
| static __gthread_mutex_t lock; /* This is the only mutex for | |
| the whole module. */ | |
| static bool initialized = false; | |
| extern "C" int EXPORT(init) (void); | |
| extern "C" void | |
| M2EXPORT(dep) (void) | |
| { | |
| } | |
| extern "C" void | |
| M2EXPORT(init) (int argc, char *argv[], char *envp[]) | |
| { | |
| } | |
| extern "C" void | |
| M2EXPORT(fini) (int argc, char *argv[], char *envp[]) | |
| { | |
| } | |
| static void | |
| initSem (threadSem *sem, int value) | |
| { | |
| __GTHREAD_COND_INIT_FUNCTION (&sem->counter); | |
| sem->waiting = false; | |
| sem->sem_value = value; | |
| } | |
| static void | |
| waitSem (threadSem *sem) | |
| { | |
| __gthread_mutex_lock (&lock); | |
| if (sem->sem_value == 0) | |
| { | |
| sem->waiting = true; | |
| __gthread_cond_wait (&sem->counter, &lock); | |
| sem->waiting = false; | |
| } | |
| else | |
| sem->sem_value--; | |
| __gthread_mutex_unlock (&lock); | |
| } | |
| static void | |
| signalSem (threadSem *sem) | |
| { | |
| __gthread_mutex_lock (&lock); | |
| if (sem->waiting) | |
| __gthread_cond_signal (&sem->counter); | |
| else | |
| sem->sem_value++; | |
| __gthread_mutex_unlock (&lock); | |
| } | |
| extern "C" void | |
| EXPORT(wait) (int sid) | |
| { | |
| EXPORT(init) (); | |
| tprintf ("wait %d\n", sid); | |
| waitSem (semArray[sid]); | |
| } | |
| extern "C" void | |
| EXPORT(signal) (int sid) | |
| { | |
| EXPORT(init) (); | |
| tprintf ("signal %d\n", sid); | |
| signalSem (semArray[sid]); | |
| } | |
| static int | |
| newSem (void) | |
| { | |
| #if defined(POOL) | |
| semArray[nSemaphores] | |
| = (threadSem *)malloc (sizeof (threadSem)); | |
| nSemaphores += 1; | |
| if (nSemaphores == SEM_POOL) | |
| m2iso_M2RTS_HaltC ("too many semaphores created", | |
| __FILE__, __FUNCTION__, __LINE__); | |
| #else | |
| threadSem *sem | |
| = (threadSem *)malloc (sizeof (threadSem)); | |
| /* We need to be careful when using realloc as the lock (semaphore) | |
| operators use the semaphore address. So we keep an array of pointer | |
| to semaphores. */ | |
| if (nSemaphores == 0) | |
| { | |
| semArray = (threadSem **)malloc (sizeof (sem)); | |
| nSemaphores = 1; | |
| } | |
| else | |
| { | |
| nSemaphores += 1; | |
| semArray = (threadSem **)realloc (semArray, | |
| sizeof (sem) * nSemaphores); | |
| } | |
| semArray[nSemaphores - 1] = sem; | |
| #endif | |
| return nSemaphores - 1; | |
| } | |
| static int | |
| initSemaphore (int value) | |
| { | |
| int sid = newSem (); | |
| initSem (semArray[sid], value); | |
| tprintf ("%d = initSemaphore (%d)\n", sid, value); | |
| return sid; | |
| } | |
| extern "C" int | |
| EXPORT(initSemaphore) (int value) | |
| { | |
| int sid; | |
| tprintf ("initSemaphore (%d) called\n", value); | |
| EXPORT(init) (); | |
| tprintf ("about to access lock\n"); | |
| __gthread_mutex_lock (&lock); | |
| sid = initSemaphore (value); | |
| __gthread_mutex_unlock (&lock); | |
| return sid; | |
| } | |
| static int | |
| currentThread (void) | |
| { | |
| int tid; | |
| for (tid = 0; tid < nThreads; tid++) | |
| if (pthread_self () == threadArray[tid].p) | |
| return tid; | |
| m2iso_M2RTS_HaltC ("failed to find currentThread", | |
| __FILE__, __FUNCTION__, __LINE__); | |
| } | |
| extern "C" int | |
| EXPORT(currentThread) (void) | |
| { | |
| int tid; | |
| EXPORT(init) (); | |
| __gthread_mutex_lock (&lock); | |
| tid = currentThread (); | |
| tprintf ("currentThread %d\n", tid); | |
| __gthread_mutex_unlock (&lock); | |
| return tid; | |
| } | |
| /* currentInterruptLevel returns the interrupt level of the current thread. */ | |
| extern "C" unsigned int | |
| EXPORT(currentInterruptLevel) (void) | |
| { | |
| EXPORT(init) (); | |
| __gthread_mutex_lock (&lock); | |
| int current = currentThread (); | |
| tprintf ("currentInterruptLevel %d\n", | |
| threadArray[current].interruptLevel); | |
| int level = threadArray[current].interruptLevel; | |
| __gthread_mutex_unlock (&lock); | |
| return level; | |
| } | |
| /* turninterrupts returns the old interrupt level and assigns the | |
| interrupt level to newLevel. */ | |
| extern "C" unsigned int | |
| EXPORT(turnInterrupts) (unsigned int newLevel) | |
| { | |
| EXPORT(init) (); | |
| __gthread_mutex_lock (&lock); | |
| int current = currentThread (); | |
| unsigned int old = threadArray[current].interruptLevel; | |
| tprintf ("turnInterrupts from %d to %d\n", old, newLevel); | |
| threadArray[current].interruptLevel = newLevel; | |
| __gthread_mutex_unlock (&lock); | |
| return old; | |
| } | |
| static void | |
| never (void) | |
| { | |
| m2iso_M2RTS_HaltC ("the main thread should never call here", | |
| __FILE__, __FUNCTION__, __LINE__); | |
| } | |
| static void * | |
| execThread (void *t) | |
| { | |
| threadCB *tp = (threadCB *)t; | |
| tprintf ("exec thread tid = %d coming to life\n", tp->tid); | |
| __gthread_mutex_lock (&lock); | |
| tprintf ("exec thread tid = %d function = 0x%p arg = 0x%p\n", tp->tid, | |
| tp->proc, t); | |
| /* Has the thread been signalled? */ | |
| if (tp->value == 0) | |
| { | |
| /* Not been signalled therefore we force ourselves to block. */ | |
| tprintf ("%s: forcing thread tid = %d to wait\n", | |
| __FUNCTION__, tp->tid); | |
| tp->waiting = true; /* We are waiting. */ | |
| __gthread_cond_wait (&tp->run_counter, &lock); | |
| tp->waiting = false; /* Running again. */ | |
| } | |
| else | |
| { | |
| /* Yes signalled, therefore just take the recorded signal and continue. */ | |
| tprintf ("%s: no need for thread tid = %d to wait\n", | |
| __FUNCTION__, tp->tid); | |
| tp->value--; | |
| } | |
| tprintf (" running exec thread [%d] function = 0x%p arg = 0x%p\n", tp->tid, | |
| tp->proc, t); | |
| __gthread_mutex_unlock (&lock); | |
| tp->proc (); /* Now execute user procedure. */ | |
| #if 0 | |
| m2iso_M2RTS_CoroutineException ( __FILE__, __LINE__, __COLUMN__, __FUNCTION__, "coroutine finishing"); | |
| #endif | |
| m2iso_M2RTS_HaltC ("execThread should never finish", | |
| __FILE__, __FUNCTION__, __LINE__); | |
| return NULL; | |
| } | |
| static int | |
| newThread (void) | |
| { | |
| #if defined(POOL) | |
| nThreads += 1; | |
| if (nThreads == THREAD_POOL) | |
| m2iso_M2RTS_HaltC ("too many threads created", | |
| __FILE__, __FUNCTION__, __LINE__); | |
| return nThreads - 1; | |
| #else | |
| if (nThreads == 0) | |
| { | |
| threadArray = (threadCB *)malloc (sizeof (threadCB)); | |
| nThreads = 1; | |
| } | |
| else | |
| { | |
| nThreads += 1; | |
| threadArray | |
| = (threadCB *)realloc (threadArray, sizeof (threadCB) * nThreads); | |
| } | |
| return nThreads - 1; | |
| #endif | |
| } | |
| static int | |
| initThread (void (*proc) (void), unsigned int stackSize, | |
| unsigned int interrupt) | |
| { | |
| int tid = newThread (); | |
| pthread_attr_t attr; | |
| int result; | |
| threadArray[tid].proc = proc; | |
| threadArray[tid].tid = tid; | |
| /* Initialize the thread run_counter used to force a context switch. */ | |
| __GTHREAD_COND_INIT_FUNCTION (&threadArray[tid].run_counter); | |
| threadArray[tid].interruptLevel = interrupt; | |
| threadArray[tid].waiting = false; /* The thread is running. */ | |
| threadArray[tid].value = 0; /* No signal has been seen yet. */ | |
| /* Set thread creation attributes. */ | |
| result = pthread_attr_init (&attr); | |
| if (result != 0) | |
| m2iso_M2RTS_HaltC ("failed to create thread attribute", | |
| __FILE__, __FUNCTION__, __LINE__); | |
| if (stackSize > 0) | |
| { | |
| result = pthread_attr_setstacksize (&attr, stackSize); | |
| if (result != 0) | |
| m2iso_M2RTS_HaltC ("failed to set stack size attribute", | |
| __FILE__, __FUNCTION__, __LINE__); | |
| } | |
| tprintf ("initThread [%d] function = 0x%p (arg = 0x%p)\n", tid, proc, | |
| (void *)&threadArray[tid]); | |
| result = pthread_create (&threadArray[tid].p, &attr, execThread, | |
| (void *)&threadArray[tid]); | |
| if (result != 0) | |
| m2iso_M2RTS_HaltC ("thread_create failed", | |
| __FILE__, __FUNCTION__, __LINE__); | |
| tprintf (" created thread [%d] function = 0x%p 0x%p\n", tid, proc, | |
| (void *)&threadArray[tid]); | |
| return tid; | |
| } | |
| extern "C" int | |
| EXPORT(initThread) (void (*proc) (void), unsigned int stackSize, | |
| unsigned int interrupt) | |
| { | |
| int tid; | |
| EXPORT(init) (); | |
| __gthread_mutex_lock (&lock); | |
| tid = initThread (proc, stackSize, interrupt); | |
| __gthread_mutex_unlock (&lock); | |
| return tid; | |
| } | |
| /* transfer unlocks thread p2 and locks the current thread. p1 is | |
| updated with the current thread id. | |
| The implementation of transfer uses a combined wait/signal. */ | |
| extern "C" void | |
| EXPORT(transfer) (int *p1, int p2) | |
| { | |
| __gthread_mutex_lock (&lock); | |
| { | |
| int current = currentThread (); | |
| if (!initialized) | |
| m2iso_M2RTS_HaltC ("cannot transfer to a process before the process has been created", | |
| __FILE__, __FUNCTION__, __LINE__); | |
| if (current == p2) | |
| { | |
| /* Error. */ | |
| m2iso_M2RTS_HaltC ("attempting to transfer to ourself", | |
| __FILE__, __FUNCTION__, __LINE__); | |
| } | |
| else | |
| { | |
| *p1 = current; | |
| int old = current; | |
| tprintf ("start, context switching from: %d to %d\n", current, p2); | |
| /* Perform signal (p2 sem). Without the mutex lock as we have | |
| already obtained it above. */ | |
| if (threadArray[p2].waiting) | |
| { | |
| /* p2 is blocked on the condition variable, release it. */ | |
| tprintf ("p1 = %d cond_signal to p2 (%d)\n", current, p2); | |
| __gthread_cond_signal (&threadArray[p2].run_counter); | |
| tprintf ("after p1 = %d cond_signal to p2 (%d)\n", current, p2); | |
| } | |
| else | |
| { | |
| /* p2 hasn't reached the condition variable, so bump value | |
| ready for p2 to test. */ | |
| tprintf ("no need for thread %d to cond_signal - bump %d value (pre) = %d\n", | |
| current, p2, threadArray[p2].value); | |
| threadArray[p2].value++; | |
| } | |
| /* Perform wait (old sem). Again without obtaining mutex as | |
| we've already claimed it. */ | |
| if (threadArray[old].value == 0) | |
| { | |
| /* Record we are about to wait on the condition variable. */ | |
| threadArray[old].waiting = true; | |
| __gthread_cond_wait (&threadArray[old].run_counter, &lock); | |
| threadArray[old].waiting = false; | |
| /* We are running again. */ | |
| } | |
| else | |
| { | |
| tprintf ("(currentThread = %d) no need for thread %d to cond_wait - taking value (pre) = %d\n", | |
| current, old, threadArray[old].value); | |
| /* No need to block as we have been told a signal has | |
| effectively already been recorded. We remove the signal | |
| notification without blocking. */ | |
| threadArray[old].value--; | |
| } | |
| tprintf ("end, context back to %d\n", current); | |
| if (current != old) | |
| m2iso_M2RTS_HaltC ("wrong process id", | |
| __FILE__, __FUNCTION__, __LINE__); | |
| } | |
| } | |
| __gthread_mutex_unlock (&lock); | |
| } | |
| extern "C" int | |
| EXPORT(select) (int p1, fd_set *p2, fd_set *p3, fd_set *p4, const timespec *p5) | |
| { | |
| EXPORT(init) (); | |
| tprintf ("[%x] RTco.select (...)\n", pthread_self ()); | |
| return pselect (p1, p2, p3, p4, p5, NULL); | |
| } | |
| extern "C" int | |
| EXPORT(init) (void) | |
| { | |
| tprintf ("checking init\n"); | |
| if (! initialized) | |
| { | |
| initialized = true; | |
| tprintf ("RTco initialized\n"); | |
| __GTHREAD_MUTEX_INIT_FUNCTION (&lock); | |
| __gthread_mutex_lock (&lock); | |
| /* Create initial thread container. */ | |
| #if defined(POOL) | |
| threadArray = (threadCB *)malloc (sizeof (threadCB) * THREAD_POOL); | |
| semArray = (threadSem **)malloc (sizeof (threadSem *) * SEM_POOL); | |
| #endif | |
| /* Create a thread control block for the main program (or process). */ | |
| int tid = newThread (); /* For the current initial thread. */ | |
| threadArray[tid].p = pthread_self (); | |
| threadArray[tid].tid = tid; | |
| __GTHREAD_COND_INIT_FUNCTION (&threadArray[tid].run_counter); | |
| threadArray[tid].interruptLevel = 0; | |
| /* The line below shouldn't be necessary as we are already running. */ | |
| threadArray[tid].proc = never; | |
| threadArray[tid].waiting = false; /* We are running. */ | |
| threadArray[tid].value = 0; /* No signal from anyone yet. */ | |
| tprintf ("RTco initialized completed\n"); | |
| __gthread_mutex_unlock (&lock); | |
| } | |
| return 0; | |
| } | |
| extern "C" void __attribute__((__constructor__)) | |
| M2EXPORT(ctor) (void) | |
| { | |
| m2iso_M2RTS_RegisterModule ("RTco", M2LIBNAME, | |
| M2EXPORT(init), M2EXPORT(fini), | |
| M2EXPORT(dep)); | |
| } |