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"
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
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
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;
/* 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;
}
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.
*
--- /dev/null
+/* 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 <errno.h>
+#include <sys/types.h>
+#include <stddef.h>
+
+#include <uk/config.h>
+#include <uk/event.h>
+#include <uk/process.h>
+#include <uk/sched.h>
+#include <uk/syscall.h>
+
+#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 */
#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__ */
/* SPDX-License-Identifier: BSD-3-Clause */
/*
- * Authors: Simon Kuenzer <simon.kuenzer@neclab.eu>
- * Felipe Huici <felipe.huici@neclab.eu>
- * Costin Lupu <costin.lupu@cs.pub.ro>
- *
* 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 <uk/config.h>
}
/* 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);
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,
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)) {
}
#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
*/
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)
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);
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,
/* SPDX-License-Identifier: BSD-3-Clause */
-/*
- * Authors: Simon Kuenzer <simon.kuenzer@neclab.eu>
- *
- * 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__
#if CONFIG_LIBPOSIX_PROCESS_MULTITHREADING
#include <linux/sched.h>
#include <uk/arch/ctx.h>
+#include <uk/semaphore.h>
#include <uk/thread.h>
#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
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;
#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 */
};
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