From ce60c73b4f6aea25c92aa32dab467103095fa84b Mon Sep 17 00:00:00 2001 From: Philippe Aubertin <39178965+phaubertin@users.noreply.github.com> Date: Fri, 25 Oct 2024 19:44:16 -0400 Subject: [PATCH] Decouple thread startup from thread creation (#72) * Have the `CREATE_THREAD` system call bind the thread it creates to a descriptor. * Decouple starting a thread (`START_THREAD`) from creating a thread (`CREATE_THREAD`). This way, a process can be given the permissions needed to manage a thread pool but not to create threads itself, to allow a system service to control resource usage. * Add `JOIN_THREAD` system call. --- COPYING | 2 +- devel/qemu/check-log.sh | 6 ++ doc/syscalls/README.md | 6 +- doc/syscalls/create-endpoint.md | 4 +- doc/syscalls/create-thread.md | 33 ++++--- doc/syscalls/dup.md | 17 +++- doc/syscalls/exit-thread.md | 34 +++++-- doc/syscalls/get-thread-local.md | 12 +-- doc/syscalls/join-thread.md | 54 +++++++++++ doc/syscalls/mclone.md | 5 + doc/syscalls/mint.md | 72 ++++++++++---- doc/syscalls/mmap.md | 19 ++-- doc/syscalls/receive.md | 3 + doc/syscalls/send.md | 3 + doc/syscalls/start-thread.md | 55 +++++++++++ include/errno.h | 6 ++ include/jinue/jinue.h | 14 ++- include/jinue/shared/asm/errno.h | 9 ++ include/jinue/shared/asm/permissions.h | 13 ++- include/jinue/shared/asm/syscalls.h | 6 ++ .../machine.h => include/jinue/threads.h | 26 +++-- include/kernel/application/syscalls.h | 6 +- include/kernel/domain/entities/thread.h | 12 +-- include/kernel/domain/services/exec.h | 1 + include/kernel/domain/services/ipc.h | 2 +- include/kernel/machine/thread.h | 2 +- include/kernel/types.h | 11 ++- kernel/Makefile | 2 + kernel/application/kmain.c | 19 +++- kernel/application/syscalls/create_endpoint.c | 2 +- kernel/application/syscalls/create_thread.c | 33 +++++-- kernel/application/syscalls/exit_thread.c | 24 ++++- kernel/application/syscalls/join_thread.c | 80 ++++++++++++++++ kernel/application/syscalls/mclone.c | 2 +- kernel/application/syscalls/start_thread.c | 71 ++++++++++++++ kernel/domain/entities/endpoint.c | 4 +- kernel/domain/entities/object.c | 1 + kernel/domain/entities/process.c | 3 +- kernel/domain/entities/thread.c | 60 +++++++----- kernel/domain/services/exec.c | 8 +- kernel/domain/services/ipc.c | 22 ++--- kernel/infrastructure/i686/thread.c | 2 +- kernel/infrastructure/i686/thread_switch.asm | 16 ++-- kernel/interface/syscalls.c | 88 ++++++++++++----- userspace/lib/jinue/Makefile | 4 +- userspace/lib/jinue/i686/stubs.asm | 28 ++++++ userspace/lib/jinue/i686/syscalls.c | 17 ---- userspace/lib/jinue/syscalls.c | 94 +++++++++++++----- userspace/lib/jinue/threads.c | 96 +++++++++++++++++++ userspace/lib/libc/crt.asm | 1 + userspace/lib/libc/string.c | 8 +- userspace/loader/loader.c | 26 ++++- userspace/testapp/testapp.c | 2 +- userspace/testapp/tests/ipc.c | 69 ++++++++----- 54 files changed, 954 insertions(+), 261 deletions(-) create mode 100644 doc/syscalls/join-thread.md create mode 100644 doc/syscalls/start-thread.md rename userspace/lib/jinue/machine.h => include/jinue/threads.h (77%) create mode 100644 kernel/application/syscalls/join_thread.c create mode 100644 kernel/application/syscalls/start_thread.c create mode 100644 userspace/lib/jinue/threads.c diff --git a/COPYING b/COPYING index 88b8d335..d0790ccd 100644 --- a/COPYING +++ b/COPYING @@ -1,4 +1,4 @@ -Copyright (C) 2019-2022 Philippe Aubertin. +Copyright (C) 2019-2024 Philippe Aubertin. All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/devel/qemu/check-log.sh b/devel/qemu/check-log.sh index a4b9a20f..5f9aa6e7 100755 --- a/devel/qemu/check-log.sh +++ b/devel/qemu/check-log.sh @@ -68,6 +68,9 @@ echo "* Check errno was set to JINUE_EPERM" RESULT=`grep -F -A 1 "Attempting to call jinue_receive() on the send-only descriptor." $1` echo "$RESULT" | grep -F 'operation not permitted' || fail +echo "* Check client thread started and got the right argument" +grep -F "Client thread is starting with argument: 0xb01dface" $1 || fail + echo "* Check main thread received message from client thread" grep -F "Main thread received message" $1 || fail @@ -110,6 +113,9 @@ echo "$RESULT" | grep -F 'I/O error' || fail echo "* Check client thread exited cleanly" grep -F "Client thread is exiting." $1 || fail +echo "* Check main thread joined the client thread and retrieved its exit value" +grep -F "Client thread exit value is 0xdeadbeef." $1 || fail + echo "* Check the main thread initiated the reboot" grep -F "Main thread is running." $1 || fail grep -F "Rebooting." $1 || fail diff --git a/doc/syscalls/README.md b/doc/syscalls/README.md index ec385697..fd921cc0 100644 --- a/doc/syscalls/README.md +++ b/doc/syscalls/README.md @@ -18,7 +18,7 @@ | 9 | [CREATE_ENDPOINT](create-endpoint.md) | Create IPC Endpoint | | 10 | [RECEIVE](receive.md) | Receive Message | | 11 | [REPLY](reply.md) | Reply to Message | -| 12 | [EXIT_THREAD](exit-thread.md) | Exit the Current Thread | +| 12 | [EXIT_THREAD](exit-thread.md) | Terminate the Current Thread | | 13 | [MMAP](mmap.md) | Map Memory | | 14 | [CREATE_PROCESS](create-process.md) | Create Process | | 15 | [MCLONE](mclone.md) | Clone a Memory Mapping | @@ -26,7 +26,9 @@ | 17 | [CLOSE](close.md) | Close a Descriptor | | 18 | [DESTROY](destroy.md) | Destroy a Kernel Object | | 19 | [MINT](mint.md) | Mint a Descriptor | -| 20-4095 | - | Reserved | +| 20 | [START_THREAD](start-thread.md) | Start a Thread | +| 21 | [JOIN_THREAD](join-thread.md) | Wait for a Thread to Exit | +| 22-4095 | - | Reserved | | 4096+ | [SEND](send.md) | Send Message | #### Reserved Function Numbers diff --git a/doc/syscalls/create-endpoint.md b/doc/syscalls/create-endpoint.md index edb170b1..818de377 100644 --- a/doc/syscalls/create-endpoint.md +++ b/doc/syscalls/create-endpoint.md @@ -34,8 +34,8 @@ The descriptor number to bind to the new IPC endpoint is set in `arg1`. ## Return Value -On success, this function returns the descriptor number for the new IPC endpoint -(in `arg0`). On failure, it returns -1 and an error number is set (in `arg1`). +On success, this function returns 0 (in `arg0`). On failure, this function +returns -1 and an error number is set (in `arg1`). ## Errors diff --git a/doc/syscalls/create-thread.md b/doc/syscalls/create-thread.md index ea43c947..9512842e 100644 --- a/doc/syscalls/create-thread.md +++ b/doc/syscalls/create-thread.md @@ -2,16 +2,20 @@ ## Description -Create a new thread in a target process. +Create a new thread in a target process and bind it to a thread descriptor. + +For this operation to succeed, the descriptor for the target process +must have the +[JINUE_PERM_CREATE_THREAD](../../include/jinue/shared/asm/permissions.h) and +[JINUE_PERM_OPEN](../../include/jinue/shared/asm/permissions.h) permissions. ## Arguments Function number (`arg0`) is 4. -The descriptor number for the target process is set in `arg1`. +The descriptor number to bind to the new thread is set in `arg1`. -The address where code execution will start is set in `arg2` and the -value of the initial stack pointer is set in `arg3`. +The descriptor number for the target process is set in `arg2`. ``` +----------------------------------------------------------------+ @@ -20,17 +24,17 @@ value of the initial stack pointer is set in `arg3`. 31 0 +----------------------------------------------------------------+ - | process | arg1 + | thread descriptor number | arg1 +----------------------------------------------------------------+ 31 0 +----------------------------------------------------------------+ - | code entry point address | arg2 + | process descriptor number | arg2 +----------------------------------------------------------------+ 31 0 +----------------------------------------------------------------+ - | user stack address | arg3 + | reserved (0) | arg3 +----------------------------------------------------------------+ 31 0 ``` @@ -42,13 +46,8 @@ returns -1 and an error number is set (in `arg1`). ## Errors -* JINUE_EINVAL if the code entry point is set to a kernel address. -* JINUE_EINVAL if the user stack address is set to a kernel address. -* JINUE_EAGAIN if the thread could not be created because of needed resources. -* JINUE_EBADF if the specified descriptor is invalid, or does not refer to a -process, or is closed. - -## Future Direction - -This system call will be modified to bind the new thread to a descriptor so -other system calls can refer to it e.g. to destroy it, join it, etc. +* JINUE_EBADF if the specified descriptor is already in use. +* JINUE_EBADF if the target process descriptor is invalid, or does not refer to +a process, or is closed. +* JINUE_EPERM if the specified process descriptor does not have the permissions +to create a thread and bind a descriptor into the process. diff --git a/doc/syscalls/dup.md b/doc/syscalls/dup.md index bbbdc2f0..baf78d51 100644 --- a/doc/syscalls/dup.md +++ b/doc/syscalls/dup.md @@ -4,10 +4,17 @@ Create a copy of a descriptor from the current process in a target process. -A owner descriptor cannot be duplicated with this (or any) function. A owner -descriptor is the descriptor that was specified in the call to the function -that created a kernel resource (e.g. [CREATE_ENDPOINT](create-endpoint.md)) and that -can be used to [mint](mint.md) other descriptors to that resource. +For this operation to succeed, the descriptor for the target process +must have the [JINUE_PERM_OPEN](../../include/jinue/shared/asm/permissions.h) +permission. + +A owner descriptor cannot be duplicated with this (or any) function. The owner +descriptor for a given kernel resource is the original descriptor to which the +resource was bound when it was created through the relevant system call (e.g. +[CREATE_ENDPOINT](create-endpoint) for an IPC endpoint). While a owner +descriptor cannot be duplicated using this function, the [MINT](mint.md) +function can be used instead to create a new descriptor that refers to the same +kernel resource and has customized permissions. ## Arguments @@ -52,3 +59,5 @@ not refer to a process, or is closed. * JINUE_EBADF if the specified source descriptor is invalid, or is a owner descriptor, or is closed. * JINUE_EBADF if the specified destination descriptor is already in use. +* JINUE_EPERM if the specified process descriptor does not have the permission +to bind a descriptor into the process. diff --git a/doc/syscalls/exit-thread.md b/doc/syscalls/exit-thread.md index 1022c5ed..ea8c0c1f 100644 --- a/doc/syscalls/exit-thread.md +++ b/doc/syscalls/exit-thread.md @@ -1,23 +1,39 @@ -# EXIT_THREAD - Exit the Current Thread +# EXIT_THREAD - Terminate the Current Thread ## Description -Exit the current thread. +Terminate the current thread. ## Arguments Function number (`arg0`) is 12. +``` + +----------------------------------------------------------------+ + | function = 12 | arg0 + +----------------------------------------------------------------+ + 31 0 + + +----------------------------------------------------------------+ + | reserved (0) | arg1 + +----------------------------------------------------------------+ + 31 0 + + +----------------------------------------------------------------+ + | reserved (0) | arg2 + +----------------------------------------------------------------+ + 31 0 + + +----------------------------------------------------------------+ + | reserved (0) | arg3 + +----------------------------------------------------------------+ + 31 0 +``` + ## Return Value -This function exits the current thread and does not return. +This function terminates the current thread and does not return. ## Errors This function always succeeds. - -## Future Direction - -The current system call only affects the current thread. It may be changed or -some other function may be added to allow another thread to be destroyed, in -which case the thread would be specified using a descriptor. diff --git a/doc/syscalls/get-thread-local.md b/doc/syscalls/get-thread-local.md index 4cfd9992..ece35b7c 100644 --- a/doc/syscalls/get-thread-local.md +++ b/doc/syscalls/get-thread-local.md @@ -35,13 +35,13 @@ Function number (`arg0`) is 7. ## Return Value -This function returns the address of the thread-local storage in `arg0`. If this -has not been set for the current thread by a previous call to the Set -Thread-Local Storage system call, then the return value is zero (i.e. the NULL -pointer in C). +This function sets `arg0` to 0 and set the address of the thread-local storage +in `arg1`. If this has not been set for the current thread by a previous call +to [SET_THREAD_LOCAL] (set-thread-local.md) then the address set in `arg1` is +zero (i.e. the NULL pointer in C). -Note that, since this function returns a pointer, it does not follow the usual -convention whereby a negative return value indicates a failure. +Note that since this function returns a pointer, it does not follow the usual +convention where `arg0` is used to return a successful value. ## Errors diff --git a/doc/syscalls/join-thread.md b/doc/syscalls/join-thread.md new file mode 100644 index 00000000..3192011d --- /dev/null +++ b/doc/syscalls/join-thread.md @@ -0,0 +1,54 @@ +# JOIN_THREAD - Wait for a Thread to Exit + +## Description + +Wait for a thread to terminate, if it hasn't already. + +The target thread must have been started with [START_THREAD](start-thread.md) +and must not be the calling thread. Furthermore, this function must be called +at most once on a given thread since it has been started. + +For this operation to succeed, the thread descriptor must have the +[JINUE_PERM_JOIN](../../include/jinue/shared/asm/permissions.h) permission. + +## Arguments + +Function number (`arg0`) is 21. + +The descriptor number for the thread is set in `arg1`. + +``` + +----------------------------------------------------------------+ + | function = 21 | arg0 + +----------------------------------------------------------------+ + 31 0 + + +----------------------------------------------------------------+ + | thread descriptor number | arg1 + +----------------------------------------------------------------+ + 31 0 + + +----------------------------------------------------------------+ + | reserved (0) | arg2 + +----------------------------------------------------------------+ + 31 0 + + +----------------------------------------------------------------+ + | reserved (0) | arg3 + +----------------------------------------------------------------+ + 31 0 +``` + +## Return Value + +On success, this function returns 0 (in `arg0`). On failure, this function +returns -1 and an error number is set (in `arg1`). + +## Errors + +* JINUE_EBADF if the thread descriptor is invalid, or does not refer to a +thread, or is closed. +* JINUE_ESRCH if the thread has not been started or has already beed joined. +* JINUE_EDEADLK if a thread attempts to join itself. +* JINUE_EPERM if the specified thread descriptor does not have the permission +to join the thread. diff --git a/doc/syscalls/mclone.md b/doc/syscalls/mclone.md index db6c59a2..b9d49766 100644 --- a/doc/syscalls/mclone.md +++ b/doc/syscalls/mclone.md @@ -4,6 +4,9 @@ Clone a contiguous memory mapping from one process to another. +For this operation to succeed, the destination process descriptor must have the +[JINUE_PERM_MAP](../../include/jinue/shared/asm/permissions.h) permission. + ## Arguments Function number (`arg0`) is 15. @@ -80,3 +83,5 @@ belongs to the kernel. to a process, or is closed. * JINUE_EIO if either process no longer exists. * JINUE_ENOMEM if not enough memory is available to allocate needed page tables. +* JINUE_EPERM if the destination process descriptor does not have the +permission to map memory into the process. diff --git a/doc/syscalls/mint.md b/doc/syscalls/mint.md index a6df0fea..a81b93c8 100644 --- a/doc/syscalls/mint.md +++ b/doc/syscalls/mint.md @@ -2,12 +2,50 @@ ## Description -Create a descriptor referencing a kernel resource with specified cookie and -permissions. +Create a descriptor referencing a kernel resource with specified permissions +and cookie. -In order to use this function, the owner descriptor for the resource must be -specified. The owner descriptor is the descriptor that was specified in the -call to the function that created the resource (e.g. CREATE_ENDPOINT). +In order to use this function, the owner descriptor for a kernel resource must +passed as an argument. The owner descriptor for a given kernel resource is the +original descriptor to which the resource was bound when it was created through +the relevant system call (e.g. [CREATE_ENDPOINT](create-endpoint) for an IPC +endpoint). This function binds the same object to a new descriptor with +customized permissions and cookie. + +The permissions that can be set on a descriptor depends on the type of kernel +object it refers to. See the +[flag definitions](../../include/jinue/shared/asm/permissions.h) for the +numerical values of the various permission flags. + +If the owner descriptor refers to an IPC endpoint, the following permission +flags can be specified: + +| Name | Description | +|-----------------------|-----------------------| +| JINUE_PERM_SEND | Send messages | +| JINUE_PERM_RECEIVE | Receive messages | + +If the owner descriptor refers to a process, the following permission flags can +be specified: + +| Name | Description | +|---------------------------|-------------------------------------------| +| JINUE_PERM_CREATE_THREAD | Create a thread | +| JINUE_PERM_MAP | Map memory into the virtual address space | +| JINUE_PERM_OPEN | Bind a descriptor | + +If the owner descriptor refers to a thread, the following permission flags can +be specified: + +| Name | Description | +|-------------------|-----------------------------------| +| JINUE_PERM_START | Start the thread | +| JINUE_PERM_JOIN | Wait for the thread to terminate | + +Other permission flags are reserved. + +For this operation to succeed, the descriptor for the target process must have +the [JINUE_PERM_OPEN](../../include/jinue/shared/asm/permissions.h) permission. ## Arguments @@ -47,24 +85,14 @@ The mint arguments structure contains the following fields: descriptor. * `fd` descriptor number in the target process in which to place the new descriptor. -* `perms` the permission flags of the new descriptor (see below). +* `perms` the permission flags of the new descriptor, which must describe a +permissions set appropriate for the type of kernel object the descriptor refers +to. See the [flag definitions](../../include/jinue/shared/asm/permissions.h) +for the numerical values of the permission flags. * `cookie` the cookie of the new descriptor. This parameter is only meaningful for references to IPC endpoints and can be set to anything, including zero, for other resource types. -`perms` must be set to the bitwise or of permission flags that are appropriate -for the type of kernel object to which the owner descriptor refers. - -If the owner descriptor refers to an IPC endpoint, the following permission -flags can be specified: - -| Value | Name | Description | -|-------|-----------------------|-----------------------------------| -| 1 | JINUE_PERM_SEND | Can be used to send messages | -| 2 | JINUE_PERM_RECEIVE | Can be used to receive messages | - -Other permission flags are reserved. - ## Return Value On success, this function returns 0 (in `arg0`). On failure, this function @@ -79,4 +107,8 @@ appropriate for the type of kernel object to which the owner descriptor refers. to a process or is closed. * JINUE_EBADF if the descriptor specified in the `fd` argument is invalid or is already in use. -* JINUE_EPERM if the specified descriptor is not the owner descriptor. +* JINUE_EPERM if the specified owner descriptor is not the owner descriptor of +a kernel resource. +* JINUE_EPERM if the specified process descriptor does not have the permission +to bind a descriptor into the process. + diff --git a/doc/syscalls/mmap.md b/doc/syscalls/mmap.md index 8190b889..26adfb1b 100644 --- a/doc/syscalls/mmap.md +++ b/doc/syscalls/mmap.md @@ -4,6 +4,9 @@ Map a contiguous block of memory into the address space of a process. +For this operation to succeed, the process descriptor must have the +[JINUE_PERM_MAP](../../include/jinue/shared/asm/permissions.h) permission. + ## Arguments Function number (`arg0`) is 13. @@ -66,15 +69,19 @@ returns -1 and an error number is set (in `arg1`). ## Errors -* JINUE_EINVAL if `addr`, `length` and/or `paddr` are not aligned to a page boundary. -* JINUE_EINVAL if any part of the mmap arguments structure as specified by `arg2` -belongs to the kernel. -* JINUE_EINVAL if `prot` is not `JINUE_PROT_NONE` or a bitwise or combination of -`JINUE_PROT_READ`, `JINUE_PROT_WRITE` and/or `JINUE_PROT_EXEC`. +* JINUE_EINVAL if `addr`, `length` and/or `paddr` are not aligned to a page +boundary. +* JINUE_EINVAL if any part of the mmap arguments structure as specified by +`arg2` belongs to the kernel. +* JINUE_EINVAL if `prot` is not `JINUE_PROT_NONE` or a bitwise or combination +of `JINUE_PROT_READ`, `JINUE_PROT_WRITE` and/or `JINUE_PROT_EXEC`. * JINUE_EBADF if the specified descriptor is invalid, or does not refer to a process, or is closed. * JINUE_EIO if the process no longer exists. -* JINUE_ENOMEM if not enough memory is available to allocate needed page tables. +* JINUE_ENOMEM if not enough memory is available to allocate needed page +tables. +* JINUE_EPERM if the process descriptor does not have the permission to map +memory into the process. ## Future Direction diff --git a/doc/syscalls/receive.md b/doc/syscalls/receive.md index bf828130..db1f8248 100644 --- a/doc/syscalls/receive.md +++ b/doc/syscalls/receive.md @@ -16,6 +16,9 @@ argument: * `reply_max_size` is set to the maximum size of the reply that can be sent back when [REPLY](reply.md)ing to the sender. +For this operation to succeed, the IPC endpoint descriptor must have the +[JINUE_PERM_RECEIVE](../../include/jinue/shared/asm/permissions.h) permission. + ## Arguments Function number (`arg0`) is 10. diff --git a/doc/syscalls/send.md b/doc/syscalls/send.md index 61615c7e..27777f71 100644 --- a/doc/syscalls/send.md +++ b/doc/syscalls/send.md @@ -5,6 +5,9 @@ Send a message to an IPC endpoint. This call blocks until the message is received and replied to. +For this operation to succeed, the IPC endpoint descriptor must have the +[JINUE_PERM_SEND](../../include/jinue/shared/asm/permissions.h) permission. + ## Arguments The function number, which must be at least 4096, is set in `arg0`. The function diff --git a/doc/syscalls/start-thread.md b/doc/syscalls/start-thread.md new file mode 100644 index 00000000..622ed634 --- /dev/null +++ b/doc/syscalls/start-thread.md @@ -0,0 +1,55 @@ +# START_THREAD - Start a Thread + +## Description + +Set the entry point and stack address of a non-running thread specified by a +descriptor number and then start the thread. + +For this operation to succeed, the thread descriptor must have the +[JINUE_PERM_START](../../include/jinue/shared/asm/permissions.h) permission. + +## Arguments + +Function number (`arg0`) is 20. + +The descriptor number for the target thread is set in `arg1`. + +The address where code execution will start is set in `arg2` and the +value of the initial stack pointer is set in `arg3`. + +``` + +----------------------------------------------------------------+ + | function = 20 | arg0 + +----------------------------------------------------------------+ + 31 0 + + +----------------------------------------------------------------+ + | thread descriptor number | arg1 + +----------------------------------------------------------------+ + 31 0 + + +----------------------------------------------------------------+ + | code entry point address | arg2 + +----------------------------------------------------------------+ + 31 0 + + +----------------------------------------------------------------+ + | user stack address | arg3 + +----------------------------------------------------------------+ + 31 0 +``` + +## Return Value + +On success, this function returns 0 (in `arg0`). On failure, this function +returns -1 and an error number is set (in `arg1`). + +## Errors + +* JINUE_EINVAL if the code entry point is set to a kernel address. +* JINUE_EINVAL if the user stack address is set to a kernel address. +* JINUE_EBADF if the thread descriptor is invalid, or does not refer to a +thread, or is closed. +* JINUE_EPERM if the thread descriptor does not have the permission to start +the thread. +* JINUE_EBUSY if the thread is already running. diff --git a/include/errno.h b/include/errno.h index 47289237..5af83b28 100755 --- a/include/errno.h +++ b/include/errno.h @@ -56,4 +56,10 @@ extern int errno; #define ENOTSUP JINUE_ENOTSUP +#define EBUSY JINUE_EBUSY + +#define ESRCH JINUE_ESRCH + +#define EDEADLK JINUE_EDEADLK + #endif diff --git a/include/jinue/jinue.h b/include/jinue/jinue.h index 3cb0b75d..b47158fe 100644 --- a/include/jinue/jinue.h +++ b/include/jinue/jinue.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Philippe Aubertin. + * Copyright (C) 2019-2024 Philippe Aubertin. * All rights reserved. * Redistribution and use in source and binary forms, with or without @@ -46,13 +46,17 @@ int jinue_init(int implementation, int *perrno); +uintptr_t jinue_syscall(jinue_syscall_args_t *args); + +void jinue_thread_entry(void); + void jinue_reboot(void); void jinue_set_thread_local(void *addr, size_t size); -void *jinue_get_thread_local(void); +int jinue_get_thread_local(void **thread_local); -int jinue_create_thread(int process, void (*entry)(), void *stack, int *perrno); +int jinue_create_thread(int fd, int process, int *perrno); void jinue_yield_thread(void); @@ -109,4 +113,8 @@ int jinue_mint( uintptr_t cookie, int *perrno); +int jinue_start_thread(int fd, void (*entry)(void), void *stack, int *perrno); + +int jinue_join_thread(int fd, int *perrno); + #endif diff --git a/include/jinue/shared/asm/errno.h b/include/jinue/shared/asm/errno.h index a2e36042..3bc4d238 100644 --- a/include/jinue/shared/asm/errno.h +++ b/include/jinue/shared/asm/errno.h @@ -62,4 +62,13 @@ /** not supported */ #define JINUE_ENOTSUP 10 +/** device or resource busy */ +#define JINUE_EBUSY 11 + +/** no such process */ +#define JINUE_ESRCH 12 + +/** resource deadlock would occur */ +#define JINUE_EDEADLK 13 + #endif diff --git a/include/jinue/shared/asm/permissions.h b/include/jinue/shared/asm/permissions.h index eb25f0e4..feb20450 100644 --- a/include/jinue/shared/asm/permissions.h +++ b/include/jinue/shared/asm/permissions.h @@ -32,16 +32,25 @@ #ifndef _JINUE_SHARED_ASM_PERMISSIONS_H #define _JINUE_SHARED_ASM_PERMISSIONS_H +/** send a message */ #define JINUE_PERM_SEND (1<<0) +/** receive a message */ #define JINUE_PERM_RECEIVE (1<<1) +/** map memory into a process */ #define JINUE_PERM_MAP (1<<2) +/** bind a descriptor into a process */ #define JINUE_PERM_OPEN (1<<3) -#define JINUE_PERM_CLOSE (1<<4) +/** create a thread within a process */ +#define JINUE_PERM_CREATE_THREAD (1<<4) -#define JINUE_PERM_CREATE_THREAD (1<<5) +/** start a thread */ +#define JINUE_PERM_START (1<<5) + +/** join a thread */ +#define JINUE_PERM_JOIN (1<<6) #endif diff --git a/include/jinue/shared/asm/syscalls.h b/include/jinue/shared/asm/syscalls.h index 5871fa8c..5abbff7a 100644 --- a/include/jinue/shared/asm/syscalls.h +++ b/include/jinue/shared/asm/syscalls.h @@ -86,6 +86,12 @@ /** create a descriptor with specified cookie and permissions */ #define JINUE_SYS_MINT 19 +/** start a thread */ +#define JINUE_SYS_START_THREAD 20 + +/** wait for a thread to exit */ +#define JINUE_SYS_JOIN_THREAD 21 + /** start of function numbers for user space messages */ #define JINUE_SYS_USER_BASE 4096 diff --git a/userspace/lib/jinue/machine.h b/include/jinue/threads.h similarity index 77% rename from userspace/lib/jinue/machine.h rename to include/jinue/threads.h index a71e40d5..530966f5 100644 --- a/userspace/lib/jinue/machine.h +++ b/include/jinue/threads.h @@ -1,22 +1,22 @@ /* - * Copyright (C) 2019-2023 Philippe Aubertin. + * Copyright (C) 2024 Philippe Aubertin. * 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 author nor the names of other contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. - * + * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 @@ -29,13 +29,19 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef LIBJINUE_MACHINE_H -#define LIBJINUE_MACHINE_H +#ifndef _JINUE_THREADS_H +#define _JINUE_THREADS_H -#include +#include -uintptr_t jinue_syscall(jinue_syscall_args_t *args); +typedef struct jinue_thread *jinue_thread_t; -intptr_t jinue_syscall_with_usual_convention(jinue_syscall_args_t *args, int *perrno); +int jinue_thread_init(jinue_thread_t *thread, int fd, size_t stacksize); + +int jinue_thread_start(jinue_thread_t thread, void *(*start_routine)(void*), void *arg); + +int jinue_thread_join(jinue_thread_t thread, void **value_ptr); + +int jinue_thread_destroy(jinue_thread_t thread); #endif diff --git a/include/kernel/application/syscalls.h b/include/kernel/application/syscalls.h index 29d025c6..fe0de473 100644 --- a/include/kernel/application/syscalls.h +++ b/include/kernel/application/syscalls.h @@ -40,7 +40,7 @@ int create_endpoint(int fd); int create_process(int fd); -int create_thread(int process_fd, const thread_params_t *params); +int create_thread(int fd, int process_fd); int destroy(int fd); @@ -52,6 +52,8 @@ void *get_thread_local(void); int get_user_memory(const jinue_buffer_t *buffer); +int join_thread(int fd); + int mclone(int src, int dest, const jinue_mclone_args_t *args); int mint(int owner, const jinue_mint_args_t *mint_args); @@ -70,6 +72,8 @@ int send(int fd, int function, const jinue_message_t *message); void set_thread_local(void *addr, size_t size); +int start_thread(int fd, const thread_params_t *params); + void yield_thread(void); #endif diff --git a/include/kernel/domain/entities/thread.h b/include/kernel/domain/entities/thread.h index f5bdde66..d5e3eb46 100644 --- a/include/kernel/domain/entities/thread.h +++ b/include/kernel/domain/entities/thread.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2022 Philippe Aubertin. + * Copyright (C) 2019-2024 Philippe Aubertin. * All rights reserved. * Redistribution and use in source and binary forms, with or without @@ -36,21 +36,21 @@ extern const object_type_t *object_type_thread; -thread_t *construct_thread(process_t *process, const thread_params_t *params); +thread_t *construct_thread(process_t *process); -void free_thread(thread_t *thread); - +void prepare_thread(thread_t *thread, const thread_params_t *params); + void ready_thread(thread_t *thread); void switch_to_thread(thread_t *thread, bool blocked); -void start_first_thread(void); +void start_first_thread(thread_t *thread); void yield_current_thread(void); void block_current_thread(void); -void exit_current_thread(void); +void switch_from_exiting_thread(thread_t *current); void set_thread_local_storage(thread_t *thread, addr_t addr, size_t size); diff --git a/include/kernel/domain/services/exec.h b/include/kernel/domain/services/exec.h index a64e6ebf..4da65936 100644 --- a/include/kernel/domain/services/exec.h +++ b/include/kernel/domain/services/exec.h @@ -36,6 +36,7 @@ void exec( process_t *process, + thread_t *thread, const exec_file_t *exec_file, const char *argv0, const char *cmdline); diff --git a/include/kernel/domain/services/ipc.h b/include/kernel/domain/services/ipc.h index dedb7ca5..ec984e8c 100644 --- a/include/kernel/domain/services/ipc.h +++ b/include/kernel/domain/services/ipc.h @@ -45,6 +45,6 @@ int receive_message(ipc_endpoint_t *endpoint, thread_t *receiver,jinue_message_t int reply_to_message(thread_t *replier, const jinue_message_t *message); -void abort_message(thread_t *thread, int errno); +void abort_message(thread_t *thread); #endif diff --git a/include/kernel/machine/thread.h b/include/kernel/machine/thread.h index faaa43b9..0e62278c 100644 --- a/include/kernel/machine/thread.h +++ b/include/kernel/machine/thread.h @@ -36,7 +36,7 @@ thread_t *get_current_thread(void); -void machine_init_thread(thread_t *thread, const thread_params_t *params); +void machine_prepare_thread(thread_t *thread, const thread_params_t *params); thread_t *machine_alloc_thread(void); diff --git a/include/kernel/types.h b/include/kernel/types.h index e8273cf9..55057c66 100644 --- a/include/kernel/types.h +++ b/include/kernel/types.h @@ -94,14 +94,23 @@ typedef struct { descriptor_t descriptors[PROCESS_MAX_DESCRIPTORS]; } process_t; +typedef enum { + THREAD_STATE_ZOMBIE, + THREAD_STATE_READY, + THREAD_STATE_RUNNING, + THREAD_STATE_BLOCKED +} thread_state_t; + struct thread_t { object_header_t header; machine_thread_t thread_ctx; jinue_node_t thread_list; + thread_state_t state; process_t *process; + struct thread_t *sender; + struct thread_t *joined; addr_t local_storage_addr; size_t local_storage_size; - struct thread_t *sender; size_t recv_buffer_size; int message_errno; uintptr_t message_function; diff --git a/kernel/Makefile b/kernel/Makefile index 7dd8e59b..6aa61e17 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -69,6 +69,7 @@ sources.kernel.c = \ application/syscalls/exit_thread.c \ application/syscalls/get_thread_local.c \ application/syscalls/get_user_memory.c \ + application/syscalls/join_thread.c \ application/syscalls/mint.c \ application/syscalls/mmap.c \ application/syscalls/mclone.c \ @@ -78,6 +79,7 @@ sources.kernel.c = \ application/syscalls/reply.c \ application/syscalls/send.c \ application/syscalls/set_thread_local.c \ + application/syscalls/start_thread.c \ application/syscalls/yield_thread.c \ application/kmain.c \ domain/alloc/page_alloc.c \ diff --git a/kernel/application/kmain.c b/kernel/application/kmain.c index 22a909f1..b4e6e55d 100644 --- a/kernel/application/kmain.c +++ b/kernel/application/kmain.c @@ -93,18 +93,31 @@ void kmain(const char *cmdline) { switch_to_process(process); + /* create user space loader main thread */ + thread_t *thread = construct_thread(process); + + if(thread == NULL) { + panic("Could not create initial thread."); + } + /* load user space loader binary */ exec_file_t loader; machine_get_loader(&loader); - exec(process, &loader, "jinue-userspace-loader", cmdline); + exec( + process, + thread, + &loader, + "jinue-userspace-loader", + cmdline + ); /* This should be the last thing the kernel prints before passing control * to the user space loader. */ info("---"); - /* Start first thread */ - start_first_thread(); + /* Start first thread. */ + start_first_thread(thread); /* should never happen */ panic("start_first_thread() returned in kmain()"); diff --git a/kernel/application/syscalls/create_endpoint.c b/kernel/application/syscalls/create_endpoint.c index 49265340..cd548279 100644 --- a/kernel/application/syscalls/create_endpoint.c +++ b/kernel/application/syscalls/create_endpoint.c @@ -68,5 +68,5 @@ int create_endpoint(int fd) { open_object(&endpoint->header, desc); - return fd; + return 0; } diff --git a/kernel/application/syscalls/create_thread.c b/kernel/application/syscalls/create_thread.c index e0c0a37b..f0546a3a 100644 --- a/kernel/application/syscalls/create_thread.c +++ b/kernel/application/syscalls/create_thread.c @@ -36,26 +36,45 @@ #include #include -int create_thread(int process_fd, const thread_params_t *params) { +int create_thread(int fd, int process_fd) { descriptor_t *desc; - int status = dereference_object_descriptor(&desc, get_current_process(), process_fd); + int status = dereference_unused_descriptor(&desc, get_current_process(), fd); + + if(status < 0) { + return -JINUE_EBADF; + } + + descriptor_t *process_desc; + status = dereference_object_descriptor(&process_desc, get_current_process(), process_fd); if(status < 0) { return status; } - process_t *process = get_process_from_descriptor(desc); + process_t *process = get_process_from_descriptor(process_desc); if(process == NULL) { return -JINUE_EBADF; } - if(!descriptor_has_permissions(desc, JINUE_PERM_CREATE_THREAD)) { + if(!descriptor_has_permissions(process_desc, JINUE_PERM_CREATE_THREAD | JINUE_PERM_OPEN)) { return -JINUE_EPERM; } - const thread_t *thread = construct_thread(process, params); + thread_t *thread = construct_thread(process); + + if(thread == 0) { + return -JINUE_ENOMEM; + } + + desc->object = &thread->header; + desc->flags = + DESCRIPTOR_FLAG_IN_USE + | DESCRIPTOR_FLAG_OWNER + | object_type_thread->all_permissions; + desc->cookie = 0; + + open_object(&thread->header, desc); - /** TODO associate new thread to a free descriptor */ - return (thread == NULL) ? -JINUE_EAGAIN : 0; + return 0; } diff --git a/kernel/application/syscalls/exit_thread.c b/kernel/application/syscalls/exit_thread.c index 8edf8b10..1770179a 100644 --- a/kernel/application/syscalls/exit_thread.c +++ b/kernel/application/syscalls/exit_thread.c @@ -29,9 +29,31 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include #include #include +#include +#include void exit_thread(void) { - exit_current_thread(); + thread_t *thread = get_current_thread(); + + if(thread->sender != NULL) { + abort_message(thread->sender); + thread->sender = NULL; + } + + if(thread->joined != NULL) { + ready_thread(thread->joined); + } + + /* When we started the thread in start_thread(), we incremented the + * reference count on it to ensure it continues running even if all + * descriptors that reference it are closed. This call here will safely + * decrement the reference count after it has switched to another thread. + * We cannot just decrement the count here because that will possibly free + * the current thread, which we don't want to do while it is still running. + * + * This call must be the last thing happening in this function. */ + switch_from_exiting_thread(thread); } diff --git a/kernel/application/syscalls/join_thread.c b/kernel/application/syscalls/join_thread.c new file mode 100644 index 00000000..7d284454 --- /dev/null +++ b/kernel/application/syscalls/join_thread.c @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2024 Philippe Aubertin. + * 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 author nor the names of other contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +int join_thread(int fd) { + descriptor_t *desc; + int status = dereference_object_descriptor(&desc, get_current_process(), fd); + + if(status < 0) { + return -JINUE_EBADF; + } + + thread_t *thread = get_thread_from_descriptor(desc); + + if(thread == NULL) { + return -JINUE_EBADF; + } + + if(!descriptor_has_permissions(desc, JINUE_PERM_JOIN)) { + return -JINUE_EPERM; + } + + thread_t *current = get_current_thread(); + + if(thread == current) { + return -JINUE_EDEADLK; + } + + if(thread->joined != NULL) { + return -JINUE_ESRCH; + } + + thread->joined = current; + + /* Keep the thread around until we actually read the exit value. */ + add_ref_to_object(&thread->header); + + if(thread->state != THREAD_STATE_ZOMBIE) { + block_current_thread(); + } + + sub_ref_to_object(&thread->header); + + return 0; +} diff --git a/kernel/application/syscalls/mclone.c b/kernel/application/syscalls/mclone.c index bc4223f8..9608a983 100644 --- a/kernel/application/syscalls/mclone.c +++ b/kernel/application/syscalls/mclone.c @@ -79,7 +79,7 @@ int mclone(int src, int dest, const jinue_mclone_args_t *args) { return -JINUE_EBADF; } - if(!descriptor_has_permissions(dest_desc, JINUE_PERM_OPEN)) { + if(!descriptor_has_permissions(dest_desc, JINUE_PERM_MAP)) { return -JINUE_EPERM; } diff --git a/kernel/application/syscalls/start_thread.c b/kernel/application/syscalls/start_thread.c new file mode 100644 index 00000000..8fd7e01e --- /dev/null +++ b/kernel/application/syscalls/start_thread.c @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2024 Philippe Aubertin. + * 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 author nor the names of other contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. + */ + +#include +#include +#include +#include +#include +#include + +int start_thread(int fd, const thread_params_t *params) { + descriptor_t *desc; + int status = dereference_object_descriptor(&desc, get_current_process(), fd); + + if(status < 0) { + return -JINUE_EBADF; + } + + thread_t *thread = get_thread_from_descriptor(desc); + + if(thread == NULL) { + return -JINUE_EBADF; + } + + if(!descriptor_has_permissions(desc, JINUE_PERM_START)) { + return -JINUE_EPERM; + } + + if(thread->state != THREAD_STATE_ZOMBIE) { + return -JINUE_EBUSY; + } + + prepare_thread(thread, params); + + /* Add a reference on the thread while it is running so it is allowed to + * run to completion even if all descriptors that reference it are + * closed. */ + add_ref_to_object(&thread->header); + + ready_thread(thread); + + return 0; +} diff --git a/kernel/domain/entities/endpoint.c b/kernel/domain/entities/endpoint.c index 630c9f41..ca778bf0 100644 --- a/kernel/domain/entities/endpoint.c +++ b/kernel/domain/entities/endpoint.c @@ -146,7 +146,7 @@ static void destroy_endpoint(object_header_t *object) { break; } - abort_message(sender, JINUE_EIO); + abort_message(sender); } while(true) { @@ -159,7 +159,7 @@ static void destroy_endpoint(object_header_t *object) { break; } - abort_message(receiver, JINUE_EIO); + abort_message(receiver); } } diff --git a/kernel/domain/entities/object.c b/kernel/domain/entities/object.c index 931388e8..c235c14e 100644 --- a/kernel/domain/entities/object.c +++ b/kernel/domain/entities/object.c @@ -72,6 +72,7 @@ void destroy_object(object_header_t *object) { } } +/* This function is called by assembly code. See thread_context_switch_stack(). */ void sub_ref_to_object(object_header_t *object) { --object->ref_count; diff --git a/kernel/domain/entities/process.c b/kernel/domain/entities/process.c index 327f8423..ff7ac216 100644 --- a/kernel/domain/entities/process.c +++ b/kernel/domain/entities/process.c @@ -49,8 +49,7 @@ static void free_process(object_header_t *object); /** runtime type definition for a process */ static const object_type_t object_type = { .all_permissions = - JINUE_PERM_CLOSE - | JINUE_PERM_CREATE_THREAD + JINUE_PERM_CREATE_THREAD | JINUE_PERM_MAP | JINUE_PERM_OPEN, .name = "process", diff --git a/kernel/domain/entities/thread.c b/kernel/domain/entities/thread.c index 3f66b99d..6d56b9aa 100644 --- a/kernel/domain/entities/thread.c +++ b/kernel/domain/entities/thread.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2022 Philippe Aubertin. + * Copyright (C) 2019-2024 Philippe Aubertin. * All rights reserved. * Redistribution and use in source and binary forms, with or without @@ -29,26 +29,26 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include #include #include #include #include #include -#include #include #include #include +static void free_thread(object_header_t *object); + /** runtime type definition for a thread */ static const object_type_t object_type = { - .all_permissions = 0, + .all_permissions = JINUE_PERM_START | JINUE_PERM_JOIN, .name = "thread", .size = sizeof(thread_t), .open = NULL, .close = NULL, .destroy = NULL, - .free = NULL, + .free = free_thread, .cache_ctor = NULL, .cache_dtor = NULL }; @@ -57,7 +57,7 @@ const object_type_t *object_type_thread = &object_type; static jinue_list_t ready_list = JINUE_LIST_STATIC; -thread_t *construct_thread(process_t *process, const thread_params_t *params) { +thread_t *construct_thread(process_t *process) { thread_t *thread = machine_alloc_thread(); if(thread == NULL) { @@ -68,26 +68,34 @@ thread_t *construct_thread(process_t *process, const thread_params_t *params) { jinue_node_init(&thread->thread_list); + thread->state = THREAD_STATE_ZOMBIE; thread->process = process; - thread->sender = NULL; + /* Arbitrary non-NULL value to signify the thread hasn't run yet and + * shouldn't be joined. This will fall in the condition in thread_join() + * that detects an attempt to join a thread that has already been joined, + * so thread_join() will fail with JINUE_ESRCH. */ + thread->joined = thread; thread->local_storage_addr = NULL; thread->local_storage_size = 0; - - machine_init_thread(thread, params); - - ready_thread(thread); - + return thread; } -/* This function is called by assembly code. See thread_context_switch_stack(). */ -void free_thread(thread_t *thread) { +static void free_thread(object_header_t *object) { + thread_t *thread = (thread_t *)object; machine_free_thread(thread); } +void prepare_thread(thread_t *thread, const thread_params_t *params) { + thread->sender = NULL; + thread->joined = NULL; + machine_prepare_thread(thread, params); +} + void ready_thread(thread_t *thread) { - /* add thread to the tail of the ready list to give other threads a chance - * to run */ + thread->state = THREAD_STATE_READY; + + /* add thread to the tail of the ready list to give other threads a chance to run */ jinue_list_enqueue(&ready_list, &thread->thread_list); } @@ -125,13 +133,17 @@ static void switch_thread(thread_t *from, thread_t *to, bool destroy_from) { switch_to_process(to->process); } + to->state = THREAD_STATE_RUNNING; + machine_switch_thread(from, to, destroy_from); } void switch_to_thread(thread_t *thread, bool blocked) { thread_t *current = get_current_thread(); - if (!blocked) { + if (blocked) { + current->state = THREAD_STATE_BLOCKED; + } else { ready_thread(current); } @@ -142,10 +154,10 @@ void switch_to_thread(thread_t *thread, bool blocked) { ); } -void start_first_thread(void) { +void start_first_thread(thread_t *thread) { switch_thread( NULL, - reschedule(false), + thread, false /* don't destroy current thread */ ); } @@ -164,15 +176,11 @@ void block_current_thread(void) { ); } -void exit_current_thread(void) { - thread_t *current = get_current_thread(); - - if(current->sender != NULL) { - abort_message(current->sender, JINUE_EIO); - } +void switch_from_exiting_thread(thread_t *thread) { + thread->state = THREAD_STATE_ZOMBIE; switch_thread( - current, + thread, reschedule(false), /* current thread cannot run */ true /* do destroy the thread */ ); diff --git a/kernel/domain/services/exec.c b/kernel/domain/services/exec.c index cc4828fc..be039905 100644 --- a/kernel/domain/services/exec.c +++ b/kernel/domain/services/exec.c @@ -36,16 +36,12 @@ void exec( process_t *process, + thread_t *thread, const exec_file_t *exec_file, const char *argv0, const char *cmdline) { thread_params_t thread_params; machine_load_exec(&thread_params, process, exec_file, argv0, cmdline); - - thread_t *thread = construct_thread(process, &thread_params); - - if(thread == NULL) { - panic("Thread creation failed."); - } + prepare_thread(thread, &thread_params); } diff --git a/kernel/domain/services/ipc.c b/kernel/domain/services/ipc.c index 26841510..8eb32c00 100644 --- a/kernel/domain/services/ipc.c +++ b/kernel/domain/services/ipc.c @@ -387,18 +387,18 @@ int reply_to_message(thread_t *replier, const jinue_message_t *message) { /** * Abort a send or receive operation in progress * - * This function should only be called on a blocked thread that was just - * dequeued from an IPC endpoint's send or receive queue. The send or receive - * operation is aborted and the IPC system call returns specified error number. - * - * @param thread blocked thread requeued from send or receive queue - * @param errno error number + * The send or receive operation fails with JINUE_EIO. + * + * Situations that make calling this function necessary: + * - The thread is queued on an IPC endpoint's send or receive queue and the + * endpoint is being destroyed. + * - The sending thread is blocked being serviced by a receiver thread and the + * receiver thread exits without replying. + * + * @param thread thread blocked on an IPC operation * */ -void abort_message(thread_t *thread, int errno) { - thread->message_errno = errno; - /* TODO fix thread servicing message itself sending a message - * (i.e. don't clear sender when aborting as send) */ - thread->sender = NULL; +void abort_message(thread_t *thread) { + thread->message_errno = JINUE_EIO; ready_thread(thread); } diff --git a/kernel/infrastructure/i686/thread.c b/kernel/infrastructure/i686/thread.c index 6b93dfab..3c0bce2d 100644 --- a/kernel/infrastructure/i686/thread.c +++ b/kernel/infrastructure/i686/thread.c @@ -88,7 +88,7 @@ static addr_t get_kernel_stack_base(machine_thread_t *thread_ctx) { return (addr_t)thread + THREAD_CONTEXT_SIZE; } -void machine_init_thread(thread_t *thread, const thread_params_t *params) { +void machine_prepare_thread(thread_t *thread, const thread_params_t *params) { /* initialize fields */ machine_thread_t *thread_ctx = &thread->thread_ctx; diff --git a/kernel/infrastructure/i686/thread_switch.asm b/kernel/infrastructure/i686/thread_switch.asm index 2a1ad965..ac25780d 100644 --- a/kernel/infrastructure/i686/thread_switch.asm +++ b/kernel/infrastructure/i686/thread_switch.asm @@ -32,7 +32,7 @@ bits 32 - extern free_thread + extern sub_ref_to_object ; ------------------------------------------------------------------------------ ; FUNCTION: thread_context_switch_stack @@ -88,11 +88,11 @@ thread_context_switch_stack: ; Restore the saved registers. ; - ; We do this before calling free_thread(). Otherwise, the frame pointer - ; still refers to the thread stack for the previous thread, i.e. the one - ; we are potentially about to destroy, when free_thread() is called. - ; This is a problem if e.g. we try to dump the call stack from free_thread() - ; or one of its callees. + ; We do this before calling sub_ref_to_object(). Otherwise, the frame + ; pointer still refers to the thread stack for the previous thread, i.e. + ; the one we are potentially about to destroy when sub_ref_to_object() is + ; called. This is a problem if e.g. we try to dump the call stack from + ; sub_ref_to_object() or one of its callees. pop edi pop esi pop ebx @@ -106,9 +106,9 @@ thread_context_switch_stack: ; destroy from thread context and ecx, THREAD_CONTEXT_MASK push ecx - call free_thread + call sub_ref_to_object - ; cleanup free_thread() arguments from stack + ; cleanup sub_ref_to_object() arguments from stack add esp, 4 .skip_destroy: diff --git a/kernel/interface/syscalls.c b/kernel/interface/syscalls.c index 259b450d..24230849 100644 --- a/kernel/interface/syscalls.c +++ b/kernel/interface/syscalls.c @@ -49,13 +49,21 @@ #define WRITE_EXEC (JINUE_PROT_WRITE | JINUE_PROT_EXEC) -static void set_return_uintptr(jinue_syscall_args_t *args, uintptr_t retval) { - args->arg0 = retval; + +static void set_return_value(jinue_syscall_args_t *args, int retval) { + args->arg0 = (uintptr_t)retval; args->arg1 = 0; args->arg2 = 0; args->arg3 = 0; } +static void set_return_pointer(jinue_syscall_args_t *args, void *ptr) { + args->arg0 = 0; + args->arg1 = (uintptr_t)ptr; + args->arg2 = 0; + args->arg3 = 0; +} + static void set_error(jinue_syscall_args_t *args, int error) { args->arg0 = (uintptr_t)-1; args->arg1 = (uintptr_t)error; @@ -63,14 +71,6 @@ static void set_error(jinue_syscall_args_t *args, int error) { args->arg3 = 0; } -static void set_return_value(jinue_syscall_args_t *args, int retval) { - set_return_uintptr(args, (uintptr_t)retval); -} - -static void set_return_ptr(jinue_syscall_args_t *args, void *retval) { - set_return_uintptr(args, (uintptr_t)retval); -} - static void set_return_value_or_error(jinue_syscall_args_t *args, int retval) { if(retval < 0) { set_error(args, -retval); @@ -109,27 +109,20 @@ static void sys_puts(jinue_syscall_args_t *args) { } static void sys_create_thread(jinue_syscall_args_t *args) { - thread_params_t thread_params; - int process_fd = get_descriptor(args->arg1); - thread_params.entry = (void *)args->arg2; - thread_params.stack_addr = (void *)args->arg3; + int fd = get_descriptor(args->arg1); + int process_fd = get_descriptor(args->arg2); - if(process_fd < 0) { - set_return_value_or_error(args, process_fd); - return; - } - - if(!is_userspace_pointer(thread_params.entry)) { - set_error(args, JINUE_EINVAL); + if(fd < 0) { + set_return_value_or_error(args, fd); return; } - if(!is_userspace_pointer(thread_params.stack_addr)) { - set_error(args, JINUE_EINVAL); + if(process_fd < 0) { + set_return_value_or_error(args, process_fd); return; } - int retval = create_thread(process_fd, &thread_params); + int retval = create_thread(fd, process_fd); set_return_value_or_error(args, retval); } @@ -140,7 +133,7 @@ static void sys_yield_thread(jinue_syscall_args_t *args) { static void sys_exit_thread(jinue_syscall_args_t *args) { exit_thread(); - set_return_value(args, 0); + /* No need to set a return value since exit_thread() does not return. */ } static void sys_set_thread_local(jinue_syscall_args_t *args) { @@ -158,7 +151,7 @@ static void sys_set_thread_local(jinue_syscall_args_t *args) { static void sys_get_thread_local(jinue_syscall_args_t *args) { void *tls = get_thread_local(); - set_return_ptr(args, tls); + set_return_pointer(args, tls); } static void sys_get_user_memory(jinue_syscall_args_t *args) { @@ -520,6 +513,43 @@ static void sys_mint(jinue_syscall_args_t *args) { set_return_value_or_error(args, retval); } +static void sys_start_thread(jinue_syscall_args_t *args) { + thread_params_t thread_params; + int fd = get_descriptor(args->arg1); + thread_params.entry = (void *)args->arg2; + thread_params.stack_addr = (void *)args->arg3; + + if(fd < 0) { + set_return_value_or_error(args, fd); + return; + } + + if(!is_userspace_pointer(thread_params.entry)) { + set_error(args, JINUE_EINVAL); + return; + } + + if(!is_userspace_pointer(thread_params.stack_addr)) { + set_error(args, JINUE_EINVAL); + return; + } + + int retval = start_thread(fd, &thread_params); + set_return_value_or_error(args, retval); +} + +static void sys_join_thread(jinue_syscall_args_t *args) { + int fd = get_descriptor(args->arg1); + + if(fd < 0) { + set_return_value_or_error(args, fd); + return; + } + + int retval = join_thread(fd); + set_return_value_or_error(args, retval); +} + /** * System call dispatching function * @@ -592,6 +622,12 @@ void dispatch_syscall(jinue_syscall_args_t *args) { case JINUE_SYS_MINT: sys_mint(args); break; + case JINUE_SYS_START_THREAD: + sys_start_thread(args); + break; + case JINUE_SYS_JOIN_THREAD: + sys_join_thread(args); + break; default: sys_nosys(args); } diff --git a/userspace/lib/jinue/Makefile b/userspace/lib/jinue/Makefile index 6e7af55b..d74614bb 100644 --- a/userspace/lib/jinue/Makefile +++ b/userspace/lib/jinue/Makefile @@ -38,13 +38,13 @@ target.utils = $(notdir $(libjinue_utils)) targets = $(target.syscalls) $(target.utils) objects.syscalls = i686/stubs.o i686/syscalls.o syscalls.o -objects.util = loader.o logging.o +objects.utils = loader.o logging.o threads.o include $(common) $(target.syscalls): $(objects.syscalls) -$(target.utils): $(objects.util) +$(target.utils): $(objects.utils) %.a: ar rcs $@ $^ diff --git a/userspace/lib/jinue/i686/stubs.asm b/userspace/lib/jinue/i686/stubs.asm index a24af2a4..ff28c182 100644 --- a/userspace/lib/jinue/i686/stubs.asm +++ b/userspace/lib/jinue/i686/stubs.asm @@ -30,6 +30,8 @@ #include bits 32 + + extern jinue_exit_thread section .text ; ------------------------------------------------------------------------------ @@ -199,3 +201,29 @@ jinue_syscall_intr: ret .end: + +; ------------------------------------------------------------------------------ +; FUNCTION: jinue_thread_entry +; C PROTOTYPE: void jinue_thread_entry(void); +; ------------------------------------------------------------------------------ + global jinue_thread_entry:function (jinue_thread_entry.end - jinue_thread_entry) +jinue_thread_entry: + ; Set up the frame pointer + mov ebp, esp + + ; Pop address of the thread function from the stack. Leave the argument on + ; the stack since this is where the thread function will expect to find it. + pop eax + + ; Call the thread function. + call eax + + ; Remove the argument from the stack now that it is no longer needed and + ; replace it with the return value of the thread function. + pop ebx + push eax + + ; Exit the thread. + call jinue_exit_thread + +.end: diff --git a/userspace/lib/jinue/i686/syscalls.c b/userspace/lib/jinue/i686/syscalls.c index 2bbf4d8d..c0402a56 100644 --- a/userspace/lib/jinue/i686/syscalls.c +++ b/userspace/lib/jinue/i686/syscalls.c @@ -30,7 +30,6 @@ */ #include -#include "../machine.h" #include "stubs.h" static jinue_syscall_stub_t syscall_stubs[] = { @@ -54,19 +53,3 @@ int jinue_init(int implementation, int *perrno) { uintptr_t jinue_syscall(jinue_syscall_args_t *args) { return syscall_stubs[syscall_stub_index](args); } - -static inline void jinue_set_errno(int *perrno, int errval) { - if(perrno != NULL) { - *perrno = errval; - } -} - -intptr_t jinue_syscall_with_usual_convention(jinue_syscall_args_t *args, int *perrno) { - const intptr_t retval = (intptr_t)jinue_syscall(args); - - if(retval < 0) { - jinue_set_errno(perrno, args->arg1); - } - - return retval; -} diff --git a/userspace/lib/jinue/syscalls.c b/userspace/lib/jinue/syscalls.c index a830ecec..cfc2cb51 100644 --- a/userspace/lib/jinue/syscalls.c +++ b/userspace/lib/jinue/syscalls.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2023 Philippe Aubertin. + * Copyright (C) 2019-2024 Philippe Aubertin. * All rights reserved. * Redistribution and use in source and binary forms, with or without @@ -31,7 +31,35 @@ #include #include -#include "machine.h" + +static inline void set_errno(int *perrno, int errval) { + if(perrno != NULL) { + *perrno = errval; + } +} + +static intptr_t call_with_usual_convention(jinue_syscall_args_t *args, int *perrno) { + const intptr_t retval = (intptr_t)jinue_syscall(args); + + if(retval < 0) { + set_errno(perrno, args->arg1); + } + + return retval; +} + +static intptr_t call_with_pointer_convention(jinue_syscall_args_t *args, void **ptr, int *perrno) { + + const intptr_t retval = (intptr_t)jinue_syscall(args); + + if(retval < 0) { + set_errno(perrno, args->arg1); + } else { + *ptr = (void *)args->arg1; + } + + return retval; +} void jinue_reboot(void) { jinue_syscall_args_t args; @@ -55,7 +83,7 @@ void jinue_set_thread_local(void *addr, size_t size) { jinue_syscall(&args); } -void *jinue_get_thread_local(void) { +int jinue_get_thread_local(void **thread_local) { jinue_syscall_args_t args; args.arg0 = JINUE_SYS_GET_THREAD_LOCAL; @@ -63,18 +91,18 @@ void *jinue_get_thread_local(void) { args.arg2 = 0; args.arg3 = 0; - return (void *)jinue_syscall(&args); + return call_with_pointer_convention(&args, thread_local, NULL); } -int jinue_create_thread(int process, void (*entry)(void), void *stack, int *perrno) { +int jinue_create_thread(int fd, int process, int *perrno) { jinue_syscall_args_t args; args.arg0 = JINUE_SYS_CREATE_THREAD; - args.arg1 = process; - args.arg2 = (uintptr_t)entry; - args.arg3 = (uintptr_t)stack; + args.arg1 = fd; + args.arg2 = process; + args.arg3 = 0; - return jinue_syscall_with_usual_convention(&args, perrno); + return call_with_usual_convention(&args, perrno); } void jinue_yield_thread(void) { @@ -107,7 +135,7 @@ int jinue_puts(int loglevel, const char *str, size_t n, int *perrno) { args.arg2 = (uintptr_t)str; args.arg3 = n; - return jinue_syscall_with_usual_convention(&args, perrno); + return call_with_usual_convention(&args, perrno); } int jinue_get_user_memory(jinue_mem_map_t *buffer, size_t buffer_size, int *perrno) { @@ -118,7 +146,7 @@ int jinue_get_user_memory(jinue_mem_map_t *buffer, size_t buffer_size, int *perr args.arg2 = buffer_size; args.arg3 = 0; - return jinue_syscall_with_usual_convention(&args, perrno); + return call_with_usual_convention(&args, perrno); } int jinue_mmap( @@ -142,7 +170,7 @@ int jinue_mmap( args.arg2 = (uintptr_t)&mmap_args; args.arg3 = 0; - return jinue_syscall_with_usual_convention(&args, perrno); + return call_with_usual_convention(&args, perrno); } intptr_t jinue_send( @@ -158,7 +186,7 @@ intptr_t jinue_send( args.arg2 = (uintptr_t)message; args.arg3 = 0; - return jinue_syscall_with_usual_convention(&args, perrno); + return call_with_usual_convention(&args, perrno); } intptr_t jinue_receive(int fd, const jinue_message_t *message, int *perrno){ @@ -169,7 +197,7 @@ intptr_t jinue_receive(int fd, const jinue_message_t *message, int *perrno){ args.arg2 = (uintptr_t)message; args.arg3 = 0; - return jinue_syscall_with_usual_convention(&args, perrno); + return call_with_usual_convention(&args, perrno); } intptr_t jinue_reply(const jinue_message_t *message, int *perrno) { @@ -180,7 +208,7 @@ intptr_t jinue_reply(const jinue_message_t *message, int *perrno) { args.arg2 = (uintptr_t)message; args.arg3 = 0; - return jinue_syscall_with_usual_convention(&args, perrno); + return call_with_usual_convention(&args, perrno); } int jinue_create_endpoint(int fd, int *perrno) { @@ -191,7 +219,7 @@ int jinue_create_endpoint(int fd, int *perrno) { args.arg2 = 0; args.arg3 = 0; - return jinue_syscall_with_usual_convention(&args, perrno); + return call_with_usual_convention(&args, perrno); } int jinue_create_process(int fd, int *perrno) { @@ -202,7 +230,7 @@ int jinue_create_process(int fd, int *perrno) { args.arg2 = 0; args.arg3 = 0; - return jinue_syscall_with_usual_convention(&args, perrno); + return call_with_usual_convention(&args, perrno); } int jinue_mclone( @@ -227,7 +255,7 @@ int jinue_mclone( args.arg2 = dest; args.arg3 = (uintptr_t)&mclone_args; - return jinue_syscall_with_usual_convention(&args, perrno); + return call_with_usual_convention(&args, perrno); } int jinue_dup(int process, int src, int dest, int *perrno) { @@ -238,7 +266,7 @@ int jinue_dup(int process, int src, int dest, int *perrno) { args.arg2 = src; args.arg3 = dest; - return jinue_syscall_with_usual_convention(&args, perrno); + return call_with_usual_convention(&args, perrno); } int jinue_close(int fd, int *perrno) { @@ -249,7 +277,7 @@ int jinue_close(int fd, int *perrno) { args.arg2 = 0; args.arg3 = 0; - return jinue_syscall_with_usual_convention(&args, perrno); + return call_with_usual_convention(&args, perrno); } int jinue_destroy(int fd, int *perrno) { @@ -260,7 +288,7 @@ int jinue_destroy(int fd, int *perrno) { args.arg2 = 0; args.arg3 = 0; - return jinue_syscall_with_usual_convention(&args, perrno); + return call_with_usual_convention(&args, perrno); } int jinue_mint( @@ -284,5 +312,27 @@ int jinue_mint( args.arg2 = (uintptr_t)&mint_args; args.arg3 = 0; - return jinue_syscall_with_usual_convention(&args, perrno); + return call_with_usual_convention(&args, perrno); +} + +int jinue_start_thread(int fd, void (*entry)(void), void *stack, int *perrno) { + jinue_syscall_args_t args; + + args.arg0 = JINUE_SYS_START_THREAD; + args.arg1 = fd; + args.arg2 = (uintptr_t)entry; + args.arg3 = (uintptr_t)stack; + + return call_with_usual_convention(&args, perrno); +} + +int jinue_join_thread(int fd, int *perrno) { + jinue_syscall_args_t args; + + args.arg0 = JINUE_SYS_JOIN_THREAD; + args.arg1 = fd; + args.arg2 = 0; + args.arg3 = 0; + + return call_with_usual_convention(&args, perrno); } diff --git a/userspace/lib/jinue/threads.c b/userspace/lib/jinue/threads.c new file mode 100644 index 00000000..bfe09137 --- /dev/null +++ b/userspace/lib/jinue/threads.c @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2024 Philippe Aubertin. + * 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 author nor the names of other contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. + */ + +#include +#include +#include +#include + +struct jinue_thread { + /* The first two members must be in this order and at the start of the + * structure, i.e. they must be at the top of the stack when the thread + * starts. jinue_thread_entry() relies on this. */ + void *(*start_routine)(void*); + void *arg_and_retval; + int fd; +}; + +int jinue_thread_init(jinue_thread_t *thread, int fd, size_t stacksize) { + int status = jinue_create_thread(fd, JINUE_DESC_SELF_PROCESS, &errno); + + if(status < 0) { + return status; + } + + char *stack = mmap( + NULL, + stacksize, + JINUE_PROT_READ | JINUE_PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, + -1, + 0 + ); + + if(stack == MAP_FAILED) { + jinue_close(fd, NULL); + return -1; + } + + char *stackbase = stack + stacksize; + *thread = (struct jinue_thread *)stackbase - 1; + + (*thread)->fd = fd; + + return 0; +} + +int jinue_thread_start(jinue_thread_t thread, void *(*start_routine)(void*), void *arg) { + thread->start_routine = start_routine; + thread->arg_and_retval = arg; + return jinue_start_thread(thread->fd, jinue_thread_entry, &thread->start_routine, &errno); +} + +int jinue_thread_join(jinue_thread_t thread, void **value_ptr) { + int status = jinue_join_thread(thread->fd, &errno); + + if(status < 0) { + return status; + } + + *value_ptr = thread->arg_and_retval; + + return 0; +} + +int jinue_thread_destroy(jinue_thread_t thread) { + /* TODO dealloc/unmap stack */ + return jinue_close(thread->fd, &errno); +} diff --git a/userspace/lib/libc/crt.asm b/userspace/lib/libc/crt.asm index 4e078755..35ebb75d 100644 --- a/userspace/lib/libc/crt.asm +++ b/userspace/lib/libc/crt.asm @@ -83,6 +83,7 @@ _start: call _init + ; Check _init() exit status, skip main() if non-zero or eax, eax jnz .exit diff --git a/userspace/lib/libc/string.c b/userspace/lib/libc/string.c index 18d406e3..4677580d 100644 --- a/userspace/lib/libc/string.c +++ b/userspace/lib/libc/string.c @@ -137,8 +137,14 @@ static const char *strerror_const(int errnum) { return "no message of the desired type"; case ENOTSUP: return "not supported"; + case EBUSY: + return "device or resource busy"; + case ESRCH: + return "no such process"; + case EDEADLK: + return "resource deadlock would occur"; default: - return "Unknown error"; + return "unknown error"; } } diff --git a/userspace/loader/loader.c b/userspace/loader/loader.c index 91a1e8f4..7522853e 100644 --- a/userspace/loader/loader.c +++ b/userspace/loader/loader.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Philippe Aubertin. + * Copyright (C) 2023-2024 Philippe Aubertin. * All rights reserved. * Redistribution and use in source and binary forms, with or without @@ -44,6 +44,8 @@ #define INIT_PROCESS_DESCRIPTOR (JINUE_DESC_SELF_PROCESS + 1) +#define INIT_THREAD_DESCRIPTOR (JINUE_DESC_SELF_PROCESS + 2) + static jinue_mem_map_t *get_memory_map(void *buffer, size_t bufsize) { int status = jinue_get_user_memory((jinue_mem_map_t *)buffer, bufsize, NULL); @@ -98,7 +100,7 @@ static int load_init(elf_info_t *elf_info, const jinue_dirent_t *init, int argc, INIT_PROCESS_DESCRIPTOR, INIT_PROCESS_DESCRIPTOR, JINUE_DESC_SELF_PROCESS, - JINUE_PERM_CLOSE | JINUE_PERM_CREATE_THREAD | JINUE_PERM_MAP | JINUE_PERM_OPEN, + JINUE_PERM_CREATE_THREAD | JINUE_PERM_MAP | JINUE_PERM_OPEN, 0, &errno ); @@ -152,15 +154,29 @@ int main(int argc, char *argv[]) { jinue_info("---"); - status = jinue_create_thread( - INIT_PROCESS_DESCRIPTOR, + status = jinue_create_thread(INIT_THREAD_DESCRIPTOR, INIT_PROCESS_DESCRIPTOR, &errno); + + if (status != 0) { + jinue_error("error: could not create thread: %s", strerror(errno)); + return EXIT_FAILURE; + } + + status = jinue_start_thread( + INIT_THREAD_DESCRIPTOR, elf_info.entry, elf_info.stack_addr, &errno ); if (status != 0) { - jinue_error("error: could not create thread: %s", strerror(errno)); + jinue_error("error: could not start thread: %s", strerror(errno)); + return EXIT_FAILURE; + } + + status = jinue_close(INIT_THREAD_DESCRIPTOR, &errno); + + if (status != 0) { + jinue_error("error: could not close thread descriptor: %s", strerror(errno)); return EXIT_FAILURE; } diff --git a/userspace/testapp/testapp.c b/userspace/testapp/testapp.c index ef410ff4..95533cd1 100644 --- a/userspace/testapp/testapp.c +++ b/userspace/testapp/testapp.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2023 Philippe Aubertin. + * Copyright (C) 2019-2024 Philippe Aubertin. * All rights reserved. * Redistribution and use in source and binary forms, with or without diff --git a/userspace/testapp/tests/ipc.c b/userspace/testapp/tests/ipc.c index 5b5473ce..bfdb2825 100644 --- a/userspace/testapp/tests/ipc.c +++ b/userspace/testapp/tests/ipc.c @@ -30,21 +30,20 @@ */ #include +#include #include #include #include #include #include "../utils.h" -#define THREAD_STACK_SIZE 4096 +#define MSG_FUNC_TEST (JINUE_SYS_USER_BASE + 42) -#define MSG_FUNC_TEST (JINUE_SYS_USER_BASE + 42) +#define ENDPOINT_DESCRIPTOR 0 -#define IPC_DESCRIPTOR 0 +#define CLIENT_ENDPOINT_DESCRIPTOR 1 -#define CLIENT_DESCRIPTOR 1 - -static char ipc_test_thread_stack[THREAD_STACK_SIZE]; +#define CLIENT_THREAD_DESCRIPTOR 2 static void ipc_test_run_client(void) { /* The order of these buffers is shuffled on purpose because they will be @@ -83,7 +82,7 @@ static void ipc_test_run_client(void) { message.recv_buffers = reply_buffers; message.recv_buffers_length = 3; - intptr_t ret = jinue_send(CLIENT_DESCRIPTOR, MSG_FUNC_TEST, &message, &errno); + intptr_t ret = jinue_send(CLIENT_ENDPOINT_DESCRIPTOR, MSG_FUNC_TEST, &message, &errno); if(ret < 0) { jinue_error("error: jinue_send() failed: %s.", strerror(errno)); @@ -99,7 +98,7 @@ static void ipc_test_run_client(void) { message.recv_buffers = NULL; message.recv_buffers_length = 0; - ret = jinue_send(CLIENT_DESCRIPTOR, MSG_FUNC_TEST, &message, &errno); + ret = jinue_send(CLIENT_ENDPOINT_DESCRIPTOR, MSG_FUNC_TEST, &message, &errno); if(ret >= 0) { jinue_error("error: jinue_send() unexpectedly succeeded"); @@ -114,12 +113,14 @@ static void ipc_test_run_client(void) { jinue_info("expected: jinue_send() set errno to: %s.", strerror(errno)); } -static void ipc_test_client_thread(void) { +static void *ipc_test_client_thread(void *arg) { + jinue_info("Client thread is starting with argument: %#p", arg); + ipc_test_run_client(); jinue_info("Client thread is exiting."); - jinue_exit_thread(); + return (void *)(uintptr_t)0xdeadbeef; } void run_ipc_test(void) { @@ -131,7 +132,7 @@ void run_ipc_test(void) { jinue_info("Running threading and IPC test..."); - int status = jinue_create_endpoint(IPC_DESCRIPTOR, &errno); + int status = jinue_create_endpoint(ENDPOINT_DESCRIPTOR, &errno); if(status < 0) { jinue_error("error: could not create IPC object: %s", strerror(errno)); @@ -139,9 +140,9 @@ void run_ipc_test(void) { } status = jinue_mint( - IPC_DESCRIPTOR, + ENDPOINT_DESCRIPTOR, JINUE_DESC_SELF_PROCESS, - CLIENT_DESCRIPTOR, + CLIENT_ENDPOINT_DESCRIPTOR, JINUE_PERM_SEND, 0xca11ab1e, &errno @@ -162,7 +163,7 @@ void run_ipc_test(void) { jinue_info("Attempting to call jinue_receive() on the send-only descriptor."); - intptr_t ret = jinue_receive(CLIENT_DESCRIPTOR, &message, &errno); + intptr_t ret = jinue_receive(CLIENT_ENDPOINT_DESCRIPTOR, &message, &errno); if(ret >= 0) { jinue_error("error: jinue_receive() unuexpectedly succeeded."); @@ -176,19 +177,22 @@ void run_ipc_test(void) { jinue_info("expected: jinue_receive() set errno to: %s.", strerror(errno)); - status = jinue_create_thread( - JINUE_DESC_SELF_PROCESS, - ipc_test_client_thread, - &ipc_test_thread_stack[THREAD_STACK_SIZE], - &errno - ); + jinue_thread_t client_thread; + status = jinue_thread_init(&client_thread, CLIENT_THREAD_DESCRIPTOR, 4096); if (status != 0) { jinue_error("error: could not create thread: %s", strerror(errno)); return; } - ret = jinue_receive(IPC_DESCRIPTOR, &message, &errno); + status = jinue_thread_start(client_thread, ipc_test_client_thread, (void *)(uintptr_t) 0xb01dface); + + if (status != 0) { + jinue_error("error: could not start thread: %s", strerror(errno)); + return; + } + + ret = jinue_receive(ENDPOINT_DESCRIPTOR, &message, &errno); if(ret < 0) { jinue_error("error: jinue_receive() failed: %s.", strerror(errno)); @@ -228,16 +232,29 @@ void run_ipc_test(void) { jinue_info("Closing receiver descriptor."); - status = jinue_close(IPC_DESCRIPTOR, &errno); + status = jinue_close(ENDPOINT_DESCRIPTOR, &errno); + + if(status < 0) { + jinue_error("error: failed to close endpoint descriptor: %s", strerror(errno)); + return; + } + void *client_exit_value; + status = jinue_thread_join(client_thread, &client_exit_value); + if(status < 0) { - jinue_error("error: jinue_close() failed: %s", strerror(errno)); + jinue_error("error: failed to join client thread: %s", strerror(errno)); return; } + + jinue_info("Client thread exit value is %#p.", client_exit_value); - /* Give a chance to the client thread to notice that its second call to - * jinue_send() failed. */ - jinue_yield_thread(); + status = jinue_thread_destroy(client_thread); + + if(status < 0) { + jinue_error("error: failed to destroy client thread: %s", strerror(errno)); + return; + } jinue_info("Main thread is running."); }