From: Michalis Pappas Date: Fri, 7 Mar 2025 07:17:24 +0000 (+0100) Subject: lib/posix-process: Add exit() and exit_group() X-Git-Url: http://xenbits.xensource.com/gitweb?a=commitdiff_plain;h=7a013bf2f8d54a943ff51966dfd1632b4c4bc774;p=unikraft%2Funikraft.git lib/posix-process: Add exit() and exit_group() exit() terminates the calling thread, and the process if it's the last / only thread in the process. exitgroup() termintates the calling process leaving it in the zombie state for wait() to reap. for more info see exit(2) and exit_group(2). Internally, exit() and exit_group() issue the POSIX_THREAD_EXIT_EVENT and POSIX_PROCESS_EXIT_EVENT respectively, to notify libraries that have registered event handlers to these events. Checkpatch-Ignore: LONG_LINE Checkpatch-Ignore: MACRO_ARG_REUSE Signed-off-by: Michalis Pappas Approved-by: Andrei Tatar Reviewed-by: Andrei Tatar Reviewed-by: Sergiu Moga GitHub-Closes: #1630 --- diff --git a/lib/posix-process/Config.uk b/lib/posix-process/Config.uk index 314c7c8cd..f242b0204 100644 --- a/lib/posix-process/Config.uk +++ b/lib/posix-process/Config.uk @@ -32,14 +32,15 @@ config LIBPOSIX_PROCESS_MULTIPROCESS bool "Multiprocess support" depends on HAVE_VFS select LIBPOSIX_PROCESS_MULTITHREADING - select LIBPOSIX_PROCESS_EXECVE + select LIBUKLOCK + select LIBUKLOCK_SEMAPHORE config LIBPOSIX_PROCESS_SIGNAL bool "POSIX signals (EXPERIMENTAL)" select LIBPOSIX_PROCESS_MULTITHREADING select LIBPOSIX_PROCESS_EXECVE - select LIBSYSCALL_SHIM select LIBUKLOCK + select LIBUKLOCK_SEMAPHORE config LIBPOSIX_PROCESS_SIGNALFD bool "signalfd4() and signalfd() syscalls" diff --git a/lib/posix-process/Makefile.uk b/lib/posix-process/Makefile.uk index ef5000150..4d47fdb70 100644 --- a/lib/posix-process/Makefile.uk +++ b/lib/posix-process/Makefile.uk @@ -10,6 +10,7 @@ CXXINCLUDES-$(CONFIG_LIBPOSIX_PROCESS) += $(LIBPOSIX_PROCESS_COMMON_INCLUDES-y) LIBPOSIX_PROCESS_CFLAGS-$(CONFIG_LIBPOSIX_PROCESS_DEBUG) += -DUK_DEBUG LIBPOSIX_PROCESS_SRCS-y += $(LIBPOSIX_PROCESS_BASE)/deprecated.c +LIBPOSIX_PROCESS_SRCS-y += $(LIBPOSIX_PROCESS_BASE)/exit.c LIBPOSIX_PROCESS_SRCS-y += $(LIBPOSIX_PROCESS_BASE)/process.c LIBPOSIX_PROCESS_SRCS-y += $(LIBPOSIX_PROCESS_BASE)/wait.c @@ -81,4 +82,4 @@ UK_PROVIDED_SYSCALLS-$(CONFIG_LIBPOSIX_PROCESS) += prctl-5 UK_PROVIDED_SYSCALLS-$(CONFIG_LIBPOSIX_PROCESS_EXECVE) += execve-3e UK_PROVIDED_SYSCALLS-$(CONFIG_LIBPOSIX_PROCESS_MULTIPROCESS) += vfork-0e UK_PROVIDED_SYSCALLS-$(CONFIG_LIBPOSIX_PROCESS_MULTITHREADING) += clone-5e -UK_PROVIDED_SYSCALLS-$(CONFIG_LIBPOSIX_PROCESS_MULTITHREADING) += exit-1 exit_group-1 +UK_PROVIDED_SYSCALLS-$(CONFIG_LIBPOSIX_PROCESS) += exit-1 exit_group-1 diff --git a/lib/posix-process/execve.c b/lib/posix-process/execve.c index 55d48e04a..0ee8eb51e 100644 --- a/lib/posix-process/execve.c +++ b/lib/posix-process/execve.c @@ -36,7 +36,7 @@ static void __noreturn execve_ctx_switch(long arg0, long arg1) this_thread = uk_thread_current(); UK_ASSERT(this_thread); - pthread = tid2pthread(ukthread2tid(this_thread)); /* FIXME */ + pthread = uk_pthread_current(); UK_ASSERT(pthread); pthread_parent = pthread->parent; @@ -69,10 +69,16 @@ switch_ctx: /* Prepare process for executing new context. For a complete list see * "Effect on process attributes" in execve(2). */ -static int pprocess_cleanup(struct uk_thread *thread __maybe_unused) +static int pprocess_cleanup(struct posix_process *pprocess) { - /* Kill this thread's siblings */ - pprocess_kill_siblings(thread); + struct posix_thread *pt, *ptn; + + /* Kill all threads of this process except current */ + uk_pprocess_foreach_pthread(pprocess, pt, ptn) { + if (pt->thread == uk_thread_current()) + continue; + pprocess_exit_pthread(pt, POSIX_THREAD_EXITED, 0); + } return 0; } @@ -169,7 +175,7 @@ UK_SYSCALL_R_E_DEFINE(int, execve, const char *, pathname, uk_pr_err("execve event error (%d)\n", rc); goto err_free_stack_new; } - pprocess_cleanup(this_thread); + pprocess_cleanup(uk_pprocess_current()); /* Prepare switch to the new context. * diff --git a/lib/posix-process/exit.c b/lib/posix-process/exit.c new file mode 100644 index 000000000..d07bb3a2a --- /dev/null +++ b/lib/posix-process/exit.c @@ -0,0 +1,315 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* Copyright (c) 2024, Unikraft GmbH and The Unikraft Authors. + * Licensed under the BSD-3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#if CONFIG_LIBPOSIX_PROCESS_SIGNAL +#include "signal/signal.h" +#include "signal.h" +#endif /* CONFIG_LIBPOSIX_PROCESS_SIGNAL */ + +#include "process.h" + +#define PTHREAD_WAITING_FOR_PID(_pt, _pid) \ + ((_pt)->state == POSIX_THREAD_BLOCKED_WAIT && \ + ((_pt)->wait_pid == (_pid) || (_pt)->wait_pid == -1)) + +#if CONFIG_LIBPOSIX_PROCESS_SIGNAL +#define PTHREAD_BLOCKING_ON_SIGNAL(_pt) \ + ((_pt)->state == POSIX_THREAD_BLOCKED_SIGNAL) +#else /* !CONFIG_LIBPOSIX_PROCESS_SIGNAL */ +#define PTHREAD_BLOCKING_ON_SIGNAL(_pt) 0 +#endif /* !CONFIG_LIBPOSIX_PROCESS_SIGNAL */ + +UK_EVENT(POSIX_PROCESS_EXIT_EVENT); + +int pprocess_exit_status; + +#if CONFIG_LIBPOSIX_PROCESS_MULTITHREADING + +/* pprocess exit state to pthread exit state */ +#define PP2PT_EXIT_STATE(_pp_state) \ + ((_pp_state) == POSIX_PROCESS_EXITED ? POSIX_THREAD_EXITED : \ + POSIX_THREAD_KILLED) + +static void pprocess_reparent_children(struct posix_process *pprocess) +{ + struct posix_process *pchild, *pchildn; + struct posix_process *pprocess_init; + + pprocess_init = pid2pprocess(UK_PID_INIT); + + uk_list_for_each_entry_safe(pchild, pchildn, &pprocess->children, + child_list_entry) { + uk_list_del(&pchild->child_list_entry); + if (pprocess_init && pprocess != pprocess_init) { + pchild->parent = pprocess_init; + uk_list_add(&pchild->child_list_entry, + &pprocess_init->children); + uk_pr_debug("Process PID %d re-assigned to PID %d\n", + pchild->pid, pchild->parent->pid); + } else { + /* There is no parent, disconnect */ + pchild->parent = NULL; + uk_pr_debug("Process PID %d loses its parent\n", + pchild->pid); + } + } +} + +#if CONFIG_LIBPOSIX_PROCESS_SIGNAL +static inline int signal_exit(struct posix_process *pprocess) +{ + struct kern_sigaction *ks; + + UK_ASSERT(pprocess); + UK_ASSERT(pprocess->signal); + + ks = KERN_SIGACTION(pprocess, SIGCHLD); + if (ks->ks_handler == SIG_IGN || ks->ks_flags & SA_NOCLDWAIT) + return 1; + + return pprocess_signal_send(pprocess, SIGCHLD, NULL); +} +#endif /* CONFIG_LIBPOSIX_PROCESS_SIGNAL */ + +/* Exit AND release pthread. + * Notice: Unlike pprocesses, pthreads are released upon exit. + */ +void pprocess_exit_pthread(struct posix_thread *pthread, + enum posix_thread_state state, + int exit_status __unused) +{ + struct posix_process_exit_event_data event_data; + struct posix_process *pprocess; + struct posix_thread *parent_pthread; + struct uk_thread *thread; + int ret; + + UK_ASSERT(state == POSIX_THREAD_EXITED || + state == POSIX_THREAD_KILLED); + + UK_ASSERT(pthread); + UK_ASSERT(pthread->state != POSIX_THREAD_EXITED && + pthread->state != POSIX_THREAD_KILLED); + + pprocess = pthread->process; + UK_ASSERT(pprocess); + UK_ASSERT(pprocess->state == POSIX_PROCESS_RUNNING); + + uk_pr_debug("pid %d: exit tid %d\n", pprocess->pid, pthread->tid); + + /* May be NULL if init terminated early */ + parent_pthread = pthread->parent; + + /* Update state */ + pthread->state = state; + + /* Raise event */ + event_data.thread = pthread->thread; + event_data.tid = pthread->tid; + event_data.pid = pprocess->pid; + ret = uk_raise_event(POSIX_PROCESS_EXIT_EVENT, &event_data); + if (unlikely(ret < 0)) + UK_CRASH("POSIX_PROCESS_EXIT_EVENT failed with %d\n", ret); + + /* Wake up parent if it was blocking on vfork */ + if (parent_pthread && + parent_pthread->state == POSIX_THREAD_BLOCKED_VFORK) { + uk_thread_wake(parent_pthread->thread); + parent_pthread->state = POSIX_THREAD_RUNNING; + } + + /* Release pthread and terminate the undelying uk_thread + * unless it's the current one. + */ + thread = pthread->thread; + pprocess_release_pthread(pthread); + if (thread != uk_thread_current()) + uk_sched_thread_terminate(thread); +} + +void pprocess_exit(struct posix_process *pprocess, + enum posix_process_state state, + int exit_status) +{ + struct posix_process_exit_event_data event_data; + struct posix_process *parent_process; + struct posix_thread *pt, *ptn; + __bool nowait = false; + int ret; + + UK_ASSERT(state == POSIX_PROCESS_EXITED || + state == POSIX_PROCESS_KILLED); + + UK_ASSERT(pprocess); + UK_ASSERT(pprocess->state == POSIX_PROCESS_RUNNING); + + uk_pr_debug("pid %d: exit process\n", pprocess->pid); + + /* May be NULL if init terminated early */ + parent_process = pprocess->parent; + +#if CONFIG_LIBPOSIX_PROCESS_SIGNAL + /* As this will be signaling the parent on behalf of the current + * pthread, we must do this before we terminate the pthreads, in + * case we are called from _exit(), exit_group() (i.e. operate on + * current). + */ + if (parent_process) { + ret = signal_exit(parent_process); + if (ret > 0) { + /* From exit(2): "If the parent has set SA_NOCLDWAIT, or + * has set the SIGCHLD handler to SIG_IGN, the status is + * discarded and the child dies immediately." + */ + uk_pr_info("Parent ignores SIGHLD, terminating\n"); + nowait = true; + } else if (unlikely(ret < 0)) { /* no choice here but crash */ + UK_CRASH("Could not signal parent (%d)\n", ret); + } + } +#endif /* CONFIG_LIBPOSIX_PROCESS_SIGNAL */ + + /* Terminate pthreads. + * + * This must happen before we unblock wait(), so that threads + * have already been released when we reap the process. + */ + uk_pprocess_foreach_pthread(pprocess, pt, ptn) + pprocess_exit_pthread(pt, PP2PT_EXIT_STATE(state), exit_status); + + /* ---> No ops at the posix level on behalf of the current <--- + * ---> thread from this point <--- + */ + UK_ASSERT(uk_list_empty(&pprocess->threads)); + + /* Update process state & exit_status*/ + if (state == POSIX_PROCESS_EXITED) + pprocess->exit_status = exit_status & 0xff; + else /* POSIX_PROCESS_KILLED */ + pprocess->exit_status = exit_status; + pprocess->state = state; + + /* Reparent child processes to init */ + pprocess_reparent_children(pprocess); + + /* Notify handlers */ + event_data.thread = NULL; + event_data.pid = pprocess->pid; + ret = uk_raise_event(POSIX_PROCESS_EXIT_EVENT, &event_data); + if (unlikely(ret < 0)) + UK_CRASH("POSIX_PROCESS_EXIT_EVENT handler returned error\n"); + + uk_semaphore_up(&pprocess->exit_semaphore); + + /* Unblock wait */ + if (parent_process && !nowait) { + uk_pprocess_foreach_pthread(parent_process, pt, ptn) { + if (PTHREAD_WAITING_FOR_PID(pt, uk_sys_getpid()) || + PTHREAD_BLOCKING_ON_SIGNAL(pt)) { + uk_semaphore_up(&parent_process->wait_semaphore); + break; + } + } + } else if (nowait) { /* release now */ + pprocess_release(pprocess); + } +} + +/* NOTE: The man pages of _exit(2) say: + * "In glibc up to version 2.3, the _exit() wrapper function invoked + * the kernel system call of the same name. Since glibc 2.3, the + * wrapper function invokes exit_group(2), in order to terminate all + * of the threads in a process. + * The raw _exit() system call terminates only the calling thread, + * and actions such as reparenting child processes or sending + * SIGCHLD to the parent process are performed only if this is the + * last thread in the thread group." + */ +UK_LLSYSCALL_R_DEFINE(int, exit, int, status) +{ + struct posix_thread *this_pthread; + struct uk_thread *this_thread; + + this_thread = uk_thread_current(); + this_pthread = uk_thread_uktls_var(this_thread, pthread_self); + + UK_ASSERT(this_pthread); + UK_ASSERT(this_pthread->process); + + /* Last thread, exit the process */ + if (uk_list_is_singular(&this_pthread->process->threads)) { + pprocess_exit(this_pthread->process, POSIX_PROCESS_EXITED, + status); + uk_sched_thread_exit(); + } + + pprocess_exit_pthread(this_pthread, POSIX_THREAD_EXITED, status); + uk_sched_thread_exit(); +} + +UK_LLSYSCALL_R_DEFINE(int, exit_group, int, status) +{ + struct posix_process *pprocess; + + pprocess = uk_pprocess_current(); + UK_ASSERT(pprocess); + + pprocess_exit(pprocess, POSIX_PROCESS_EXITED, status); + uk_sched_thread_exit(); +} +#else /* !CONFIG_LIBPOSIX_PROCESS_MULTITHREADING */ +void pprocess_exit_stub(int status) +{ + if (pprocess_thread_main) { + pprocess_exit_status = status; + uk_sched_thread_exit(); /* noreturn */ + } + + UK_CRASH("Invalid system configuration:\n" + "Process support without multithreading does not support _exit() / exit_group()\n" + "Select LIBPOSIX_PROCESS_MULTITHREADING, or at minimum LIBUKBOOT_MAINTHREAD\n"); +} + +UK_LLSYSCALL_R_DEFINE(int, exit, int, status) +{ + pprocess_exit_stub(status); + UK_BUG(); /* noreturn */ +} + +UK_LLSYSCALL_R_DEFINE(int, exit_group, int, status) +{ + pprocess_exit_stub(status); + UK_BUG(); /* noreturn */ +} +#endif /* !CONFIG_LIBPOSIX_PROCESS_MULTITHREADING */ + +#if UK_LIBC_SYSCALLS +__noreturn void exit(int status) +{ + /* According to _exit(2): "Since glibc 2.3, the wrapper function invokes + * exit_group(2), in order to terminate all of the threads in a + * process." + */ + uk_syscall_e_exit_group(status); + UK_BUG(); /* noreturn */ +} + +__noreturn void exit_group(int status) +{ + uk_syscall_e_exit_group(status); + UK_BUG(); /* noreturn */ +} +#endif /* UK_LIBC_SYSCALLS */ diff --git a/lib/posix-process/include/uk/process.h b/lib/posix-process/include/uk/process.h index df322fdb4..dd48bf500 100644 --- a/lib/posix-process/include/uk/process.h +++ b/lib/posix-process/include/uk/process.h @@ -235,4 +235,16 @@ struct posix_process_execve_event_data { #endif /* CONFIG_LIBPOSIX_PROCESS_EXECVE */ +/* Data delivered to the handlers of POSIX_PROCESS_EXIT_EVENT. + * This event is triggered upon both pthread and pprocess exit. + * - pthread exit: all fields are populated. + * - process exit: thread is set to NULL and the value of + * tid is undefined. + */ +struct posix_process_exit_event_data { + struct uk_thread *thread; + pid_t pid; + pid_t tid; +}; + #endif /* __UK_PROCESS_H__ */ diff --git a/lib/posix-process/process.c b/lib/posix-process/process.c index ac2ce44b8..dcd811e98 100644 --- a/lib/posix-process/process.c +++ b/lib/posix-process/process.c @@ -1,37 +1,11 @@ /* SPDX-License-Identifier: BSD-3-Clause */ /* - * Authors: Simon Kuenzer - * Felipe Huici - * Costin Lupu - * * Copyright (c) 2017, NEC Europe Ltd., NEC Corporation. All rights reserved. * Copyright (c) 2022, NEC Laboratories Europe GmbH, NEC Corporation. * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the copyright holder nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. + * Copyright (c) 2024, Unikraft GmbH and The Unikraft Authors. + * Licensed under the BSD-3-Clause License (the "License"). + * You may not use this file except in compliance with the License. */ #include @@ -194,13 +168,17 @@ err_out: } /* Free thread that is part of a process - * NOTE: The process is not free'd here when its thread list + * NOTE: The process is not released here when its thread list * becomes empty. */ -static void pprocess_release_pthread(struct posix_thread *pthread) +void pprocess_release_pthread(struct posix_thread *pthread) { UK_ASSERT(pthread); UK_ASSERT(pthread->_a); + UK_ASSERT(pthread->process); + + uk_pr_debug("pid %d: Release tid %d\n", + pthread->process->pid, pthread->tid); #if CONFIG_LIBPOSIX_PROCESS_SIGNAL pprocess_signal_tdesc_free(pthread); @@ -233,7 +211,6 @@ int uk_posix_process_create_pthread(struct uk_thread *thread) return 0; } -static void pprocess_release(struct posix_process *pprocess); /* Create a new posix process for a given thread */ int pprocess_create(struct uk_alloc *a, @@ -274,6 +251,8 @@ int pprocess_create(struct uk_alloc *a, UK_INIT_LIST_HEAD(&pprocess->threads); UK_INIT_LIST_HEAD(&pprocess->children); + pprocess->state = POSIX_PROCESS_RUNNING; + #if CONFIG_LIBPOSIX_PROCESS_SIGNAL ret = pprocess_signal_pdesc_alloc(pprocess); if (unlikely(ret)) { @@ -291,6 +270,9 @@ int pprocess_create(struct uk_alloc *a, } #endif /* CONFIG_LIBPOSIX_PROCESS_SIGNAL */ + uk_semaphore_init(&pprocess->wait_semaphore, 0); + uk_semaphore_init(&pprocess->exit_semaphore, 0); + /* Check if we have a pthread structure already for this thread * or if we need to allocate one */ @@ -361,131 +343,33 @@ err_out: return ret; } -/* Releases pprocess memory and re-links its child to the parent - * NOTE: All related threads must be removed already from this pprocess +/* Releases pprocess memory and other resources. + * NOTE: All pthreads must be removed already + * from this pprocess. All chilren must + * be already reparented. */ -static void pprocess_release(struct posix_process *pprocess) +void pprocess_release(struct posix_process *pprocess) { - struct posix_process *pchild, *pchildn; + pid_t pid; + UK_ASSERT(pprocess); UK_ASSERT(uk_list_empty(&pprocess->threads)); + UK_ASSERT(uk_list_empty(&pprocess->children)); - uk_list_for_each_entry_safe(pchild, pchildn, - &pprocess->children, - child_list_entry) { - /* check for violation of the tree structure */ - UK_ASSERT(pchild != pprocess); - - uk_list_del(&pchild->child_list_entry); - if (pprocess->parent) { - pchild->parent = pprocess->parent; - uk_list_add(&pchild->child_list_entry, - &pprocess->parent->children); - uk_pr_debug("Process PID %d re-assigned to parent PID %d\n", - pchild->pid, pprocess->parent->pid); - } else { - /* There is no parent, disconnect */ - pchild->parent = NULL; - uk_pr_debug("Process PID %d loses its parent\n", - pchild->pid); - } - } + /* Unlink this process from its parent */ + if (pprocess->parent) + uk_list_del(&pprocess->child_list_entry); #if CONFIG_LIBPOSIX_PROCESS_SIGNAL pprocess_signal_pdesc_free(pprocess); #endif /* CONFIG_LIBPOSIX_PROCESS_SIGNAL */ - pid_process[pprocess->pid] = NULL; + pid = pprocess->pid; - uk_pr_debug("Process PID %d released\n", - pprocess->pid); + pid_process[pid] = NULL; uk_free(pprocess->_a, pprocess); -} - -void pprocess_kill_siblings(struct uk_thread *thread) -{ - struct posix_thread *pthread, *pthreadn; - struct posix_thread *this_thread; - struct posix_process *pprocess; - pid_t this_tid; - - this_tid = ukthread2tid(thread); - this_thread = tid2pthread(this_tid); - - pprocess = this_thread->process; - UK_ASSERT(pprocess); - - /* Kill all remaining threads of the process */ - uk_list_for_each_entry_safe(pthread, pthreadn, - &pprocess->threads, thread_list_entry) { - if (pthread->tid == this_tid) - continue; - - /* If this thread is already exited it may - * be waiting to be garbage-collected. - */ - if (uk_thread_is_exited(pthread->thread)) - continue; - - uk_pr_debug("Terminating siblings of tid: %d (pid: %d): Killing TID %d: thread %p (%s)...\n", - this_thread->tid, pprocess->pid, - pthread->tid, pthread->thread, - pthread->thread->name); - - /* Terminating the thread will lead to calling - * `posix_thread_fini()` which will clean-up the related - * pthread resources and pprocess resources on the last - * thread - */ - uk_sched_thread_terminate(pthread->thread); - } -} - -void pprocess_kill(struct posix_process *pprocess) -{ - struct posix_thread *pthread, *pthreadn, *pthread_self = NULL; - - /* Kill all remaining threads of the process */ - uk_list_for_each_entry_safe(pthread, pthreadn, - &pprocess->threads, thread_list_entry) { - /* Double-check that this thread is part of this process */ - UK_ASSERT(pthread->process == pprocess); - - if (pthread->thread == uk_thread_current()) { - /* Self-destruct this thread as last work of this - * function. The reason is that nothing of this - * function is executed anymore as soon as the - * thread killed itself. - */ - pthread_self = pthread; - continue; - } - if (uk_thread_is_exited(pthread->thread)) { - /* Thread already exited, might wait for getting - * garbage collected. - */ - continue; - } - - uk_pr_debug("Terminating PID %d: Killing TID %d: thread %p (%s)...\n", - pprocess->pid, pthread->tid, - pthread->thread, pthread->thread->name); - - /* Terminating the thread will lead to calling - * `posix_thread_fini()` which will clean-up the related - * pthread resources and pprocess resources on the last - * thread - */ - uk_sched_thread_terminate(pthread->thread); - } - if (pthread_self) { - uk_pr_debug("Terminating PID %d: Self-killing TID %d...\n", - pprocess->pid, pthread_self->tid); - uk_sched_thread_terminate(uk_thread_current()); - - /* NOTE: Nothing will be executed from here on */ - } + uk_pr_debug("pid %d released\n", pid); } static int posix_process_init(struct uk_init_ctx *ictx) @@ -513,25 +397,29 @@ static int posix_process_init(struct uk_init_ctx *ictx) uk_late_initcall(posix_process_init, 0x0); /* Thread release: Release TID and posix_thread */ -static void posix_thread_fini(struct uk_thread *child) +static void posix_thread_fini(struct uk_thread *thread) { struct posix_process *pprocess; + struct posix_thread *pthread; - if (!pthread_self) - return; /* no posix thread was assigned */ + pthread = uk_thread_uktls_var(thread, pthread_self); - pprocess = pthread_self->process; + if (!pthread) + return; /* no posix thread was assigned */ + pprocess = pthread->process; UK_ASSERT(pprocess); - uk_pr_debug("thread %p (%s): Releasing thread with TID: %d (PID: %d)\n", - child, child->name, (int) pthread_self->tid, - (int) pprocess->pid); - pprocess_release_pthread(pthread_self); + /* Perform thread termination and relase the thread */ + pprocess_exit_pthread(pthread_self, POSIX_THREAD_EXITED, 0); - /* Release process if it became empty of threads */ - if (uk_list_empty(&pprocess->threads)) - pprocess_release(pprocess); + /* If last thread, also release the process */ + if (uk_list_empty(&pprocess->threads)) { + pprocess_exit(pprocess, POSIX_PROCESS_EXITED, 0); + /* UK_PID_INIT cannot be waited, release here */ + if (pprocess->pid == UK_PID_INIT) + pprocess_release(pprocess); + } } UK_THREAD_INIT_PRIO(0, posix_thread_fini, UK_PRIO_EARLIEST); @@ -633,49 +521,6 @@ pid_t uk_sys_getppid(void) return pthread_self->process->parent->pid; } - /* NOTE: The man pages of _exit(2) say: - * "In glibc up to version 2.3, the _exit() wrapper function invoked - * the kernel system call of the same name. Since glibc 2.3, the - * wrapper function invokes exit_group(2), in order to terminate all - * of the threads in a process. - * The raw _exit() system call terminates only the calling thread, - * and actions such as reparenting child processes or sending - * SIGCHLD to the parent process are performed only if this is the - * last thread in the thread group." - */ -UK_LLSYSCALL_R_DEFINE(int, exit, int, status) -{ - uk_sched_thread_exit(); /* won't return */ - UK_CRASH("sys_exit() unexpectedly returned\n"); - return -EFAULT; -} - -static int pprocess_exit(int status __unused) -{ - pprocess_kill(uk_pprocess_current()); /* won't return */ - UK_CRASH("sys_exit_group() unexpectedly returned\n"); - return -EFAULT; -} - -UK_LLSYSCALL_R_DEFINE(int, exit_group, int, status) -{ - return pprocess_exit(status); -} - -#if UK_LIBC_SYSCALLS -__noreturn void exit(int status) -{ - pprocess_exit(status); - UK_CRASH("sys_exit_group() unexpectedly returned\n"); -} - -__noreturn void exit_group(int status) -{ - pprocess_exit(status); - UK_CRASH("sys_exit_group() unexpectedly returned\n"); -} -#endif /* UK_LIBC_SYSCALLS */ - /* Store child PID at given location for parent */ static int pprocess_parent_settid(const struct clone_args *cl_args, size_t cl_args_len __unused, diff --git a/lib/posix-process/process.h b/lib/posix-process/process.h index 5c060ea21..70996a67d 100644 --- a/lib/posix-process/process.h +++ b/lib/posix-process/process.h @@ -1,34 +1,9 @@ /* SPDX-License-Identifier: BSD-3-Clause */ -/* - * Authors: Simon Kuenzer - * - * Copyright (c) 2022, NEC Laboratories Europe GmbH, NEC Corporation. +/* Copyright (c) 2022, NEC Laboratories Europe GmbH, NEC Corporation. * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the copyright holder nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. + * Copyright (c) 2024, Unikraft GmbH and The Unikraft Authors. + * Licensed under the BSD-3-Clause License (the "License"). + * You may not use this file except in compliance with the License. */ #ifndef __PROCESS_H_INTERNAL__ #define __PROCESS_H_INTERNAL__ @@ -39,11 +14,14 @@ #if CONFIG_LIBPOSIX_PROCESS_MULTITHREADING #include #include +#include #include #endif /* CONFIG_LIBPOSIX_PROCESS_MULTITHREADING */ extern struct uk_thread *pprocess_thread_main; +extern int pprocess_exit_status; + #if CONFIG_LIBPOSIX_PROCESS_MULTITHREADING #define UK_PID_INIT 1 @@ -64,6 +42,12 @@ enum posix_thread_state { POSIX_THREAD_KILLED, /* terminated by signal */ }; +enum posix_process_state { + POSIX_PROCESS_RUNNING, /* not terminated */ + POSIX_PROCESS_EXITED, /* terminated normally */ + POSIX_PROCESS_KILLED, /* terminated by signal */ +}; + struct posix_process { pid_t pid; struct posix_process *parent; @@ -74,6 +58,10 @@ struct posix_process { #if CONFIG_LIBPOSIX_PROCESS_SIGNAL struct uk_signal_pdesc *signal; #endif /* CONFIG_LIBPOSIX_PROCESS_SIGNAL */ + struct uk_semaphore wait_semaphore; + struct uk_semaphore exit_semaphore; + enum posix_process_state state; + int exit_status; /* TODO: Mutex */ }; @@ -120,9 +108,64 @@ struct posix_process *tid2pprocess(pid_t tid); pid_t ukthread2tid(struct uk_thread *thread); pid_t ukthread2pid(struct uk_thread *thread); -void pprocess_kill(struct posix_process *pprocess); +/** + * INTERNAL. Exit and release a pthread + * + * Performs termination tasks and raises the POSIX_PROCESS_EXIT_EVENT. + * Unlike processes, pthreads are released upon exit. Besides the pthread, + * this function terminates the underlying uk_thread, unless called on + * uk_thread_current()'s pthread. In this last case, the caller should + * take care of terminating the underlying uk_thread, as needed. + * + * @param pthread The terminating thread. + * @param state The terminating thread's state. Must be either + * POSIX_THREAD_STATE_EXITED if terminated volunatrily, or + * POSIX_THREAD_STATE_KILLED if terminated by signal. + * @param exit_status The exit status of the terminating thread. + */ +void pprocess_exit_pthread(struct posix_thread *pthread, + enum posix_thread_state state, + int exit_status); +/** + * INTERNAL. Exit process + * + * Performs process termination tasks and raises POSIX_PROCESS_EXIT_EVENT. + * Does not release the process. Upon completion, if the process has not been + * reaped by an existing wait, the process becomes a zombie and its resources + * remain allocated until reaped by a subsequent call to wait(). + * + * Notice: It's the caller's responsibility to terminate the current uk_thread + * upon return, if the calling thread is a member of the terminating + * process. + * + * @param pprocess The terminating process. + * @param state The new process state. Must be either + * POSIX_PROCESS_STATE_EXITED if terminated volunatrily, or + * POSIX_PROCESS_STATE_KILLED if terminated by signal. + * @param exit_status The exit status of the terminating process. + */ +void pprocess_exit(struct posix_process *process, + enum posix_process_state state, + int exit_status); + +/** + * INTERNAL. Release pthread + * + * Releases thread resources. + * + * @param pthread The thread to release. + */ +void pprocess_release_pthread(struct posix_thread *pthread); -void pprocess_kill_siblings(struct uk_thread *thread); +/** + * INTERNAL. Release process + * + * Releases process resources and removes from process table. + * Requires that all pthreads are already terminated. + * + * @param pprocess The process to release. + */ +void pprocess_release(struct posix_process *pprocess); /** * INTERNAL. Create pthread