/* * Copyright (c) 2011-2016, 2018, 2021-2022 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * thread.cpp * Describes a thread belonging to a process. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__i386__) || defined(__x86_64__) #include "x86-family/float.h" #endif namespace Sortix { Thread::Thread() { assert(!((uintptr_t) registers.fpuenv & 0xFUL)); name = ""; system_tid = (uintptr_t) this; yield_to_tid = 0; id = 0; // TODO: Make a thread id. process = NULL; prevsibling = NULL; nextsibling = NULL; scheduler_list_prev = NULL; scheduler_list_next = NULL; state = NONE; memset(®isters, 0, sizeof(registers)); kernelstackpos = 0; kernelstacksize = 0; signal_count = 0; signal_single_frame = 0; signal_canary = 0; kernelstackmalloced = false; pledged_destruction = false; force_no_signals = false; signal_single = false; has_saved_signal_mask = false; sigemptyset(&signal_pending); sigemptyset(&signal_mask); sigemptyset(&saved_signal_mask); memset(&signal_stack, 0, sizeof(signal_stack)); signal_stack.ss_flags = SS_DISABLE; // execute_clock initialized in member constructor. // system_clock initialized in member constructor. Time::InitializeThreadClocks(this); futex_address = 0; kutex_address = 0; futex_woken = false; kutex_woken = false; futex_prev_waiting = NULL; futex_next_waiting = NULL; kutex_prev_waiting = NULL; kutex_next_waiting = NULL; yield_operation = YIELD_OPERATION_NONE; } Thread::~Thread() { if ( process ) process->OnThreadDestruction(this); assert(CurrentThread() != this); if ( kernelstackmalloced ) delete[] (uint8_t*) kernelstackpos; } Thread* CreateKernelThread(Process* process, struct thread_registers* regs, const char* name) { assert(process && regs && process->addrspace); #if defined(__x86_64__) if ( regs->fsbase >> 48 != 0x0000 && regs->fsbase >> 48 != 0xFFFF ) return errno = EINVAL, (Thread*) NULL; if ( regs->gsbase >> 48 != 0x0000 && regs->gsbase >> 48 != 0xFFFF ) return errno = EINVAL, (Thread*) NULL; #endif kthread_mutex_lock(&process->threadlock); // Note: Only allow the process itself to make threads, except the initial // thread. This requirement is because kthread_exit() needs to know when // it's the last thread in the process (using threads_not_exiting_count), // and that no more threads will appear, so it can run some final process // termination steps without any interference. It's always allowed to create // threads in the kernel process as it never exits. assert(!process->firstthread || process == CurrentProcess() || process == Scheduler::GetKernelProcess()); Thread* thread = new Thread(); if ( !thread ) return NULL; thread->name = name; memcpy(&thread->registers, regs, sizeof(struct thread_registers)); // Create the family tree. thread->process = process; Thread* firsty = process->firstthread; if ( firsty ) firsty->prevsibling = thread; thread->nextsibling = firsty; process->firstthread = thread; process->threads_not_exiting_count++; kthread_mutex_unlock(&process->threadlock); return thread; } static void SetupKernelThreadRegs(struct thread_registers* regs, Process* process, void (*entry)(void*), void* user, uintptr_t stack, size_t stack_size) { memset(regs, 0, sizeof(*regs)); size_t stack_alignment = 16; while ( stack & (stack_alignment-1) ) { assert(stack_size); stack++; stack_size--; } stack_size &= ~(stack_alignment-1); #if defined(__i386__) uintptr_t* stack_values = (uintptr_t*) (stack + stack_size); assert(5 * sizeof(uintptr_t) <= stack_size); /* -- 16-byte aligned -- */ /* -1 padding */ stack_values[-2] = (uintptr_t) 0; /* null eip */ stack_values[-3] = (uintptr_t) 0; /* null ebp */ stack_values[-4] = (uintptr_t) user; /* thread parameter */ /* -- 16-byte aligned -- */ stack_values[-5] = (uintptr_t) kthread_exit; /* return to kthread_exit */ /* upcoming ebp */ /* -7 padding */ /* -8 padding */ /* -- 16-byte aligned -- */ regs->eip = (uintptr_t) entry; regs->esp = (uintptr_t) (stack_values - 5); regs->eax = 0; regs->ebx = 0; regs->ecx = 0; regs->edx = 0; regs->edi = 0; regs->esi = 0; regs->ebp = (uintptr_t) (stack_values - 3); regs->cs = KCS | KRPL; regs->ds = KDS | KRPL; regs->ss = KDS | KRPL; regs->eflags = FLAGS_RESERVED1 | FLAGS_INTERRUPT | FLAGS_ID; regs->kerrno = 0; regs->signal_pending = 0; regs->kernel_stack = stack + stack_size; regs->cr3 = process->addrspace; memcpy(regs->fpuenv, Float::fpu_initialized_regs, 512); #elif defined(__x86_64__) uintptr_t* stack_values = (uintptr_t*) (stack + stack_size); assert(3 * sizeof(uintptr_t) <= stack_size); stack_values[-1] = (uintptr_t) 0; /* null rip */ stack_values[-2] = (uintptr_t) 0; /* null rbp */ stack_values[-3] = (uintptr_t) kthread_exit; /* return to kthread_exit */ regs->rip = (uintptr_t) entry; regs->rsp = (uintptr_t) (stack_values - 3); regs->rax = 0; regs->rbx = 0; regs->rcx = 0; regs->rdx = 0; regs->rdi = (uintptr_t) user; regs->rsi = 0; regs->rbp = 0; regs->r8 = 0; regs->r9 = 0; regs->r10 = 0; regs->r11 = 0; regs->r12 = 0; regs->r13 = 0; regs->r14 = 0; regs->r15 = 0; regs->cs = KCS | KRPL; regs->ds = KDS | KRPL; regs->ss = KDS | KRPL; regs->rflags = FLAGS_RESERVED1 | FLAGS_INTERRUPT | FLAGS_ID; regs->kerrno = 0; regs->signal_pending = 0; regs->kernel_stack = stack + stack_size; regs->cr3 = process->addrspace; memcpy(regs->fpuenv, Float::fpu_initialized_regs, 512); #else #warning "You need to add kernel thread register initialization support" #endif } Thread* CreateKernelThread(Process* process, void (*entry)(void*), void* user, const char* name, size_t stacksize) { const size_t DEFAULT_KERNEL_STACK_SIZE = 8 * 1024UL; if ( !stacksize ) stacksize = DEFAULT_KERNEL_STACK_SIZE; uint8_t* stack = new uint8_t[stacksize]; if ( !stack ) return NULL; struct thread_registers regs; SetupKernelThreadRegs(®s, process, entry, user, (uintptr_t) stack, stacksize); Thread* thread = CreateKernelThread(process, ®s, name); if ( !thread ) { delete[] stack; return NULL; } thread->kernelstackpos = (uintptr_t) stack; thread->kernelstacksize = stacksize; thread->kernelstackmalloced = true; return thread; } Thread* CreateKernelThread(void (*entry)(void*), void* user, const char* name, size_t stacksize) { return CreateKernelThread(CurrentProcess(), entry, user, name, stacksize); } void StartKernelThread(Thread* thread) { Scheduler::SetThreadState(thread, ThreadState::RUNNABLE); } Thread* RunKernelThread(Process* process, struct thread_registers* regs, const char* name) { Thread* thread = CreateKernelThread(process, regs, name); if ( !thread ) return NULL; StartKernelThread(thread); return thread; } Thread* RunKernelThread(Process* process, void (*entry)(void*), void* user, const char* name, size_t stacksize) { Thread* thread = CreateKernelThread(process, entry, user, name, stacksize); if ( !thread ) return NULL; StartKernelThread(thread); return thread; } Thread* RunKernelThread(void (*entry)(void*), void* user, const char* name, size_t stacksize) { Thread* thread = CreateKernelThread(entry, user, name, stacksize); if ( !thread ) return NULL; StartKernelThread(thread); return thread; } int sys_exit_thread(int requested_exit_code, int flags, const struct exit_thread* user_extended) { if ( flags & ~(EXIT_THREAD_ONLY_IF_OTHERS | EXIT_THREAD_UNMAP | EXIT_THREAD_ZERO | EXIT_THREAD_TLS_UNMAP | EXIT_THREAD_PROCESS | EXIT_THREAD_DUMP_CORE | EXIT_THREAD_FUTEX_WAKE) ) return errno = EINVAL, -1; if ( (flags & EXIT_THREAD_ONLY_IF_OTHERS) && (flags & EXIT_THREAD_PROCESS) ) return errno = EINVAL, -1; Thread* thread = CurrentThread(); Process* process = CurrentProcess(); struct exit_thread extended; if ( !user_extended ) memset(&extended, 0, sizeof(extended)); else if ( !CopyFromUser(&extended, user_extended, sizeof(extended)) ) return -1; extended.unmap_size = Page::AlignUp(extended.unmap_size); kthread_mutex_lock(&thread->process->threadlock); bool is_others = false; for ( Thread* iter = thread->process->firstthread; !is_others && iter; iter = iter->nextsibling ) { if ( iter == thread ) continue; if ( iter->pledged_destruction ) continue; is_others = true; } if ( !(flags & EXIT_THREAD_ONLY_IF_OTHERS) || is_others ) thread->pledged_destruction = true; bool are_threads_exiting = false; bool do_exit = (flags & EXIT_THREAD_PROCESS) || !is_others; if ( do_exit ) process->threads_exiting = true; else if ( process->threads_exiting ) are_threads_exiting = true; kthread_mutex_unlock(&thread->process->threadlock); // Self-destruct if another thread began exiting the process. if ( are_threads_exiting ) kthread_exit(); if ( (flags & EXIT_THREAD_ONLY_IF_OTHERS) && !is_others ) return errno = ESRCH, -1; if ( flags & EXIT_THREAD_UNMAP && Page::IsAligned((uintptr_t) extended.unmap_from) && extended.unmap_size ) { ScopedLock lock(&process->segment_lock); extended.unmap_size = Page::AlignDown(extended.unmap_size); Memory::UnmapMemory(process, (uintptr_t) extended.unmap_from, extended.unmap_size); Memory::Flush(); // TODO: The segment is not actually removed! } if ( flags & EXIT_THREAD_TLS_UNMAP && Page::IsAligned((uintptr_t) extended.tls_unmap_from) && extended.tls_unmap_size ) { ScopedLock lock(&process->segment_lock); extended.tls_unmap_size = Page::AlignDown(extended.tls_unmap_size); Memory::UnmapMemory(process, (uintptr_t) extended.tls_unmap_from, extended.tls_unmap_size); Memory::Flush(); } if ( flags & EXIT_THREAD_ZERO ) ZeroUser(extended.zero_from, extended.zero_size); if ( flags & EXIT_THREAD_FUTEX_WAKE ) sys_futex((int*) extended.zero_from, FUTEX_WAKE, 1, NULL); if ( do_exit ) { // Validate the requested exit code such that the process can't exit // with an impossible exit status or that it wasn't actually terminated. int the_nature = WNATURE(requested_exit_code); int the_status = WEXITSTATUS(requested_exit_code); int the_signal = WTERMSIG(requested_exit_code); if ( the_nature == WNATURE_EXITED ) the_signal = 0; else if ( the_nature == WNATURE_SIGNALED ) { if ( the_signal == 0 /* null signal */ || the_signal == SIGSTOP || the_signal == SIGTSTP || the_signal == SIGTTIN || the_signal == SIGTTOU || the_signal == SIGCONT ) the_signal = SIGKILL; the_status = 128 + the_signal; } else { the_nature = WNATURE_SIGNALED; the_signal = SIGKILL; } requested_exit_code = WCONSTRUCT(the_nature, the_status, the_signal); thread->process->ExitWithCode(requested_exit_code); } kthread_exit(); } static void futex_timeout(Clock* /*clock*/, Timer* /*timer*/, void* ctx) { Thread* thread = (Thread*) ctx; thread->timer_woken = true; kthread_wake_futex(thread); } int sys_futex(int* user_address, int op, int value, const struct timespec* user_timeout) { ioctx_t ctx; SetupKernelIOCtx(&ctx); Thread* thread = CurrentThread(); Process* process = thread->process; if ( FUTEX_GET_OP(op) == FUTEX_WAIT ) { kthread_mutex_lock(&process->futex_lock); thread->futex_address = (uintptr_t) user_address; thread->futex_woken = false; thread->futex_prev_waiting = process->futex_last_waiting; thread->futex_next_waiting = NULL; (process->futex_last_waiting ? process->futex_last_waiting->futex_next_waiting : process->futex_first_waiting) = thread; process->futex_last_waiting = thread; kthread_mutex_unlock(&process->futex_lock); thread->timer_woken = false; Timer timer; if ( user_timeout ) { clockid_t clockid = FUTEX_GET_CLOCK(op); bool absolute = op & FUTEX_ABSOLUTE; struct timespec timeout; if ( !CopyFromUser(&timeout, user_timeout, sizeof(timeout)) ) return -1; if ( !timespec_is_canonical(timeout) ) return errno = EINVAL, -1; Clock* clock = Time::GetClock(clockid); timer.Attach(clock); struct itimerspec timerspec; timerspec.it_value = timeout; timerspec.it_interval.tv_sec = 0; timerspec.it_interval.tv_nsec = 0; int timer_flags = (absolute ? TIMER_ABSOLUTE : 0) | TIMER_FUNC_INTERRUPT_HANDLER; timer.Set(&timerspec, NULL, timer_flags, futex_timeout, thread); } int result = 0; int current; if ( !ReadAtomicFromUser(¤t, user_address) ) result = -1; else if ( current != value ) { errno = EAGAIN; result = -1; } else kthread_wait_futex_signal(); if ( user_timeout ) timer.Cancel(); kthread_mutex_lock(&process->futex_lock); if ( result == 0 && !thread->futex_woken ) { if ( Signal::IsPending() ) { errno = EINTR; result = -1; } else if ( thread->timer_woken ) { errno = ETIMEDOUT; result = -1; } } thread->futex_address = 0; thread->futex_woken = false; (thread->futex_prev_waiting ? thread->futex_prev_waiting->futex_next_waiting : process->futex_first_waiting) = thread->futex_next_waiting; (thread->futex_next_waiting ? thread->futex_next_waiting->futex_prev_waiting : process->futex_last_waiting) = thread->futex_prev_waiting; thread->futex_prev_waiting = NULL; thread->futex_next_waiting = NULL; kthread_mutex_unlock(&process->futex_lock); return result; } else if ( FUTEX_GET_OP(op) == FUTEX_WAKE ) { kthread_mutex_lock(&process->futex_lock); int result = 0; for ( Thread* waiter = process->futex_first_waiting; 0 < value && waiter; waiter = waiter->futex_next_waiting ) { if ( waiter->futex_address == (uintptr_t) user_address ) { waiter->futex_woken = true; kthread_wake_futex(waiter); if ( value != INT_MAX ) value--; if ( result != INT_MAX ) result++; } } kthread_mutex_unlock(&process->futex_lock); return result; } else return errno = EINVAL, -1; } } // namespace Sortix