diff --git a/.vscode/settings.json b/.vscode/settings.json index ee38262e..08ac39a1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,11 @@ "*.h": "c", "*.tcc": "c", "functional": "c" - } + }, + "editor.wordWrapColumn": 80, + "rewrap.wrappingColumn": 80, + "editor.rulers": [ + 80, + 120 + ], } \ No newline at end of file diff --git a/kernel/inc/drivers/keyboard/keyboard.h b/kernel/inc/drivers/keyboard/keyboard.h index fc6ae4b4..d9de3541 100644 --- a/kernel/inc/drivers/keyboard/keyboard.h +++ b/kernel/inc/drivers/keyboard/keyboard.h @@ -44,6 +44,13 @@ int keyboard_peek_front(void); /// @return 0 on success, 1 on error. int keyboard_initialize(void); +/// @brief Sleep until a keypress is available on the global keyboard queue. +/// +/// This is a simple helper used by higher‑level input code to block the +/// current process until a character arrives. It is safe to call from +/// kernel contexts that know they have the keyboard lock. +void keyboard_wait(void); + /// @brief De-initializes the keyboard drivers. /// @return 0 on success, 1 on error. int keyboard_finalize(void); diff --git a/kernel/inc/hardware/timer.h b/kernel/inc/hardware/timer.h index a3141032..a211b624 100644 --- a/kernel/inc/hardware/timer.h +++ b/kernel/inc/hardware/timer.h @@ -156,6 +156,11 @@ void remove_timer(struct timer_list *timer); /// the process. int sys_nanosleep(const struct timespec *req, struct timespec *rem); +/// @brief Cancels nanosleep timer for a task interrupted by a signal. +/// @param task The task whose sleep timer should be canceled. +/// @return 0 on success, -1 if no sleep timer exists. +int cancel_sleep_timer(struct task_struct *task); + /// @brief Send signal to calling thread after desired seconds. /// @param seconds The number of seconds in the interval /// @return the number of seconds remaining until any previously scheduled diff --git a/kernel/inc/klib/spinlock.h b/kernel/inc/klib/spinlock.h index 6565f140..6d04755f 100644 --- a/kernel/inc/klib/spinlock.h +++ b/kernel/inc/klib/spinlock.h @@ -11,6 +11,8 @@ #define SPINLOCK_FREE 0 /// Determines if the spinlock is busy. #define SPINLOCK_BUSY 1 +/// Initializes a spinlock with the value of SPINLOCK_FREE. +#define SPINLOCK_INIT SPINLOCK_FREE /// @brief Spinlock structure. typedef atomic_t spinlock_t; diff --git a/kernel/inc/process/process.h b/kernel/inc/process/process.h index 4e760034..e5bfd149 100644 --- a/kernel/inc/process/process.h +++ b/kernel/inc/process/process.h @@ -139,6 +139,8 @@ typedef struct task_struct { /// Timer for alarm syscall. struct timer_list *real_timer; + /// Timer for nanosleep syscall (cancelled on signal delivery). + struct timer_list *sleep_timer; /// Next value for the real timer (ITIMER_REAL). unsigned long it_real_incr; @@ -158,6 +160,9 @@ typedef struct task_struct { /// Buffer for managing inputs from keyboard. rb_keybuffer_t keyboard_rb; + /// Wait queue this task is currently sleeping on (NULL if not sleeping). + struct wait_queue_head *waiting_on; + //==== Future work ========================================================= // - task's attributes: // struct task_struct __rcu *real_parent; diff --git a/kernel/inc/process/scheduler.h b/kernel/inc/process/scheduler.h index c5e4398d..72102704 100644 --- a/kernel/inc/process/scheduler.h +++ b/kernel/inc/process/scheduler.h @@ -69,6 +69,17 @@ void scheduler_enqueue_task(task_struct *process); /// @param process Process that has to be activated. void scheduler_dequeue_task(task_struct *process); +/// @brief Wake up a sleeping/stopped task and make it runnable. +/// @details Handles all state transitions, queue management, and cleanup atomically: +/// - Transitions task to TASK_RUNNING state +/// - Removes task from any wait queue it's sleeping on +/// - Cancels any active sleep timer +/// - Enqueues task to scheduler runqueue +/// - Clears wait queue tracking fields +/// @param task The task to wake up +/// @return 0 on success, negative on error +int wake_up_process(task_struct *task); + /// @brief The RR implementation of the scheduler. /// @param f The context of the process. void scheduler_run(pt_regs_t *f); diff --git a/kernel/inc/process/wait.h b/kernel/inc/process/wait.h index 152b3f37..81e9a419 100644 --- a/kernel/inc/process/wait.h +++ b/kernel/inc/process/wait.h @@ -61,10 +61,9 @@ /// @brief Head of the waiting queue. typedef struct wait_queue_head { - /// Locking element for the waiting queque. - spinlock_t lock; - /// Head of the waiting queue, it contains wait_queue_entry_t elements. - struct list_head task_list; + const char *name; ///< Name of the wait queue (Added for debug purpose). + spinlock_t lock; ///< Locking element for the waiting queque. + struct list_head task_list; ///< Head of the waiting queue, it contains wait_queue_entry_t elements. } wait_queue_head_t; /// @brief Entry of the waiting queue. @@ -123,3 +122,32 @@ int default_wake_function(wait_queue_entry_t *entry, unsigned mode, int sync); /// @return Pointer to the entry inside the wq representing the /// sleeping process. wait_queue_entry_t *sleep_on(wait_queue_head_t *head); + +/// @brief Sets the state of the current process to TASK_INTERRUPTIBLE +/// and inserts it into the specified wait queue. +/// +/// @param head Waitqueue where to sleep. +/// @return Pointer to the entry inside the wq representing the +/// sleeping process. +wait_queue_entry_t *sleep_on_interruptible(wait_queue_head_t *head); + +/// @brief Wake all tasks waiting on the queue. +/// +/// Calls each entry's wake function and removes entries whose function +/// returns non‑zero. This is the generic helper used by drivers when +/// data arrives. +void wake_up_all(wait_queue_head_t *head); + +/// @brief Wake up a specific process if it is waiting on the given queue. +/// @param head The wait queue head to search. +/// @param task The specific task to wake up. +/// @return 1 if the task was found and woken up, 0 otherwise. +int wake_up_process_on_queue(wait_queue_head_t *head, struct task_struct *task); + +/// @brief Wake a specific wait queue entry. +/// @param head The wait queue head that owns the entry. +/// @param entry The wait queue entry to evaluate and wake. +/// @param mode Wake mode passed to the entry wake function. +/// @param sync Wake sync flag passed to the entry wake function. +/// @return 1 if the task was woken, 0 otherwise. +int wake_up_wait_queue_entry(wait_queue_head_t *head, wait_queue_entry_t *entry, unsigned mode, int sync); diff --git a/kernel/src/drivers/keyboard/keyboard.c b/kernel/src/drivers/keyboard/keyboard.c index 8bb94c84..f06d28ae 100644 --- a/kernel/src/drivers/keyboard/keyboard.c +++ b/kernel/src/drivers/keyboard/keyboard.c @@ -20,6 +20,7 @@ #include "io/port_io.h" #include "io/video.h" #include "process/scheduler.h" +#include "process/wait.h" #include "ring_buffer.h" #include "string.h" #include "sys/bitops.h" @@ -33,6 +34,15 @@ rb_keybuffer_t scancodes; /// Spinlock to protect access to the scancode buffer. spinlock_t scancodes_lock; +/* wait queue used by keyboard_pop_back(), initialized in + * keyboard_initialize(). Readers sleep here when the scancode buffer + * is empty; the ISR wakes them when a new key arrives. */ +static wait_queue_head_t keyboard_wait_queue = { + .name = "keyboard_wait_queue", + .lock = SPINLOCK_INIT, + .task_list = LIST_HEAD_INIT(keyboard_wait_queue.task_list), +}; + #define KBD_LEFT_SHIFT (1 << 0) ///< Flag which identifies the left shift. #define KBD_RIGHT_SHIFT (1 << 1) ///< Flag which identifies the right shift. #define KBD_CAPS_LOCK (1 << 2) ///< Flag which identifies the caps lock. @@ -91,6 +101,9 @@ static inline void keyboard_push_back(unsigned int c) // Unlock the buffer after the push operation is complete. spinlock_unlock(&scancodes_lock); + + /* wake any readers waiting for input */ + wake_up_all(&keyboard_wait_queue); } /// @brief Pushes a character into the scancode ring buffer. @@ -105,6 +118,9 @@ static inline void keyboard_push_front(unsigned int c) // Unlock the buffer after the push operation is complete. spinlock_unlock(&scancodes_lock); + + /* wake readers as well; front or back both add data */ + wake_up_all(&keyboard_wait_queue); } /// @brief Pushes a sequence of characters (scancodes) into the keyboard buffer. @@ -143,12 +159,22 @@ static inline void keyboard_push_front_sequence(char *sequence) /// @return the value we removed from the ring buffer. int keyboard_pop_back(void) { + int c; + spinlock_lock(&scancodes_lock); - int c = rb_keybuffer_pop_back(&scancodes); + c = rb_keybuffer_pop_back(&scancodes); spinlock_unlock(&scancodes_lock); + + /* simply return what we have; caller decides what to do when empty */ return c; } +/// @brief Put the current task to sleep waiting for keyboard data. +void keyboard_wait(void) +{ + sleep_on(&keyboard_wait_queue); +} + int keyboard_peek_back(void) { spinlock_lock(&scancodes_lock); diff --git a/kernel/src/fs/pipe.c b/kernel/src/fs/pipe.c index 93b37749..94ea5b64 100644 --- a/kernel/src/fs/pipe.c +++ b/kernel/src/fs/pipe.c @@ -496,17 +496,13 @@ int pipe_read_wake_function(wait_queue_entry_t *wait, unsigned mode, int sync) // Validate that data is available in the pipe for reading. if ((pipe_info_has_data(pipe_info) > 0) || (pipe_info->writers == 0)) { - // Check if the task is in an appropriate sleep state to be woken up. - if ((wait->task->state == TASK_UNINTERRUPTIBLE) || (wait->task->state == TASK_STOPPED)) { - // Set the task's state to the specified wake-up mode. - wait->task->state = mode; - - // Signal that the task has been woken up. + // Check if the task is in uninterruptible sleep state (the state it + // entered when starting the pipe read operation). + if (wait->task->state == TASK_UNINTERRUPTIBLE) { pr_debug("Data available or no more writers, waking up reader %d.\n", wait->task->pid); return 1; } pr_debug("Reader %d not in the correct state for wake-up.\n", wait->task->pid); - } else { pr_debug("No data available, reader %d should continue waiting.\n", wait->task->pid); } @@ -533,17 +529,13 @@ int pipe_write_wake_function(wait_queue_entry_t *wait, unsigned mode, int sync) // Check if there is available space in the pipe for writing. if (pipe_info_has_space(pipe_info) > 0) { - // Only tasks in the state TASK_UNINTERRUPTIBLE or TASK_STOPPED can be woken up. - if ((wait->task->state == TASK_UNINTERRUPTIBLE) || (wait->task->state == TASK_STOPPED)) { - // Set the wake-up mode for the task. - wait->task->state = mode; - - // Signal that the task has been woken up. + // Check if the task is in uninterruptible sleep state (the state it + // entered when starting the pipe write operation). + if (wait->task->state == TASK_UNINTERRUPTIBLE) { pr_debug("Space available, waking up writer %d.\n", wait->task->pid); return 1; } pr_debug("Writer %d not in the correct state for wake-up.\n", wait->task->pid); - } else { pr_debug("No space available, writer %d should continue waiting.\n", wait->task->pid); } @@ -566,15 +558,10 @@ static void pipe_wake_up_tasks(wait_queue_head_t *wait_queue, const char *debug_ list_for_each_safe_decl(it, store, &wait_queue->task_list) { wait_queue_entry_t *wait_queue_entry = list_entry(it, wait_queue_entry_t, task_list); + int target_pid = wait_queue_entry->task ? wait_queue_entry->task->pid : -1; - // Run the wakeup test function for the waiting task. - if (wait_queue_entry->func(wait_queue_entry, TASK_RUNNING, 0)) { - // Task is ready, remove from the wait queue. - remove_wait_queue(wait_queue, wait_queue_entry); - - // Log and free the memory associated with the wait entry. - pr_debug("%s: Process %d woken up.\n", debug_msg, wait_queue_entry->task->pid); - wait_queue_entry_dealloc(wait_queue_entry); + if (wake_up_wait_queue_entry(wait_queue, wait_queue_entry, TASK_RUNNING, 0)) { + pr_debug("%s: waking up process %d\n", debug_msg, target_pid); } } } diff --git a/kernel/src/hardware/timer.c b/kernel/src/hardware/timer.c index c9ac1a36..740565a8 100644 --- a/kernel/src/hardware/timer.c +++ b/kernel/src/hardware/timer.c @@ -4,10 +4,10 @@ /// See LICENSE.md for details. // Setup the logging for this file (do this before any other include). -#include "sys/kernel_levels.h" // Include kernel log levels. -#define __DEBUG_HEADER__ "[TIMER ]" ///< Change header. +#include "sys/kernel_levels.h" // Include kernel log levels. +#define __DEBUG_HEADER__ "[TIMER ]" ///< Change header. #define __DEBUG_LEVEL__ LOGLEVEL_NOTICE ///< Set log level. -#include "io/debug.h" // Include debugging functions. +#include "io/debug.h" // Include debugging functions. #include "assert.h" #include "descriptor_tables/isr.h" @@ -75,7 +75,11 @@ static __volatile__ unsigned long timer_ticks = 0; /// Contains timer for each CPU (for now only one) static tvec_base_t cpu_base = {0}; /// Contains all process waiting for a sleep. -static wait_queue_head_t sleep_queue; +static wait_queue_head_t sleep_queue = { + .name = "sleep_queue", + .lock = SPINLOCK_INIT, + .task_list = LIST_HEAD_INIT(sleep_queue.task_list), +}; void timer_phase(const uint32_t hz) { @@ -558,6 +562,50 @@ static inline void debug_timeout(unsigned long data) data, timer_ticks, timer_get_seconds()); } +/// @brief Cancels a sleep timer for a task being woken up by signals. +/// This handles the race where a signal interrupts a sleeping task: +/// the signal handler calls this to stop the sleep timer from firing. +/// +/// With boundary-based context switching, we don't need to trigger +/// immediate scheduling - the signal handler will return to the next +/// interrupt/exception boundary where scheduler_run() picks the next task. +/// +/// @param task The task whose sleep timer should be canceled. +/// @return 0 on success, -1 if no sleep timer exists. +int cancel_sleep_timer(struct task_struct *task) +{ + // Check if the task has a sleep timer. + if (!task || !task->sleep_timer) { + return -1; + } + + // Remove the timer from the timer system. + remove_timer(task->sleep_timer); + + // Extract the sleep data to prevent the timer callback from using stale pointers. + sleep_data_t *sleep_data = (sleep_data_t *)task->sleep_timer->data; + + // Mark the wait_queue_entry as NULL so if the timer fires during removal, + // sleep_timeout() will see NULL and skip waking (the signal already did). + if (sleep_data && sleep_data->wait_queue_entry) { + sleep_data->wait_queue_entry = NULL; + } + + // Free the timer. + __timer_list_dealloc(task->sleep_timer); + + // Clear the sleep_timer reference in the task. + task->sleep_timer = NULL; + + // Free the sleep data. + if (sleep_data) { + __sleep_data_dealloc(sleep_data); + } + + pr_debug("cancel_sleep_timer: canceled for process (pid: %d)\n", task->pid); + return 0; +} + /// @brief Callback for when a sleep timer expires. /// @param data Custom data stored in the timer. static inline void sleep_timeout(unsigned long data) @@ -566,16 +614,24 @@ static inline void sleep_timeout(unsigned long data) sleep_data_t *sleep_data = (sleep_data_t *)data; // Get the wait_queue_entry. wait_queue_entry_t *wait_queue_entry = sleep_data->wait_queue_entry; - // Executed entry's wakeup test function - if (wait_queue_entry->func(wait_queue_entry, TASK_RUNNING, 0) == 1) { - pr_debug("Process (pid: %d) restored from sleep\n", wait_queue_entry->task->pid); - // Removes entry from list and memory. - remove_wait_queue(&sleep_queue, wait_queue_entry); - // Free the memory of the wait queue item. - wait_queue_entry_dealloc(wait_queue_entry); - // Free the memory of the sleep_data. + // Check if entry is still valid (not already freed by signal). + if (!wait_queue_entry || !wait_queue_entry->task) { + pr_debug("sleep_timeout: wait_queue_entry is NULL (already woken by signal)\n"); __sleep_data_dealloc(sleep_data); + return; } + + task_struct *task = wait_queue_entry->task; + pr_debug("sleep_timeout: timer expired for process (pid: %d)\n", task->pid); + + // Clear the sleep_timer reference in the task before waking. + task->sleep_timer = NULL; + + // Delegate full wake mechanics to wait.c (wake check + remove + enqueue + free). + wake_up_wait_queue_entry(&sleep_queue, wait_queue_entry, TASK_RUNNING, 0); + + // Free sleep_data owned by the timer callback. + __sleep_data_dealloc(sleep_data); } /// @brief Function executed when the real_timer of a process expires, sends @@ -625,6 +681,13 @@ int sys_nanosleep(const struct timespec *req, struct timespec *rem) // We need to store rem somewhere, because it contains how much time left // until the timer expires, when the timer is stopped early by a signal. pr_debug("sys_nanosleep([s:%d; ns:%d],...)\n", req->tv_sec, req->tv_nsec); + // Get the current task. + task_struct *current = scheduler_get_current_process(); + assert(current && "No current process in sys_nanosleep"); + // Prevent a race between entering sleep state and arming the wake timer. + // If a timer interrupt preempts in that window, the task can become + // TASK_UNINTERRUPTIBLE without a wake source and block the system. + uint8_t irqs = irq_disable(); // Create a dinamic timer to wake up the process after some time struct timer_list *sleep_timer = __timer_list_alloc(); // First, we save the remaining time. Then, we remove the current process @@ -633,13 +696,17 @@ int sys_nanosleep(const struct timespec *req, struct timespec *rem) // req and rem pointers (?) sleep_data_t *sleep_data = __sleep_data_alloc(); sleep_data->remaining = rem; - sleep_data->wait_queue_entry = sleep_on(&sleep_queue); + sleep_data->wait_queue_entry = sleep_on_interruptible(&sleep_queue); // Setup the timer. sleep_timer->expires = timer_get_ticks() + __timespec_to_ticks(req); sleep_timer->function = &sleep_timeout; sleep_timer->data = (unsigned long)sleep_data; + // Store the timer in the task so signals can cancel it if needed. + current->sleep_timer = sleep_timer; // Add the timer. add_timer(sleep_timer); + // Sleep state + wait queue entry + timer arm are now atomically visible. + irq_enable(irqs); return 0; } diff --git a/kernel/src/io/proc_video.c b/kernel/src/io/proc_video.c index b02cf732..2e11c4e6 100644 --- a/kernel/src/io/proc_video.c +++ b/kernel/src/io/proc_video.c @@ -88,7 +88,11 @@ static ssize_t procv_read(vfs_file_t *file, char *buf, off_t offset, size_t nbyt // Check that it's a valid character. if (c < 0) { - return 0; // No valid character received. + if ((file->flags & O_NONBLOCK) == 0) { + /* blocking descriptor; sleep until a key arrives. */ + keyboard_wait(); + } + return -EAGAIN; } // Keep only the character, not the scancode. diff --git a/kernel/src/klib/assert.c b/kernel/src/klib/assert.c index 789a709c..98833f3b 100644 --- a/kernel/src/klib/assert.c +++ b/kernel/src/klib/assert.c @@ -16,10 +16,10 @@ void __assert_fail(const char *assertion, const char *file, const char *function, unsigned int line) { pr_emerg( - "\n=== ASSERTION FAILED ===\n" + "=== ASSERTION FAILED ===\n" "Assertion: %s\n" "Location : %s:%d\n" - "Function : %s\n\n", + "Function : %s\n", assertion, file, line, (function ? function : "Unknown function")); kernel_panic("Assertion failed."); } diff --git a/kernel/src/klib/rbtree.c b/kernel/src/klib/rbtree.c index 50fca44a..aed98545 100644 --- a/kernel/src/klib/rbtree.c +++ b/kernel/src/klib/rbtree.c @@ -500,7 +500,7 @@ int rbtree_tree_test(rbtree_t *tree, rbtree_node_t *root) rbtree_node_t *ln = root->link[0]; rbtree_node_t *rn = root->link[1]; - /* Consecutive red links */ + // Consecutive red links. if (rbtree_node_is_red(root)) { if (rbtree_node_is_red(ln) || rbtree_node_is_red(rn)) { pr_err("Red violation"); @@ -511,19 +511,19 @@ int rbtree_tree_test(rbtree_t *tree, rbtree_node_t *root) lh = rbtree_tree_test(tree, ln); rh = rbtree_tree_test(tree, rn); - /* Invalid binary search tree */ + // Invalid binary search tree. if ((ln != NULL && tree->cmp(tree, ln, root) >= 0) || (rn != NULL && tree->cmp(tree, rn, root) <= 0)) { pr_err("Binary tree violation"); return 0; } - /* Black height mismatch */ + // Black height mismatch. if (lh != 0 && rh != 0 && lh != rh) { pr_err("Black violation"); return 0; } - /* Only count black links */ + // Only count black links. if (lh != 0 && rh != 0) { return rbtree_node_is_red(root) ? lh : lh + 1; } diff --git a/kernel/src/klib/string.c b/kernel/src/klib/string.c index 7b2c3e59..bb2923bd 100644 --- a/kernel/src/klib/string.c +++ b/kernel/src/klib/string.c @@ -260,9 +260,7 @@ void *memmove(void *dst, const void *src, size_t n) void *ret = dst; if (dst <= src || (char *)dst >= ((char *)src + n)) { - /* Non-overlapping buffers; copy from lower addresses to higher - * addresses. - */ + // Non-overlapping buffers; copy from lower addresses to higher addresses. while (n--) { *(char *)dst = *(char *)src; dst = (char *)dst + 1; @@ -387,29 +385,23 @@ char *strtok_r(char *str, const char *delim, char **saveptr) map[*ctrl >> 3] |= (char)(1 << (*ctrl & 7)); } while (*ctrl++); - /* Initialize s. If str is NULL, set s to the saved - * pointer (i.e., continue breaking tokens out of the str - * from the last strtok call). - */ + // Initialize s. If str is NULL, set s to the saved pointer (i.e., continue breaking tokens out of the str from the + // last strtok call). if (str) { s = str; } else { s = *saveptr; } - /* Find beginning of token (skip over leading delimiters). Note that - * there is no token iff this loop sets s to point to the terminal - * null (*s == '\0'). - */ + // Find beginning of token (skip over leading delimiters). Note that there is no token iff this loop sets s to point + // to the terminal null (*s == '\0'). while ((map[*s >> 3] & (1 << (*s & 7))) && *s) { s++; } str = s; - /* Find the end of the token. If it is not the end of the str, - * put a null there. - */ + // Find the end of the token. If it is not the end of the str, put a null there. for (; *s; s++) { if (map[*s >> 3] & (1 << (*s & 7))) { *s++ = '\0'; @@ -428,19 +420,6 @@ char *strtok_r(char *str, const char *delim, char **saveptr) return str; } -// Intrinsic functions. - -/* - * #pragma function(memset) - * #pragma function(memcmp) - * #pragma function(memcpy) - * #pragma function(strcpy) - * #pragma function(strlen) - * #pragma function(strcat) - * #pragma function(strcmp) - * #pragma function(strset) - */ - void *memset(void *ptr, int value, size_t num) { // Turn the pointer into a char * pointer. Here, we use the volatile keyword @@ -609,9 +588,7 @@ char *trim(char *str) len = strlen(str); endp = str + len; - /* Move the front and back pointers to address the first non-whitespace - * characters from each end. - */ + // Move the front and back pointers to address the first non-whitespace characters from each end. while (isspace((unsigned char)*frontp)) { ++frontp; } @@ -624,10 +601,9 @@ char *trim(char *str) } else if (frontp != str && endp == frontp) { *str = '\0'; } - /* Shift the string so that it starts at str so that if it's dynamically - * allocated, we can still free it on the returned pointer. Note the reuse - * of endp to mean the front of the string buffer now. - */ + + // Shift the string so that it starts at str so that if it's dynamically allocated, we can still free it on the + // returned pointer. Note the reuse of endp to mean the front of the string buffer now. endp = str; if (frontp != str) { while (*frontp) { diff --git a/kernel/src/mem/mm/mm.c b/kernel/src/mem/mm/mm.c index 712b8026..a66f510d 100644 --- a/kernel/src/mem/mm/mm.c +++ b/kernel/src/mem/mm/mm.c @@ -114,9 +114,12 @@ mm_struct_t *mm_clone(mm_struct_t *mmp) // Copy the contents of the source mm_struct to the new one. memcpy(mm, mmp, sizeof(mm_struct_t)); - // Get the main page directory. + // We deliberately **do not** copy the parent's page directory verbatim because that would reproduce every + // user‑space mapping in the child. vm_area_clone()/mem_clone_vm_area below will populate the child's page tables + // explicitly, which gives us better control and avoids aliasing the same physical page tables between processes. + // + // Instead we copy the *kernel* portion of the main page directory and leave the user half empty. page_directory_t *main_pgd = paging_get_main_pgd(); - // Error handling: Failed to get the main page directory. if (!main_pgd) { pr_crit("Failed to get the main page directory\n"); return NULL; diff --git a/kernel/src/process/process.c b/kernel/src/process/process.c index 78edfcff..81e05836 100644 --- a/kernel/src/process/process.c +++ b/kernel/src/process/process.c @@ -703,5 +703,6 @@ int sys_execve(pt_regs_t *f) scheduler_restore_context(current, f); pr_debug("Executing '%s' (pid: %d)...\n", current->name, current->pid); + return 0; } diff --git a/kernel/src/process/scheduler.c b/kernel/src/process/scheduler.c index d8f64036..ccd82bd3 100644 --- a/kernel/src/process/scheduler.c +++ b/kernel/src/process/scheduler.c @@ -4,10 +4,10 @@ /// See LICENSE.md for details. // Setup the logging for this file (do this before any other include). -#include "sys/kernel_levels.h" // Include kernel log levels. -#define __DEBUG_HEADER__ "[SCHED ]" ///< Change header. +#include "sys/kernel_levels.h" // Include kernel log levels. +#define __DEBUG_HEADER__ "[SCHED ]" ///< Change header. #define __DEBUG_LEVEL__ LOGLEVEL_NOTICE ///< Set log level. -#include "io/debug.h" // Include debugging functions. +#include "io/debug.h" // Include debugging functions. #include "assert.h" #include "descriptor_tables/tss.h" @@ -34,6 +34,22 @@ runqueue_t runqueue; // Definition of the global init process pointer task_struct *init_process = NULL; +/// Wait queue for processes blocked in waitpid(). +static wait_queue_head_t waitpid_queue = { + .name = "waitpid_queue", + .lock = SPINLOCK_INIT, + .task_list = LIST_HEAD_INIT(waitpid_queue.task_list), +}; + +void __dump_runqueue(int log_level) +{ + pr_log(log_level, "Dumping runqueue (num_active: %zu, num_periodic: %zu):\n", runqueue.num_active, runqueue.num_periodic); + list_for_each_decl (it, &runqueue.queue) { + task_struct *entry = list_entry(it, task_struct, run_list); + pr_log(log_level, " PID %d (%s) - state: %ld, vruntime: %lu\n", entry->pid, entry->name, entry->state, entry->se.vruntime); + } +} + void scheduler_initialize(void) { // Initialize the runqueue list of tasks. @@ -44,6 +60,8 @@ void scheduler_initialize(void) runqueue.curr = NULL; // Reset the number of active tasks. runqueue.num_active = 0; + // Initialize the waitpid wait queue. + wait_queue_head_init(&waitpid_queue); } task_struct *scheduler_get_current_process(void) { return runqueue.curr; } @@ -89,6 +107,13 @@ task_struct *scheduler_get_running_process(pid_t pid) void scheduler_enqueue_task(task_struct *process) { assert(process && "Received a NULL process."); + + // If the process is already in a list, raise an error. + if (!list_head_empty(&process->run_list)) { + pr_err("scheduler_enqueue_task: Process %d is already in a list.\n", process->pid); + __dump_runqueue(LOGLEVEL_ERR); + return; + } // If current_process is NULL, then process is the current process. if (runqueue.curr == NULL) { runqueue.curr = process; @@ -106,6 +131,13 @@ void scheduler_enqueue_task(task_struct *process) void scheduler_dequeue_task(task_struct *process) { assert(process && "Received a NULL process."); + + // If the process is not in a list, raise an error. + if (list_head_empty(&process->run_list)) { + pr_err("scheduler_dequeue_task: Process %d is not in a list.\n", process->pid); + __dump_runqueue(LOGLEVEL_ERR); + return; + } // Delete the process from the list of running processes. list_head_remove(&process->run_list); // Decrement the number of active processes. @@ -113,12 +145,56 @@ void scheduler_dequeue_task(task_struct *process) if (process->se.is_periodic) { runqueue.num_periodic--; } - #ifdef ENABLE_SCHEDULER_FEEDBACK scheduler_feedback_task_remove(process->pid); #endif } +int wake_up_process(task_struct *task) +{ + /// Wakes up a blocked or stopped task by transitioning it to TASK_RUNNING. + /// + /// Architecture: Tasks remain on the runqueue even when blocked. The + /// scheduler naturally skips non-TASK_RUNNING tasks when picking next. + /// This function only manages task state transitions, NOT queue membership. + /// + /// The wait queue layer (wait.c) is responsible for: + /// - Removing task from wait queue before calling this + /// - Freeing wait queue entry after this returns + /// + /// With boundary-based scheduling, the task becomes eligible at the + /// next interrupt/exception boundary when scheduler_run() executes. + /// + /// @param task The task to wake up (must be on runqueue). + /// @return 0 on success, -1 if task was already TASK_RUNNING. + + if (!task) { + pr_err("wake_up_process: task is NULL\n"); + return -1; + } + + // If already running, nothing to do. + if (task->state == TASK_RUNNING) { + pr_debug("wake_up_process: task %d is already TASK_RUNNING\n", task->pid); + return -1; + } + + // Transition task to runnable state. + task->state = TASK_RUNNING; + + // Clear wait queue tracking. + task->waiting_on = NULL; + + // Safety check: task should already be on runqueue. Only enqueue if + // somehow it was removed (e.g., during process creation). + if (list_head_empty(&task->run_list)) { + pr_warning("wake_up_process: task %d not on runqueue, re-enqueueing\n", task->pid); + scheduler_enqueue_task(task); + } + + return 0; +} + void scheduler_run(pt_regs_t *f) { // Check if there is a running process. @@ -134,41 +210,96 @@ void scheduler_run(pt_regs_t *f) // We check the existence of pending signals every time we finish // handling an interrupt or an exception. if (!do_signal(f)) { -#if 1 + if (runqueue.curr->state == EXIT_ZOMBIE) { - //==== Handle Zombies ================================================= - //pr_debug("Handle zombie %d\n", runqueue.curr->pid); - // get the next process after the current one - list_head_t *nNode = runqueue.curr->run_list.next; - // check if we reached the head of list_head_t - if (nNode == &runqueue.queue) { - nNode = nNode->next; - } - // get the task_struct - next = list_entry(nNode, task_struct, run_list); - // Remove the zombie task. + + pr_debug("SCHEDULER_RUN: Current task %d is a zombie. Removing it and picking next task.\n", runqueue.curr->pid); + + // Remove the zombie task first, so it cannot be selected again. scheduler_dequeue_task(runqueue.curr); - assert(next && "No valid task selected after removing ZOMBIE."); - //===================================================================== - } else { -#endif - //==== Scheduling ===================================================== - // If we are currently executing a periodic process, and this process - // has yet to complete, keep executing it. + } + + // If we are currently executing a periodic process, and this process + // has yet to complete, keep executing it. #ifdef SCHEDULER_EDF - if (runqueue.curr->se.is_periodic) - if (!runqueue.curr->se.executed) - return; + if (runqueue.curr->se.is_periodic) + if (!runqueue.curr->se.executed) + return; #endif - // Pointer to the next process to be executed. - next = scheduler_pick_next_task(&runqueue); - //===================================================================== + // Pointer to the next process to be executed. + next = scheduler_pick_next_task(&runqueue); + + // If no runnable task was found we may be in a situation where every user process is blocked. rather than + // assert or return to the blocked task we idle the cpu until some other task is enqueued; the + // scheduler_pick_next_task call above will have already re-enabled the cpu if necessary. + if (!next) { + if (runqueue.num_active == 0) { + + pr_debug("SCHEDULER_RUN: No active tasks in the runqueue.\n"); + + while (runqueue.num_active == 0) { + sti(); + __asm__ __volatile__("hlt"); + cli(); + } + + next = scheduler_pick_next_task(&runqueue); + + pr_debug("SCHEDULER_RUN: No active tasks, idling until a task is enqueued. Next task: %d\n", next ? next->pid : -1); + } + if (!next) { + + pr_debug("SCHEDULER_RUN: No runnable tasks found in the runqueue.\n"); + + // Resume current only if it is still runnable and queued. + if ((runqueue.curr->state == TASK_RUNNING) && !list_head_empty(&runqueue.curr->run_list)) { + next = runqueue.curr; + } else { + // Last-resort attempt to find a runnable queued task. + list_for_each_decl (it, &runqueue.queue) { + task_struct *entry = list_entry(it, task_struct, run_list); + if (entry->state == TASK_RUNNING) { + next = entry; + pr_debug("SCHEDULER_RUN: No runnable tasks found, but found queued task %d in state TASK_RUNNING. Resuming it.\n", entry->pid); + break; + } + } + } + + // If there is still no candidate, wait for an event and + // retry rather than panicking or returning to an invalid + // context. + while (!next) { + sti(); + __asm__ __volatile__("hlt"); + cli(); + next = scheduler_pick_next_task(&runqueue); + } + pr_debug("SCHEDULER_RUN: No runnable tasks, idling until a task is enqueued. Next task: %d\n", next ? next->pid : -1); + } } + // Check if the next and current processes are different. if (next != runqueue.curr) { + pr_debug("SCHEDULER_RUN: Picked next task %d to run.\n", next->pid); // Copy into Kernel stack the next process's context. scheduler_restore_context(next, f); } + } else { + // Signal handling may have changed task state (e.g., stop/exit). + // If current is no longer runnable or queued, pick another task now. + if ((runqueue.curr->state != TASK_RUNNING) || list_head_empty(&runqueue.curr->run_list)) { + next = scheduler_pick_next_task(&runqueue); + while (!next) { + sti(); + __asm__ __volatile__("hlt"); + cli(); + next = scheduler_pick_next_task(&runqueue); + } + if (next != runqueue.curr) { + scheduler_restore_context(next, f); + } + } } //========================================================================== } @@ -350,7 +481,7 @@ int sys_setpgid(pid_t pid, pid_t pgid) // Set the new process group ID. task->pgid = pgid; - pr_debug("Process %d assigned to process group %d.", task->pid, pgid); + pr_debug("Process %d assigned to process group %d.\n", task->pid, pgid); return 0; } @@ -565,17 +696,44 @@ pid_t sys_waitpid(pid_t pid, int *status, int options) pid_manager_mark_free(child->pid); // Free the PID. vfs_destroy_task(child); // Finalize VFS structures. list_head_remove(&child->sibling); // Remove from parent's child list. - scheduler_dequeue_task(child); // Remove from the scheduler. - kmem_cache_free(child); // Free the `task_struct`. - pr_debug("Process %d cleaned up child process %d.\n", runqueue.curr->pid, child_pid); + // Zombie tasks are typically dequeued in scheduler_run() when they + // become current. During waitpid() reap they may already be out of + // the runqueue, so only dequeue if still linked. + if (!list_head_empty(&child->run_list)) { + scheduler_dequeue_task(child); + } + + kmem_cache_free(child); // Free the `task_struct`. + + pr_debug("Process %d (%s) CLEANED UP process %d.\n", runqueue.curr->pid, runqueue.curr->name, child_pid); // Return the PID of the cleaned-up child. return child_pid; } // No eligible child process was found. - return 0; + // If WNOHANG is set, return immediately. + if (options & WNOHANG) { + return 0; + } + + // Otherwise, block until a child exits (and sends SIGCHLD to wake us up). + // Task will remain on runqueue but in TASK_UNINTERRUPTIBLE state. + // Context switch will happen when this syscall returns and syscall_handler calls scheduler_run(f). + // When woken up (by wake_up_all in do_exit), task state transitions to TASK_RUNNING, + // and eventually scheduler picks it again, returning to userspace with -EINTR. + // Userspace must retry the syscall, which will then find and reap the zombie. + pr_debug("Process %d (%s) sleeping in waitpid (no zombie child yet)\n", runqueue.curr->pid, runqueue.curr->name); + wait_queue_entry_t *wait_entry = sleep_on(&waitpid_queue); + if (!wait_entry) { + pr_err("Failed to sleep in waitpid\n"); + return -ENOMEM; + } + + // Return -EINTR to indicate the syscall was interrupted. + // The userspace waitpid() wrapper should retry the syscall on -EINTR. + return -EINTR; } void do_exit(int exit_code) @@ -588,6 +746,8 @@ void do_exit(int exit_code) kernel_panic("Init process cannot call sys_exit!"); } + pr_debug("Process %d is exiting with code %d.\n", runqueue.curr->pid, exit_code); + // Set the termination code of the process. runqueue.curr->exit_code = exit_code; // Set the state of the process to zombie. @@ -599,6 +759,9 @@ void do_exit(int exit_code) pr_err( "[%d] %5d failed sending signal %d : %s\n", ret, runqueue.curr->parent->pid, SIGCHLD, strerror(errno)); } + // Wake up the parent if it's sleeping in waitpid(). + // Only wake the parent, not all processes waiting on this queue. + wake_up_process_on_queue(&waitpid_queue, runqueue.curr->parent); } // If it has children, then init process has to take care of them. diff --git a/kernel/src/process/scheduler_algorithm.c b/kernel/src/process/scheduler_algorithm.c index 6b2e7c2d..0c6e3406 100644 --- a/kernel/src/process/scheduler_algorithm.c +++ b/kernel/src/process/scheduler_algorithm.c @@ -39,30 +39,32 @@ static inline bool_t __is_periodic_task(task_struct *task) /// @return the next task on success, NULL on failure. static inline task_struct *__scheduler_rr(runqueue_t *runqueue, bool_t skip_periodic) { - // If there is just one task, return it; no need to do anything. - if (list_head_size(&runqueue->curr->run_list) <= 1) { - return runqueue->curr; - } - // This will hold a given entry, while iterating the list of tasks. - task_struct *entry = NULL; - // Search for the next task (we do not start from the head, so INSIDE, skip the head). - list_for_each_decl (it, &runqueue->curr->run_list) { - // Check if we reached the head of list_head, and skip it. - if (it == &runqueue->queue) { - continue; - } - // Get the current entry. - entry = list_entry(it, task_struct, run_list); - // We consider only runnable processes. - if (entry->state != TASK_RUNNING) { - continue; + // If the current task is still enqueued, start searching from its next node + // to preserve round-robin fairness. Otherwise, start from the runqueue head. + list_head_t *start = list_head_empty(&runqueue->curr->run_list) + ? runqueue->queue.next + : runqueue->curr->run_list.next; + list_head_t *it = start; + + // Iterate at most one full runqueue cycle. + while (it != &runqueue->queue) { + task_struct *entry = list_entry(it, task_struct, run_list); + if ((entry->state == TASK_RUNNING) && + !(__is_periodic_task(entry) && skip_periodic)) { + return entry; } - // If entry is a periodic task, and we were asked to skip periodic tasks, skip it. - if (__is_periodic_task(entry) && skip_periodic) { - continue; + it = it->next; + } + + // Wrap once if we started from current->next and did not reach start yet. + it = runqueue->queue.next; + while ((it != &runqueue->queue) && (it != start)) { + task_struct *entry = list_entry(it, task_struct, run_list); + if ((entry->state == TASK_RUNNING) && + !(__is_periodic_task(entry) && skip_periodic)) { + return entry; } - // We have our next entry. - return entry; + it = it->next; } return NULL; @@ -360,8 +362,11 @@ static inline task_struct *__scheduler_rm(runqueue_t *runqueue) task_struct *scheduler_pick_next_task(runqueue_t *runqueue) { - // Update task statistics. - __update_task_statistics(runqueue->curr); + // Update statistics only for a runnable task still present in the runqueue. + if (runqueue->curr && (runqueue->curr->state == TASK_RUNNING) && + !list_head_empty(&runqueue->curr->run_list)) { + __update_task_statistics(runqueue->curr); + } // Pointer to the next task to schedule. task_struct *next = NULL; @@ -381,17 +386,17 @@ task_struct *scheduler_pick_next_task(runqueue_t *runqueue) #error "You should enable a scheduling algorithm!" #endif - assert(next && "No valid task selected by the scheduling algorithm."); - - // Update the last context switch time of the next task. - next->se.exec_start = timer_get_ticks(); - + if (next != NULL) { + // Update the last context switch time of the next task. + next->se.exec_start = timer_get_ticks(); #ifdef ENABLE_SCHEDULER_FEEDBACK - // Update the feedback statistics for the new scheduled task. - scheduler_feedback_task_update(next); - // Update the overall feedback system. - scheduler_feedback_update(); + // Update the feedback statistics for the new scheduled task. + scheduler_feedback_task_update(next); + // Update the overall feedback system. + scheduler_feedback_update(); #endif + } + return next; } diff --git a/kernel/src/process/wait.c b/kernel/src/process/wait.c index 92fef203..63d81def 100644 --- a/kernel/src/process/wait.c +++ b/kernel/src/process/wait.c @@ -4,17 +4,19 @@ /// See LICENSE.md for details. // Setup the logging for this file (do this before any other include). -#include "sys/kernel_levels.h" // Include kernel log levels. -#define __DEBUG_HEADER__ "[WAIT ]" ///< Change header. +#include "sys/kernel_levels.h" // Include kernel log levels. +#define __DEBUG_HEADER__ "[WAIT ]" ///< Change header. #define __DEBUG_LEVEL__ LOGLEVEL_NOTICE ///< Set log level. -#include "io/debug.h" // Include debugging functions. +#include "io/debug.h" // Include debugging functions. #include "process/wait.h" #include "assert.h" +#include "klib/irqflags.h" #include "mem/alloc/slab.h" #include "process/scheduler.h" #include "string.h" +#include /// @brief Adds the entry to the wait queue. /// @param head the wait queue. @@ -52,6 +54,14 @@ static inline void __remove_wait_queue(wait_queue_head_t *head, wait_queue_entry int default_wake_function(wait_queue_entry_t *entry, unsigned mode, int sync) { + /// Default wake predicate for wait queue entries. + /// Returns 1 if the task is in a sleep state and should be woken, + /// 0 if it should remain waiting. + /// + /// Wake functions are policy: they decide whether a task's wait + /// condition is satisfied. The wait.c layer handles mechanics: + /// removing from queue, calling wake_up_process(), freeing entry. + // Validate the input. if (!entry) { pr_err("Variable entry is NULL.\n"); @@ -61,18 +71,16 @@ int default_wake_function(wait_queue_entry_t *entry, unsigned mode, int sync) pr_err("Variable entry->task is NULL.\n"); return 0; } - // Only wake up tasks in TASK_INTERRUPTIBLE or TASK_UNINTERRUPTIBLE states. - if ((entry->task->state == TASK_INTERRUPTIBLE) || (entry->task->state == TASK_UNINTERRUPTIBLE)) { - // Set the task state to the specified mode. - entry->task->state = mode; - - // Optionally handle sync-specific operations here if needed. - // For now, sync is unused. + // Wake if task is in a sleep state (interruptible or uninterruptible). + if ((entry->task->state == TASK_INTERRUPTIBLE) || + (entry->task->state == TASK_UNINTERRUPTIBLE)) { + pr_debug("Task %d (%s) wake condition met (state: %d)\n", + entry->task->pid, entry->task->name, entry->task->state); return 1; } - // Task is not in a wakeable state. + // Task is not in a wakeable state (already running, stopped, or zombie). return 0; } @@ -94,7 +102,7 @@ wait_queue_entry_t *wait_queue_entry_alloc(void) { // Allocate the memory. wait_queue_entry_t *entry = (wait_queue_entry_t *)kmalloc(sizeof(wait_queue_entry_t)); - pr_debug("ALLOCATE wait_queue_entry_t 0x%p\n", entry); + // pr_debug("ALLOCATE wait_queue_entry_t %p\n", entry); // Check the allocated memory. assert(entry && "Failed to allocate memory for a wait_queue_entry_t."); // Clean the memory. @@ -112,11 +120,101 @@ wait_queue_entry_t *wait_queue_entry_alloc(void) void wait_queue_entry_dealloc(wait_queue_entry_t *entry) { assert(entry && "Received a NULL pointer."); - pr_debug("FREE wait_queue_entry_t 0x%p\n", entry); + // pr_debug("FREE wait_queue_entry_t %p\n", entry); // Deallocate the memory. kfree(entry); } +int wake_up_wait_queue_entry(wait_queue_head_t *head, wait_queue_entry_t *entry, unsigned mode, int sync) +{ + /// Core wait queue wake primitive. Evaluates wake condition via entry's + /// wake function, and if satisfied, performs the full wake sequence: + /// 1. Remove entry from wait queue (wait layer responsibility) + /// 2. Call wake_up_process() to transition task to TASK_RUNNING + /// 3. Free wait queue entry (wait layer responsibility) + /// + /// This is the ONLY function that should perform this sequence. + /// Subsystems (pipes, timers) delegate full wake mechanics here. + + if (!head) { + pr_err("wake_up_wait_queue_entry: head is NULL\n"); + return 0; + } + if (!entry) { + pr_err("wake_up_wait_queue_entry: entry is NULL\n"); + return 0; + } + if (!entry->task) { + pr_err("wake_up_wait_queue_entry: entry->task is NULL\n"); + return 0; + } + + // Evaluate wake condition via function pointer (policy decision). + int should_wake = 0; + if (entry->func) { + should_wake = entry->func(entry, mode, sync); + } else { + should_wake = default_wake_function(entry, mode, sync); + } + + // Also wake if task is already TASK_RUNNING (race or redundant wake). + if (!should_wake && (entry->task->state != TASK_RUNNING)) { + return 0; + } + + pr_debug("Process %d (%s) WOKEN UP from %s\n", + entry->task->pid, entry->task->name, head->name); + + // Perform wake sequence: remove, wake, free (wait layer mechanics). + remove_wait_queue(head, entry); + wake_up_process(entry->task); + wait_queue_entry_dealloc(entry); + return 1; +} + +void wake_up_all(wait_queue_head_t *head) +{ + if (!head) { + pr_err("wake_up_all: head is NULL\n"); + return; + } + + // Iterate through the queue and wake each task. + // Each subsystem (pipes, signals, timers) owns its wait queue entries. + // This function: removes entries, calls wake_up_process() to handle scheduling. + list_for_each_safe_decl(it, store, &head->task_list) + { + wait_queue_entry_t *entry = list_entry(it, wait_queue_entry_t, task_list); + wake_up_wait_queue_entry(head, entry, TASK_RUNNING, 0); + } +} + +int wake_up_process_on_queue(wait_queue_head_t *head, struct task_struct *task) +{ + if (!head) { + pr_err("wake_up_process_on_queue: head is NULL\n"); + return 0; + } + if (!task) { + pr_err("wake_up_process_on_queue: task is NULL\n"); + return 0; + } + + // Search for the specific task in the wait queue. + list_for_each_safe_decl(it, store, &head->task_list) + { + wait_queue_entry_t *entry = list_entry(it, wait_queue_entry_t, task_list); + + // Check if this entry corresponds to our target task. + if (entry->task == task) { + return wake_up_wait_queue_entry(head, entry, TASK_RUNNING, 0); + } + } + + // Task was not found in this wait queue + return 0; +} + void wait_queue_entry_init(wait_queue_entry_t *entry, struct task_struct *task) { // Validate the input. @@ -166,9 +264,11 @@ void remove_wait_queue(wait_queue_head_t *head, wait_queue_entry_t *entry) spinlock_lock(&head->lock); __remove_wait_queue(head, entry); spinlock_unlock(&head->lock); + + pr_debug("Removed process %d (%s) from wait queue %s (state: %d)\n", entry->task->pid, entry->task->name, head->name, entry->task->state); } -wait_queue_entry_t *sleep_on(wait_queue_head_t *head) +static wait_queue_entry_t *__sleep_on_state(wait_queue_head_t *head, int state) { // Validate input parameters. if (!head) { @@ -176,6 +276,12 @@ wait_queue_entry_t *sleep_on(wait_queue_head_t *head) return NULL; } + // We want to avoid a race where an interrupt arrives between setting the task state to TASK_UNINTERRUPTIBLE and the + // caller adding the entry to the queue. If the wakeup occurs in that window the notification is lost and the task + // could sleep forever. The simple way to prevent this is to disable interrupts while changing the state and + // inserting the entry; IRQs are restored before returning so normal operation resumes. + uint8_t irqs = irq_disable(); + // Retrieve the current process/task. task_struct *sleeping_task = scheduler_get_current_process(); if (!sleeping_task) { @@ -183,23 +289,40 @@ wait_queue_entry_t *sleep_on(wait_queue_head_t *head) return NULL; } - // Set the task state to uninterruptible to indicate it is sleeping. - sleeping_task->state = TASK_UNINTERRUPTIBLE; - - // Allocate memory for a new wait queue entry. wait_queue_entry_t *entry = wait_queue_entry_alloc(); if (!entry) { pr_err("Failed to allocate memory for wait queue entry.\n"); + irq_enable(irqs); return NULL; } + // Set the task state to indicate it is sleeping. + // Task remains on runqueue - scheduler will skip it when picking next task. + sleeping_task->state = state; + + // Track which wait queue this task is sleeping on. + sleeping_task->waiting_on = head; + // Initialize the wait queue entry with the current task. wait_queue_entry_init(entry, sleeping_task); // Add the wait queue entry to the specified wait queue. add_wait_queue(head, entry); - pr_debug("Added process %d to the wait queue.\n", sleeping_task->pid); + pr_debug("Process %d (%s) SLEEPS ON %s (state: %d, stays on runqueue)\n", sleeping_task->pid, sleeping_task->name, head->name, sleeping_task->state); + + // Restore interrupts before returning. + irq_enable(irqs); return entry; } + +wait_queue_entry_t *sleep_on(wait_queue_head_t *head) +{ + return __sleep_on_state(head, TASK_UNINTERRUPTIBLE); +} + +wait_queue_entry_t *sleep_on_interruptible(wait_queue_head_t *head) +{ + return __sleep_on_state(head, TASK_INTERRUPTIBLE); +} diff --git a/kernel/src/system/panic.c b/kernel/src/system/panic.c index abb32305..4d738cba 100644 --- a/kernel/src/system/panic.c +++ b/kernel/src/system/panic.c @@ -13,8 +13,7 @@ extern int runtests; void kernel_panic(const char *msg) { - pr_emerg("\nPANIC:\n%s\n\nWelcome to Kernel Debugging Land...\n\n", msg); - pr_emerg("\n"); + pr_emerg("\nPANIC:\n%s\n\nWelcome to Kernel Debugging Land...\n", msg); __asm__ __volatile__("cli"); // Disable interrupts if (runtests) { outports(SHUTDOWN_PORT, 0x2000); diff --git a/kernel/src/system/signal.c b/kernel/src/system/signal.c index 372b91f4..979a7bfc 100644 --- a/kernel/src/system/signal.c +++ b/kernel/src/system/signal.c @@ -13,6 +13,7 @@ #include "assert.h" #include "errno.h" +#include "hardware/timer.h" #include "klib/irqflags.h" #include "klib/stack_helper.h" #include "process/process.h" @@ -26,9 +27,6 @@ /// SLAB caches for signal bits. static kmem_cache_t *sigqueue_cachep; -/// Contains all stopped process waiting for a continue signal -static wait_queue_head_t stopped_queue; - /// @brief The list of signal names. static const char *sys_siglist[] = { "HUP", @@ -198,6 +196,28 @@ static int __send_signal(int sig, siginfo_t *info, struct task_struct *t) pr_debug( "Added pending signal (%2d:%s) to task (%2d:%s), pending `%d, %d`.\n", sig, strsignal(sig), t->pid, t->name, t->pending.signal.sig[0], t->pending.signal.sig[1]); + + // If the task is in an interruptible sleep, wake it so the signal can be + // processed at the next return-to-user boundary. + if ((t->state == TASK_INTERRUPTIBLE) && (t->waiting_on != NULL)) { + // If task is sleeping in nanosleep, cancel its timer before waking. + if (t->sleep_timer != NULL) { + cancel_sleep_timer(t); + } + + if (!wake_up_process_on_queue(t->waiting_on, t)) { + wake_up_process(t); + } + } + + // SIGKILL cannot be caught or ignored, so stopped tasks must be woken + // to process it. Note that SIGCONT already wakes stopped tasks via + // handle_stop_signal() which is called before __send_signal(). + if ((sig == SIGKILL) && (t->state == TASK_STOPPED)) { + pr_debug("SIGKILL: waking stopped process %d (%s) so it can be killed\n", t->pid, t->name); + t->state = TASK_RUNNING; + } + __unlock_task_sighand(t); return 0; } @@ -428,37 +448,6 @@ static void __rm_from_queue(sigset_t *mask, sigpending_t *q) } } -/// @brief Wakes up a task that is waiting for a signal. -/// @param entry the entry to wake up. -/// @param mode the mode to set. -/// @param sync the sync flag. -/// @return 1 on success, 0 on failure. -int stop_wake_function(wait_queue_entry_t *entry, unsigned mode, int sync) -{ - // Validate the input. - if (!entry) { - pr_err("Variable entry is NULL.\n"); - return 0; - } - if (!entry->task) { - pr_err("Variable entry->task is NULL.\n"); - return 0; - } - // Only wake up tasks in TASK_STOPPED state. - if (entry->task->state == TASK_STOPPED) { - // Set the task state to the specified mode. - entry->task->state = mode; - - // Optionally handle sync-specific operations here if needed. - // For now, sync is unused. - - return 1; - } - - // Task is not in a wakeable state. - return 0; -} - /// @brief We do not consider group stopping because for now we don't have thread groups. /// @param current the current process. /// @param f the stack frame. @@ -474,13 +463,11 @@ static void __do_signal_stop(struct task_struct *current, struct pt_regs *f, int } } - // The state is now TASK_UNINTERRUPTABLE - wait_queue_entry_t *entry = sleep_on(&stopped_queue); - entry->task->state = TASK_STOPPED; - entry->task->exit_code = signr; - entry->func = stop_wake_function; + // Mark task as stopped (stays on runqueue, scheduler will skip it). + current->state = TASK_STOPPED; + current->exit_code = signr; - // Call the scheduler. + // Call the scheduler to pick next runnable task. scheduler_run(f); } @@ -640,8 +627,6 @@ int signals_init(void) pr_emerg("Failed to allocate cache for signals.\n"); return 0; } - // Initialize wait queue. - wait_queue_head_init(&stopped_queue); return 1; } @@ -679,24 +664,11 @@ void handle_stop_signal(int sig, siginfo_t *info, struct task_struct *p) __rm_from_queue(&mask, &p->pending); - list_for_each_safe_decl(it, store, &stopped_queue.task_list) - { - wait_queue_entry_t *entry = list_entry(it, wait_queue_entry_t, task_list); - - // Select only the waiting entry for the timer task pid. - if (entry->task->pid == p->pid) { - // Executed entry's wakeup test function - if (entry->func(entry, TASK_RUNNING, 0)) { - // Removes entry from list and memory - remove_wait_queue(&stopped_queue, entry); - // Free its memory. - wait_queue_entry_dealloc(entry); - pr_debug("Restored process (%d) from stop.\n", p->pid); - } else { - pr_err("Failed to restore process (%d) from stop.\n", p->pid); - } - break; - } + // If process is stopped, transition it back to running. + // Stopped tasks stay on runqueue but scheduler skips them. + if (p->state == TASK_STOPPED) { + pr_debug("SIGCONT: resuming stopped process %d\n", p->pid); + p->state = TASK_RUNNING; } } } @@ -764,7 +736,9 @@ int sys_kill(pid_t pid, int sig) info.si_addr = NULL; info.si_status = 0; info.si_band = 0; - return __send_sig_info(sig, &info, process); + int ret = __send_sig_info(sig, &info, process); + + return ret; } sighandler_t sys_signal(int signum, sighandler_t handler, uint32_t sigreturn_addr) diff --git a/lib/inc/list_head.h b/lib/inc/list_head.h index af8012b0..3a5d0d29 100644 --- a/lib/inc/list_head.h +++ b/lib/inc/list_head.h @@ -14,6 +14,11 @@ typedef struct list_head { struct list_head *next; ///< The subsequent element. } list_head_t; +#define LIST_HEAD_INIT(name) \ + { \ + &(name), &(name) \ + } + /// @brief Get the struct for this entry. /// @param ptr The &list_head pointer. /// @param type The type of the struct this is embedded in. @@ -29,7 +34,7 @@ typedef struct list_head { /// @param pos the name of the iterator used to visit the list. /// @param store another list iterator to use as temporary storage. /// @param head the head for your list. -#define list_for_each_safe(pos, store, head) \ +#define list_for_each_safe(pos, store, head) \ for ((pos) = (head)->next, (store) = (pos)->next; (pos) != (head); (pos) = (store), (store) = (pos)->next) /// @brief Iterates over a list, but declares the iterator. @@ -41,8 +46,8 @@ typedef struct list_head { /// @param pos the name of the iterator used to visit the list. /// @param store another list iterator to use as temporary storage. /// @param head the head for your list. -#define list_for_each_safe_decl(pos, store, head) \ - for (list_head_t * (pos) = (head)->next, *(store) = (pos)->next; (pos) != (head); \ +#define list_for_each_safe_decl(pos, store, head) \ + for (list_head_t * (pos) = (head)->next, *(store) = (pos)->next; (pos) != (head); \ (pos) = (store), (store) = (pos)->next) /// @brief Iterates over a list backwards. @@ -53,7 +58,7 @@ typedef struct list_head { /// @brief Iterates over a list backwards, but declares the iterator. /// @param pos the name of the iterator used to visit the list. /// @param head the head for your list. -#define list_for_each_prev_decl(pos, head) \ +#define list_for_each_prev_decl(pos, head) \ for (list_head_t * (pos) = (head)->prev; (pos) != (head); (pos) = (pos)->prev) /// @brief Ensures that the given list is valid. diff --git a/lib/inc/ring_buffer.h b/lib/inc/ring_buffer.h index 0fc099d2..f14b42a7 100644 --- a/lib/inc/ring_buffer.h +++ b/lib/inc/ring_buffer.h @@ -72,356 +72,356 @@ #pragma once /// @brief Declares a fixed-size ring buffer. -#define DECLARE_FIXED_SIZE_RING_BUFFER(type, name, length, init) \ - typedef struct { \ - type buffer[length]; \ - unsigned size; \ - unsigned head; \ - unsigned tail; \ - unsigned count; \ - } rb_##name##_t; \ - \ - static inline void rb_##name##_init(rb_##name##_t *rb) \ - { \ - rb->head = 0; \ - rb->tail = 0; \ - rb->count = 0; \ - rb->size = length; \ - for (unsigned i = 0; i < (length); i++) { \ - rb->buffer[i] = init; \ - } \ - } \ - \ - static inline unsigned rb_##name##_is_empty(rb_##name##_t *rb) { return rb->count == 0; } \ - \ - static inline unsigned rb_##name##_is_full(rb_##name##_t *rb) { return rb->count == rb->size; } \ - \ - static inline void rb_##name##_push_back(rb_##name##_t *rb, type item) \ - { \ - rb->buffer[rb->head] = item; \ - if (rb_##name##_is_full(rb)) { \ - rb->tail = (rb->tail + 1) % rb->size; \ - } else { \ - rb->count++; \ - } \ - rb->head = (rb->head + 1) % rb->size; \ - } \ - \ - static inline void rb_##name##_push_front(rb_##name##_t *rb, type item) \ - { \ - if (rb_##name##_is_full(rb)) { \ - rb->head = (rb->head == 0) ? rb->size - 1 : rb->head - 1; \ - } else { \ - rb->count++; \ - } \ - rb->tail = (rb->tail == 0) ? rb->size - 1 : rb->tail - 1; \ - rb->buffer[rb->tail] = item; \ - } \ - \ - static inline type rb_##name##_pop_front(rb_##name##_t *rb) \ - { \ - type item = init; \ - if (!rb_##name##_is_empty(rb)) { \ - item = rb->buffer[rb->tail]; \ - rb->buffer[rb->tail] = init; \ - rb->tail = (rb->tail + 1) % rb->size; \ - rb->count--; \ - } \ - return item; \ - } \ - \ - static inline type rb_##name##_pop_back(rb_##name##_t *rb) \ - { \ - type item = init; \ - if (!rb_##name##_is_empty(rb)) { \ - rb->head = (rb->head == 0) ? rb->size - 1 : rb->head - 1; \ - item = rb->buffer[rb->head]; \ - rb->buffer[rb->head] = init; \ - rb->count--; \ - } \ - return item; \ - } \ - \ - static inline type rb_##name##_peek_front(rb_##name##_t *rb) \ - { \ - if (rb_##name##_is_empty(rb)) { \ - return init; \ - } \ - return rb->buffer[rb->tail]; \ - } \ - \ - static inline type rb_##name##_peek_back(rb_##name##_t *rb) \ - { \ - if (rb_##name##_is_empty(rb)) { \ - return init; \ - } \ - return rb->buffer[(rb->head == 0) ? rb->size - 1 : rb->head - 1]; \ - } \ - \ - static inline type rb_##name##_get(rb_##name##_t *rb, unsigned position) \ - { \ - type item = init; \ - if (!rb_##name##_is_empty(rb) && (position < rb->size)) { \ - item = rb->buffer[(rb->tail + position) % rb->size]; \ - } \ - return item; \ - } \ - \ - static inline void rb_##name##_iterate(rb_##name##_t *rb, void (*callback)(type)) \ - { \ - for (unsigned i = 0; i < rb->count; i++) { \ - callback(rb_##name##_get(rb, i)); \ - } \ +#define DECLARE_FIXED_SIZE_RING_BUFFER(type, name, length, init) \ + typedef struct { \ + type buffer[length]; \ + unsigned size; \ + unsigned head; \ + unsigned tail; \ + unsigned count; \ + } rb_##name##_t; \ + \ + static inline void rb_##name##_init(rb_##name##_t *rb) \ + { \ + rb->head = 0; \ + rb->tail = 0; \ + rb->count = 0; \ + rb->size = length; \ + for (unsigned i = 0; i < (length); i++) { \ + rb->buffer[i] = init; \ + } \ + } \ + \ + static inline unsigned rb_##name##_is_empty(rb_##name##_t *rb) { return rb->count == 0; } \ + \ + static inline unsigned rb_##name##_is_full(rb_##name##_t *rb) { return rb->count == rb->size; } \ + \ + static inline void rb_##name##_push_back(rb_##name##_t *rb, type item) \ + { \ + rb->buffer[rb->head] = item; \ + if (rb_##name##_is_full(rb)) { \ + rb->tail = (rb->tail + 1) % rb->size; \ + } else { \ + rb->count++; \ + } \ + rb->head = (rb->head + 1) % rb->size; \ + } \ + \ + static inline void rb_##name##_push_front(rb_##name##_t *rb, type item) \ + { \ + if (rb_##name##_is_full(rb)) { \ + rb->head = (rb->head == 0) ? rb->size - 1 : rb->head - 1; \ + } else { \ + rb->count++; \ + } \ + rb->tail = (rb->tail == 0) ? rb->size - 1 : rb->tail - 1; \ + rb->buffer[rb->tail] = item; \ + } \ + \ + static inline type rb_##name##_pop_front(rb_##name##_t *rb) \ + { \ + type item = init; \ + if (!rb_##name##_is_empty(rb)) { \ + item = rb->buffer[rb->tail]; \ + rb->buffer[rb->tail] = init; \ + rb->tail = (rb->tail + 1) % rb->size; \ + rb->count--; \ + } \ + return item; \ + } \ + \ + static inline type rb_##name##_pop_back(rb_##name##_t *rb) \ + { \ + type item = init; \ + if (!rb_##name##_is_empty(rb)) { \ + rb->head = (rb->head == 0) ? rb->size - 1 : rb->head - 1; \ + item = rb->buffer[rb->head]; \ + rb->buffer[rb->head] = init; \ + rb->count--; \ + } \ + return item; \ + } \ + \ + static inline type rb_##name##_peek_front(rb_##name##_t *rb) \ + { \ + if (rb_##name##_is_empty(rb)) { \ + return init; \ + } \ + return rb->buffer[rb->tail]; \ + } \ + \ + static inline type rb_##name##_peek_back(rb_##name##_t *rb) \ + { \ + if (rb_##name##_is_empty(rb)) { \ + return init; \ + } \ + return rb->buffer[(rb->head == 0) ? rb->size - 1 : rb->head - 1]; \ + } \ + \ + static inline type rb_##name##_get(rb_##name##_t *rb, unsigned position) \ + { \ + type item = init; \ + if (!rb_##name##_is_empty(rb) && (position < rb->size)) { \ + item = rb->buffer[(rb->tail + position) % rb->size]; \ + } \ + return item; \ + } \ + \ + static inline void rb_##name##_iterate(rb_##name##_t *rb, void (*callback)(type)) \ + { \ + for (unsigned i = 0; i < rb->count; i++) { \ + callback(rb_##name##_get(rb, i)); \ + } \ } /// @brief Declares a dynamic-size ring-buffer. -#define DECLARE_DYNAMIC_SIZE_RING_BUFFER(type, name, init) \ - typedef struct { \ - type *buffer; \ - unsigned size; \ - unsigned head; \ - unsigned tail; \ - unsigned count; \ - } rb_##name##_t; \ - \ - static inline int rb_##name##_init(rb_##name##_t *rb, unsigned length, void *(*alloc_func)(size_t)) \ - { \ - rb->buffer = (type *)alloc_func(length * sizeof(type)); \ - if (!rb->buffer) { \ - return -1; /* Memory allocation failed */ \ - } \ - rb->head = 0; \ - rb->tail = 0; \ - rb->count = 0; \ - rb->size = length; \ - for (unsigned i = 0; i < length; i++) { \ - rb->buffer[i] = init; \ - } \ - return 0; /* Success */ \ - } \ - \ - static inline void rb_##name##_free(rb_##name##_t *rb, void (*free_func)(void *)) \ - { \ - if (rb->buffer) { \ - free_func(rb->buffer); \ - } \ - rb->buffer = 0; \ - rb->size = 0; \ - rb->head = 0; \ - rb->tail = 0; \ - rb->count = 0; \ - } \ - \ - static inline unsigned rb_##name##_is_empty(rb_##name##_t *rb) { return rb->count == 0; } \ - \ - static inline unsigned rb_##name##_is_full(rb_##name##_t *rb) { return rb->count == rb->size; } \ - \ - static inline void rb_##name##_push_back(rb_##name##_t *rb, type item) \ - { \ - rb->buffer[rb->head] = item; \ - if (rb_##name##_is_full(rb)) { \ - rb->tail = (rb->tail + 1) % rb->size; \ - } else { \ - rb->count++; \ - } \ - rb->head = (rb->head + 1) % rb->size; \ - } \ - \ - static inline void rb_##name##_push_front(rb_##name##_t *rb, type item) \ - { \ - if (rb_##name##_is_full(rb)) { \ - rb->head = (rb->head == 0) ? rb->size - 1 : rb->head - 1; \ - } else { \ - rb->count++; \ - } \ - rb->tail = (rb->tail == 0) ? rb->size - 1 : rb->tail - 1; \ - rb->buffer[rb->tail] = item; \ - } \ - \ - static inline type rb_##name##_pop_front(rb_##name##_t *rb) \ - { \ - if (rb_##name##_is_empty(rb)) { \ - return init; \ - } \ - type item = rb->buffer[rb->tail]; \ - rb->buffer[rb->tail] = init; \ - rb->tail = (rb->tail + 1) % rb->size; \ - rb->count--; \ - return item; \ - } \ - \ - static inline type rb_##name##_pop_back(rb_##name##_t *rb) \ - { \ - if (rb_##name##_is_empty(rb)) { \ - return init; \ - } \ - rb->head = (rb->head == 0) ? rb->size - 1 : rb->head - 1; \ - type item = rb->buffer[rb->head]; \ - rb->buffer[rb->head] = init; \ - rb->count--; \ - return item; \ - } \ - \ - static inline type rb_##name##_peek_front(rb_##name##_t *rb) \ - { \ - if (rb_##name##_is_empty(rb)) { \ - return init; \ - } \ - return rb->buffer[rb->tail]; \ - } \ - \ - static inline type rb_##name##_peek_back(rb_##name##_t *rb) \ - { \ - if (rb_##name##_is_empty(rb)) { \ - return init; \ - } \ - return rb->buffer[(rb->head == 0) ? rb->size - 1 : rb->head - 1]; \ - } \ - \ - static inline type rb_##name##_get(rb_##name##_t *rb, unsigned position) \ - { \ - if (rb_##name##_is_empty(rb) || (position >= rb->size)) { \ - return init; \ - } \ - return rb->buffer[(rb->tail + position) % rb->size]; \ - } \ - \ - static inline void rb_##name##_iterate(rb_##name##_t *rb, void (*callback)(type)) \ - { \ - for (unsigned i = 0; i < rb->count; i++) { \ - callback(rb_##name##_get(rb, i)); \ - } \ +#define DECLARE_DYNAMIC_SIZE_RING_BUFFER(type, name, init) \ + typedef struct { \ + type *buffer; \ + unsigned size; \ + unsigned head; \ + unsigned tail; \ + unsigned count; \ + } rb_##name##_t; \ + \ + static inline int rb_##name##_init(rb_##name##_t *rb, unsigned length, void *(*alloc_func)(size_t)) \ + { \ + rb->buffer = (type *)alloc_func(length * sizeof(type)); \ + if (!rb->buffer) { \ + return -1; \ + } \ + rb->head = 0; \ + rb->tail = 0; \ + rb->count = 0; \ + rb->size = length; \ + for (unsigned i = 0; i < length; i++) { \ + rb->buffer[i] = init; \ + } \ + return 0; \ + } \ + \ + static inline void rb_##name##_free(rb_##name##_t *rb, void (*free_func)(void *)) \ + { \ + if (rb->buffer) { \ + free_func(rb->buffer); \ + } \ + rb->buffer = 0; \ + rb->size = 0; \ + rb->head = 0; \ + rb->tail = 0; \ + rb->count = 0; \ + } \ + \ + static inline unsigned rb_##name##_is_empty(rb_##name##_t *rb) { return rb->count == 0; } \ + \ + static inline unsigned rb_##name##_is_full(rb_##name##_t *rb) { return rb->count == rb->size; } \ + \ + static inline void rb_##name##_push_back(rb_##name##_t *rb, type item) \ + { \ + rb->buffer[rb->head] = item; \ + if (rb_##name##_is_full(rb)) { \ + rb->tail = (rb->tail + 1) % rb->size; \ + } else { \ + rb->count++; \ + } \ + rb->head = (rb->head + 1) % rb->size; \ + } \ + \ + static inline void rb_##name##_push_front(rb_##name##_t *rb, type item) \ + { \ + if (rb_##name##_is_full(rb)) { \ + rb->head = (rb->head == 0) ? rb->size - 1 : rb->head - 1; \ + } else { \ + rb->count++; \ + } \ + rb->tail = (rb->tail == 0) ? rb->size - 1 : rb->tail - 1; \ + rb->buffer[rb->tail] = item; \ + } \ + \ + static inline type rb_##name##_pop_front(rb_##name##_t *rb) \ + { \ + if (rb_##name##_is_empty(rb)) { \ + return init; \ + } \ + type item = rb->buffer[rb->tail]; \ + rb->buffer[rb->tail] = init; \ + rb->tail = (rb->tail + 1) % rb->size; \ + rb->count--; \ + return item; \ + } \ + \ + static inline type rb_##name##_pop_back(rb_##name##_t *rb) \ + { \ + if (rb_##name##_is_empty(rb)) { \ + return init; \ + } \ + rb->head = (rb->head == 0) ? rb->size - 1 : rb->head - 1; \ + type item = rb->buffer[rb->head]; \ + rb->buffer[rb->head] = init; \ + rb->count--; \ + return item; \ + } \ + \ + static inline type rb_##name##_peek_front(rb_##name##_t *rb) \ + { \ + if (rb_##name##_is_empty(rb)) { \ + return init; \ + } \ + return rb->buffer[rb->tail]; \ + } \ + \ + static inline type rb_##name##_peek_back(rb_##name##_t *rb) \ + { \ + if (rb_##name##_is_empty(rb)) { \ + return init; \ + } \ + return rb->buffer[(rb->head == 0) ? rb->size - 1 : rb->head - 1]; \ + } \ + \ + static inline type rb_##name##_get(rb_##name##_t *rb, unsigned position) \ + { \ + if (rb_##name##_is_empty(rb) || (position >= rb->size)) { \ + return init; \ + } \ + return rb->buffer[(rb->tail + position) % rb->size]; \ + } \ + \ + static inline void rb_##name##_iterate(rb_##name##_t *rb, void (*callback)(type)) \ + { \ + for (unsigned i = 0; i < rb->count; i++) { \ + callback(rb_##name##_get(rb, i)); \ + } \ } /// @brief Declares a fixed-size 2d ring buffer. -#define DECLARE_FIXED_SIZE_2D_RING_BUFFER(type, name, size1, size2, init) \ - typedef struct { \ - type buffer[size2]; \ - unsigned size; \ - } rb_##name##_entry_t; \ - \ - void rb_##name##_entry_default_copy_fun(type *dest, const type *src, unsigned size) \ - { \ - for (unsigned i = 0; i < size; i++) { \ - dest[i] = src[i]; \ - } \ - } \ - \ - typedef struct { \ - rb_##name##_entry_t buffer[size1]; \ - unsigned size; \ - unsigned head; \ - unsigned tail; \ - unsigned count; \ - void (*copy)(type * dest, const type *src, unsigned); \ - } rb_##name##_t; \ - \ - static inline void rb_##name##_init_entry(rb_##name##_entry_t *entry) \ - { \ - entry->size = size2; \ - for (unsigned i = 0; i < (size2); i++) { \ - entry->buffer[i] = init; \ - } \ - } \ - \ - static inline void rb_##name##_init(rb_##name##_t *rb, void (*copy_fun)(type * dest, const type *src, unsigned)) \ - { \ - rb->head = 0; \ - rb->tail = 0; \ - rb->count = 0; \ - rb->size = size1; \ - if (copy_fun) { \ - rb->copy = copy_fun; \ - } else { \ - rb->copy = rb_##name##_entry_default_copy_fun; \ - } \ - for (unsigned i = 0; i < (size1); i++) { \ - rb_##name##_init_entry(rb->buffer + i); \ - } \ - } \ - \ - static inline unsigned rb_##name##_is_empty(rb_##name##_t *rb) { return rb->count == 0; } \ - \ - static inline unsigned rb_##name##_is_full(rb_##name##_t *rb) { return rb->count == (size1); } \ - \ - static inline void rb_##name##_push_back(rb_##name##_t *rb, rb_##name##_entry_t *item) \ - { \ - rb->copy(rb->buffer[rb->head].buffer, item->buffer, size2); \ - if (rb_##name##_is_full(rb)) { \ - rb->tail = (rb->tail + 1) % (size1); \ - } else { \ - rb->count++; \ - } \ - rb->head = (rb->head + 1) % (size1); \ - } \ - \ - static inline void rb_##name##_push_front(rb_##name##_t *rb, rb_##name##_entry_t *item) \ - { \ - if (rb_##name##_is_full(rb)) { \ - rb->head = (rb->head == 0) ? (size1) - 1 : rb->head - 1; \ - } else { \ - rb->count++; \ - } \ - rb->tail = (rb->tail == 0) ? (size1) - 1 : rb->tail - 1; \ - rb->copy(rb->buffer[rb->tail].buffer, item->buffer, size2); \ - } \ - \ - static inline int rb_##name##_pop_back(rb_##name##_t *rb, rb_##name##_entry_t *item) \ - { \ - if (rb_##name##_is_empty(rb)) { \ - return 1; \ - } \ - rb->head = (rb->head == 0) ? (size1) - 1 : rb->head - 1; \ - rb->copy(item->buffer, rb->buffer[rb->head].buffer, size2); \ - rb->count--; \ - return 0; \ - } \ - \ - static inline int rb_##name##_pop_front(rb_##name##_t *rb, rb_##name##_entry_t *item) \ - { \ - if (rb_##name##_is_empty(rb)) { \ - return 1; \ - } \ - rb->copy(item->buffer, rb->buffer[rb->tail].buffer, size2); \ - rb->tail = (rb->tail + 1) % (size1); \ - rb->count--; \ - return 0; \ - } \ - \ - static inline int rb_##name##_peek_back(rb_##name##_t *rb, rb_##name##_entry_t *item) \ - { \ - if (rb_##name##_is_empty(rb)) { \ - return 1; \ - } \ - unsigned index = ((rb->head == 0) ? (size1) - 1 : rb->head - 1); \ - rb->copy(item->buffer, rb->buffer[index].buffer, size2); \ - return 0; \ - } \ - \ - static inline int rb_##name##_peek_front(rb_##name##_t *rb, rb_##name##_entry_t *item) \ - { \ - if (rb_##name##_is_empty(rb)) { \ - return 1; \ - } \ - rb->copy(item->buffer, rb->buffer[rb->tail].buffer, size2); \ - return 0; \ - } \ - \ - static inline int rb_##name##_get(rb_##name##_t *rb, unsigned position, rb_##name##_entry_t *item) \ - { \ - if (!rb_##name##_is_empty(rb) && (position < rb->count)) { \ - unsigned index = (rb->tail + position) % (size1); \ - rb->copy(item->buffer, rb->buffer[index].buffer, size2); \ - return 1; \ - } \ - return 0; \ - } \ - \ - static inline void rb_##name##_iterate(rb_##name##_t *rb, void (*callback)(rb_##name##_entry_t *)) \ - { \ - rb_##name##_entry_t item; \ - for (unsigned i = 0; i < rb->count; i++) { \ - rb_##name##_get(rb, i, &item); \ - callback(&item); \ - } \ +#define DECLARE_FIXED_SIZE_2D_RING_BUFFER(type, name, size1, size2, init) \ + typedef struct { \ + type buffer[size2]; \ + unsigned size; \ + } rb_##name##_entry_t; \ + \ + void rb_##name##_entry_default_copy_fun(type *dest, const type *src, unsigned size) \ + { \ + for (unsigned i = 0; i < size; i++) { \ + dest[i] = src[i]; \ + } \ + } \ + \ + typedef struct { \ + rb_##name##_entry_t buffer[size1]; \ + unsigned size; \ + unsigned head; \ + unsigned tail; \ + unsigned count; \ + void (*copy)(type * dest, const type *src, unsigned); \ + } rb_##name##_t; \ + \ + static inline void rb_##name##_init_entry(rb_##name##_entry_t *entry) \ + { \ + entry->size = size2; \ + for (unsigned i = 0; i < (size2); i++) { \ + entry->buffer[i] = init; \ + } \ + } \ + \ + static inline void rb_##name##_init(rb_##name##_t *rb, void (*copy_fun)(type * dest, const type *src, unsigned)) \ + { \ + rb->head = 0; \ + rb->tail = 0; \ + rb->count = 0; \ + rb->size = size1; \ + if (copy_fun) { \ + rb->copy = copy_fun; \ + } else { \ + rb->copy = rb_##name##_entry_default_copy_fun; \ + } \ + for (unsigned i = 0; i < (size1); i++) { \ + rb_##name##_init_entry(rb->buffer + i); \ + } \ + } \ + \ + static inline unsigned rb_##name##_is_empty(rb_##name##_t *rb) { return rb->count == 0; } \ + \ + static inline unsigned rb_##name##_is_full(rb_##name##_t *rb) { return rb->count == (size1); } \ + \ + static inline void rb_##name##_push_back(rb_##name##_t *rb, rb_##name##_entry_t *item) \ + { \ + rb->copy(rb->buffer[rb->head].buffer, item->buffer, size2); \ + if (rb_##name##_is_full(rb)) { \ + rb->tail = (rb->tail + 1) % (size1); \ + } else { \ + rb->count++; \ + } \ + rb->head = (rb->head + 1) % (size1); \ + } \ + \ + static inline void rb_##name##_push_front(rb_##name##_t *rb, rb_##name##_entry_t *item) \ + { \ + if (rb_##name##_is_full(rb)) { \ + rb->head = (rb->head == 0) ? (size1) - 1 : rb->head - 1; \ + } else { \ + rb->count++; \ + } \ + rb->tail = (rb->tail == 0) ? (size1) - 1 : rb->tail - 1; \ + rb->copy(rb->buffer[rb->tail].buffer, item->buffer, size2); \ + } \ + \ + static inline int rb_##name##_pop_back(rb_##name##_t *rb, rb_##name##_entry_t *item) \ + { \ + if (rb_##name##_is_empty(rb)) { \ + return 1; \ + } \ + rb->head = (rb->head == 0) ? (size1) - 1 : rb->head - 1; \ + rb->copy(item->buffer, rb->buffer[rb->head].buffer, size2); \ + rb->count--; \ + return 0; \ + } \ + \ + static inline int rb_##name##_pop_front(rb_##name##_t *rb, rb_##name##_entry_t *item) \ + { \ + if (rb_##name##_is_empty(rb)) { \ + return 1; \ + } \ + rb->copy(item->buffer, rb->buffer[rb->tail].buffer, size2); \ + rb->tail = (rb->tail + 1) % (size1); \ + rb->count--; \ + return 0; \ + } \ + \ + static inline int rb_##name##_peek_back(rb_##name##_t *rb, rb_##name##_entry_t *item) \ + { \ + if (rb_##name##_is_empty(rb)) { \ + return 1; \ + } \ + unsigned index = ((rb->head == 0) ? (size1) - 1 : rb->head - 1); \ + rb->copy(item->buffer, rb->buffer[index].buffer, size2); \ + return 0; \ + } \ + \ + static inline int rb_##name##_peek_front(rb_##name##_t *rb, rb_##name##_entry_t *item) \ + { \ + if (rb_##name##_is_empty(rb)) { \ + return 1; \ + } \ + rb->copy(item->buffer, rb->buffer[rb->tail].buffer, size2); \ + return 0; \ + } \ + \ + static inline int rb_##name##_get(rb_##name##_t *rb, unsigned position, rb_##name##_entry_t *item) \ + { \ + if (!rb_##name##_is_empty(rb) && (position < rb->count)) { \ + unsigned index = (rb->tail + position) % (size1); \ + rb->copy(item->buffer, rb->buffer[index].buffer, size2); \ + return 1; \ + } \ + return 0; \ + } \ + \ + static inline void rb_##name##_iterate(rb_##name##_t *rb, void (*callback)(rb_##name##_entry_t *)) \ + { \ + rb_##name##_entry_t item; \ + for (unsigned i = 0; i < rb->count; i++) { \ + rb_##name##_get(rb, i, &item); \ + callback(&item); \ + } \ } diff --git a/lib/src/abort.c b/lib/src/abort.c index 4c329265..5349a303 100644 --- a/lib/src/abort.c +++ b/lib/src/abort.c @@ -19,7 +19,7 @@ void abort(void) struct sigaction action; sigset_t sigset; - /* Unblock SIGABRT. */ + // Unblock SIGABRT. if (stage == 0) { ++stage; @@ -28,7 +28,7 @@ void abort(void) sigprocmask(SIG_UNBLOCK, &sigset, 0); } - /* Send signal which possibly calls a user handler. */ + // Send signal which possibly calls a user handler. if (stage == 1) { // We must allow recursive calls of abort int save_stage = stage; @@ -38,7 +38,7 @@ void abort(void) stage = save_stage + 1; } - /* There was a handler installed. Now remove it. */ + // There was a handler installed. Now remove it. if (stage == 2) { ++stage; @@ -51,21 +51,21 @@ void abort(void) sigaction(SIGABRT, &action, NULL); } - /* Try again. */ + // Try again. if (stage == 3) { ++stage; kill(getpid(), SIGABRT); } - /* Now try to abort using the system specific command. */ + // Now try to abort using the system specific command. if (stage == 4) { ++stage; __asm__ __volatile__("hlt"); } - /* If we can't signal ourselves and the abort instruction failed, exit. */ + // If we can't signal ourselves and the abort instruction failed, exit. if (stage == 5) { ++stage; diff --git a/lib/src/setenv.c b/lib/src/setenv.c index ae4924e9..c3251b87 100644 --- a/lib/src/setenv.c +++ b/lib/src/setenv.c @@ -132,12 +132,12 @@ int unsetenv(const char *name) char **ep = environ; while (*ep != NULL) { if (!strncmp(*ep, name, len) && (*ep)[len] == '=') { - /* Found it. Remove this pointer by moving later ones back. */ + // Found it. Remove this pointer by moving later ones back. char **dp = ep; do { dp[0] = dp[1]; } while (*dp++); - /* Continue the loop in case NAME appears again. */ + // Continue the loop in case NAME appears again. } else { ++ep; } diff --git a/lib/src/stdio.c b/lib/src/stdio.c index 260f817e..0570c129 100644 --- a/lib/src/stdio.c +++ b/lib/src/stdio.c @@ -18,10 +18,24 @@ void puts(const char *str) { write(STDOUT_FILENO, str, strlen(str)); } int getchar(void) { - char c = 0; - while (read(STDIN_FILENO, &c, 1) == 0) { + unsigned char c; + ssize_t r; + + while (1) { + r = read(STDIN_FILENO, &c, 1); + if (r > 0) { + return (int)c; + } + if (r == 0) { + // EOF reached; keep looping in case more data comes later + continue; + } + // error: for blocking descriptors we ignore EAGAIN/EWOULDBLOCK and retry; otherwise propagate EOF. + if (errno == EAGAIN || errno == EWOULDBLOCK) { + continue; + } + return EOF; } - return c; } char *gets(char *str) @@ -190,22 +204,37 @@ int fgetc(int fd) char c; ssize_t bytes_read; - // Read a single character from the file descriptor. - bytes_read = read(fd, &c, 1); + for (;;) { + // Read a single character from the file descriptor. + bytes_read = read(fd, &c, 1); - // Check for errors or EOF. - if (bytes_read == -1) { - perror("Error reading from file descriptor"); - return EOF; // Return EOF on error. - } - if (bytes_read == 0) { - return EOF; // Return EOF if no bytes were read (end of file). - } + // Check for errors or EOF. + if (bytes_read == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + continue; // retry on temporary unavailability + } + return EOF; // other error treated as EOF + } + if (bytes_read == 0) { + return EOF; // End of file + } - // Return the character as an unsigned char. - return (unsigned char)c; + // Return the character as an unsigned char. + return (unsigned char)c; + } } +/* + * Simple fgets implementation used throughout userspace. It performs a + * byte‑oriented read(2) loop until a newline is observed, the buffer is + * full, or an EOF/error is returned. Note that the behaviour of the + * underlying read(2) call is outside of libc's control: if the descriptor + * refers to a terminal in non‑canonical mode, the kernel will deliver + * whatever bytes are available (typically a single keystroke) and return + * immediately. In that case fgets may return a partial line (or even NULL + * if no data is ready); programs wishing to receive complete lines should + * either leave ICANON enabled or implement their own buffering logic. + */ char *fgets(char *buf, int n, int fd) { char *p = buf; @@ -247,12 +276,12 @@ int fflush(int fd) // If fd is negative, the standard requires flushing all open output // streams, but since we don't maintain a list of open streams and // writes are unbuffered, we simply return success. - + // For a valid file descriptor, we could verify it exists, but for // simplicity and compatibility, we just return success. // If buffering is added in the future, this function should be updated // to actually flush any pending data. - + (void)fd; // Mark parameter as intentionally unused return 0; // Success } diff --git a/lib/src/unistd/waitpid.c b/lib/src/unistd/waitpid.c index f35e7724..27680d88 100644 --- a/lib/src/unistd/waitpid.c +++ b/lib/src/unistd/waitpid.c @@ -13,15 +13,29 @@ pid_t waitpid(pid_t pid, int *status, int options) { -#if 1 pid_t __res; int __status = 0; do { __inline_syscall_3(__res, waitpid, pid, &__status, options); - if (__res != 0) { + + // Success: A child process was reaped. + if (__res > 0) { break; } - if (options && WNOHANG) { + + // WNOHANG was set and no child changed state - return immediately. + if ((options & WNOHANG) && __res == 0) { + break; + } + + // If the syscall was interrupted by a signal (EINTR), retry it. + // This happens when we were sleeping in waitpid and SIGCHLD woke us up. + if (__res == -EINTR) { + continue; + } + + // Any other error (ECHILD, ESRCH, etc.) should be returned. + if (__res < 0) { break; } } while (1); @@ -30,44 +44,6 @@ pid_t waitpid(pid_t pid, int *status, int options) *status = __status; } __syscall_return(pid_t, __res); -#else - - pid_t ret; - - while (1) { - __inline_syscall_3(ret, waitpid, pid, status, options); - - // Success: A child process state has changed. - if (ret > 0) { - break; - } - - if (ret < 0) { - __syscall_set_errno(ret); - - // Interrupted by a signal: Retry the syscall. - if (errno == EINTR) { - continue; - } - - // No children to wait for, but WNOHANG allows non-blocking behavior. - if (errno == ECHILD) { - if (options & WNOHANG) { - // Return 0 to indicate no state change. - ret = 0; - break; - } - continue; - } - - // Unrecoverable error: Return -1 and let the caller handle `errno`. - break; - } - } - - // Return the PID of the child whose state has changed (or 0 for WNOHANG). - return ret; -#endif } pid_t wait(int *status) { return waitpid(-1, status, 0); } diff --git a/qemu_test_output.txt b/qemu_test_output.txt new file mode 100644 index 00000000..32b0bced --- /dev/null +++ b/qemu_test_output.txt @@ -0,0 +1,8 @@ +==================================================== +Starting QEMU test run (timeout: 120s)... +==================================================== +QEMU started with PID: 216157 +Monitoring test progress... +Build directory: build + +qemu-system-i386: -cdrom build/cdrom_test.iso: Could not open 'build/cdrom_test.iso': No such file or directory diff --git a/userspace/bin/init.c b/userspace/bin/init.c index 1a25c6da..ca98be85 100644 --- a/userspace/bin/init.c +++ b/userspace/bin/init.c @@ -11,10 +11,6 @@ int main(int argc, char *argv[], char *envp[]) { char *_argv[] = {"login", NULL}; - int status; - -#pragma clang diagnostic push -#pragma ide diagnostic ignored "EndlessLoop" while (1) { pid_t login = fork(); if (login == 0) { @@ -22,9 +18,8 @@ int main(int argc, char *argv[], char *envp[]) printf("This is bad, I should not be here! EXEC NOT WORKING\n"); } - while (wait(&status) != login) { + while (waitpid(-1, NULL, 0) != login) { } } -#pragma clang diagnostic pop return 0; } diff --git a/userspace/bin/login.c b/userspace/bin/login.c index 4403966b..3613e51f 100644 --- a/userspace/bin/login.c +++ b/userspace/bin/login.c @@ -75,8 +75,12 @@ static inline int __read_input(char *buffer, size_t size, int show) do { c = getchar(); // Read a character from input - // Ignore EOF and null or tab characters - if (c == EOF || c == 0 || c == '\t') { + // Detect error/EOF and abort early so caller can handle it. + if (c == EOF) { + return -1; + } + // Ignore null or tab characters + if (c == 0 || c == '\t') { continue; } diff --git a/userspace/bin/shell.c b/userspace/bin/shell.c index 59b69ebc..0d739244 100644 --- a/userspace/bin/shell.c +++ b/userspace/bin/shell.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -43,10 +44,6 @@ DECLARE_FIXED_SIZE_2D_RING_BUFFER(char, history, HISTORY_MAX, CMD_LEN, 0) static rb_history_t history; // History reading index. static unsigned history_index; -// Store the last command status -static int status = 0; -// Store the last command status as string -static char status_buf[4] = {0}; static sigset_t oldmask; @@ -242,9 +239,12 @@ static char *__getenv(const char *var) } // Handle special variables like `$?`, which represents the status of the last command. if (var[0] == '?') { - // Assuming 'status' is a global or accessible variable containing the last command status. - sprintf(status_buf, "%d", status); // Convert the status to a string. - return status_buf; // Return the status as a string. + // Get the 'status' environment variable, which should have been set by the last command execution. + char *status_str = getenv("status"); + if (status_str == NULL) { + return "0"; // Default to "0" if the status variable is not set. + } + return status_str; // Return the status as a string. } // TODO: Implement access to `argv` for positional parameters (e.g., $1, $2). #if 0 @@ -822,7 +822,7 @@ static inline int __read_command(rb_history_entry_t *entry) // Handle newline character to finish input if (c == '\n') { - putchar('\n'); // Display a newline + putchar('\n'); // Display a newline return buffer_full ? -2 : length; // Return -2 if buffer was full } @@ -1205,7 +1205,10 @@ static void __setup_redirects(int *argcp, char ***argvp) /// @return Returns the exit status of the command. static int __execute_command(rb_history_entry_t *entry) { - int _status = 0; + syslog(LOG_DEBUG, "==========\n"); + syslog(LOG_DEBUG, "shell: executing command: %s\n", entry->buffer); + + int status = 0; // Variable to store the exit status of the command. // Retrieve the arguments from the command buffer. int _argc = 1; // Initialize the argument count. @@ -1245,8 +1248,10 @@ static int __execute_command(rb_history_entry_t *entry) __block_sigchld(); // Fork the current process to create a child process. pid_t cpid = fork(); + syslog(LOG_DEBUG, "shell: fork() returned cpid=%d\n", cpid); if (cpid == 0) { // Child process: Execute the command. + syslog(LOG_DEBUG, "shell: child process, executing: %s\n", _argv[0]); pid_t pid = getpid(); setpgid(cpid, pid); // Make the new process a group leader. __unblock_sigchld(); // Unblock SIGCHLD signals in the child process. @@ -1260,20 +1265,23 @@ static int __execute_command(rb_history_entry_t *entry) } if (blocking) { // Parent process: Wait for the child process to finish, retrying on EINTR. + syslog(LOG_DEBUG, "shell: parent blocking in waitpid for cpid=%d\n", cpid); int ret; do { - ret = waitpid(cpid, &_status, 0); + ret = waitpid(cpid, &status, 0); + syslog(LOG_DEBUG, "shell: waitpid returned ret=%d errno=%d\n", ret, errno); } while (ret == -1 && errno == EINTR); + syslog(LOG_DEBUG, "shell: waitpid finished, status=%d\n", status); // Handle different exit statuses of the child process. - if (WIFSIGNALED(_status)) { + if (WIFSIGNALED(status)) { printf( - FG_RED "\nExit status %d, killed by signal %d\n" FG_RESET, WEXITSTATUS(_status), WTERMSIG(_status)); - } else if (WIFSTOPPED(_status)) { + FG_RED "\nExit status %d, killed by signal %d\n" FG_RESET, WEXITSTATUS(status), WTERMSIG(status)); + } else if (WIFSTOPPED(status)) { printf( - FG_YELLOW "\nExit status %d, stopped by signal %d\n" FG_RESET, WEXITSTATUS(_status), - WSTOPSIG(_status)); - } else if (WEXITSTATUS(_status) != 0) { - printf(FG_RED "\nExit status %d\n" FG_RESET, WEXITSTATUS(_status)); + FG_YELLOW "\nExit status %d, stopped by signal %d\n" FG_RESET, WEXITSTATUS(status), + WSTOPSIG(status)); + } else if (WEXITSTATUS(status) != 0) { + printf(FG_RED "\nExit status %d\n" FG_RESET, WEXITSTATUS(status)); } } __unblock_sigchld(); // Unblock SIGCHLD signals after command execution. @@ -1281,12 +1289,13 @@ static int __execute_command(rb_history_entry_t *entry) // Free the memory allocated for the argument list. __free_argv(_argc, _argv); // Update the global status variable with the exit status of the command. - status = WEXITSTATUS(_status); + status = WEXITSTATUS(status); return status; // Return the exit status of the command. } static int __execute_file(char *path) { + int status = 0; rb_history_entry_t entry; rb_history_init_entry(&entry); int fd; @@ -1359,20 +1368,44 @@ static void __interactive_mode(void) } // Execute the command. - __execute_command(&entry); + int status = __execute_command(&entry); + + // Set in the environment the variable "status" with the exit status of the last command. + char status_str[4]; + snprintf(status_str, sizeof(status_str), "%d", status); + setenv("status", status_str, 1); } #pragma clang diagnostic pop } void wait_for_child(int signum) { + int status = 0; + pid_t child_pid; // Reap all terminated children without blocking. - while (waitpid(-1, NULL, WNOHANG) > 0) { + while ((child_pid = waitpid(-1, &status, WNOHANG)) > 0) { + syslog(LOG_DEBUG, "shell: SIGCHLD handler reaped child pid=%d, status=%d\n", child_pid, status); } + syslog(LOG_DEBUG, "shell: SIGCHLD handler finished reaping children, last waitpid returned pid=%d, errno=%d, status=%d\n", child_pid, errno, status); + + // Set in the environment the variable "status" with the exit status of the last command. + char status_str[4]; + snprintf(status_str, sizeof(status_str), "%d", status); + setenv("status", status_str, 1); } int main(int argc, char *argv[]) { + // Initialize syslog for debugging + openlog("shell", LOG_CONS | LOG_PID, LOG_USER); + + // Set log mask to include info messages. + setlogmask(LOG_UPTO(LOG_INFO)); + + syslog(LOG_INFO, "shell: starting, pid=%d\n", getpid()); + + int status = 0; // Variable to store the exit status of commands. + setsid(); // Initialize the history. rb_history_init(&history, rb_history_entry_copy); diff --git a/userspace/tests/t_kill.c b/userspace/tests/t_kill.c index be733b45..ea4a3f6b 100644 --- a/userspace/tests/t_kill.c +++ b/userspace/tests/t_kill.c @@ -42,19 +42,21 @@ int main(int argc, char *argv[]) memset(&action, 0, sizeof(action)); // Clear the action structure action.sa_handler = child_sigusr1_handler; // Set handler function - // Check if setting up the signal handler fails + printf("Setting up signal handler for SIGUSR1 in child (pid: %d)...\n", cpid); + + // Check if setting up the signal handler fails. if (sigaction(SIGUSR1, &action, NULL) == -1) { fprintf(STDERR_FILENO, "Failed to set signal handler for SIGUSR1: %s\n", strerror(errno)); return EXIT_FAILURE; // Return failure if handler setup fails } - // Request to sleep for 100 ms. - struct timespec req = {0, 100000000}; + // Request to sleep for 500 ms. + struct timespec req = {0, 500000000}; // Child process loop - waiting for signals while (1) { printf("I'm the child (pid: %d): I'm waiting...\n", cpid); - // Sleep for 100 ms. + // Sleep for 500 ms. nanosleep(&req, NULL); } @@ -62,19 +64,21 @@ int main(int argc, char *argv[]) // Parent process printf("I'm the parent (pid: %d)!\n", getpid()); - // Request to sleep for 500 ms. - struct timespec req = {0, 500000000}; + // Request to sleep for 1.5 seconds. + struct timespec req = {1, 500000000}; - // Sleep for 500 ms. + // Sleep for 1.5 seconds. nanosleep(&req, NULL); + printf("Sending SIGUSR1 to child (pid: %d)!\n", cpid); + // Send SIGUSR1 to the child process if (kill(cpid, SIGUSR1) == -1) { fprintf(STDERR_FILENO, "Failed to send SIGUSR1 to child: %s\n", strerror(errno)); return EXIT_FAILURE; // Return failure if signal sending fails } - // Wait before terminating the child process, sleep for 500 ms. + // Wait before terminating the child process, sleep for 1.5 seconds. nanosleep(&req, NULL); // Send SIGTERM to the child process to terminate it diff --git a/userspace/tests/t_semget.c b/userspace/tests/t_semget.c index 2158cdc8..aa0ff869 100644 --- a/userspace/tests/t_semget.c +++ b/userspace/tests/t_semget.c @@ -15,6 +15,7 @@ #include #include #include +#include #include int main(int argc, char *argv[]) @@ -30,38 +31,38 @@ int main(int argc, char *argv[]) // Generate a unique key using ftok. key = ftok("/", 5); if (key < 0) { - perror("Failed to generate key using ftok"); + syslog(LOG_ERR, "[t_semget] Failed to generate key using ftok: %s\n", strerror(errno)); return 1; } - printf("Generated key using ftok (key = %d)\n", key); + syslog(LOG_INFO, "[t_semget] Generated key using ftok (key = %d)\n", key); // ======================================================================== // Create a semaphore set with one semaphore. semid = semget(key, 1, IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); if (semid < 0) { - perror("Failed to create semaphore set"); + syslog(LOG_ERR, "[t_semget] Failed to create semaphore set: %s\n", strerror(errno)); return 1; } - printf("[father] Created semaphore set (id : %ld)\n", semid); + syslog(LOG_INFO, "[t_semget][father] Created semaphore set (id : %ld)\n", semid); // ======================================================================== // Set the value of the semaphore to 1. arg.val = 1; ret = semctl(semid, 0, SETVAL, &arg); if (ret < 0) { - perror("Failed to set semaphore value"); + syslog(LOG_ERR, "[t_semget] Failed to set semaphore value: %s\n", strerror(errno)); return 1; } - printf("[father] Set semaphore value to 1 (id : %ld)\n", semid); + syslog(LOG_INFO, "[t_semget][father] Set semaphore value to 1 (id : %ld)\n", semid); // ======================================================================== // Verify that the semaphore value is set correctly. ret = semctl(semid, 0, GETVAL, NULL); if (ret < 0) { - perror("Failed to get semaphore value"); + syslog(LOG_ERR, "[t_semget] Failed to get semaphore value: %s\n", strerror(errno)); return 1; } - printf("[father] Semaphore value is %ld (expected: 1)\n", ret); + syslog(LOG_INFO, "[t_semget][father] Semaphore value is %ld (expected: 1)\n", ret); // ======================================================================== // Fork a child process. @@ -77,42 +78,42 @@ int main(int argc, char *argv[]) // Increment the semaphore, unblocking the parent. if (semop(semid, &op_child, 1) < 0) { - perror("Failed to perform child semaphore operation"); + syslog(LOG_ERR, "[t_semget][child] Failed to perform child semaphore operation: %s\n", strerror(errno)); return 1; } - printf("[child] Performed first semaphore operation (id: %ld)\n", semid); + syslog(LOG_INFO, "[t_semget][child] Performed first semaphore operation (id: %ld)\n", semid); // Verify the updated semaphore value. ret = semctl(semid, 0, GETVAL, NULL); if (ret < 0) { - perror("Failed to get semaphore value in child"); + syslog(LOG_ERR, "[t_semget][child] Failed to get semaphore value in child: %s\n", strerror(errno)); return 1; } - printf("[child] Semaphore value is %ld (expected: 2)\n", ret); + syslog(LOG_INFO, "[t_semget][child] Semaphore value after first increment is %ld (concurrent parent may consume immediately)\n", ret); // Sleep and perform another increment operation. nanosleep(&req, NULL); if (semop(semid, &op_child, 1) < 0) { - perror("Failed to perform second child semaphore operation"); + syslog(LOG_ERR, "[t_semget][child] Failed to perform second child semaphore operation: %s\n", strerror(errno)); return 1; } - printf("[child] Performed second semaphore operation (id: %ld)\n", semid); + syslog(LOG_INFO, "[t_semget][child] Performed second semaphore operation (id: %ld)\n", semid); // Check final semaphore value. ret = semctl(semid, 0, GETVAL, NULL); if (ret < 0) { - perror("Failed to get final semaphore value in child"); + syslog(LOG_ERR, "[t_semget][child] Failed to get final semaphore value in child: %s\n", strerror(errno)); return 1; } - printf("[child] Final semaphore value is %ld\n", ret); + syslog(LOG_INFO, "[t_semget][child] Final semaphore value is %ld\n", ret); // Delete the semaphore set. ret = semctl(semid, 0, IPC_RMID, 0); if (ret < 0) { - perror("Failed to remove semaphore set in child"); + syslog(LOG_ERR, "[t_semget][child] Failed to remove semaphore set in child: %s\n", strerror(errno)); return 1; } - printf("[child] Removed semaphore set (id: %ld)\n", semid); + syslog(LOG_INFO, "[t_semget][child] Removed semaphore set (id: %ld)\n", semid); // Exit the child process. return 0; @@ -135,22 +136,22 @@ int main(int argc, char *argv[]) // ======================================================================== // Perform the blocking semaphore operations. if (semop(semid, op_father, 3) < 0) { - perror("Failed to perform parent semaphore operations"); + syslog(LOG_ERR, "[t_semget][father] Failed to perform parent semaphore operations: %s\n", strerror(errno)); return 1; } - printf("[father] Performed semaphore operations (id: %ld)\n", semid); + syslog(LOG_INFO, "[t_semget][father] Performed semaphore operations (id: %ld)\n", semid); // Verify that the semaphore value is updated correctly. ret = semctl(semid, 0, GETVAL, NULL); if (ret < 0) { - perror("Failed to get semaphore value in parent"); + syslog(LOG_ERR, "[t_semget][father] Failed to get semaphore value in parent: %s\n", strerror(errno)); return 1; } - printf("[father] Semaphore value is %ld (expected: 0)\n", ret); + syslog(LOG_INFO, "[t_semget][father] Semaphore value is %ld (expected: 0)\n", ret); // Wait for the child process to terminate. if (wait(NULL) == -1) { - fprintf(stderr, "Failed to wait for child process: %s\n", strerror(errno)); + syslog(LOG_ERR, "[t_semget] Failed to wait for child process: %s\n", strerror(errno)); return EXIT_FAILURE; // Return failure if wait fails. }