]> xenbits.xensource.com Git - unikraft/unikraft.git/commitdiff
lib/posix-process: Add exit() and exit_group()
authorMichalis Pappas <michalis@unikraft.io>
Fri, 7 Mar 2025 07:17:24 +0000 (08:17 +0100)
committerUnikraft Bot <monkey@unikraft.io>
Wed, 30 Apr 2025 09:56:07 +0000 (09:56 +0000)
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 <michalis@unikraft.io>
Approved-by: Andrei Tatar <andrei@unikraft.io>
Reviewed-by: Andrei Tatar <andrei@unikraft.io>
Reviewed-by: Sergiu Moga <sergiu@unikraft.io>
GitHub-Closes: #1630

lib/posix-process/Config.uk
lib/posix-process/Makefile.uk
lib/posix-process/execve.c
lib/posix-process/exit.c [new file with mode: 0644]
lib/posix-process/include/uk/process.h
lib/posix-process/process.c
lib/posix-process/process.h

index 314c7c8cd2367b09980f33478c6f7771628446b5..f242b0204032dfa3754115ebcdb5ee53032b0c69 100644 (file)
@@ -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"
index ef5000150aead9158b475ee4b630e6dd1955115f..4d47fdb700edf6a2eeb9e50077998674085beddc 100644 (file)
@@ -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
index 55d48e04ab44dfa58862bebf20bb2ecc104ff345..0ee8eb51e5e91ce45c6c8f66380d75047434dd1e 100644 (file)
@@ -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 (file)
index 0000000..d07bb3a
--- /dev/null
@@ -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 <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 */
index df322fdb409690427f1b13853f7ac747143b9522..dd48bf5008b0fe30496dab4a732b5884d056cb81 100644 (file)
@@ -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__ */
index ac2ce44b8ae93323d29d461d997b83affdc9ecb7..dcd811e980e90a2119fdb3ea6948e3bb5723b1ee 100644 (file)
@@ -1,37 +1,11 @@
 /* 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>
@@ -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,
index 5c060ea21ab7549320e4ecd45f0bf48e296807e0..70996a67d40407ebb8215773066f295cd3ea2b5e 100644 (file)
@@ -1,34 +1,9 @@
 /* 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
@@ -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