commit 59b10ca5186d691ee2071af0a3aef145ee195dc2 from: int16h date: Fri Dec 5 15:08:46 2025 UTC Tame IPC wait spins - stable commit - 1419dd05375a71fc5791606d438ee109402a909a commit + 59b10ca5186d691ee2071af0a3aef145ee195dc2 blob - a1b2ba91c955c13110251a1ec90dd3c01d3f8e8b blob + fdfee83678e75f525652dfe78f6e533877c1915d --- changelog.md +++ changelog.md @@ -1,5 +1,14 @@ # Changelog +# 2025-12-06 + +## IPC wait-path fixes and scheduler instrumentation + +- Added per-task yield/block counters and exposed them via a new console `yields` command; perf counters now include `sched_yields`/`sched_blocks` to spot spinners quickly. +- Service-to-service IPC waits now block instead of spin: `ipc_service_client` and the kernel VFS service record the waiting task, block after a short spin, and wake the waiter on response, eliminating million-spin timeouts during init/handoff. +- Trimmed the default IPC service spin budget (1024 iterations) to reduce idle context switches without hurting responsiveness. +- Console idle loop reverted to the stable poll+yield path while keeping wake helpers, restoring boot stability after earlier blocking experiments. + # 2025-12-05 ## Blocking IPC: Wait queues and sleeping receivers blob - 1b85ac9392d27383c40b00d9aedb19739b081aeb blob + 31b58b25573823a43e48779adca80212fbc1145f --- kernel/console/service.c +++ kernel/console/service.c @@ -44,8 +44,6 @@ static size_t console_ctrl_head; static size_t console_ctrl_tail; static struct sched_task *console_task; static struct sched_task *console_service_task; -static struct sched_task *console_waiter; -static bool console_wait_flag; static ipc_portal_handle_t console_tty_portal = IPC_PORTAL_INVALID_HANDLE; static struct sched_task *console_tty_task; static ipc_portal_handle_t console_ttyctl_portal = IPC_PORTAL_INVALID_HANDLE; @@ -72,7 +70,6 @@ static bool console_logged_deprecation; static spinlock_t line_queue_lock = SPINLOCK_INIT; static spinlock_t char_queue_lock = SPINLOCK_INIT; static spinlock_t ctrl_queue_lock = SPINLOCK_INIT; -static spinlock_t console_wait_lock = SPINLOCK_INIT; static void console_task_main(void *arg); static void console_queue_push(const char *line); @@ -107,18 +104,8 @@ void console_service_ttyctl_send(const char *buf, siz static void console_wake_service_task(void) { - struct sched_task *task = NULL; - - spinlock_lock(&console_wait_lock); - console_wait_flag = true; - if (console_waiter != NULL) { - task = console_waiter; - console_waiter = NULL; - } - spinlock_unlock(&console_wait_lock); - - if (task != NULL) - sched_wake(task); + if (console_task != NULL) + sched_wake(console_task); } static bool @@ -759,9 +746,8 @@ console_task_main(void *arg) } console_tty_drain_queue(); if (!queue_pop(&entry)) { - /* Drain any pending UART characters before sleeping. */ + /* Drain UART then yield; avoids hangs on missed wakeups */ serial_poll_rx(); - if (console_idle_debug < 4) { #ifdef LENIX_DEBUG printk("[console] idle, polling serial\n"); blob - e53ae9251c89c46d65c613dc34e503e8decd0286 blob + d18cae1daceb5f4842fd3bdaf0c8df2f7057e05e --- kernel/ipc/service_client.c +++ kernel/ipc/service_client.c @@ -162,7 +162,7 @@ ipc_service_request_issue_timeout(struct ipc_service_c struct ipc_service_pending *slot; uint8_t tmp_req[IPC_MAILBOX_MAX_PAYLOAD]; uint64_t spins = 0; - uint64_t spin_limit = 10000000; /* Default iteration budget */ + uint64_t spin_limit = 1024; /* Default iteration budget (tuned down to avoid spin storms) */ if (spin_limit_override != 0) spin_limit = spin_limit_override; @@ -287,6 +287,7 @@ ipc_service_handle_response(ipc_portal_handle_t portal { struct pending_service_request *preq; struct ipc_service_pending *slot; + struct sched_task *waiter = NULL; struct sched_task *server_task; if (portal == IPC_PORTAL_INVALID_HANDLE || resp == NULL) @@ -335,14 +336,15 @@ ipc_service_handle_response(ipc_portal_handle_t portal __atomic_store_n(&slot->ready, true, __ATOMIC_RELEASE); /* Clean up the registry entry */ + waiter = preq->waiter; preq->in_use = false; preq->waiter = NULL; spinlock_unlock(&pending_requests_lock); /* Wake the waiting client task (if any) now that the response is ready */ - if (preq->waiter != NULL) - sched_wake(preq->waiter); + if (waiter != NULL) + sched_wake(waiter); return 0; } blob - 194bde48e75e5bede694aa4f98a25fbb2397c487 blob + 146231de7d4dd46b8e2b53025e950ab300b11332 --- kernel/ipc/vfs_service.c +++ kernel/ipc/vfs_service.c @@ -23,6 +23,7 @@ struct vfs_pending { bool in_use; uint32_t token; struct sched_task *client; + struct sched_task *waiter; struct ipc_vfs_response response; bool ready; }; @@ -62,6 +63,7 @@ vfs_pending_alloc(void) slot = &vfs_state.slots[i]; slot->in_use = true; slot->ready = false; + slot->waiter = NULL; break; } } @@ -255,6 +257,7 @@ vfs_request_issue(const struct ipc_vfs_request *req, /* slot->in_use and slot->ready already set by vfs_pending_alloc() */ slot->client = sched_current_task(); + slot->waiter = slot->client; slot->token = vfs_state.next_token++; if (vfs_state.next_token == 0) vfs_state.next_token = 1; @@ -285,6 +288,7 @@ vfs_request_issue(const struct ipc_vfs_request *req, ++spins > spin_limit) { spinlock_lock(&vfs_pending_lock); slot->in_use = false; + slot->waiter = NULL; spinlock_unlock(&vfs_pending_lock); #ifdef LENIX_DEBUG printk("[vfs] request timeout token="); @@ -297,7 +301,11 @@ vfs_request_issue(const struct ipc_vfs_request *req, #endif return -1; } - sched_yield(); + if (spins < (spin_limit / 4)) { + sched_yield(); + } else { + sched_block_current(); + } } #ifdef LENIX_DEBUG t_wait = __builtin_ia32_rdtsc(); @@ -307,6 +315,7 @@ vfs_request_issue(const struct ipc_vfs_request *req, spinlock_lock(&vfs_pending_lock); slot->in_use = false; slot->ready = false; + slot->waiter = NULL; spinlock_unlock(&vfs_pending_lock); #ifdef LENIX_DEBUG t_end = __builtin_ia32_rdtsc(); @@ -369,5 +378,9 @@ vfs_service_respond(const struct ipc_vfs_response *res __atomic_store_n(&slot->ready, true, __ATOMIC_RELEASE); spinlock_unlock(&vfs_pending_lock); + + /* Wake the waiting client (if any) now that the response is ready */ + if (slot->waiter != NULL) + sched_wake(slot->waiter); return 0; }