From 413c2b57f2156ad919d650760f353a5702591bc7 Mon Sep 17 00:00:00 2001 From: Yatis Date: Sun, 3 Jan 2021 20:58:49 +0100 Subject: [PATCH] Thread: * allow exception when atomic operation is used ! * Add documentation --- include/gint/thread.h | 185 ++++++++++++++++++++++++++++++++--------- src/kernel/start.c | 11 +-- src/thread/atomic.S | 2 +- src/thread/idle.c | 6 +- src/thread/kernel.S | 14 ++-- src/thread/mutex.c | 4 +- src/thread/scheduler.c | 2 +- src/thread/signals.c | 27 ++---- src/thread/thread.c | 13 +-- 9 files changed, 177 insertions(+), 87 deletions(-) diff --git a/include/gint/thread.h b/include/gint/thread.h index a76636a..c3aca31 100644 --- a/include/gint/thread.h +++ b/include/gint/thread.h @@ -92,17 +92,18 @@ extern int thread_attr_getdetachstate(thread_attr_t *attr, int *detachstate); /* thread_attr_enablelongjmp() thread_attr_getlongjmpstatus() - This part is CUSTOM to the Gint kernel, it will allow, when the thread with - this attibute die, instead of release its memories and been removed from the - scheduler, to perform a "longjmp()" and returns ("rewinds") to the - "thread_create()" function which will return the special THREAD_ATTR_REWIND - and all child thread of the thread will be killed (using signals). + This feature is CUSTOM to the Gint kernel. It will allow, when a thread with + this attribute die, instead of release its memories and removed it from the + scheduler, allow it to perform a "longjmp()" and return ("rewinds") to the + "thread_create()" function involved to create the current thread. The + original "thread_create()" will return the special value: + THREAD_CREATE_LONGJMP_RETURN. In the case of Gint, this feature is very useful because the "main" function can be threaded and we can return the to "dark-side" of the kernel to manage - the destructors or to recall the main function again. All this while allowing - (potential) threaded driver that can continue working while being isolated - from the Gint "bootstrap" part. + destructors or to recall the main function again. We cann allow this while + continuing running (potential) threaded drivers that can continue working + while being isolated from the Gint "bootstrap" part. The following values may be specified in "status": - THREAD_ATTR_LONGJMP_ENABLE @@ -119,12 +120,16 @@ extern int thread_attr_getlongjmpstatus(thread_attr_t *attr, int *status); /* thread_attr_destroy(): Destroy thread attribute object When a thread attributes object is no longer required, it should be destroyed - using the pthread_attr_destroy() function. Destroying a thread attributes + using the "pthread_attr_destroy()" function. Destroying a thread attributes object has no effect on threads that were created using that object. Once a thread attributes object has been destroyed, it can be reinitialized using "thread_attr_init()". Any other use of a destroyed thread attributes - object has undefined results. */ + object has undefined results. + + @return + * negative value if ATTR is NULL or uninitialized + * 0 if success */ extern int thread_attr_destroy(thread_attr_t *attr); @@ -132,6 +137,21 @@ extern int thread_attr_destroy(thread_attr_t *attr); //--- // Signals interface +// +// A “signal” is a software interrupt delivered to a process. The operating +// system uses signals to report exceptional situations to an executing +// program. Some signals report errors such as references to invalid +// memory addresses; others report asynchronous events, such as +// disconnection of a phone line. +// +// If you anticipate an event that causes signals, you can define a handler +// function and tell the operating system to run it when that particular +// type of signal arrives. +// +// Finally, one thread can send a signal to another process; this allows a +// parent thread to abort a child, or two related thread to communicate +// and synchronize. +// //--- /* Define thread signal set type */ typedef uint32_t sigset_t; @@ -148,7 +168,7 @@ typedef void (*sighandler_t)(int); #define NSIG 19 /* Define all signum - (note: all signal are not currently supported but will be in near futur) */ + (note: All signals are not currently supported, but will be in near future)*/ #define SIGKILL 0 /* (unblockable) Killed */ #define SIGSTOP 1 /* (unblockable) Stop */ #define SIGTERM 2 /* (unblockable) Termination request. */ @@ -175,15 +195,14 @@ typedef void (*sighandler_t)(int); //--- // User thread interface //--- -/* define specrial return value with the thread_create() function */ +/* define special return value with the thread_create() function */ #define THREAD_CREATE_LONGJMP_RETURN (0xd1ceca5e) /* Define thread ID alias */ typedef uint32_t thread_t; -/* cpu_ctx: whole SH3-based CPU context definition */ -struct cpu_ctx -{ +/* cpu_ctx: whole SH3-based CPU hardware context definition */ +struct cpu_ctx { uint32_t reg[16]; uint32_t gbr; uint32_t macl; @@ -194,9 +213,6 @@ struct cpu_ctx }; /* struct thread: Thread structure definition */ -//TODO: all hardware context ! -//TODO: signals context ! -//TODO: signals mask ! struct thread { /* hardware context */ struct cpu_ctx context; @@ -225,7 +241,7 @@ struct thread { thread_t id; /* hierarchical information */ - struct thread *next; /* potential next process */ + struct thread *next; } scheduler; /* private information */ @@ -239,9 +255,38 @@ struct thread { } private; }; -/* thread_create(): Create a new thread */ +/* thread_create(): Create a new thread + + This function creates a new thread which starts execution by invoking + "start_routine()"; You can passe many argument as you want and + all args is passed as argument of start_routine(). + + The new thread terminates in one of the following ways: + - It calls "thread_exit()", specifying an exit status value that is + available to another thread in the same process that calls + "thread_join()" + - It returns from start_routine(). This is equivalent to calling + "thread_exit()" with the value supplied in the return statement. + - It is canceled (see "thread_cancel()"). + - If one thread with the special THREAD_ATTR_MAIN_THREAD die. In this case, + all thread created with this special (custom) attribute will be killed + + The "attr" argument points to a pthread_attr_t structure whose contents + are used at thread creation time to determine attributes for the new thread; + this structure is initialized using "thread_attr_init()" and related + functions. If "attr" is NULL, then the thread is created with default + attributes. + + Before returning, a successful call to "thread_create()" stores the ID of + the new thread in the buffer pointed to by "thread"; this identifier is used + to refer to the thread in subsequent calls to other "thread_*" functions. + + @return: + * negative value if error occurs + * 0 if success +*/ extern int thread_create(thread_t *thread, - thread_attr_t *attr, void *function, ...); + thread_attr_t *attr, void *start_routine, ...); /* Makes sure an argument is always provided, for va_arg() and for definite the last arguments sended by the user */ @@ -264,7 +309,14 @@ extern int thread_create(thread_t *thread, If multiple threads simultaneously try to join with the same thread, the results are undefined. If the thread calling "thread_join()" is canceled, then the target thread will remain joinable (i.e., it will not be detached). - */ + + The "thread_tryjoin()" function have the same behaviour that the + "thread_join()" excet that it will not block the current thread and returns + directly if the wanted thread is busy or unjoinable. + + @return: + * negative value if error occurs + * 0 if success */ extern int thread_join(thread_t thread, void **retvel); extern int thread_tryjoin(thread_t thread, void **retval); @@ -284,23 +336,53 @@ extern int thread_tryjoin(thread_t thread, void **retval); variables, semaphores, and file descriptors) are not released. */ extern void thread_exit(void *retval); -/* thread_kill(): Kill a thread +/* thread_kill(): Send signal to a thread - This function will destroy a thread without care about its attributes. The - thread SHOUL NEVER be used anymore after this function. + This fucntion will raise any signals to any thread. The value of the thread + can have special value: + * if thread is positive then signal is sent to the thread with the ID + specified by thread. + * if thread is equals 0, then signal is sent to threads which have the + calling has parent. - This function is used by the scheduler to destroy thread from its queue - before removing it. */ + @return: + * negative value if error occurs + * 0 if success */ extern int thread_kill(thread_t thread, int sig); -/* TODO doc */ +/* thread_signal(): sets the disposition of the signal signum to handler, which + is either SIG_IGN, SIG_DFL, or the address of a + programmer-defined function (a "signal handler"). + + This part provides a simple interface for establishing an action for a + particular signal. + + If the signal signum is delivered to the process, then one of the following + happens: + - If the disposition is set to SIG_IGN, then the signal is ignored. + - If the disposition is set to SIG_DFL, then the default action associated + with the signal + - If the disposition is set to a function, then first either the disposition + is reset to SIG_DFL, or the signal is blocked, and then handler is called + with argument signum. If invocation of the handler caused the signal to + be blocked, then the signal is unblocked upon return from the handler. + + The signals SIGKILL and SIGSTOP cannot be caught or ignored. + + @return + the previous value of the signal handler, or SIG_ERR on error. */ extern void (*thread_signal(int signum, void (*handler)(int)))(int); -/* KERNEL-ONLY TODO Doc */ +/* thread_terminate(): KERNEL-ONLY: destroy a thread + + This function will destroy a thread regardless of its attributes, scheduler + status, ... This function is used by the kernel to remove definitively a + thread. */ extern int thread_terminate(struct thread *thread); + //--- // Thread mutex interface // @@ -477,7 +559,7 @@ extern uint32_t thread_atomic_stop(void); //--- -// Signals management (kernel) +// Signals management (kernel-level) //--- enum { thread_signals_deliver_retval_running = 0, @@ -497,16 +579,24 @@ extern int thread_signals_raise(struct thread *thread, int sig); /* thread_signals_deliver_pending(): Deliver all pending signals - This function is KERNEL-ONLY and SOULD NEVER be called because it is + This function is KERNEL-ONLY and SHOULD NEVER be called because it is exclusively reserved for the internal thread scheduler. (see for more information). */ extern int thread_signals_pending_deliver(struct thread *thread); -/* thread_signals_replace(): TODO */ +/* thread_signals_replace(): Replace current signal handler + + This function will replace the signum signal handler with the new handler. + This part is used by the "thread_kill()" function. */ extern void (*thread_signals_replace(struct thread *thread, int signum, void (*handler)(int)))(int); -/* thread_signals_sigreturn(): TODO */ +/* thread_signals_sigreturn(): KERNEL-ONLY: Signals return handler + + This function is involved when an custom signals handler come to the end. It + will restore previous context and stack then it will invalidate the current + thread to force the scheudler to not save the current thread context; this + mecanism will restore the previous context. */ extern void thread_signals_sigreturn(void); @@ -525,21 +615,36 @@ extern struct thread *thread_idle_get(void); //--- -// Scheduler interface +// Scheduler interface (kernel-only) //--- +/* thread_sched_initialize(): Initialize the scheduler */ extern void thread_sched_init(void); extern void thread_sched_uninit(void); -/* thread_sched_start(): */ +/* thread_sched_start() / thread_sched_stop: Control the scheduler timer */ extern void thread_sched_start(void); extern void thread_sched_stop(void); +/* thread_sched_add(): thread_sched_remove(): handle internal thread queue */ extern int thread_sched_add(struct thread *thread); extern int thread_sched_remove(struct thread *thread); +/* thread_sched_find(): Find a thread strcuture using his ID */ extern struct thread *thread_sched_find(thread_t thread); + +/* thread_sched_get_current(): Get the current thread context */ extern struct thread *thread_sched_get_current(void); + +/* thread_sched_get_counter(): Return the number of thread in the queue */ extern int thread_sched_get_counter(void); + +/* thread_sched_invalidate(): Invalidate the current thread. + + This function will invalidate the current thread, it means that the current + thread will not be saved on the next schedule. This is really useful for the + signals' management to restore previously saved thread context. + + You SHOULD never use it. */ extern void thread_sched_invalidate(void); @@ -549,11 +654,13 @@ extern void thread_sched_invalidate(void); // Kernel thread interface //--- -/* thread_kernel_terminate_trampoline(): Termination trampiline code. +/* thread_kernel_terminate_trampoline(): Termination trampoline code. - This function will invoke the "thread_exit()" function with 0. This function - is automatically involved when a thread return from his main procedure and - SHOULD NOT be called. */ + This function is automatically involved when a thread return from his main + procedure and will invoke the "thread_exit()" function with the returned + value. + + You SHOULD never use it. */ extern void thread_kernel_terminate_trampoline(void); /* thread_kernel_yield(): Cause the calling thread to relinquish the CPU */ diff --git a/src/kernel/start.c b/src/kernel/start.c index 7dacee3..02a0fb2 100644 --- a/src/kernel/start.c +++ b/src/kernel/start.c @@ -187,7 +187,7 @@ int start(int isappli, int optnum) use the "classic" way to do the job. @note - When we return from the thread_create() function with the longjmp() + When we return from the "thread_create()" function with the longjmp() feature, we are always in a thread execution (the scheduler always performs context switch) */ int rc = -1; @@ -199,13 +199,8 @@ int start(int isappli, int optnum) int retval = thread_create(&thread, &attr, &main, isappli, optnum); if (retval == 0) thread_join(thread, (void**)&rc); - //if (retval != 0 && (uint32_t)retval != THREAD_CREATE_LONGJMP_RETURN) - // rc = main(isappli, optnum); - - dclear(C_BLACK); - dtext(0, 0, C_WHITE, "longjmp, success !"); - dupdate(); - while (1); + if (retval != 0 && (uint32_t)retval != THREAD_CREATE_LONGJMP_RETURN) + rc = main(isappli, optnum); /* main loop */ while (gint_restart == 0) { diff --git a/src/thread/atomic.S b/src/thread/atomic.S index 3b45e04..169d897 100644 --- a/src/thread/atomic.S +++ b/src/thread/atomic.S @@ -70,7 +70,7 @@ atomic_end_exit: .align 4 thread_atomic_counter: .long _thread_atomic_counter thread_atomic_sr_save: .long _thread_atomic_sr_save -sr_mask: .long 0x100000f0 +sr_mask: .long 0x000000f0 diff --git a/src/thread/idle.c b/src/thread/idle.c index 2b9e337..38bffa8 100644 --- a/src/thread/idle.c +++ b/src/thread/idle.c @@ -4,7 +4,7 @@ #include #include -/* define private sombols */ +/* define private symbols */ static struct thread thread_idle; static uint32_t thread_idle_stack[THREAD_IDLE_STACK_SIZE]; @@ -29,12 +29,11 @@ static void thread_idle_code(void) void thread_idle_init(void) { memset(&thread_idle, 0x00, sizeof(struct thread)); - thread_idle.context.reg[15] = (uintptr_t)&thread_idle_stack; + thread_idle.context.reg[15] = (uintptr_t)thread_idle_stack; thread_idle.context.reg[15] += (THREAD_IDLE_STACK_SIZE << 2); thread_idle.context.spc = (uintptr_t)&thread_idle_code; thread_idle.context.pr = (uintptr_t)0xa0000000; } - void thread_idle_uninit(void) { return; @@ -46,6 +45,7 @@ void thread_idle_uninit(void) //--- // User interface //--- +/* thread_idle_get(): Return the idle thread */ struct thread *thread_idle_get(void) { return (&thread_idle); diff --git a/src/thread/kernel.S b/src/thread/kernel.S index b24112c..d1409bf 100644 --- a/src/thread/kernel.S +++ b/src/thread/kernel.S @@ -29,11 +29,11 @@ a common subroutine, PR register has been saved into the R0 register and the R15 register has been saved into the R1 register. - We are always with the "interrupt" bank register configuration. So, we only + We are always with the "privileged" bank register configuration. So, we only can use r0~r7 register until the current thread context has been saved. When involved, R0 content the PR register snapshot when interrupts occur, - same for the R1 register which content the stack snapshot. (Yes, I knewn that + same for the R1 register which content the stack snapshot. (Yes, I know that the SH4-based MPU provide the SGR register which saves the stack snapshot when interrupts / exceptions occur, but we need to be SH3 compatible) */ _thread_kernel_sched_interrupt_procedure: @@ -277,6 +277,7 @@ intevt_register: .long 0xff000028 This function will move the calling thread to the end of the queue for its static priority and new thread gets on run. */ +/* TODO preemption ? */ _thread_kernel_yield: /* start atomic operation + bank switch*/ stc sr, r0 @@ -317,13 +318,8 @@ pouet: .global _thread_kernel_terminate_trampoline .type _thread_kernel_terminate_trampoline, @function -.global _thread_kernel_exit -.type _thread_kernel_exit, @function - - -/* -r0 - SR backup -*/ +/* thread_kernel_terminate_trampoline() + call the thread_exit function with the returned value.*/ _thread_kernel_terminate_trampoline: mov.l 2f, r1 jmp @r1 diff --git a/src/thread/mutex.c b/src/thread/mutex.c index 9c8cd42..cfc3ce8 100644 --- a/src/thread/mutex.c +++ b/src/thread/mutex.c @@ -102,7 +102,7 @@ int thread_mutex_lock(thread_mutex_t *mutex) break; }; - /* Wait next schedule */ + /* Force schedule */ thread_kernel_yield(); } @@ -159,7 +159,7 @@ int thread_mutex_timedlock(thread_mutex_t *mutex, uint64_t delay_us) break; }; - /* Wait next schedule */ + /* Force schedule */ thread_kernel_yield(); } diff --git a/src/thread/scheduler.c b/src/thread/scheduler.c index 33e1b1c..6bbb2b3 100644 --- a/src/thread/scheduler.c +++ b/src/thread/scheduler.c @@ -133,7 +133,7 @@ int thread_sched_remove(struct thread *thread) break; parent = &(*parent)->scheduler.next; } - if (parent == NULL) { + if (*parent == NULL) { thread_atomic_stop(); return (-1); } diff --git a/src/thread/signals.c b/src/thread/signals.c index 5490c0a..99cbe39 100644 --- a/src/thread/signals.c +++ b/src/thread/signals.c @@ -72,13 +72,7 @@ static int thread_signals_deliver(struct thread *thread, int sig) //--- // Kernel interface //--- -/* thread_signals_raise(): Tricky part to handle signals - - @return: - * 1 Cannot be scheduled - * 0 Can be scheduled - * -1 The thread SHOULD be removed ! -*/ +/* thread_signals_raise():Raise a signal */ int thread_signals_raise(struct thread *thread, int sig) { if (sig >= NSIG) @@ -95,13 +89,7 @@ int thread_signals_raise(struct thread *thread, int sig) return (0); } -/* thread_signals_deliver_pending(): Deliver all pending signals - - @return - * 0 - can be scheduled - * negative value - should not be scheduled - */ -#include +/* thread_signals_deliver_pending(): Deliver pending signals */ int thread_signals_pending_deliver(struct thread *thread) { sigset_t sig; @@ -125,13 +113,13 @@ int thread_signals_pending_deliver(struct thread *thread) return (retval); } -/* thread_signals_sigreturn(): TODO */ +/* thread_signals_sigreturn(): Signals return handler */ void thread_signals_sigreturn(void) { struct thread *thread; void *stack; - /* thread */ + /* get the current thread */ thread_atomic_start(); thread = thread_sched_get_current(); if (thread == NULL) { @@ -145,16 +133,19 @@ void thread_signals_sigreturn(void) /* restore stack */ thread->context.reg[15] += (sizeof(struct cpu_ctx) + 3) >> 2 << 2; + + /* Invalidate the thread to force the scheduler to not save the current + context otherwise, the restored context will be overwritten. */ thread_sched_invalidate(); - /* force schedule */ + /* force schedule, on the next schedule, we will return to the job */ thread_atomic_stop(); while (1) { thread_kernel_yield(); } } -/* thread_signals_replace(): TODO */ +/* thread_signals_replace(): Replace current signal handler */ void (*thread_signals_replace(struct thread *thread, int signum, void (*handler)(int)))(int) { diff --git a/src/thread/thread.c b/src/thread/thread.c index a8a8375..d873184 100644 --- a/src/thread/thread.c +++ b/src/thread/thread.c @@ -19,11 +19,10 @@ #if 0 /* thread_kill_child() - If the THREAD_ATTR_LONGJMP_ENABLE is set, this function is involved when the - thread is created. If the "longjmp()" feature is set then the "setjmp()" - function will return with an retval that differ from 0. In this case, it's - signifiacte that the created thread just died. So, we should kill all child - thread generated by him */ + If the THREAD_ATTR_MAIN_THREAD is set, this function is involved when the + thread die. This function will kill all child thread generated by the parent + */ +/* TODO */ static void thread_kill_child(struct thread **thread) { /* walking */ @@ -205,7 +204,8 @@ void thread_exit(void *retval) } } -/* thread_kill(): Destroy the thread */ +/* thread_kill(): Send signals */ +/* TODO: hierarchical handling ! */ int thread_kill(thread_t id, int sig) { struct thread *thread; @@ -242,6 +242,7 @@ void (*thread_signal(int signum, void (*handler)(int)))(int) //--- // Kernel interface //--- + /* thread_terminate(): Terminate a thread */ int thread_terminate(struct thread *thread) {