From b748a2fbaa0d8f9e1f5d732ed006961e61af0a4b Mon Sep 17 00:00:00 2001 From: Philippe Aubertin <39178965+phaubertin@users.noreply.github.com> Date: Mon, 18 Nov 2024 21:21:08 -0500 Subject: [PATCH] Synchronization (#84) In the current situation, only one thread runs at a time since only one CPU is enabled. Furthermore, threading is fully cooperative since interrupts are always disabled, so threads can never be preempted. This PR starts laying the foundations for multiprocessing by creating the necessary synchronization primitives (spinlock, atomic increment/decrement, "block thread and unlock", etc.) and by ensuring they are used where needed. --- doc/syscalls/destroy.md | 2 +- include/jinue/shared/types.h | 8 +- include/kernel/application/syscalls.h | 2 +- include/kernel/domain/entities/descriptor.h | 66 +++- include/kernel/domain/entities/endpoint.h | 4 + include/kernel/domain/entities/object.h | 11 +- include/kernel/domain/entities/process.h | 4 + include/kernel/domain/entities/thread.h | 20 +- .../kernel/infrastructure/i686/asm/eflags.h | 4 +- include/kernel/infrastructure/i686/exports.h | 2 + include/kernel/infrastructure/i686/percpu.h | 2 +- .../kernel/infrastructure/i686/pmap/private.h | 1 - include/kernel/infrastructure/i686/thread.h | 5 +- include/kernel/infrastructure/i686/types.h | 8 - include/kernel/machine/atomic.h | 39 ++ include/kernel/machine/pmap.h | 2 - include/kernel/machine/spinlock.h | 46 +++ include/kernel/machine/thread.h | 6 +- include/kernel/machine/tls.h | 2 +- include/kernel/types.h | 11 +- kernel/Makefile | 2 + kernel/application/kmain.c | 4 +- kernel/application/syscalls/await_thread.c | 44 ++- kernel/application/syscalls/close.c | 15 +- kernel/application/syscalls/create_endpoint.c | 17 +- kernel/application/syscalls/create_process.c | 21 +- kernel/application/syscalls/create_thread.c | 65 ++-- kernel/application/syscalls/destroy.c | 16 +- kernel/application/syscalls/dup.c | 62 ++- kernel/application/syscalls/exit_thread.c | 16 +- kernel/application/syscalls/mclone.c | 97 +++-- kernel/application/syscalls/mint.c | 91 +++-- kernel/application/syscalls/mmap.c | 39 +- kernel/application/syscalls/receive.c | 14 +- kernel/application/syscalls/send.c | 14 +- .../application/syscalls/set_thread_local.c | 7 +- kernel/application/syscalls/start_thread.c | 15 +- kernel/domain/entities/descriptor.c | 290 ++++++++++++-- kernel/domain/entities/endpoint.c | 71 +++- kernel/domain/entities/object.c | 62 ++- kernel/domain/entities/process.c | 103 ++++- kernel/domain/entities/thread.c | 367 ++++++++++++++---- kernel/domain/services/exec.c | 53 ++- kernel/domain/services/ipc.c | 105 +++-- kernel/infrastructure/elf.c | 4 +- kernel/infrastructure/i686/atomic/atomic.asm | 69 ++++ .../infrastructure/i686/atomic/spinlock.asm | 86 ++++ kernel/infrastructure/i686/cpuinfo.c | 2 +- kernel/infrastructure/i686/percpu.c | 2 +- kernel/infrastructure/i686/pmap/nopae.c | 9 - kernel/infrastructure/i686/pmap/pae.c | 8 - kernel/infrastructure/i686/pmap/pmap.c | 32 +- kernel/infrastructure/i686/thread.asm | 62 ++- kernel/infrastructure/i686/thread.c | 87 +++-- kernel/interface/syscalls.c | 1 - 55 files changed, 1589 insertions(+), 608 deletions(-) create mode 100644 include/kernel/machine/atomic.h create mode 100644 include/kernel/machine/spinlock.h create mode 100644 kernel/infrastructure/i686/atomic/atomic.asm create mode 100644 kernel/infrastructure/i686/atomic/spinlock.asm diff --git a/doc/syscalls/destroy.md b/doc/syscalls/destroy.md index 41e3026c..405a69d0 100644 --- a/doc/syscalls/destroy.md +++ b/doc/syscalls/destroy.md @@ -2,7 +2,7 @@ ## Description -Close a descriptor and destroy the kernel object to which it refers. +Destroy the kernel object to which a descriptor refers. 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 diff --git a/include/jinue/shared/types.h b/include/jinue/shared/types.h index e982e578..7e0fa8da 100644 --- a/include/jinue/shared/types.h +++ b/include/jinue/shared/types.h @@ -107,10 +107,10 @@ typedef struct { } jinue_mclone_args_t; typedef struct { - int process; - int fd; - int perms; - uintptr_t cookie; + int process; + int fd; + int perms; + uintptr_t cookie; } jinue_mint_args_t; #endif diff --git a/include/kernel/application/syscalls.h b/include/kernel/application/syscalls.h index 929203bb..51e17547 100644 --- a/include/kernel/application/syscalls.h +++ b/include/kernel/application/syscalls.h @@ -56,7 +56,7 @@ int get_user_memory(const jinue_buffer_t *buffer); int mclone(int src, int dest, const jinue_mclone_args_t *args); -int mint(int owner, const jinue_mint_args_t *mint_args); +int mint(int owner, const jinue_mint_args_t *args); int mmap(int process_fd, const jinue_mmap_args_t *args); diff --git a/include/kernel/domain/entities/descriptor.h b/include/kernel/domain/entities/descriptor.h index 6008fd4a..96059825 100644 --- a/include/kernel/domain/entities/descriptor.h +++ b/include/kernel/domain/entities/descriptor.h @@ -38,39 +38,69 @@ /* These flags are numbered downward starting at 31 to not conflict with PERM_... flags * which share the same flags field. */ -#define DESCRIPTOR_FLAG_NONE 0 +#define DESC_FLAG_NONE 0 -#define DESCRIPTOR_FLAG_IN_USE (1<<31) +#define DESC_FLAG_STATE1 (1<<31) -#define DESCRIPTOR_FLAG_DESTROYED (1<<30) +#define DESC_FLAG_STATE0 (1<<30) -#define DESCRIPTOR_FLAG_OWNER (1<<29) +#define DESC_FLAG_OWNER (1<<29) -static inline bool descriptor_is_in_use(descriptor_t *desc) { - return desc != NULL && (desc->flags & DESCRIPTOR_FLAG_IN_USE); +#define DESC_FLAG_STATE (DESC_FLAG_STATE1 | DESC_FLAG_STATE0) + +#define DESC_STATE_FREE 0 + +#define DESC_STATE_RESERVED DESC_FLAG_STATE0 + +#define DESC_STATE_OPEN DESC_FLAG_STATE1 + +#define DESC_STATE_DESTROYED (DESC_FLAG_STATE1 | DESC_FLAG_STATE0) + + +static inline bool descriptor_is_free(const descriptor_t *desc) { + return (desc->flags & DESC_FLAG_STATE) == DESC_STATE_FREE; +} + +static inline bool descriptor_is_reserved(const descriptor_t *desc) { + return (desc->flags & DESC_FLAG_STATE) == DESC_STATE_RESERVED; +} + +static inline bool descriptor_is_open(const descriptor_t *desc) { + return (desc->flags & DESC_FLAG_STATE) == DESC_STATE_OPEN; +} + +static inline bool descriptor_is_destroyed(const descriptor_t *desc) { + return (desc->flags & DESC_FLAG_STATE) == DESC_STATE_DESTROYED; } -static inline bool descriptor_is_destroyed(descriptor_t *desc) { - return !!(desc->flags & DESCRIPTOR_FLAG_DESTROYED); +static inline bool descriptor_is_closeable(const descriptor_t *desc) { + return descriptor_is_open(desc) || descriptor_is_destroyed(desc); } -static inline bool descriptor_is_owner(descriptor_t *desc) { - return !!(desc->flags & DESCRIPTOR_FLAG_OWNER); +static inline bool descriptor_is_owner(const descriptor_t *desc) { + return !!(desc->flags & DESC_FLAG_OWNER); } static inline bool descriptor_has_permissions(const descriptor_t *desc, int perms) { return (desc->flags & perms) == perms; } +void clear_descriptor(descriptor_t *desc); + int dereference_object_descriptor( - descriptor_t **pdesc, - process_t *process, - int fd); - -int dereference_unused_descriptor( - descriptor_t **pdesc, - process_t *process, - int fd); + descriptor_t *pout, + process_t *process, + int fd); + +void unreference_descriptor_object(descriptor_t *desc); + +int reserve_free_descriptor(process_t *process, int fd); + +void free_reserved_descriptor(process_t *process, int fd); + +void open_descriptor(process_t *process, int fd, const descriptor_t *in); + +int close_descriptor(process_t *process, int fd); ipc_endpoint_t *get_endpoint_from_descriptor(descriptor_t *desc); diff --git a/include/kernel/domain/entities/endpoint.h b/include/kernel/domain/entities/endpoint.h index e43879fc..8e19ac35 100644 --- a/include/kernel/domain/entities/endpoint.h +++ b/include/kernel/domain/entities/endpoint.h @@ -36,6 +36,10 @@ extern const object_type_t *object_type_ipc_endpoint; +static inline object_header_t *endpoint_object(ipc_endpoint_t *endpoint) { + return &endpoint->header; +} + void initialize_endpoint_cache(void); ipc_endpoint_t *construct_endpoint(void); diff --git a/include/kernel/domain/entities/object.h b/include/kernel/domain/entities/object.h index 3ffbb81d..b4846752 100644 --- a/include/kernel/domain/entities/object.h +++ b/include/kernel/domain/entities/object.h @@ -40,11 +40,6 @@ #define OBJECT_FLAG_DESTROYED (1<<0) - -static inline void mark_object_destroyed(object_header_t *object) { - object->flags |= OBJECT_FLAG_DESTROYED; -} - static inline bool object_is_destroyed(object_header_t *object) { return !!(object->flags & OBJECT_FLAG_DESTROYED); } @@ -55,10 +50,6 @@ static inline void init_object_header(object_header_t *object, const object_type object->flags = OBJECT_FLAG_NONE; } -static inline void add_ref_to_object(object_header_t *object) { - ++object->ref_count; -} - void init_object_cache(slab_cache_t *cache, const object_type_t *type); void open_object(object_header_t *object, const descriptor_t *desc); @@ -67,6 +58,8 @@ void close_object(object_header_t *object, const descriptor_t *desc); void destroy_object(object_header_t *object); +void add_ref_to_object(object_header_t *object); + void sub_ref_to_object(object_header_t *object); #endif diff --git a/include/kernel/domain/entities/process.h b/include/kernel/domain/entities/process.h index 95652b42..94226926 100644 --- a/include/kernel/domain/entities/process.h +++ b/include/kernel/domain/entities/process.h @@ -36,6 +36,10 @@ extern const object_type_t *object_type_process; +static inline object_header_t *process_object(process_t *process) { + return &process->header; +} + void initialize_process_cache(void); process_t *construct_process(void); diff --git a/include/kernel/domain/entities/thread.h b/include/kernel/domain/entities/thread.h index 0daae08f..34f28e4b 100644 --- a/include/kernel/domain/entities/thread.h +++ b/include/kernel/domain/entities/thread.h @@ -36,23 +36,29 @@ extern const object_type_t *object_type_thread; +static inline object_header_t *thread_object(thread_t *thread) { + return &thread->header; +} + thread_t *construct_thread(process_t *process); 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 run_first_thread(thread_t *thread); -void start_first_thread(thread_t *thread); +void run_thread(thread_t *thread); -void yield_current_thread(void); +void terminate_current_thread(void); -void block_current_thread(void); +void switch_to(thread_t *to); -void thread_is_starting(thread_t *thread); +void switch_to_and_block(thread_t *to); -void current_thread_is_exiting(void); +void block_and_unlock(spinlock_t *lock); + +void yield_current_thread(void); void set_thread_local_storage(thread_t *thread, addr_t addr, size_t size); diff --git a/include/kernel/infrastructure/i686/asm/eflags.h b/include/kernel/infrastructure/i686/asm/eflags.h index 8ece872b..fbfb5301 100644 --- a/include/kernel/infrastructure/i686/asm/eflags.h +++ b/include/kernel/infrastructure/i686/asm/eflags.h @@ -32,6 +32,8 @@ #ifndef JINUE_KERNEL_INFRASTRUCTURE_I686_ASM_EFLAGS_H #define JINUE_KERNEL_INFRASTRUCTURE_I686_ASM_EFLAGS_H -#define CPU_EFLAGS_ID (1<<21) +#define EFLAGS_ALWAYS_1 (1<<1) + +#define EFLAGS_ID (1<<21) #endif diff --git a/include/kernel/infrastructure/i686/exports.h b/include/kernel/infrastructure/i686/exports.h index ff5224f3..c21dfee2 100644 --- a/include/kernel/infrastructure/i686/exports.h +++ b/include/kernel/infrastructure/i686/exports.h @@ -94,4 +94,6 @@ typedef struct { bool vga_enable; } machine_config_t; +typedef struct { uint32_t lock; } spinlock_t; + #endif diff --git a/include/kernel/infrastructure/i686/percpu.h b/include/kernel/infrastructure/i686/percpu.h index f5ecc180..f9dd2565 100644 --- a/include/kernel/infrastructure/i686/percpu.h +++ b/include/kernel/infrastructure/i686/percpu.h @@ -43,7 +43,7 @@ static inline percpu_t *get_percpu_data(void) { return (percpu_t *)get_gs_ptr( (uint32_t *)&zero->self ); } -static inline tss_t *get_tss(void) { +static inline tss_t *get_percpu_tss(void) { return &get_percpu_data()->tss; } diff --git a/include/kernel/infrastructure/i686/pmap/private.h b/include/kernel/infrastructure/i686/pmap/private.h index 1dec358b..74807f8a 100644 --- a/include/kernel/infrastructure/i686/pmap/private.h +++ b/include/kernel/infrastructure/i686/pmap/private.h @@ -74,7 +74,6 @@ void destroy_page_directory(void *page_directory, unsigned int last_index); * * @param pte page table or page directory entry * @return true if page is present in memory, false otherwise - * */ static inline bool pte_is_present(const pte_t *pte) { /* Micro-optimization: both flags we are interested in are in the lower four diff --git a/include/kernel/infrastructure/i686/thread.h b/include/kernel/infrastructure/i686/thread.h index ad4faaaf..155a82da 100644 --- a/include/kernel/infrastructure/i686/thread.h +++ b/include/kernel/infrastructure/i686/thread.h @@ -35,9 +35,6 @@ #include #include -void switch_thread_stack( - machine_thread_t *from_ctx, - machine_thread_t *to_ctx, - bool destroy_from); +void switch_thread_stack(machine_thread_t *from, machine_thread_t *to); #endif diff --git a/include/kernel/infrastructure/i686/types.h b/include/kernel/infrastructure/i686/types.h index ad8d3134..6998422f 100644 --- a/include/kernel/infrastructure/i686/types.h +++ b/include/kernel/infrastructure/i686/types.h @@ -122,12 +122,4 @@ struct percpu_t { typedef struct percpu_t percpu_t; -typedef struct { - uint32_t edi; - uint32_t esi; - uint32_t ebx; - uint32_t ebp; - uint32_t eip; -} kernel_context_t; - #endif diff --git a/include/kernel/machine/atomic.h b/include/kernel/machine/atomic.h new file mode 100644 index 00000000..b55eb634 --- /dev/null +++ b/include/kernel/machine/atomic.h @@ -0,0 +1,39 @@ +/* + * 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. + */ + +#ifndef JINUE_KERNEL_MACHINE_ATOMIC_H +#define JINUE_KERNEL_MACHINE_ATOMIC_H + +int add_atomic(int *value, int increment); + +int or_atomic(int *value, int mask); + +#endif diff --git a/include/kernel/machine/pmap.h b/include/kernel/machine/pmap.h index 450079c0..d95bcbd4 100644 --- a/include/kernel/machine/pmap.h +++ b/include/kernel/machine/pmap.h @@ -55,6 +55,4 @@ bool machine_clone_userspace_mapping( kern_paddr_t machine_lookup_kernel_paddr(void *addr); -void machine_switch_to_kernel_addr_space(void); - #endif diff --git a/include/kernel/machine/spinlock.h b/include/kernel/machine/spinlock.h new file mode 100644 index 00000000..e9cb219c --- /dev/null +++ b/include/kernel/machine/spinlock.h @@ -0,0 +1,46 @@ +/* + * 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. + */ + +#ifndef JINUE_KERNEL_MACHINE_SPINLOCK_H +#define JINUE_KERNEL_MACHINE_SPINLOCK_H + +#include + +#define SPINLOCK_STATIC { .lock = 0 } + +void init_spinlock(spinlock_t *lock); + +void spin_lock(spinlock_t *lock); + +void spin_unlock(spinlock_t *lock); + +#endif + diff --git a/include/kernel/machine/thread.h b/include/kernel/machine/thread.h index 0e62278c..1c63de76 100644 --- a/include/kernel/machine/thread.h +++ b/include/kernel/machine/thread.h @@ -42,6 +42,10 @@ thread_t *machine_alloc_thread(void); void machine_free_thread(thread_t *thread); -void machine_switch_thread(thread_t *from, thread_t *to, bool destroy_from); +void machine_switch_thread(thread_t *from, thread_t *to); + +void machine_switch_and_unref_thread(thread_t *from, thread_t *to); + +void machine_switch_thread_and_unlock(thread_t *from, thread_t *to, spinlock_t *lock); #endif diff --git a/include/kernel/machine/tls.h b/include/kernel/machine/tls.h index e3197e07..94119b56 100644 --- a/include/kernel/machine/tls.h +++ b/include/kernel/machine/tls.h @@ -34,6 +34,6 @@ #include -void machine_set_tls(const thread_t *thread); +void machine_set_thread_local_storage(const thread_t *thread); #endif diff --git a/include/kernel/types.h b/include/kernel/types.h index d229a33b..654399d8 100644 --- a/include/kernel/types.h +++ b/include/kernel/types.h @@ -91,24 +91,28 @@ typedef struct { object_header_t header; addr_space_t addr_space; int running_threads_count; + spinlock_t descriptors_lock; descriptor_t descriptors[JINUE_DESC_NUM]; } process_t; typedef enum { - THREAD_STATE_ZOMBIE, + THREAD_STATE_CREATED, + THREAD_STATE_STARTING, THREAD_STATE_READY, THREAD_STATE_RUNNING, - THREAD_STATE_BLOCKED + THREAD_STATE_BLOCKED, + THREAD_STATE_ZOMBIE } thread_state_t; struct thread_t { object_header_t header; - machine_thread_t thread_ctx; + machine_thread_t machine_thread; jinue_node_t thread_list; thread_state_t state; process_t *process; struct thread_t *sender; struct thread_t *awaiter; + spinlock_t await_lock; addr_t local_storage_addr; size_t local_storage_size; size_t recv_buffer_size; @@ -129,6 +133,7 @@ typedef struct { typedef struct { object_header_t header; + spinlock_t lock; jinue_list_t send_list; jinue_list_t recv_list; int receivers_count; diff --git a/kernel/Makefile b/kernel/Makefile index e6b575f4..ad99635d 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -120,6 +120,8 @@ sources.kernel.c = \ interface/syscalls.c sources.kernel.nasm = \ + infrastructure/i686/atomic/atomic.asm \ + infrastructure/i686/atomic/spinlock.asm \ infrastructure/i686/isa/abi.asm \ infrastructure/i686/isa/instrs.asm \ infrastructure/i686/isa/io.asm \ diff --git a/kernel/application/kmain.c b/kernel/application/kmain.c index b4e6e55d..a37666fb 100644 --- a/kernel/application/kmain.c +++ b/kernel/application/kmain.c @@ -117,8 +117,8 @@ void kmain(const char *cmdline) { info("---"); /* Start first thread. */ - start_first_thread(thread); + run_first_thread(thread); /* should never happen */ - panic("start_first_thread() returned in kmain()"); + panic("run_first_thread() returned in kmain()"); } diff --git a/kernel/application/syscalls/await_thread.c b/kernel/application/syscalls/await_thread.c index 0b82b116..4b0060ba 100644 --- a/kernel/application/syscalls/await_thread.c +++ b/kernel/application/syscalls/await_thread.c @@ -35,23 +35,17 @@ #include #include #include +#include #include -int await_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); +static int with_thread(descriptor_t *thread_desc) { + thread_t *thread = get_thread_from_descriptor(thread_desc); if(thread == NULL) { return -JINUE_EBADF; } - if(!descriptor_has_permissions(desc, JINUE_PERM_AWAIT)) { + if(!descriptor_has_permissions(thread_desc, JINUE_PERM_AWAIT)) { return -JINUE_EPERM; } @@ -61,21 +55,35 @@ int await_thread(int fd) { return -JINUE_EDEADLK; } - /* TODO this check and the following assignment should be atomic */ - if(thread->awaiter != NULL) { + spin_lock(&thread->await_lock); + + if(thread->state == THREAD_STATE_CREATED || thread->awaiter != NULL) { + spin_unlock(&thread->await_lock); return -JINUE_ESRCH; } thread->awaiter = current; - /* Keep the thread around until we actually read the exit value. */ - add_ref_to_object(&thread->header); + if(thread->state == THREAD_STATE_ZOMBIE) { + spin_unlock(&thread->await_lock); + } else { + block_and_unlock(&thread->await_lock); + } - if(thread->state != THREAD_STATE_ZOMBIE) { - block_current_thread(); + return 0; +} + +int await_thread(int fd) { + descriptor_t thread_desc; + int status = dereference_object_descriptor(&thread_desc, get_current_process(), fd); + + if(status < 0) { + return -JINUE_EBADF; } - sub_ref_to_object(&thread->header); + status = with_thread(&thread_desc); - return 0; + unreference_descriptor_object(&thread_desc); + + return status; } diff --git a/kernel/application/syscalls/close.c b/kernel/application/syscalls/close.c index 7543b83f..bf8e543d 100644 --- a/kernel/application/syscalls/close.c +++ b/kernel/application/syscalls/close.c @@ -37,18 +37,5 @@ int close(int fd) { - descriptor_t *desc; - int status = dereference_object_descriptor(&desc, get_current_process(), fd); - - if(status < 0) { - return status; - } - - object_header_t *object = desc->object; - - close_object(object, desc); - - desc->flags = DESCRIPTOR_FLAG_NONE; - - return 0; + return close_descriptor(get_current_process(), fd); } diff --git a/kernel/application/syscalls/create_endpoint.c b/kernel/application/syscalls/create_endpoint.c index cd548279..416f9f18 100644 --- a/kernel/application/syscalls/create_endpoint.c +++ b/kernel/application/syscalls/create_endpoint.c @@ -46,8 +46,8 @@ * */ int create_endpoint(int fd) { - descriptor_t *desc; - int status = dereference_unused_descriptor(&desc, get_current_process(), fd); + process_t *process = get_current_process(); + int status = reserve_free_descriptor(process, fd); if(status < 0) { return status; @@ -56,17 +56,16 @@ int create_endpoint(int fd) { ipc_endpoint_t *endpoint = construct_endpoint(); if(endpoint == NULL) { + free_reserved_descriptor(process, fd); return -JINUE_EAGAIN; } - desc->object = &endpoint->header; - desc->flags = - DESCRIPTOR_FLAG_IN_USE - | DESCRIPTOR_FLAG_OWNER - | object_type_ipc_endpoint->all_permissions; - desc->cookie = 0; + descriptor_t desc; + desc.object = endpoint_object(endpoint); + desc.flags = DESC_FLAG_OWNER | object_type_ipc_endpoint->all_permissions; + desc.cookie = 0; - open_object(&endpoint->header, desc); + open_descriptor(process, fd, &desc); return 0; } diff --git a/kernel/application/syscalls/create_process.c b/kernel/application/syscalls/create_process.c index b625ecfb..1be3c802 100644 --- a/kernel/application/syscalls/create_process.c +++ b/kernel/application/syscalls/create_process.c @@ -36,27 +36,26 @@ #include int create_process(int fd) { - descriptor_t *desc; - int status = dereference_unused_descriptor(&desc, get_current_process(), fd); + process_t *current = get_current_process(); + int status = reserve_free_descriptor(current, fd); if(status < 0) { return status; } - process_t *process = construct_process(); + process_t *new_process = construct_process(); - if(process == NULL) { + if(new_process == NULL) { + free_reserved_descriptor(current, fd); return -JINUE_EAGAIN; } - desc->object = &process->header; - desc->flags = - DESCRIPTOR_FLAG_IN_USE - | DESCRIPTOR_FLAG_OWNER - | object_type_process->all_permissions; - desc->cookie = 0; + descriptor_t desc; + desc.object = process_object(new_process); + desc.flags = DESC_FLAG_OWNER | object_type_process->all_permissions; + desc.cookie = 0; - open_object(&process->header, desc); + open_descriptor(current, fd, &desc); return 0; } diff --git a/kernel/application/syscalls/create_thread.c b/kernel/application/syscalls/create_thread.c index f0546a3a..eb31aead 100644 --- a/kernel/application/syscalls/create_thread.c +++ b/kernel/application/syscalls/create_thread.c @@ -36,45 +36,62 @@ #include #include -int create_thread(int fd, int process_fd) { - descriptor_t *desc; - int status = dereference_unused_descriptor(&desc, get_current_process(), fd); - if(status < 0) { +static int with_target_process(process_t *current, int fd, descriptor_t *target_desc) { + process_t *target = get_process_from_descriptor(target_desc); + + if(target == NULL) { return -JINUE_EBADF; } - descriptor_t *process_desc; - status = dereference_object_descriptor(&process_desc, get_current_process(), process_fd); + if(!descriptor_has_permissions(target_desc, JINUE_PERM_CREATE_THREAD | JINUE_PERM_OPEN)) { + return -JINUE_EPERM; + } + + thread_t *thread = construct_thread(target); + + if(thread == NULL) { + return -JINUE_ENOMEM; + } + + descriptor_t desc; + desc.object = thread_object(thread); + desc.flags = DESC_FLAG_OWNER | object_type_thread->all_permissions; + desc.cookie = 0; + + open_descriptor(current, fd, &desc); + + return 0; +} + +static int with_descriptor_reserved(process_t *current, int fd, int process_fd) { + descriptor_t target_desc; + int status = dereference_object_descriptor(&target_desc, current, process_fd); if(status < 0) { return status; } - process_t *process = get_process_from_descriptor(process_desc); + status = with_target_process(current, fd, &target_desc); - if(process == NULL) { - return -JINUE_EBADF; - } + unreference_descriptor_object(&target_desc); - if(!descriptor_has_permissions(process_desc, JINUE_PERM_CREATE_THREAD | JINUE_PERM_OPEN)) { - return -JINUE_EPERM; - } + return status; +} - thread_t *thread = construct_thread(process); +int create_thread(int fd, int process_fd) { + process_t *current = get_current_process(); + int status = reserve_free_descriptor(current, fd); - if(thread == 0) { - return -JINUE_ENOMEM; + if(status < 0) { + return -JINUE_EBADF; } - desc->object = &thread->header; - desc->flags = - DESCRIPTOR_FLAG_IN_USE - | DESCRIPTOR_FLAG_OWNER - | object_type_thread->all_permissions; - desc->cookie = 0; + status = with_descriptor_reserved(current, fd, process_fd); - open_object(&thread->header, desc); + if(status < 0) { + free_reserved_descriptor(current, fd); + } - return 0; + return status; } diff --git a/kernel/application/syscalls/destroy.c b/kernel/application/syscalls/destroy.c index 24ff51bc..1cf38b60 100644 --- a/kernel/application/syscalls/destroy.c +++ b/kernel/application/syscalls/destroy.c @@ -37,29 +37,31 @@ #include int destroy(int fd) { - descriptor_t *desc; - int status = dereference_object_descriptor(&desc, get_current_process(), fd); + process_t *process = get_current_process(); + + descriptor_t desc; + int status = dereference_object_descriptor(&desc, process, fd); if(status < 0) { return status; } - object_header_t *object = desc->object; + object_header_t *object = desc.object; /* TODO support other object types */ if(object->type != object_type_ipc_endpoint) { + unreference_descriptor_object(&desc); return -JINUE_EBADF; } - if(!descriptor_is_owner(desc)) { + if(!descriptor_is_owner(&desc)) { + unreference_descriptor_object(&desc); return -JINUE_EPERM; } destroy_object(object); - close_object(object, desc); - - desc->flags = DESCRIPTOR_FLAG_NONE; + unreference_descriptor_object(&desc); return 0; } diff --git a/kernel/application/syscalls/dup.c b/kernel/application/syscalls/dup.c index d335dccc..217d1e12 100644 --- a/kernel/application/syscalls/dup.c +++ b/kernel/application/syscalls/dup.c @@ -35,51 +35,71 @@ #include #include -int dup(int process_fd, int src, int dest) { - process_t *current_process = get_current_process(); - descriptor_t *process_desc; - int status = dereference_object_descriptor(&process_desc, current_process, process_fd); - +static int with_source( + process_t *current, + process_t *target, + descriptor_t *src_desc, + int dest) { + + if(descriptor_is_owner(src_desc)) { + return -JINUE_EBADF; + } + + int status = reserve_free_descriptor(target, dest); + if(status < 0) { return status; } + + open_descriptor(target, dest, src_desc); + + return 0; +} + +static int with_target_process( + process_t *current, + descriptor_t *target_desc, + int src, + int dest) { - process_t *process = get_process_from_descriptor(process_desc); + process_t *target = get_process_from_descriptor(target_desc); - if(process == NULL) { + if(target == NULL) { return -JINUE_EBADF; } - if(!descriptor_has_permissions(process_desc, JINUE_PERM_OPEN)) { + if(!descriptor_has_permissions(target_desc, JINUE_PERM_OPEN)) { return -JINUE_EPERM; } - descriptor_t *src_desc; - status = dereference_object_descriptor(&src_desc, current_process, src); + descriptor_t src_desc; + int status = dereference_object_descriptor(&src_desc, current, src); if(status < 0) { return status; } - object_header_t *object = src_desc->object; + status = with_source(current, target, &src_desc, dest); - if(descriptor_is_owner(src_desc)) { - return -JINUE_EBADF; - } + unreference_descriptor_object(&src_desc); - descriptor_t *dest_desc; - status = dereference_unused_descriptor(&dest_desc, process, dest); + return status; +} + +int dup(int process_fd, int src, int dest) { + process_t *current = get_current_process(); + descriptor_t target_desc; + int status = dereference_object_descriptor(&target_desc, current, process_fd); + if(status < 0) { return status; } - dest_desc->object = src_desc->object; - dest_desc->flags = src_desc->flags; - dest_desc->cookie = src_desc->cookie; + status = with_target_process(current, &target_desc, src, dest); - open_object(object, dest_desc); + unreference_descriptor_object(&target_desc); - return 0; + return status; } diff --git a/kernel/application/syscalls/exit_thread.c b/kernel/application/syscalls/exit_thread.c index 1ef8c4c2..cf47201e 100644 --- a/kernel/application/syscalls/exit_thread.c +++ b/kernel/application/syscalls/exit_thread.c @@ -31,21 +31,7 @@ #include #include -#include -#include void exit_thread(void) { - thread_t *thread = get_current_thread(); - - if(thread->sender != NULL) { - abort_message(thread->sender); - thread->sender = NULL; - } - - if(thread->awaiter != NULL) { - ready_thread(thread->awaiter); - } - - /* This call must be the last thing happening in this function. */ - current_thread_is_exiting(); + terminate_current_thread(); } diff --git a/kernel/application/syscalls/mclone.c b/kernel/application/syscalls/mclone.c index 1c56b239..bbe7d861 100644 --- a/kernel/application/syscalls/mclone.c +++ b/kernel/application/syscalls/mclone.c @@ -36,43 +36,12 @@ #include #include -/** - * Implementation for the MCLONE system call - * - * Clone memory mappings from one process to another. - * - * @param src source process descriptor number - * @param dest destination process descriptor number - * @param args MCLONE system call arguments structure - * @return zero on success, negated error code on failure - * - */ -int mclone(int src, int dest, const jinue_mclone_args_t *args) { - process_t *current_process = get_current_process(); - - descriptor_t *src_desc; - int status = dereference_object_descriptor(&src_desc, current_process, src); - - if(status < 0) { - return status; - } - - process_t *src_process = get_process_from_descriptor(src_desc); - - if(src_process == NULL) { - return -JINUE_EBADF; - } +static int with_destination( + process_t *current, + process_t *src_process, + descriptor_t *dest_desc, + const jinue_mclone_args_t *args) { - /* TODO what permissions do we need on the source for this? Should the - * source just implicitly be the current process? */ - - descriptor_t *dest_desc; - status = dereference_object_descriptor(&dest_desc, current_process, dest); - - if(status < 0) { - return status; - } - process_t *dest_process = get_process_from_descriptor(dest_desc); if(dest_process == NULL) { @@ -98,3 +67,59 @@ int mclone(int src, int dest, const jinue_mclone_args_t *args) { return 0; } + +static int with_source( + process_t *current, + descriptor_t *src_desc, + int dest, + const jinue_mclone_args_t *args) { + + process_t *src_process = get_process_from_descriptor(src_desc); + + if(src_process == NULL) { + return -JINUE_EBADF; + } + + /* TODO what permissions do we need on the source for this? Should the + * source just implicitly be the current process? */ + + descriptor_t dest_desc; + int status = dereference_object_descriptor(&dest_desc, current, dest); + + if(status < 0) { + return status; + } + + status = with_destination(current, src_process, &dest_desc, args); + + unreference_descriptor_object(&dest_desc); + + return status; +} + +/** + * Implementation for the MCLONE system call + * + * Clone memory mappings from one process to another. + * + * @param src source process descriptor number + * @param dest destination process descriptor number + * @param args MCLONE system call arguments structure + * @return zero on success, negated error code on failure + */ +int mclone(int src, int dest, const jinue_mclone_args_t *args) { + process_t *current = get_current_process(); + + descriptor_t src_desc; + int status = dereference_object_descriptor(&src_desc, current, src); + + if(status < 0) { + return status; + } + + status = with_source(current, &src_desc, dest, args); + + unreference_descriptor_object(&src_desc); + + return status; +} diff --git a/kernel/application/syscalls/mint.c b/kernel/application/syscalls/mint.c index 98f60046..8b74dae4 100644 --- a/kernel/application/syscalls/mint.c +++ b/kernel/application/syscalls/mint.c @@ -35,68 +35,85 @@ #include #include -static int check_mint_permissions(const object_header_t *object, int perms) { - if((perms & ~object->type->all_permissions) != 0) { - return -JINUE_EINVAL; +static int with_target_process( + process_t *current, + descriptor_t *owner_desc, + descriptor_t *target_desc, + const jinue_mint_args_t *args) { + + process_t *target = get_process_from_descriptor(target_desc); + + if(target == NULL) { + return -JINUE_EBADF; } - if(perms == 0) { - return -JINUE_EINVAL; + if(!descriptor_has_permissions(target_desc, JINUE_PERM_OPEN)) { + return -JINUE_EPERM; } - return 0; -} - -int mint(int owner, const jinue_mint_args_t *mint_args) { - process_t *current_process = get_current_process(); - - descriptor_t *src_desc; - int status = dereference_object_descriptor(&src_desc, current_process, owner); + int status = reserve_free_descriptor(target, args->fd); if(status < 0) { return status; } - object_header_t *object = src_desc->object; + descriptor_t dest_desc; + dest_desc.object = owner_desc->object; + dest_desc.flags = args->perms; + dest_desc.cookie = args->cookie; - status = check_mint_permissions(object, mint_args->perms); + open_descriptor(target, args->fd, &dest_desc); - if(status < 0) { - return status; + return 0; +} + +static int with_owner( + process_t *current, + descriptor_t *owner_desc, + const jinue_mint_args_t *args) { + + int perms = args->perms; + int all_perms = owner_desc->object->type->all_permissions; + + if((perms & ~all_perms) != 0) { + return -JINUE_EINVAL; } - if(!descriptor_is_owner(src_desc)) { - return -JINUE_EPERM; + if(perms == 0) { + return -JINUE_EINVAL; } - descriptor_t *process_desc; - status = dereference_object_descriptor(&process_desc, current_process, mint_args->process); + if(! descriptor_is_owner(owner_desc)) { + return -JINUE_EPERM; + } - process_t *process = get_process_from_descriptor(process_desc); + descriptor_t target_desc; + int status = dereference_object_descriptor(&target_desc, current, args->process); - if(process == NULL) { - return -JINUE_EBADF; + if(status < 0) { + return status; } - if(!descriptor_has_permissions(process_desc, JINUE_PERM_OPEN)) { - return -JINUE_EPERM; - } + status = with_target_process(current, owner_desc, &target_desc, args); - descriptor_t *dest_desc; - status = dereference_unused_descriptor(&dest_desc, process, mint_args->fd); + unreference_descriptor_object(&target_desc); + + return status; +} + +int mint(int owner, const jinue_mint_args_t *args) { + process_t *current = get_current_process(); + + descriptor_t owner_desc; + int status = dereference_object_descriptor(&owner_desc, current, owner); if(status < 0) { return status; } - dest_desc->object = src_desc->object; - dest_desc->flags = - mint_args->perms - | (src_desc->flags & OBJECT_FLAG_DESTROYED) - | DESCRIPTOR_FLAG_IN_USE; - dest_desc->cookie = mint_args->cookie; + status = with_owner(current, &owner_desc, args); - open_object(object, dest_desc); + unreference_descriptor_object(&owner_desc); - return 0; + return status; } diff --git a/kernel/application/syscalls/mmap.c b/kernel/application/syscalls/mmap.c index 93b8574e..b9752e38 100644 --- a/kernel/application/syscalls/mmap.c +++ b/kernel/application/syscalls/mmap.c @@ -35,31 +35,14 @@ #include #include -/** - * Implementation for the MMAP system call - * - * Map a contiguous memory range into a process' address space. - * - * @param process_fd process descriptor number - * @param args MMAP system call arguments structure - * @return zero on success, negated error code on failure - * - */ -int mmap(int process_fd, const jinue_mmap_args_t *args) { - descriptor_t *desc; - int status = dereference_object_descriptor(&desc, get_current_process(), process_fd); - - if(status < 0) { - return status; - } - - process_t *process = get_process_from_descriptor(desc); +int with_process(descriptor_t *process_desc, const jinue_mmap_args_t *args) { + process_t *process = get_process_from_descriptor(process_desc); if(process == NULL) { return -JINUE_EBADF; } - if(!descriptor_has_permissions(desc, JINUE_PERM_MAP)) { + if(!descriptor_has_permissions(process_desc, JINUE_PERM_MAP)) { return -JINUE_EPERM; } @@ -69,3 +52,19 @@ int mmap(int process_fd, const jinue_mmap_args_t *args) { return 0; } + + +int mmap(int process_fd, const jinue_mmap_args_t *args) { + descriptor_t process_desc; + int status = dereference_object_descriptor(&process_desc, get_current_process(), process_fd); + + if(status < 0) { + return status; + } + + status = with_process(&process_desc, args); + + unreference_descriptor_object(&process_desc); + + return status; +} diff --git a/kernel/application/syscalls/receive.c b/kernel/application/syscalls/receive.c index 50a0e073..c38aa888 100644 --- a/kernel/application/syscalls/receive.c +++ b/kernel/application/syscalls/receive.c @@ -39,22 +39,28 @@ int receive(int fd, jinue_message_t *message) { thread_t *receiver = get_current_thread(); - descriptor_t *desc; + descriptor_t desc; int status = dereference_object_descriptor(&desc, receiver->process, fd); if(status < 0) { return status; } - ipc_endpoint_t *endpoint = get_endpoint_from_descriptor(desc); + ipc_endpoint_t *endpoint = get_endpoint_from_descriptor(&desc); if(endpoint == NULL) { + unreference_descriptor_object(&desc); return -JINUE_EBADF; } - if(!descriptor_has_permissions(desc, JINUE_PERM_RECEIVE)) { + if(!descriptor_has_permissions(&desc, JINUE_PERM_RECEIVE)) { + unreference_descriptor_object(&desc); return -JINUE_EPERM; } - return receive_message(endpoint, receiver, message); + status = receive_message(endpoint, receiver, message); + + unreference_descriptor_object(&desc); + + return status; } diff --git a/kernel/application/syscalls/send.c b/kernel/application/syscalls/send.c index 05067622..27129b14 100644 --- a/kernel/application/syscalls/send.c +++ b/kernel/application/syscalls/send.c @@ -39,22 +39,28 @@ int send(uintptr_t *errcode, int fd, int function, const jinue_message_t *message) { thread_t *sender = get_current_thread(); - descriptor_t *desc; + descriptor_t desc; int status = dereference_object_descriptor(&desc, sender->process, fd); if(status < 0) { return status; } - ipc_endpoint_t *endpoint = get_endpoint_from_descriptor(desc); + ipc_endpoint_t *endpoint = get_endpoint_from_descriptor(&desc); if(endpoint == NULL) { + unreference_descriptor_object(&desc); return -JINUE_EBADF; } - if(!descriptor_has_permissions(desc, JINUE_PERM_SEND)) { + if(!descriptor_has_permissions(&desc, JINUE_PERM_SEND)) { + unreference_descriptor_object(&desc); return -JINUE_EPERM; } - return send_message(errcode, endpoint, sender, function, desc->cookie, message); + status = send_message(errcode, endpoint, sender, function, desc.cookie, message); + + unreference_descriptor_object(&desc); + + return status; } diff --git a/kernel/application/syscalls/set_thread_local.c b/kernel/application/syscalls/set_thread_local.c index 34517d24..31e6f17f 100644 --- a/kernel/application/syscalls/set_thread_local.c +++ b/kernel/application/syscalls/set_thread_local.c @@ -32,12 +32,7 @@ #include #include #include -#include void set_thread_local(void *addr, size_t size) { - thread_t *thread = get_current_thread(); - - set_thread_local_storage(thread, addr, size); - - machine_set_tls(thread); + set_thread_local_storage(get_current_thread(), addr, size); } diff --git a/kernel/application/syscalls/start_thread.c b/kernel/application/syscalls/start_thread.c index b3975960..3948b0e3 100644 --- a/kernel/application/syscalls/start_thread.c +++ b/kernel/application/syscalls/start_thread.c @@ -36,32 +36,35 @@ #include int start_thread(int fd, const thread_params_t *params) { - descriptor_t *desc; + 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); + thread_t *thread = get_thread_from_descriptor(&desc); if(thread == NULL) { + unreference_descriptor_object(&desc); return -JINUE_EBADF; } - if(!descriptor_has_permissions(desc, JINUE_PERM_START)) { + if(!descriptor_has_permissions(&desc, JINUE_PERM_START)) { + unreference_descriptor_object(&desc); return -JINUE_EPERM; } - if(thread->state != THREAD_STATE_ZOMBIE) { + if(thread->state != THREAD_STATE_CREATED && thread->state != THREAD_STATE_ZOMBIE) { + unreference_descriptor_object(&desc); return -JINUE_EBUSY; } prepare_thread(thread, params); - thread_is_starting(thread); + run_thread(thread); - ready_thread(thread); + unreference_descriptor_object(&desc); return 0; } diff --git a/kernel/domain/entities/descriptor.c b/kernel/domain/entities/descriptor.c index d09499c6..94ebd7ce 100644 --- a/kernel/domain/entities/descriptor.c +++ b/kernel/domain/entities/descriptor.c @@ -35,14 +35,33 @@ #include #include #include +#include +#include + + +/** + * Clear a descriptor + * + * A cleared descriptor is in the free state (i.e. is unused) and is not + * referring to any object. + * + * @param desc descriptor + */ +void clear_descriptor(descriptor_t *desc) { + desc->flags = DESC_STATE_FREE; + desc->object = NULL; + desc->cookie = 0; +} /** - * Get an object reference by descriptor in a specified process + * Get an object reference by descriptor number in a specified process + * + * This function does not do any kind of locking or reference count update, so + * it should only be called in this file by functions that do these things. * * @param process process for which the descriptor is looked up - * @param fd descriptor + * @param fd descriptor number (DESC_STATE_... constant) * @return object reference on success, NULL if out of bound - * */ static descriptor_t *dereference_descriptor(process_t *process, int fd) { if(fd < 0 || fd > JINUE_DESC_NUM) { @@ -53,18 +72,87 @@ static descriptor_t *dereference_descriptor(process_t *process, int fd) { } /** - * Get the object referenced by a descriptor + * Set the state of a descriptor. * - * @param pdesc pointer to where to store the pointer to the object reference (out) + * This function does not do any kind of locking, so it should only be called + * in this file by functions that it when needed. + * + * @param desc descriptor in a process' descriptor array + * @param state state to set (DESC_STATE_... constant) + * @return object reference on success, NULL if out of bound + */ +static void set_state(descriptor_t *desc, int state) { + desc->flags &= ~DESC_FLAG_STATE; + desc->flags |= state; +} + +/** + * Portion of dereference_object_descriptor() performed under lock + * + * @param pout pointer to where to copy the descriptor (out) * @param process process for which the descriptor is looked up - * @param fd descriptor + * @param desc referenced descriptor * @return zero on success, negated error number on error + */ +static int dereference_object_descriptor_locked( + descriptor_t *pout, + process_t *process, + descriptor_t *desc) { + + if(descriptor_is_destroyed(desc)) { + return -JINUE_EIO; + } + + if(! descriptor_is_open(desc)) { + return -JINUE_EBADF; + } + + object_header_t *object = desc->object; + + if(object_is_destroyed(object)) { + set_state(desc, DESC_STATE_DESTROYED); + close_object(object, desc); + return -JINUE_EIO; + } + + add_ref_to_object(desc->object); + *pout = *desc; + + return 0; +} + +/** + * Dereference an object descriptor * + * This function finds a descriptor by descriptor number is a specified + * process. It expects the descriptor to reference an object (i.e. be in the + * open state) and fails with JINUE_EBADF if it doesn't, unless the descriptor + * is in the destroyed state, in which case it fails with JINUE_EIO. + * + * It also checks that the object referenced hasn't been destroyed. If it has, + * it changes the descriptor's state to destroyed, decrement the referenced + * object's reference count (because the descriptor no longer references it) + * and fails with JINUE_EIO. + * + * If the call succeeds, it copies the descriptor to the storage pointed to by + * the pout argument and increments the reference count of the referenced + * object. These two things ensure the caller can continue to access the + * descriptor data and referenced object even while not under the process' + * descriptor lock and even if the descriptor is concurrently modified. + * + * The caller *must* decrement the referenced object reference call when it is + * done with it to free the reference added by this call. This can be done by + * calling unreference_descriptor_object() on the descriptor copy. + * + * @param pout pointer to where to copy the descriptor (out) + * @param process process in which the descriptor is looked up + * @param fd descriptor number + * @return zero on success, negated error number on error */ int dereference_object_descriptor( - descriptor_t **pdesc, - process_t *process, - int fd) { + descriptor_t *pout, + process_t *process, + int fd) { descriptor_t *desc = dereference_descriptor(process, fd); @@ -72,56 +160,172 @@ int dereference_object_descriptor( return -JINUE_EBADF; } - if(! descriptor_is_in_use(desc)) { - return -JINUE_EBADF; - } + spin_lock(&process->descriptors_lock); - if(descriptor_is_destroyed(desc)) { - return -JINUE_EIO; + int status = dereference_object_descriptor_locked(pout, process, desc); + + spin_unlock(&process->descriptors_lock); + + return status; +} + +/** + * Unreference the object referenced by a descriptor + * + * Must be called on the copy of the descriptor obtained by calling + * dereference_object_descriptor() when the caller is done accessing the + * object. + * + * @param desc descriptor + */ +void unreference_descriptor_object(descriptor_t *desc) { + sub_ref_to_object(desc->object); +} + +/** + * Reserve an unused descriptor by descriptor number + * + * This function ensures the descriptor is free. If it is, it sets it state to + * reserve (DESC_STATE_RESERVED) to prevent concurrent attempts to assign it. + * + * Once a descriptor is reserved, it must be set with open_descriptor(), or the + * reservation must be released with free_reserved_descriptor() if rolling back + * becomes necessary. + * + * This two step process allows the caller to confirm the availability of the + * descriptor before it starts allocating resources or performing some + * similarly expensive operation, and it ensures this operation does not need + * to be done while holding the process' descriptor lock. + * + * @param pdesc pointer to where to store the pointer to the object reference (out) + * @param process process for which the descriptor is looked up + * @param fd descriptor number + * @return zero on success, negated error number on error + */ +int reserve_free_descriptor(process_t *process, int fd) { + descriptor_t *desc = dereference_descriptor(process, fd); + + if(desc == NULL) { + return -JINUE_EBADF; } - object_header_t *object = desc->object; + spin_lock(&process->descriptors_lock); - if(object_is_destroyed(object)) { - desc->flags |= DESCRIPTOR_FLAG_DESTROYED; - close_object(object, desc); - return -JINUE_EIO; + if(!descriptor_is_free(desc)) { + spin_unlock(&process->descriptors_lock); + return -JINUE_EBADF; } - *pdesc = desc; + set_state(desc, DESC_STATE_RESERVED); + + spin_unlock(&process->descriptors_lock); return 0; } /** - * Get an unused object reference by descriptor + * Free a reserved descriptor + * + * This function must be called for a descriptor reserved with + * reserve_free_descriptor() if the descriptor will not be set with + * open_descriptor(). * - * @param pdesc pointer to where to store the pointer to the object reference (out) * @param process process for which the descriptor is looked up - * @param fd descriptor - * @return zero on success, negated error number on error - * + * @param fd descriptor number */ -int dereference_unused_descriptor( - descriptor_t **pdesc, - process_t *process, - int fd) { +void free_reserved_descriptor(process_t *process, int fd) { + descriptor_t *desc = dereference_descriptor(process, fd); + + spin_lock(&process->descriptors_lock); + + assert(descriptor_is_reserved(desc)); + + clear_descriptor(desc); + + spin_unlock(&process->descriptors_lock); +} + +/** + * Open a descriptor + * + * This function assigns an object to a descriptor and sets its flags and + * cookie. The object's reference count is incremented to reflect the new + * reference by the descriptor. The object's open op is also called. + * + * The descriptor must have been reserved with reserve_free_descriptor() before + * calling this function. This function does not do error checking because this + * will have been done by reserve_free_descriptor(). + * + * @param process process in which the descriptor is opened + * @param fd descriptor number + * @param in data to set on the descriptor + */ +void open_descriptor(process_t *process, int fd, const descriptor_t *in) { + descriptor_t *desc = dereference_descriptor(process, fd); + spin_lock(&process->descriptors_lock); + + assert(descriptor_is_reserved(desc)); + + *desc = *in; + + set_state(desc, DESC_STATE_OPEN); + + spin_unlock(&process->descriptors_lock); + + open_object(in->object, in); +} + +/** + * Close a descriptor + * + * This function closes a descriptor so it becomes available for reuse. In + * addition to clearing the descriptor state, it also calls the referenced + * object's close op if the descriptor is initially in the open state. + * + * @param process process in which the descriptor is closed + * @param fd descriptor number + * @return zero on success, negated error number on error + */ +int close_descriptor(process_t *process, int fd) { descriptor_t *desc = dereference_descriptor(process, fd); if(desc == NULL) { return -JINUE_EBADF; } - if(descriptor_is_in_use(desc)) { + spin_lock(&process->descriptors_lock); + + if(! descriptor_is_closeable(desc)) { + spin_unlock(&process->descriptors_lock); return -JINUE_EBADF; } - *pdesc = desc; + const descriptor_t copy = *desc; + + clear_descriptor(desc); + + spin_unlock(&process->descriptors_lock); + + if(descriptor_is_open(©)) { + close_object(copy.object, ©); + } return 0; } +/** + * Get IPC endpoint referenced by descriptor + * + * If the specified descriptor refers to an IPC endpoint, a pointer to that + * endpoint is returned. Otherwise, the function fails by returning NULL. + * + * This function is typically called on a descriptor copy obtain by calling + * dereference_object_descriptor(). + * + * @param desc descriptor + * @return IPC endpoint on success, NULL on failure + */ ipc_endpoint_t *get_endpoint_from_descriptor(descriptor_t *desc) { object_header_t *object = desc->object; @@ -132,6 +336,18 @@ ipc_endpoint_t *get_endpoint_from_descriptor(descriptor_t *desc) { return (ipc_endpoint_t *)object; } +/** + * Get process referenced by descriptor + * + * If the specified descriptor refers to a process, a pointer to that process + * is returned. Otherwise, the function fails by returning NULL. + * + * This function is typically called on a descriptor copy obtain by calling + * dereference_object_descriptor(). + * + * @param desc descriptor + * @return process on success, NULL on failure + */ process_t *get_process_from_descriptor(descriptor_t *desc) { object_header_t *object = desc->object; @@ -142,6 +358,18 @@ process_t *get_process_from_descriptor(descriptor_t *desc) { return (process_t *)object; } +/** + * Get thread referenced by descriptor + * + * If the specified descriptor refers to a thread, a pointer to that thread + * is returned. Otherwise, the function fails by returning NULL. + * + * This function is typically called on a descriptor copy obtain by calling + * dereference_object_descriptor(). + * + * @param desc descriptor + * @return thread on success, NULL on failure + */ thread_t *get_thread_from_descriptor(descriptor_t *desc) { object_header_t *object = desc->object; diff --git a/kernel/domain/entities/endpoint.c b/kernel/domain/entities/endpoint.c index ca778bf0..91ac00ad 100644 --- a/kernel/domain/entities/endpoint.c +++ b/kernel/domain/entities/endpoint.c @@ -36,6 +36,8 @@ #include #include #include +#include +#include #include #include @@ -49,7 +51,6 @@ static void destroy_endpoint(object_header_t *object); static void free_endpoint(object_header_t *object); -/** runtime type definition for an IPC endpoint */ static const object_type_t object_type = { .all_permissions = JINUE_PERM_SEND | JINUE_PERM_RECEIVE, .name = "ipc_endpoint", @@ -62,6 +63,7 @@ static const object_type_t object_type = { .cache_dtor = NULL }; +/** runtime type definition for an IPC endpoint */ const object_type_t *object_type_ipc_endpoint = &object_type; /** slab cache used for allocating IPC endpoint objects */ @@ -74,7 +76,6 @@ static slab_cache_t ipc_endpoint_cache; * * @param buffer IPC endpoint object being constructed * @param size size in bytes of the IPC endpoint object (ignored) - * */ static void cache_endpoint_ctor(void *buffer, size_t size) { ipc_endpoint_t *endpoint = buffer; @@ -82,21 +83,38 @@ static void cache_endpoint_ctor(void *buffer, size_t size) { init_object_header(&endpoint->header, object_type_ipc_endpoint); jinue_list_init(&endpoint->send_list); jinue_list_init(&endpoint->recv_list); + init_spinlock(&endpoint->lock); endpoint->receivers_count = 0; } +/** + * Add a reference that can be used to receive on the endpoint + * + * @param endpoint the endpoint + */ static void add_receiver(ipc_endpoint_t *endpoint) { - ++endpoint->receivers_count; -} - -static void sub_receiver(ipc_endpoint_t *endpoint) { - --endpoint->receivers_count; + add_atomic(&endpoint->receivers_count, 1); } -static bool has_receivers(const ipc_endpoint_t *endpoint) { - return endpoint->receivers_count > 0; +/** + * Remove a reference that can be used to receive on the endpoint + * + * @param endpoint the endpoint + * @return updated number of references allowed to receive + */ +static int sub_receiver(ipc_endpoint_t *endpoint) { + return add_atomic(&endpoint->receivers_count, -1); } +/** + * Open an IPC endpoint + * + * This function is defined as the "open" op in the runtime type definition, + * called when a new descriptor references the endpoint. + * + * @param object the endpoint object + * @param desc the new descriptor + */ static void open_endpoint(object_header_t *object, const descriptor_t *desc) { if(descriptor_has_permissions(desc, JINUE_PERM_RECEIVE)) { ipc_endpoint_t *endpoint = (ipc_endpoint_t *)object; @@ -104,20 +122,29 @@ static void open_endpoint(object_header_t *object, const descriptor_t *desc) { } } +/** + * Close an IPC endpoint + * + * This function is defined as the "close" op in the runtime type definition, + * called when a a descriptor that references the endpoint is closed and stops + * referencing it. + * + * @param object the endpoint object + * @param desc the descriptor being closed + */ static void close_endpoint(object_header_t *object, const descriptor_t *desc) { if(descriptor_has_permissions(desc, JINUE_PERM_RECEIVE)) { ipc_endpoint_t *endpoint = (ipc_endpoint_t *)object; - sub_receiver(endpoint); + int receivers = sub_receiver(endpoint); - if(!has_receivers(endpoint)) { + if(receivers < 1) { destroy_object(object); } } } /** - * Perform boot-time initialization for IPC - * + * Initialize the IPC endpoint slab cache */ void initialize_endpoint_cache(void) { init_object_cache(&ipc_endpoint_cache, object_type_ipc_endpoint); @@ -126,13 +153,19 @@ void initialize_endpoint_cache(void) { /** * Constructor for IPC endpoint object * - * @return pointer to endpoint on success, NULL on allocation failure - * + * @return endpoint on success, NULL on allocation failure */ ipc_endpoint_t *construct_endpoint(void) { return slab_cache_alloc(&ipc_endpoint_cache); } +/** + * Destroy an IPC endpoint + * + * This function is defined as the "destroy" op in the runtime type definition. + * + * @param object the endpoint object + */ static void destroy_endpoint(object_header_t *object) { ipc_endpoint_t *endpoint = (ipc_endpoint_t *)object; @@ -163,6 +196,14 @@ static void destroy_endpoint(object_header_t *object) { } } +/** + * Free an IPC endpoint + * + * This function is defined as the "free" op in the runtime type definition, + * called automatically when the endpoint's reference count falls to zero. + * + * @param object the endpoint object + */ static void free_endpoint(object_header_t *object) { slab_cache_free(object); } diff --git a/kernel/domain/entities/object.c b/kernel/domain/entities/object.c index d2e6471c..283b20d7 100644 --- a/kernel/domain/entities/object.c +++ b/kernel/domain/entities/object.c @@ -32,7 +32,14 @@ #include #include #include +#include +/** + * Initialize a slab cache using parameters from a runtime object type + * + * @param cache the cache to initialize + * @param type the runtime object type definition + */ void init_object_cache(slab_cache_t *cache, const object_type_t *type) { slab_cache_init( cache, @@ -45,6 +52,12 @@ void init_object_cache(slab_cache_t *cache, const object_type_t *type) { ); } +/** + * Update object state to reflect an additional descriptor referencing it + * + * @param object the object + * @param desc new descriptor + */ void open_object(object_header_t *object, const descriptor_t *desc) { add_ref_to_object(object); @@ -53,6 +66,12 @@ void open_object(object_header_t *object, const descriptor_t *desc) { } } +/** + * Update object state to reflect a descriptor no longer referencing it + * + * @param object the object + * @param desc descriptor being closed + */ void close_object(object_header_t *object, const descriptor_t *desc) { if(object->type->close != NULL) { object->type->close(object, desc); @@ -61,27 +80,56 @@ void close_object(object_header_t *object, const descriptor_t *desc) { sub_ref_to_object(object); } +/** + * Mark object as destroyed + * + * The state of the object is destoyed after this function is called but the + * object is not freed because it might still be referenced. + * + * @param object the object + * @param desc descriptor being closed + */ void destroy_object(object_header_t *object) { - if(object_is_destroyed(object)) { + int original_flags = or_atomic(&object->flags, OBJECT_FLAG_DESTROYED); + + if(original_flags & OBJECT_FLAG_DESTROYED) { + /* Object was already marked destroyed, do nothing. */ return; } - mark_object_destroyed(object); - if(object->type->destroy != NULL) { object->type->destroy(object); } } -/* This function is called by assembly code. See switch_thread_stack(). */ +/** + * Increment the reference count of an object + * + * @param object the object + */ +void add_ref_to_object(object_header_t *object) { + (void)add_atomic(&object->ref_count, 1); +} + +/** + * Decrement the reference count of an object + * + * If the reference count falls to zero, the object is destroyed and the "free" + * op from the runtime type definition is called, freeing the object. + * + * This function is called by assembly code. See machine_switch_thread() and + * machine_switch_and_unref_thread(). + * + * @param object the object + */ void sub_ref_to_object(object_header_t *object) { - --object->ref_count; + int ref_count = add_atomic(&object->ref_count, -1); - if(object->ref_count > 0) { + if(ref_count > 0) { return; } - if(object->ref_count != 0) { + if(ref_count != 0) { panic("Object reference count decremented to negative value"); } diff --git a/kernel/domain/entities/process.c b/kernel/domain/entities/process.c index ba192a95..d0ef463a 100644 --- a/kernel/domain/entities/process.c +++ b/kernel/domain/entities/process.c @@ -34,7 +34,7 @@ #include #include #include -#include +#include #include #include #include @@ -47,7 +47,6 @@ static void destroy_process(object_header_t *object); 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_CREATE_THREAD @@ -63,30 +62,57 @@ static const object_type_t object_type = { .cache_dtor = NULL }; +/** runtime type definition for a process */ const object_type_t *object_type_process = &object_type; /** slab cache used for allocating process objects */ static slab_cache_t process_cache; +/** + * Constructor for process object in slab cache + * + * This constructor is called when the slab cache is grown. It should only + * initialize state that persists when the object is freed and then reused, + * such as the object type. + * + * See construct_process() for the run time constructor. + * + * @param buffer the process to construct + * @param ignore size of object - ignored + */ static void cache_process_ctor(void *buffer, size_t ignore) { process_t *process = buffer; - init_object_header(&process->header, object_type_process); } +/** + * Process slab cache initialization + */ void initialize_process_cache(void) { init_object_cache(&process_cache, object_type_process); } -static void init_process(process_t *process) { - memset(&process->descriptors, 0, sizeof(process->descriptors)); +/** + * Initialize the descriptors of a process being constructed + * + * @param process the process being constructed + */ +static void initialize_descriptors(process_t *process) { + for(int idx = 0; idx < JINUE_DESC_NUM; ++idx) { + clear_descriptor(&process->descriptors[idx]); + } } +/** + * Process constructor + * + * @return process if successful, NULL if out of memory + */ process_t *construct_process(void) { process_t *process = slab_cache_alloc(&process_cache); if(process != NULL) { - init_process(process); + initialize_descriptors(process); if(!machine_init_process(process)) { slab_cache_free(process); @@ -97,50 +123,97 @@ process_t *construct_process(void) { return process; } -static void close_all_descriptors(process_t *process) { +/** + * Close all descriptors of a process being destroyed + * + * @param process the process being destroyed + */ +static void close_descriptors(process_t *process) { for(int idx = 0; idx < JINUE_DESC_NUM; ++idx) { descriptor_t *desc = &process->descriptors[idx]; - if(descriptor_is_in_use(desc)) { + if(descriptor_is_open(desc)) { close_object(desc->object, desc); } } } +/** + * Destroy a process + * + * This is the "destroy" op of the object type, hence why the type of the + * argument. + * + * @param object process object + */ static void destroy_process(object_header_t *object) { process_t *process = (process_t *)object; /* TODO destroy remaining threads */ - close_all_descriptors(process); + close_descriptors(process); machine_finalize_process(process); } +/** + * Free a process + * + * This is the "free" op of the object type, hence why the type of the + * argument. This function is called automatically once the process no + * longer has any references. + * + * @param object process object + */ static void free_process(object_header_t *object) { slab_cache_free(object); } +/** + * Switch to specified process address space + * + * @param process the process + */ void switch_to_process(process_t *process) { machine_switch_to_process(process); } +/** + * Get process running on current CPU + * + * @return running process + */ process_t *get_current_process(void) { return get_current_thread()->process; } +/** + * Update process state to account for a new running thread + * + * This function is called not when a new thread is created but when it + * actually starts running. + * + * @param process the process that gained a running thread + */ void add_running_thread_to_process(process_t *process) { - ++process->running_threads_count; + add_atomic(&process->running_threads_count, 1); add_ref_to_object(&process->header); } +/** + * Update process state to account for a running thread exiting + * + * This function is called not when a thread is destroyed but when it exits. + * When a thread has exited, the kernel thread object can be reused for + * another application thread. However, when *all* of a process' threads have + * exited, the process is destroyed, which this funtion takes care of. + * + * @param process the process that lost a running thread + */ void remove_running_thread_from_process(process_t *process) { - --process->running_threads_count; + int running_count = add_atomic(&process->running_threads_count, -1); /* Destroy the process when there are no more running threads. The * reference count alone is not enough because the process might have * descriptors that reference itself. */ - if(process->running_threads_count < 1) { - /* We must switch to a safe address space before destroying the process - * so the current thread still has an address space to run into. */ - machine_switch_to_kernel_addr_space(); + if(running_count < 1) { destroy_object(&process->header); } diff --git a/kernel/domain/entities/thread.c b/kernel/domain/entities/thread.c index 669e8a6b..a4ab9495 100644 --- a/kernel/domain/entities/thread.c +++ b/kernel/domain/entities/thread.c @@ -34,13 +34,15 @@ #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 = JINUE_PERM_START | JINUE_PERM_AWAIT, .name = "thread", @@ -53,10 +55,32 @@ static const object_type_t object_type = { .cache_dtor = NULL }; +/** runtime type definition for a thread */ const object_type_t *object_type_thread = &object_type; -static jinue_list_t ready_list = JINUE_LIST_STATIC; +/** ready threads queue with lock */ +static struct { + jinue_list_t queue; + spinlock_t lock; +} ready_queue = { + .queue = JINUE_LIST_STATIC, + .lock = SPINLOCK_STATIC +}; +/** + * Thread constructor + * + * The in-kernel thread implementation separates thread creation and thread + * startup, which allows an application to keep kernel thread objects around in + * a thread pool and reuse them as application threads exit and new ones start. + * This function constructs a thread but does not start it. run_thread() (or + * run_first_thread()) does that on an already constructed thread that has + * been prepared for a first or new run with prepare_thread(). + * + * @param process process in which to create the new thread + * @return thread on success, NULL on memory allocation error + * + */ thread_t *construct_thread(process_t *process) { thread_t *thread = machine_alloc_thread(); @@ -66,47 +90,174 @@ thread_t *construct_thread(process_t *process) { init_object_header(&thread->header, object_type_thread); + init_spinlock(&thread->await_lock); jinue_node_init(&thread->thread_list); - thread->state = THREAD_STATE_ZOMBIE; + thread->state = THREAD_STATE_CREATED; thread->process = process; - /* Arbitrary non-NULL value to signify the thread hasn't run yet and - * shouldn't be awaited. This will fall in the condition in await_thread() - * that detects an attempt to await a thread that has already been awaited, - * so await_thread() will fail with JINUE_ESRCH. */ - thread->awaiter = thread; + thread->awaiter = NULL; thread->local_storage_addr = NULL; thread->local_storage_size = 0; return thread; } +/** + * Free a thread + * + * This function implements the "free" operation of the runtime type + * definition. + * + * @param object object header of thread object + * + */ static void free_thread(object_header_t *object) { thread_t *thread = (thread_t *)object; machine_free_thread(thread); } +/** + * Prepare a thread to be run + * + * Initializes the thread state. This initialization includes preparing the + * context on the stack with the specified initial stack pointer and entry + * point. + * + * This function is separate from the thread constructor to allow a thread + * to be reused by the application. (See construct_thread()) + * + * Once a thread has been prepared by calling this function, it can be run + * by calling run_thread() (or run_first_thread()). + * + * @param thread the thread + * @param params initialization parameters + * + */ void prepare_thread(thread_t *thread, const thread_params_t *params) { thread->sender = NULL; + + spin_lock(&thread->await_lock); + thread->awaiter = NULL; + thread->state = THREAD_STATE_STARTING; + + spin_unlock(&thread->await_lock); + machine_prepare_thread(thread, params); } -void ready_thread(thread_t *thread) { +/** + * Add a thread to the ready queue (without locking) + * + * This funtion contains the business logic for ready_thread() without the + * locking. Some functions beside ready_thread() that need to block and then + * unlock call it, hence why it is a separate function. + * + * @param thread the thread + * + */ +static void ready_thread_locked(thread_t *thread) { 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); + jinue_list_enqueue(&ready_queue.queue, &thread->thread_list); } -static thread_t *reschedule(bool current_can_run) { - thread_t *to_thread = jinue_node_entry( - jinue_list_dequeue(&ready_list), +/** + * Add a thread to the ready queue + * + * @param thread the thread + * + */ +void ready_thread(thread_t *thread) { + spin_lock(&ready_queue.lock); + + ready_thread_locked(thread); + + spin_unlock(&ready_queue.lock); +} + +/** + * Common logic for a starting thread + * + * @param thread the thread that is starting + * + */ +static void thread_is_starting(thread_t *thread) { + add_running_thread_to_process(thread->process); + + /* 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 get closed. */ + add_ref_to_object(&thread->header); + + thread->state = THREAD_STATE_RUNNING; +} + +/** + * Run the first thread + * + * This function must not call get_current_thread() because that function will + * not find a thread_t structure where it expects to find it relative to the + * stack pointer. It must also actually switch to the thread instead of just + * adding it to the ready queue for the thread to actually run. + * + * @param thread the thread to run + * + */ +void run_first_thread(thread_t *thread) { + switch_to_process(thread->process); + + thread_is_starting(thread); + + machine_switch_thread(NULL, thread); +} + +/** + * Run a thread + * + * Before this function is called, the thread must have been prepared with + * prepare_thread(). + * + * @param thread the thread to run + * + */ +void run_thread(thread_t *thread) { + thread_is_starting(thread); + + ready_thread(thread); +} + +/** + * Get the thread at the head of the ready queue + * + * @return thread ready to run, NULL if there are none + * + */ +static thread_t *dequeue_ready_thread(void) { + spin_lock(&ready_queue.lock); + + thread_t *thread = jinue_node_entry( + jinue_list_dequeue(&ready_queue.queue), thread_t, thread_list ); + + spin_unlock(&ready_queue.lock); + + return thread; +} + +/** + * Get the next thread to run + * + * @param current_can_run whether the current thread can continue running + * @return thread ready to run + * + */ +static thread_t *reschedule(bool current_can_run) { + thread_t *to = dequeue_ready_thread(); - if(to_thread == NULL) { + if(to == NULL) { /* Special case to take into account: when scheduling the first thread, * there is no current thread. We should not call get_current_thread() * in that case. */ @@ -121,90 +272,166 @@ static thread_t *reschedule(bool current_can_run) { panic("No thread to schedule"); } - return to_thread; + return to; } -static void switch_thread(thread_t *from, thread_t *to, bool destroy_from) { - if(to == from) { - return; +/** + * Terminate the current thread + * + * The thread is destroyed and freed only if there are no more references to it + * (i.e. no descriptors referencing it). Otherwise, it remains available to be + * reused by calling prepare_thread() and then run_thread() again. + */ +void terminate_current_thread(void) { + thread_t *current = get_current_thread(); + + spin_lock(¤t->await_lock); + + /* This state transition must be done under lock to avoid a race condition + * with await_thread(). */ + current->state = THREAD_STATE_ZOMBIE; + + if(current->awaiter != NULL) { + ready_thread(current->awaiter); } - if(from == NULL || from->process != to->process || destroy_from) { + spin_unlock(¤t->await_lock); + + if(current->sender != NULL) { + abort_message(current->sender); + current->sender = NULL; + } + + thread_t *to = reschedule(false); + to->state = THREAD_STATE_RUNNING; + + if(current->process != to->process) { switch_to_process(to->process); } - to->state = THREAD_STATE_RUNNING; + /* This must be done after switching process since it will destroy the process + * if the current thread is the last one. We don't want to destroy the address + * space we are still running in... */ + remove_running_thread_from_process(current->process); - machine_switch_thread(from, to, destroy_from); + /* This function takes care of safely decrementing the reference count on + * the thread after having switched to the other one. We cannot just do it + * here because that will possibly free the current thread, which we don't + * want to do while it is still running. */ + machine_switch_and_unref_thread(current, to); } -void switch_to_thread(thread_t *thread, bool blocked) { - thread_t *current = get_current_thread(); +/** + * Switch to another thread + * + * The current thread remains ready to run and is added to the ready queue. + * + * @param to thread to switch to + * + */ +void switch_to(thread_t *to) { + thread_t *current = get_current_thread(); + + to->state = THREAD_STATE_RUNNING; - if (blocked) { - current->state = THREAD_STATE_BLOCKED; - } else { - ready_thread(current); + if(current->process != to->process) { + switch_to_process(to->process); } - switch_thread( - current, - thread, - false /* don't destroy current thread */ - ); -} + spin_lock(&ready_queue.lock); -void start_first_thread(thread_t *thread) { - thread_is_starting(thread); + ready_thread_locked(current); - switch_thread( - NULL, - thread, - false /* don't destroy current thread */ - ); + machine_switch_thread_and_unlock(current, to, &ready_queue.lock); } -void yield_current_thread(void) { - switch_to_thread( - reschedule(true), /* current thread can run */ - false /* don't block current thread */ - ); -} +/** + * Switch to another thread and block the current thread + * + * @param to thread to switch to + * + */ +void switch_to_and_block(thread_t *to) { + thread_t *current = get_current_thread(); + current->state = THREAD_STATE_BLOCKED; + to->state = THREAD_STATE_RUNNING; -void block_current_thread(void) { - switch_to_thread( - reschedule(false), /* current thread cannot run */ - true /* do block current thread */ - ); + if(current->process != to->process) { + switch_to_process(to->process); + } + + machine_switch_thread(current, to); } -void thread_is_starting(thread_t *thread) { - add_running_thread_to_process(thread->process); - - /* 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 get closed. */ - add_ref_to_object(&thread->header); +/** + * Block the current thread and then unlock a lock + * + * The lock is unlocked *after* the switch to another thread. This function + * eliminates race conditions when enqueuing the current thread to a queue, + * setting it as the awaiter of another thread, etc. and then blocking, if + * the following sequence is followed: + * + * 1. Take the lock (e.g. the lock protecting a queue). + * 2. Add the thread (e.g. to the queue). + * 3. Call this function to block the thread and release the lock atomically. + * + * @param lock the lock to unlock after switching thread + * + */ +void block_and_unlock(spinlock_t *lock) { + thread_t *current = get_current_thread(); + current->state = THREAD_STATE_BLOCKED; + + thread_t *to = reschedule(false); + to->state = THREAD_STATE_RUNNING; + + if(current->process != to->process) { + switch_to_process(to->process); + } + + machine_switch_thread_and_unlock(current, to, lock); } -void current_thread_is_exiting(void) { - thread_t *thread = get_current_thread(); +/** + * Yield the current thread + * + * The current thread is added at the tail of the ready queue. It continues + * running if no other thread is ready to run. + */ +void yield_current_thread(void) { + thread_t *current = get_current_thread(); + thread_t *to = reschedule(true); + + if(to == current) { + return; + } - thread->state = THREAD_STATE_ZOMBIE; + to->state = THREAD_STATE_RUNNING; - remove_running_thread_from_process(thread->process); + if(current->process != to->process) { + switch_to_process(to->process); + } - /* switch_thread() takes care of safely decrementing the reference count on - * the thread after having switched to another one. We cannot just do it - * here because that will possibly free the current thread, which we don't - * want to do while it is still running. */ - switch_thread( - thread, - reschedule(false), /* current thread cannot run */ - true /* decrement reference count */ - ); + spin_lock(&ready_queue.lock); + + ready_thread_locked(current); + + machine_switch_thread_and_unlock(current, to, &ready_queue.lock); } +/** + * Set the address and size of the Thread-Local Storage (TLS) + * + * @param thread the thread + * @param addr address of thread-local storage + * @param size size of thread-local storage + * + */ void set_thread_local_storage(thread_t *thread, addr_t addr, size_t size) { thread->local_storage_addr = addr; thread->local_storage_size = size; + + if(thread == get_current_thread()) { + machine_set_thread_local_storage(thread); + } } diff --git a/kernel/domain/services/exec.c b/kernel/domain/services/exec.c index 0cf843dc..a4684321 100644 --- a/kernel/domain/services/exec.c +++ b/kernel/domain/services/exec.c @@ -38,22 +38,59 @@ #include #include +/** + * Set up a predefined descriptor for the user space loader + * + * @param process user space loader process + * @param fd descriptor number + * @param object object the descriptor will reference + */ static void set_descriptor(process_t *process, int fd, object_header_t *object) { - descriptor_t *desc; - (void)dereference_unused_descriptor(&desc, process, fd); + int status = reserve_free_descriptor(process, fd); + + if(status < 0) { + panic("Could not set up predefined descriptor for user space loader"); + } - desc->object = object; - desc->flags = DESCRIPTOR_FLAG_IN_USE | object->type->all_permissions; - desc->cookie = 0; + descriptor_t desc; + desc.object = object; + desc.flags = object->type->all_permissions; + desc.cookie = 0; - open_object(object, desc); + open_descriptor(process, fd, &desc); } +/** + * Initialize the predefined descriptors for the user space loader + * + * @param process user space loader process + * @param thread initial thread + */ static void initialize_descriptors(process_t *process, thread_t *thread) { - set_descriptor(process, JINUE_DESC_SELF_PROCESS, &process->header); - set_descriptor(process, JINUE_DESC_MAIN_THREAD, &thread->header); + set_descriptor(process, JINUE_DESC_SELF_PROCESS, process_object(process)); + set_descriptor(process, JINUE_DESC_MAIN_THREAD, thread_object(thread)); } +/** + * Load an executable file into a new process and prepare the initial thread + * + * This function is intended to load the user space loader binary, and any + * other program will be loaded from user space. The executable file must be + * a static binary. + * + * This function sets up the loadable segments into the process address space + * and prepares the initial thread with the proper entry point and stack + * address. In addition, it also sets up two predefined descriptors: one that + * refers to the process and another one to the thread. These descriptors have + * the same purpose and descriptor numbers as two of the descriptors set up for + * the initial process by the user space loader (see doc/init-process.md). + * + * @param process user space loader process in which to load the executable + * @param thread initial thread + * @param exec_file executable binary file + * @param argv0 program name (argv[0]) + * @param cmdline full kernel command line, used for arguments and environment + */ void exec( process_t *process, thread_t *thread, diff --git a/kernel/domain/services/ipc.c b/kernel/domain/services/ipc.c index 2e9621e6..4804ba2a 100644 --- a/kernel/domain/services/ipc.c +++ b/kernel/domain/services/ipc.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -233,23 +234,24 @@ int send_message( return gather_result; } + spin_lock(&endpoint->lock); + thread_t *receiver = jinue_node_entry( jinue_list_dequeue(&endpoint->recv_list), thread_t, thread_list); if(receiver == NULL) { - /* No thread is waiting to receive this message, so we must wait on the - * sender list. */ + /* No thread is waiting to receive this message, so we must wait on the sender list. */ jinue_list_enqueue(&endpoint->send_list, &sender->thread_list); - block_current_thread(); + block_and_unlock(&endpoint->lock); } else { - add_ref_to_object(&sender->header); + spin_unlock(&endpoint->lock); receiver->sender = sender; /* switch to receiver thread, which will resume inside syscall_receive() */ - switch_to_thread(receiver, true); + switch_to_and_block(receiver); } if(sender->message_errno == JINUE_EPROTO) { @@ -300,55 +302,55 @@ int receive_message(ipc_endpoint_t *endpoint, thread_t *receiver, jinue_message_ receiver->message_errno = 0; - thread_t *sender = jinue_node_entry( - jinue_list_dequeue(&endpoint->send_list), - thread_t, - thread_list); - - if(sender == NULL) { - /* No thread is waiting to send a message, so we must wait on the receive - * list. */ - jinue_list_enqueue(&endpoint->recv_list, &receiver->thread_list); - block_current_thread(); + while(true) { + spin_lock(&endpoint->lock); + + thread_t *sender = jinue_node_entry( + jinue_list_dequeue(&endpoint->send_list), + thread_t, + thread_list); - /* set by sending thread */ - sender = receiver->sender; - } - else { - add_ref_to_object(&sender->header); - receiver->sender = sender; - } + if(sender == NULL) { + /* No thread is waiting to send a message, so we must wait on the receive list. */ + jinue_list_enqueue(&endpoint->recv_list, &receiver->thread_list); + block_and_unlock(&endpoint->lock); + + /* set by sending thread */ + sender = receiver->sender; + } + else { + spin_unlock(&endpoint->lock); + receiver->sender = sender; + } - if(receiver->message_errno != 0) { - receiver->sender = NULL; - return -receiver->message_errno; - } + if(receiver->message_errno != 0) { + receiver->sender = NULL; + return -receiver->message_errno; + } + + if(sender->message_size > recv_buffer_size) { + /* message is too big for the receive buffer */ + sender->message_errno = JINUE_E2BIG; + receiver->sender = NULL; + + ready_thread(sender); + continue; + } + + /* copy reply to user space buffer */ + int scatter_result = scatter_message(sender, message); - if(sender->message_size > recv_buffer_size) { - /* message is too big for the receive buffer */ - sender->message_errno = JINUE_E2BIG; - sub_ref_to_object(&sender->header); - receiver->sender = NULL; + if(scatter_result < 0) { + receiver->sender = NULL; + return scatter_result; + } - /* switch back to sender thread to return from call immediately */ - switch_to_thread(sender, false); - - return -JINUE_E2BIG; - } - - /* copy reply to user space buffer */ - int scatter_result = scatter_message(sender, message); + message->recv_function = sender->message_function; + message->recv_cookie = sender->message_cookie; + message->reply_max_size = sender->recv_buffer_size; - if(scatter_result < 0) { - receiver->sender = NULL; - return scatter_result; + return sender->message_size; } - - message->recv_function = sender->message_function; - message->recv_cookie = sender->message_cookie; - message->reply_max_size = sender->recv_buffer_size; - - return sender->message_size; } /** @@ -384,10 +386,9 @@ int reply_to_message(thread_t *replier, const jinue_message_t *message) { } replier->sender = NULL; - sub_ref_to_object(&replyto->header); /* switch back to sender thread to return from call immediately */ - switch_to_thread(replyto, false); + switch_to(replyto); return 0; } @@ -410,14 +411,12 @@ int reply_error_to_message(thread_t *replier, uintptr_t errcode) { return -JINUE_ENOMSG; } - replyto->message_errno = JINUE_EPROTO; replyto->message_reply_errcode = errcode; replier->sender = NULL; - sub_ref_to_object(&replyto->header); /* switch back to sender thread to return from call immediately */ - switch_to_thread(replyto, false); + switch_to(replyto); return 0; } diff --git a/kernel/infrastructure/elf.c b/kernel/infrastructure/elf.c index 20264cc1..6478ee53 100644 --- a/kernel/infrastructure/elf.c +++ b/kernel/infrastructure/elf.c @@ -509,8 +509,8 @@ static void initialize_stack( * @param thread_params initial thread parameters (output) * @param process process in which to load the ELF binary * @param exec_file executable ELF file - * @param argv0 name of binary - * @param cmdline full kernel command line + * @param argv0 program name (argv[0]) + * @param cmdline full kernel command line, used for arguments and environment * * */ void machine_load_exec( diff --git a/kernel/infrastructure/i686/atomic/atomic.asm b/kernel/infrastructure/i686/atomic/atomic.asm new file mode 100644 index 00000000..012bc403 --- /dev/null +++ b/kernel/infrastructure/i686/atomic/atomic.asm @@ -0,0 +1,69 @@ +; 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. + + bits 32 + +; ----------------------------------------------------------------------------- +; FUNCTION: add_atomic +; C PROTOTYPE: int add_atomic(int *value, int increment); +; ----------------------------------------------------------------------------- + global add_atomic:function (add_atomic.end - add_atomic) +add_atomic: + mov ecx, [esp+8] ; second argument: increment + mov edx, [esp+4] ; first argument: value + + mov eax, ecx ; Copy increment in eax. + lock xadd dword [edx], eax ; Exchange and add. + add eax, ecx ; Add increment to return value. + + ret +.end: + +; ----------------------------------------------------------------------------- +; FUNCTION: or_atomic +; C PROTOTYPE: int or_atomic(int *value, int mask); +; ----------------------------------------------------------------------------- + global or_atomic:function (or_atomic.end - or_atomic) +or_atomic: + mov edx, [esp+4] ; first argument: pointer to value + mov eax, dword [edx] ; initial value in eax + +.again: + mov ecx, eax ; copy value + or ecx, dword [esp+8] ; second argument: mask + + ; Copy or result (ecx) into value ([edx]), but only if current value is the + ; one we are expecting (eax). Otherwise, the curent value ([edx]) is copied + ; into eax so we can try again. + lock cmpxchg dword [edx], ecx + + jnz .again ; value changed, retry + + ret +.end: diff --git a/kernel/infrastructure/i686/atomic/spinlock.asm b/kernel/infrastructure/i686/atomic/spinlock.asm new file mode 100644 index 00000000..7a1b2c0c --- /dev/null +++ b/kernel/infrastructure/i686/atomic/spinlock.asm @@ -0,0 +1,86 @@ +; 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. + + bits 32 + +; ----------------------------------------------------------------------------- +; FUNCTION: init_spinlock +; C PROTOTYPE: void init_spinlock(spinlock_t *lock); +; ----------------------------------------------------------------------------- + global init_spinlock:function (init_spinlock.end - init_spinlock) +init_spinlock: + mov eax, [esp+4] ; first argument: lock + mov dword [eax], 0 ; set lock value to zero + + ret +.end: + +; ----------------------------------------------------------------------------- +; FUNCTION: spin_lock +; C PROTOTYPE: void spin_lock(spinlock_t *lock); +; DESCRIPTION: +; Simple ticket lock implementation. The whole lock is a double word (32 +; bits) with the low word (16 bits) being the "now serving" number and the +; high word being the ticket number. +; +; For now, the assumption is that interrupts are disabled whenever we are in +; the kernel, so there is no need to disable interrupts here. +; ----------------------------------------------------------------------------- + global spin_lock:function (spin_lock.end - spin_lock) +spin_lock: + mov eax, [esp+4] ; first argument: lock + mov ecx, (1 << 16) ; operand: add 1 to high word + lock xadd dword [eax], ecx ; Exchange and add to lock value. + ; Value before increment is now in ecx. + + shr ecx, 16 ; Move ticket number to low word. +.loop: + mov edx, [eax] ; Get lock value. + and edx, (1 << 16) - 1 ; Mask to keep only the low word. + cmp edx, ecx ; Compare with our ticket number. + jz .done ; If it matches, we are done. + + pause ; Yes, this is a spinlock. + jmp .loop ; Loop one more time. + +.done: + ret +.end: + +; ----------------------------------------------------------------------------- +; FUNCTION: spin_unlock +; C PROTOTYPE: void spin_unlock(spinlock_t *lock); +; ----------------------------------------------------------------------------- + global spin_unlock:function (spin_unlock.end - spin_unlock) +spin_unlock: + mov eax, [esp+4] ; first argument: lock + inc word [eax] ; Increase lower word to indicate completion. + + ret +.end: diff --git a/kernel/infrastructure/i686/cpuinfo.c b/kernel/infrastructure/i686/cpuinfo.c index a9f6b70b..c6c27142 100644 --- a/kernel/infrastructure/i686/cpuinfo.c +++ b/kernel/infrastructure/i686/cpuinfo.c @@ -53,7 +53,7 @@ void detect_cpu_features(void) { /* The CPUID instruction is available if we can change the value of eflags * bit 21 (ID) */ temp_eflags = get_eflags(); - temp_eflags ^= CPU_EFLAGS_ID; + temp_eflags ^= EFLAGS_ID; set_eflags(temp_eflags); if(temp_eflags == get_eflags()) { diff --git a/kernel/infrastructure/i686/percpu.c b/kernel/infrastructure/i686/percpu.c index 2965e8bd..e148b8db 100644 --- a/kernel/infrastructure/i686/percpu.c +++ b/kernel/infrastructure/i686/percpu.c @@ -42,7 +42,7 @@ static void set_tls_segment(percpu_t *data, void *addr, size_t size) { ); } -void machine_set_tls(const thread_t *thread) { +void machine_set_thread_local_storage(const thread_t *thread) { percpu_t *data = get_percpu_data(); set_tls_segment(data, thread->local_storage_addr, thread->local_storage_size); } diff --git a/kernel/infrastructure/i686/pmap/nopae.c b/kernel/infrastructure/i686/pmap/nopae.c index 6649d5d5..a49227fb 100644 --- a/kernel/infrastructure/i686/pmap/nopae.c +++ b/kernel/infrastructure/i686/pmap/nopae.c @@ -68,7 +68,6 @@ void nopae_destroy_addr_space(addr_space_t *addr_space) { * * @param addr virtual address * @return entry offset of address within page table - * */ unsigned int nopae_page_table_offset_of(void *addr) { return PAGE_TABLE_OFFSET_OF(addr); @@ -79,7 +78,6 @@ unsigned int nopae_page_table_offset_of(void *addr) { * * @param addr virtual address * @return entry offset of address within page directory - * */ unsigned int nopae_page_directory_offset_of(void *addr) { return PAGE_DIRECTORY_OFFSET_OF(addr); @@ -104,7 +102,6 @@ pte_t *nopae_lookup_page_directory(addr_space_t *addr_space) { * @param pte base page table entry (e.g. the beginning of a page table) * @param offset entry offset * @return PTE at specified offset - * */ pte_t *nopae_get_pte_with_offset(pte_t *pte, unsigned int offset) { return &pte[offset]; @@ -124,7 +121,6 @@ pte_t *nopae_get_pte_with_offset(pte_t *pte, unsigned int offset) { * @param paddr physical address of page frame * @param flags flags * @return flags - * */ static uint32_t filter_pte_flags(uint64_t flags) { uint32_t mask = PAGE_MASK; @@ -141,7 +137,6 @@ static uint32_t filter_pte_flags(uint64_t flags) { * @param pte page table or page directory entry * @param paddr physical address of page frame * @param flags flags - * */ void nopae_set_pte(pte_t *pte, uint32_t paddr, uint64_t flags) { assert((paddr & PAGE_MASK) == 0); @@ -157,7 +152,6 @@ void nopae_set_pte(pte_t *pte, uint32_t paddr, uint64_t flags) { * * @param pte page table entry * @param pte flags flags - * */ void nopae_set_pte_flags(pte_t *pte, uint64_t flags) { pte->entry = (pte->entry & ~PAGE_MASK) | filter_pte_flags(flags); @@ -168,7 +162,6 @@ void nopae_set_pte_flags(pte_t *pte, uint64_t flags) { * * @param pte page table or page directory entry array * @return physical address - * */ uint32_t nopae_get_pte_paddr(const pte_t *pte) { return pte->entry & ~PAGE_MASK; @@ -181,7 +174,6 @@ uint32_t nopae_get_pte_paddr(const pte_t *pte) { * present in memory. * * @param pte page table or page directory entry - * */ void nopae_clear_pte(pte_t *pte) { pte->entry = 0; @@ -192,7 +184,6 @@ void nopae_clear_pte(pte_t *pte) { * * @param dest destination page table/directory entry * @param src source page table/directory entry - * */ void nopae_copy_pte(pte_t *dest, const pte_t *src) { dest->entry = src->entry; diff --git a/kernel/infrastructure/i686/pmap/pae.c b/kernel/infrastructure/i686/pmap/pae.c index 6169dcd2..04753ebe 100644 --- a/kernel/infrastructure/i686/pmap/pae.c +++ b/kernel/infrastructure/i686/pmap/pae.c @@ -483,7 +483,6 @@ pte_t *pae_lookup_page_directory( * * @param addr virtual address * @return entry offset of address within page table - * */ unsigned int pae_page_table_offset_of(void *addr) { return PAGE_TABLE_OFFSET_OF(addr); @@ -494,7 +493,6 @@ unsigned int pae_page_table_offset_of(void *addr) { * * @param addr virtual address * @return entry offset of address within page directory - * */ unsigned int pae_page_directory_offset_of(void *addr) { return PAGE_DIRECTORY_OFFSET_OF(addr); @@ -506,7 +504,6 @@ unsigned int pae_page_directory_offset_of(void *addr) { * @param pte base page table entry (e.g. the beginning of a page table) * @param offset entry offset * @return PTE at specified offset - * */ pte_t *pae_get_pte_with_offset(pte_t *pte, unsigned int offset) { return &pte[offset]; @@ -522,7 +519,6 @@ pte_t *pae_get_pte_with_offset(pte_t *pte, unsigned int offset) { * @param pte page table or page directory entry * @param paddr physical address of page frame * @param flags flags - * */ void pae_set_pte(pte_t *pte, uint64_t paddr, uint64_t flags) { assert((paddr & ~page_frame_number_mask) == 0); @@ -538,7 +534,6 @@ void pae_set_pte(pte_t *pte, uint64_t paddr, uint64_t flags) { * * @param pte page table entry * @param pte flags flags - * */ void pae_set_pte_flags(pte_t *pte, uint64_t flags) { pte->entry = (pte->entry & page_frame_number_mask) | flags; @@ -549,7 +544,6 @@ void pae_set_pte_flags(pte_t *pte, uint64_t flags) { * * @param pte page table or page directory entry array * @return physical address - * */ uint64_t pae_get_pte_paddr(const pte_t *pte) { return (pte->entry & page_frame_number_mask); @@ -562,7 +556,6 @@ uint64_t pae_get_pte_paddr(const pte_t *pte) { * present in memory. * * @param pte page table or page directory entry - * */ void pae_clear_pte(pte_t *pte) { pte->entry = 0; @@ -573,7 +566,6 @@ void pae_clear_pte(pte_t *pte) { * * @param dest destination page table/directory entry * @param src source page table/directory entry - * */ void pae_copy_pte(pte_t *dest, const pte_t *src) { dest->entry = src->entry; diff --git a/kernel/infrastructure/i686/pmap/pmap.c b/kernel/infrastructure/i686/pmap/pmap.c index 783b6a08..4ab16ded 100644 --- a/kernel/infrastructure/i686/pmap/pmap.c +++ b/kernel/infrastructure/i686/pmap/pmap.c @@ -83,9 +83,7 @@ bool pgtable_format_pae; /** First address space created during kernel initialization. * * This address space is used as a template for kernel page tables/directories - * when creating new address spaces. It can also be used for threads to run - * into as long as they only access kernel virtual memory. See - * machine_switch_to_kernel_addr_space(). */ + * when creating new address spaces. */ static addr_space_t initial_addr_space; /** @@ -94,7 +92,6 @@ static addr_space_t initial_addr_space; * @param pte base page table entry (e.g. the beginning of a page table) * @param offset entry offset * @return PTE at specified offset - * */ static pte_t *get_pte_with_offset(pte_t *pte, unsigned int offset) { if(pgtable_format_pae) { @@ -114,7 +111,6 @@ static pte_t *get_pte_with_offset(pte_t *pte, unsigned int offset) { * @param pte base page table entry (e.g. the beginning of a page table) * @param offset entry offset * @return PTE at specified offset - * */ static inline const pte_t *get_pte_with_offset_const( const pte_t *pte, @@ -128,7 +124,6 @@ static inline const pte_t *get_pte_with_offset_const( * * @param addr virtual address * @return entry offset of address within page table - * */ static unsigned int page_table_offset_of(void *addr) { if(pgtable_format_pae) { @@ -144,7 +139,6 @@ static unsigned int page_table_offset_of(void *addr) { * * @param addr virtual address * @return entry offset of address within page directory - * */ static unsigned int page_directory_offset_of(void *addr) { if(pgtable_format_pae) { @@ -164,7 +158,6 @@ static unsigned int page_directory_offset_of(void *addr) { * * @param pte page table entry * @param pte flags flags - * */ static void set_pte_flags(pte_t *pte, uint64_t flags) { if(pgtable_format_pae) { @@ -180,7 +173,6 @@ static void set_pte_flags(pte_t *pte, uint64_t flags) { * * @param dest destination page table/directory entry * @param src source page table/directory entry - * */ static void copy_pte(pte_t *dest, const pte_t *src) { if(pgtable_format_pae) { @@ -197,7 +189,6 @@ static void copy_pte(pte_t *dest, const pte_t *src) { * @param dest destination array * @param src source array * @param n number of entries to copy - * */ void copy_ptes(pte_t *dest, const pte_t *src, int n) { for(int idx = 0; idx < n; ++idx) { @@ -217,7 +208,6 @@ void copy_ptes(pte_t *dest, const pte_t *src, int n) { * @param pte page table or page directory entry * @param paddr physical address of page frame * @param flags flags - * */ static void set_pte(pte_t *pte, user_paddr_t paddr, uint64_t flags) { if(pgtable_format_pae) { @@ -235,7 +225,6 @@ static void set_pte(pte_t *pte, user_paddr_t paddr, uint64_t flags) { * present in memory. * * @param pte page table or page directory entry - * */ static void clear_pte(pte_t *pte) { if(pgtable_format_pae) { @@ -254,7 +243,6 @@ static void clear_pte(pte_t *pte) { * * @param pte page table or page directory entry array * @param n number of entries to clear - * */ void clear_ptes(pte_t *pte, int n) { for(int idx = 0; idx < n; ++idx) { @@ -267,7 +255,6 @@ void clear_ptes(pte_t *pte, int n) { * * @param pte page table or page directory entry array * @return physical address - * */ static user_paddr_t get_pte_paddr(const pte_t *pte) { if(pgtable_format_pae) { @@ -283,7 +270,6 @@ static user_paddr_t get_pte_paddr(const pte_t *pte) { * * During initialization, the kernel either calls this function or calls * pae_enable() to enable PAE. - * */ void pmap_set_no_pae(void) { pgtable_format_pae = false; @@ -298,7 +284,6 @@ void pmap_set_no_pae(void) { * @param flags page table entry flags * @param num_entries number of entries to initialize * @return first page table entry after affected ones - * */ pte_t *initialize_page_table_linear( pte_t *page_table, @@ -328,7 +313,6 @@ pte_t *initialize_page_table_linear( * new copy is read only. * * @param bootinfo boot information structure - * */ void pmap_write_protect_kernel_image(const bootinfo_t *bootinfo) { size_t image_size = (char *)bootinfo->image_top - (char *)bootinfo->image_start; @@ -694,7 +678,6 @@ static pte_t *lookup_page_table_entry( * @param addr_space address space in which a mapping change was performed * @param addr virtual address of mapping change * @param reload_cr3 true if CR3 register must be reloaded - * */ static void invalidate_mapping( addr_space_t *addr_space, @@ -729,7 +712,6 @@ static void invalidate_mapping( * * @param prot architecture-independent protection flags * @return architecture-dependent flags - * */ static uint64_t map_page_access_flags(int prot) { const int rwe_mask = JINUE_PROT_READ | JINUE_PROT_WRITE | JINUE_PROT_EXEC; @@ -769,7 +751,6 @@ static uint64_t map_page_access_flags(int prot) { * @param paddr address of page frame * @param flags flags used for mapping (see X86_PTE_... constants) * @return true on success, false on page table allocation error - * */ static bool map_page( addr_space_t *addr_space, @@ -808,7 +789,6 @@ static bool map_page( * @param vaddr virtual address of mapping * @param paddr address of page frame * @param prot protections flags - * */ void machine_map_kernel_page(void *vaddr, kern_paddr_t paddr, int prot) { assert(is_kernel_pointer(vaddr)); @@ -826,7 +806,6 @@ void machine_map_kernel_page(void *vaddr, kern_paddr_t paddr, int prot) { * @param paddr address of page frame * @param prot protections flags * @return true on success, false on page table allocation error - * */ static bool map_userspace_page( addr_space_t *addr_space, @@ -850,7 +829,6 @@ static bool map_userspace_page( * @param length length of mapping * @param prot protection flags * @return true on success, false on page table allocation error - * */ bool machine_map_userspace( process_t *process, @@ -886,7 +864,6 @@ bool machine_map_userspace( * * @param addr_space address space from which to unmap, NULL for kernel mappings * @param addr address of page to unmap - * */ static void unmap_page(addr_space_t *addr_space, void *addr) { /** ASSERTION: addr is aligned on a page boundary */ @@ -909,7 +886,6 @@ static void unmap_page(addr_space_t *addr_space, void *addr) { * This function does not perform any page table deallocation. * * @param addr address of page to unmap - * */ void machine_unmap_kernel_page(void *addr) { assert(is_kernel_pointer(addr)); @@ -931,7 +907,6 @@ void machine_unmap_kernel_page(void *addr) { * @param src_addr start of range in source address space * @param length length of mapping range * @param prot protections flags - * */ bool machine_clone_userspace_mapping( process_t *dest_process, @@ -972,7 +947,6 @@ bool machine_clone_userspace_mapping( * * @param addr virtual address of kernel page * @return physical address of page frame - * */ kern_paddr_t machine_lookup_kernel_paddr(void *addr) { assert( is_kernel_pointer(addr) ); @@ -983,7 +957,3 @@ kern_paddr_t machine_lookup_kernel_paddr(void *addr) { return (kern_paddr_t)get_pte_paddr(pte); } - -void machine_switch_to_kernel_addr_space(void) { - pmap_switch_addr_space(&initial_addr_space); -} diff --git a/kernel/infrastructure/i686/thread.asm b/kernel/infrastructure/i686/thread.asm index 16c3419e..5da9c961 100644 --- a/kernel/infrastructure/i686/thread.asm +++ b/kernel/infrastructure/i686/thread.asm @@ -37,9 +37,8 @@ ; ------------------------------------------------------------------------------ ; FUNCTION: switch_thread_stack ; C PROTOTYPE: void switch_thread_stack( -; machine_thread_t *from_ctx, -; machine_thread_t *to_ctx, -; bool destroy_from); +; machine_thread_t *from, +; machine_thread_t *to); ; ------------------------------------------------------------------------------ global switch_thread_stack:function (switch_thread_stack.end - switch_thread_stack) switch_thread_stack: @@ -52,20 +51,23 @@ switch_thread_stack: push ebx push esi push edi + push dword 0 + push dword 0 ; At this point, the stack looks like this: ; - ; esp+28 destroy_from boolean (third function argument) - ; esp+24 to thread context pointer (second function argument) - ; esp+20 from thread context pointer (first function argument) - ; esp+16 return address - ; esp+12 ebp - ; esp+ 8 ebx - ; esp+ 4 esi - ; esp+ 0 edi + ; esp+32 to thread context pointer (second function argument) + ; esp+28 from thread context pointer (first function argument) + ; esp+24 return address + ; esp+20 ebp + ; esp+16 ebx + ; esp+12 esi + ; esp+ 8 edi + ; esp+ 4 space reserved for cleanup handler argument + ; esp+ 0 space reserved for cleanup handler ; retrieve the from thread context argument - mov ecx, [esp+20] ; from thread context (first function argument) + mov ecx, [esp+28] ; from thread context (first function argument) ; On the first thread context switch after boot, the kernel is using a ; temporary stack and the from/current thread context is NULL. Skip saving @@ -78,39 +80,29 @@ switch_thread_stack: mov [ecx], esp .do_switch: - ; read remaining arguments from stack before switching - mov esi, [esp+24] ; to thread context (second function argument) - mov eax, [esp+28] ; destroy_from boolean (third function argument) + ; read remaining argument from stack before switching + mov esi, [esp+32] ; to thread context (second function argument) ; Load the saved stack pointer from the thread context to which we are ; switching (to thread). This is where we actually switch thread. mov esp, [esi] ; saved stack pointer is the first member + + ; Call the cleanup handler, if any + pop eax + or eax, eax + jz .no_handler + + call eax + +.no_handler: + ; Remove the cleanup handler argument from the stack + pop edi ; Restore the saved registers. - ; - ; 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 pop ebp - ; Now that we switched stack, see if the caller requested the from thread - ; context be destroyed. - or eax, eax - jz .skip_destroy - - ; destroy from thread context - and ecx, THREAD_CONTEXT_MASK - push ecx - call sub_ref_to_object - - ; cleanup sub_ref_to_object() arguments from stack - add esp, 4 - -.skip_destroy: ret .end: diff --git a/kernel/infrastructure/i686/thread.c b/kernel/infrastructure/i686/thread.c index 5a5bc5be..ab498ed9 100644 --- a/kernel/infrastructure/i686/thread.c +++ b/kernel/infrastructure/i686/thread.c @@ -30,6 +30,8 @@ */ #include +#include +#include #include #include #include @@ -43,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -81,25 +84,32 @@ * */ -static addr_t get_kernel_stack_base(machine_thread_t *thread_ctx) { - thread_t *thread = (thread_t *)( (uintptr_t)thread_ctx & THREAD_CONTEXT_MASK ); +/* Stack frame for switch_thread_stack(). */ +typedef struct { + void (*cleanup_handler)(void *); + void *cleanup_arg; + uint32_t edi; + uint32_t esi; + uint32_t ebx; + uint32_t ebp; + uint32_t eip; +} kernel_context_t; + +static addr_t get_kernel_stack_base(thread_t *thread) { return (addr_t)thread + THREAD_CONTEXT_SIZE; } void machine_prepare_thread(thread_t *thread, const thread_params_t *params) { - /* initialize fields */ - machine_thread_t *thread_ctx = &thread->thread_ctx; - /* setup stack for initial return to user space */ - void *kernel_stack_base = get_kernel_stack_base(thread_ctx); + void *kernel_stack_base = get_kernel_stack_base(thread); - trapframe_t *trapframe = (trapframe_t *)kernel_stack_base - 1; + trapframe_t *trapframe = (trapframe_t *)kernel_stack_base - 1; memset(trapframe, 0, sizeof(trapframe_t)); trapframe->eip = (uint32_t)params->entry; trapframe->esp = (uint32_t)params->stack_addr; - trapframe->eflags = 2; + trapframe->eflags = EFLAGS_ALWAYS_1; trapframe->cs = SEG_SELECTOR(GDT_USER_CODE, RPL_USER); trapframe->ss = SEG_SELECTOR(GDT_USER_DATA, RPL_USER); trapframe->ds = SEG_SELECTOR(GDT_USER_DATA, RPL_USER); @@ -115,7 +125,7 @@ void machine_prepare_thread(thread_t *thread, const thread_params_t *params) { kernel_context->eip = (uint32_t)return_from_interrupt; /* set thread stack pointer */ - thread_ctx->saved_stack_pointer = (addr_t)kernel_context; + thread->machine_thread.saved_stack_pointer = (addr_t)kernel_context; } thread_t *machine_alloc_thread(void) { @@ -126,33 +136,60 @@ void machine_free_thread(thread_t *thread) { page_free(thread); } -void machine_switch_thread(thread_t *from, thread_t *to, bool destroy_from) { - /** ASSERTION: to argument must not be NULL */ - assert(to != NULL); - - /** ASSERTION: from argument must not be NULL if destroy_from is true */ - assert(from != NULL || !destroy_from); - - machine_thread_t *from_ctx = (from == NULL) ? NULL : &from->thread_ctx; - machine_thread_t *to_ctx = &to->thread_ctx; - +static void set_kernel_stack(thread_t *thread) { /* setup TSS with kernel stack base for this thread context */ - addr_t kernel_stack_base = get_kernel_stack_base(to_ctx); - tss_t *tss = get_tss(); + tss_t *tss = get_percpu_tss(); + addr_t kernel_stack_base = get_kernel_stack_base(thread); tss->esp0 = kernel_stack_base; tss->esp1 = kernel_stack_base; tss->esp2 = kernel_stack_base; - machine_set_tls(to); - /* update kernel stack address for SYSENTER instruction */ if(cpu_has_feature(CPUINFO_FEATURE_SYSENTER)) { wrmsr(MSR_IA32_SYSENTER_ESP, (uint64_t)(uintptr_t)kernel_stack_base); } +} + +void machine_switch_thread(thread_t *from, thread_t *to) { + assert(to != NULL); - /* switch thread stack */ - switch_thread_stack(from_ctx, to_ctx, destroy_from); + set_kernel_stack(to); + + machine_set_thread_local_storage(to); + + machine_thread_t *machine_from = (from == NULL) ? NULL : &from->machine_thread; + machine_thread_t *machine_to = &to->machine_thread; + + switch_thread_stack(machine_from, machine_to); +} + +static void unref_cleanup_handler(void *arg) { + thread_t *thread = arg; + sub_ref_to_object(&thread->header); +} + +void machine_switch_and_unref_thread(thread_t *from, thread_t *to) { + assert(from != NULL); + + kernel_context_t *context = (kernel_context_t *)to->machine_thread.saved_stack_pointer; + context->cleanup_handler = unref_cleanup_handler; + context->cleanup_arg = from; + + machine_switch_thread(from, to); +} + +static void unlock_cleanup_handler(void *arg) { + spinlock_t *lock = arg; + spin_unlock(lock); +} + +void machine_switch_thread_and_unlock(thread_t *from, thread_t *to, spinlock_t *lock) { + kernel_context_t *context = (kernel_context_t *)to->machine_thread.saved_stack_pointer; + context->cleanup_handler = unlock_cleanup_handler; + context->cleanup_arg = lock; + + machine_switch_thread(from, to); } thread_t *get_current_thread(void) { diff --git a/kernel/interface/syscalls.c b/kernel/interface/syscalls.c index 07c2be33..382bbcf9 100644 --- a/kernel/interface/syscalls.c +++ b/kernel/interface/syscalls.c @@ -561,7 +561,6 @@ static void sys_reply_error(jinue_syscall_args_t *args) { * arguments. * * @param trapframe trap frame for current system call - * */ void dispatch_syscall(jinue_syscall_args_t *args) { intptr_t function = args->arg0;