From f7894f74a633bb1f2ae93abfca7ae4140e726141 Mon Sep 17 00:00:00 2001 From: nakst <> Date: Sat, 6 Nov 2021 14:52:00 +0000 Subject: [PATCH] document TerminateThread and TerminateProcess --- drivers/acpica.cpp | 1 - kernel/objects.cpp | 12 ++- kernel/scheduler.cpp | 151 ++++++++++++++++++++----------------- kernel/synchronisation.cpp | 2 - 4 files changed, 93 insertions(+), 73 deletions(-) diff --git a/drivers/acpica.cpp b/drivers/acpica.cpp index 4726d05..56e30c6 100644 --- a/drivers/acpica.cpp +++ b/drivers/acpica.cpp @@ -106,7 +106,6 @@ void RunACPICAEvent(void *e) { ACPICAEvent *event = (ACPICAEvent *) e; event->function(event->context); EsHeapFree(event, 0, K_FIXED); - scheduler.TerminateThread(GetCurrentThread()); } ES_EXTERN_C ACPI_STATUS AcpiOsExecute(ACPI_EXECUTE_TYPE type, ACPI_OSD_EXEC_CALLBACK function, void *context) { diff --git a/kernel/objects.cpp b/kernel/objects.cpp index 46da99e..b00d16d 100644 --- a/kernel/objects.cpp +++ b/kernel/objects.cpp @@ -250,7 +250,17 @@ void CloseHandleToObject(void *object, KernelObjectType type, uint32_t flags) { } break; case KERNEL_OBJECT_THREAD: { - CloseHandleToThread(object); + KSpinlockAcquire(&scheduler.lock); + Thread *thread = (Thread *) object; + if (!thread->handles) KernelPanic("CloseHandleToThread - All handles to the thread have been closed.\n"); + thread->handles--; + bool removeThread = thread->handles == 0; + // EsPrint("Thread %d has %d handles\n", thread->id, thread->handles); + KSpinlockRelease(&scheduler.lock); + + if (removeThread) { + scheduler.RemoveThread(thread); + } } break; case KERNEL_OBJECT_NODE: { diff --git a/kernel/scheduler.cpp b/kernel/scheduler.cpp index 725b620..be49aa6 100644 --- a/kernel/scheduler.cpp +++ b/kernel/scheduler.cpp @@ -1,18 +1,15 @@ #ifndef IMPLEMENTATION -#define PREEMPT_AFTER_MUTEX_RELEASE - #define THREAD_PRIORITY_NORMAL (0) // Lower value = higher priority. #define THREAD_PRIORITY_LOW (1) #define THREAD_PRIORITY_COUNT (2) -void CloseHandleToThread(void *_thread); void CloseHandleToProcess(void *_thread); void KillThread(EsGeneric _thread); void RegisterAsyncTask(KAsyncTaskCallback callback, EsGeneric argument, struct Process *targetProcess, - bool needed /*If false, the task may not be registered if there are many queued tasks.*/, - bool unlocked = false /*Set to true if you haven't acquired the scheduler's lock.*/); + bool needed /* if false, the task may not be registered if there are many queued tasks */, + bool unlocked = false /* set to true if you haven't acquired the scheduler's lock */); enum ThreadState : int8_t { THREAD_ACTIVE, // An active thread. Not necessarily executing; `executing` determines if it executing. @@ -20,7 +17,6 @@ enum ThreadState : int8_t { THREAD_WAITING_EVENT, // Waiting for a event to be notified. THREAD_WAITING_WRITER_LOCK, // Waiting for a writer lock to be notified. THREAD_TERMINATED, // The thread has been terminated. It will be deallocated when all handles are closed. - // I believe this is called a "zombie thread" in UNIX terminology. }; enum ThreadType : int8_t { @@ -143,7 +139,7 @@ struct Process { // Termination: bool allThreadsTerminated; - bool terminating; + bool terminating; // This never gets set if TerminateProcess is not called, and instead the process is killed because all its threads exit naturally. int exitStatus; // TODO Remove this. KEvent killedEvent; @@ -174,14 +170,46 @@ struct Scheduler { #define SPAWN_THREAD_PAUSED (8) Thread *SpawnThread(const char *cName, uintptr_t startAddress, uintptr_t argument1 = 0, uint32_t flags = ES_FLAGS_DEFAULT, Process *process = nullptr, uintptr_t argument2 = 0); - void TerminateThread(Thread *thread, bool lockAlreadyAcquired = false); void PauseThread(Thread *thread, bool resume /*true to resume, false to pause*/, bool lockAlreadyAcquired = false); Process *SpawnProcess(ProcessType processType = PROCESS_NORMAL); - void TerminateProcess(Process *process, int status); void PauseProcess(Process *process, bool resume); void CrashProcess(Process *process, EsCrashReason *reason); + // How thread termination works: + // 1. TerminateThread + // - terminating is set to true. + // - If the thread is executing, then on the next context switch, KillThread is called on a async task. + // - If it is not executing, KillThread is called on a async task. + // - Note, terminatableState must be set to THREAD_TERMINATABLE. + // - When a thread terminates itself, its terminatableState is automatically set to THREAD_TERMINATABLE. + // 2. KillThread + // - Removes the thread from the lists, frees the stacks, and sets killedEvent. + // - The thread's handles to itself and its process are closed. + // - If this is the last thread in the process, KillProcess is called. + // 3. CloseHandleToObject KERNEL_OBJECT_THREAD + // - If the last handle to the thread has been closed, then RemoveThread is called. + // 4. RemoveThread + // - The thread structure is deallocated. + void TerminateThread(Thread *thread, bool lockAlreadyAcquired = false); + + // How process termination works: + // 1. TerminateProcess (optional) + // - terminating is set to true (to prevent creation of new threads). + // - TerminateThread is called on each thread, leading to an eventual call to KillProcess. + // - This is optional because KillProcess is called if all threads get terminated naturally; in this case, terminating is never set. + // 2. KillProcess + // - Destroys the handle table and memory space, and sets killedEvent. + // - Sends a message to Desktop informing it the process was killed. + // - Since KillProcess is only called from KillThread, there is an associated closing of a process handle from the killed thread. + // 3. CloseHandleToObject KERNEL_OBJECT_PROCESS (CloseHandleToProcess) + // - If the last handle to the process has been closed, then RemoveProcess is called on an async task. + // 4. RemoveProcess + // - Removes the process from the lists, destroys its message queue, and closes its handle to its executable node. + // (These tasks are done here because processes that are created but never started will not reach KillProcess.) + // - The process and memory space structures are deallocated. + void TerminateProcess(Process *process, int status); + Process *OpenProcess(uint64_t id); void WaitMutex(KMutex *mutex); @@ -195,8 +223,8 @@ struct Scheduler { void RemoveProcess(Process *process); // Do not call. Use TerminateProcess/CloseHandleToObject. void RemoveThread(Thread *thread); // Do not call. Use TerminateThread/CloseHandleToObject. - void AddActiveThread(Thread *thread, bool start /*Put it at the start*/); // Add an active thread into the queue. - void InsertNewThread(Thread *thread, bool addToActiveList, Process *owner); // Used during thread creation. + void AddActiveThread(Thread *thread, bool start /* put it at the start of the active list */); // Add an active thread into the queue. + void InsertNewThread(Thread *thread, bool addToActiveList, Process *owner); // Used during thread creation. void MaybeUpdateActiveList(Thread *thread); // After changing the priority of a thread, call this to move it to the correct active thread queue if needed. void NotifyObject(LinkedList *blockedThreads, bool unblockAll, Thread *previousMutexOwner = nullptr); @@ -957,8 +985,6 @@ void Scheduler::RemoveThread(Thread *thread) { } #endif - // TODO Deallocate user and kernel stacks. - scheduler.threadPool.Remove(thread); } @@ -1108,17 +1134,48 @@ void CloseHandleToProcess(void *_process) { ProcessorFakeTimerInterrupt(); // Process the asynchronous task. } -void CloseHandleToThread(void *_thread) { - KSpinlockAcquire(&scheduler.lock); - Thread *thread = (Thread *) _thread; - if (!thread->handles) KernelPanic("CloseHandleToThread - All handles to the thread have been closed.\n"); - thread->handles--; - bool removeThread = thread->handles == 0; - // EsPrint("Thread %d has %d handles\n", thread->id, thread->handles); +void KillProcess(Process *process) { + KernelLog(LOG_INFO, "Scheduler", "killing process", "Killing process (%d) %x...\n", process->id, process); + + process->allThreadsTerminated = true; + scheduler.activeProcessCount--; + + bool setProcessKilledEvent = true; + +#ifdef ENABLE_POSIX_SUBSYSTEM + if (process->posixForking) { + // If the process is from an incomplete vfork(), + // then the parent process gets to set the killed event + // and the exit status. + setProcessKilledEvent = false; + } +#endif + + if (setProcessKilledEvent) { + // We can now also set the killed event on the process. + KEventSet(&process->killedEvent, true); + } + KSpinlockRelease(&scheduler.lock); - if (removeThread) { - scheduler.RemoveThread(thread); + // There are no threads left in this process. + // We should destroy the handle table at this point. + // Otherwise, the process might never be freed + // because of a cyclic-dependency. + process->handleTable.Destroy(); + + // Destroy the virtual memory space. + // Don't actually deallocate it yet though; that is done on an async task queued by RemoveProcess. + // This must be destroyed after the handle table! + MMSpaceDestroy(process->vmm); + + // Tell Desktop the process has terminated. + if (!scheduler.shutdown) { + _EsMessageWithObject m; + EsMemoryZero(&m, sizeof(m)); + m.message.type = ES_MSG_PROCESS_TERMINATED; + m.message.crash.pid = process->id; + desktopProcess->messageQueue.SendMessage(&m); } } @@ -1133,50 +1190,7 @@ void KillThread(EsGeneric _thread) { "Killing thread (ID %d, %d remain in process %d) %x...\n", thread->id, thread->process->threads.count, thread->process->id, _thread); if (thread->process->threads.count == 0) { - Process *process = thread->process; - KernelLog(LOG_INFO, "Scheduler", "killing process", - "Killing process (%d) %x...\n", process->id, process); - - process->allThreadsTerminated = true; - scheduler.activeProcessCount--; - - bool setProcessKilledEvent = true; - -#ifdef ENABLE_POSIX_SUBSYSTEM - if (process->posixForking) { - // If the process is from an incomplete vfork(), - // then the parent process gets to set the killed event - // and the exit status. - setProcessKilledEvent = false; - } -#endif - - if (setProcessKilledEvent) { - // We can now also set the killed event on the process. - KEventSet(&process->killedEvent, true); - } - - KSpinlockRelease(&scheduler.lock); - - // There are no threads left in this process. - // We should destroy the handle table at this point. - // Otherwise, the process might never be freed - // because of a cyclic-dependency. - process->handleTable.Destroy(); - - // Destroy the virtual memory space. - // Don't actually deallocate it yet though; that is done on an async task queued by RemoveProcess. - // This must be destroyed after the handle table! - MMSpaceDestroy(process->vmm); - - // Tell Desktop the process has terminated. - if (!scheduler.shutdown) { - _EsMessageWithObject m; - EsMemoryZero(&m, sizeof(m)); - m.message.type = ES_MSG_PROCESS_TERMINATED; - m.message.crash.pid = process->id; - desktopProcess->messageQueue.SendMessage(&m); - } + KillProcess(thread->process); // Releases the scheduler's lock. } else { KSpinlockRelease(&scheduler.lock); } @@ -1186,10 +1200,9 @@ void KillThread(EsGeneric _thread) { KEventSet(&thread->killedEvent); - // Close the handle that this thread owns of its owner process. + // Close the handle that this thread owns of its owner process, and the handle it owns of itself. CloseHandleToObject(thread->process, KERNEL_OBJECT_PROCESS); - - CloseHandleToThread(thread); + CloseHandleToObject(thread, KERNEL_OBJECT_THREAD); } Thread *Scheduler::PickThread(CPULocalStorage *local) { diff --git a/kernel/synchronisation.cpp b/kernel/synchronisation.cpp index 04a69f9..f2ef8d4 100644 --- a/kernel/synchronisation.cpp +++ b/kernel/synchronisation.cpp @@ -244,9 +244,7 @@ void KMutexRelease(KMutex *mutex) { } #endif -#ifdef PREEMPT_AFTER_MUTEX_RELEASE if (preempt) ProcessorFakeTimerInterrupt(); -#endif } void KMutexAssertLocked(KMutex *mutex) {