commit - 1419dd05375a71fc5791606d438ee109402a909a
commit + 59b10ca5186d691ee2071af0a3aef145ee195dc2
blob - a1b2ba91c955c13110251a1ec90dd3c01d3f8e8b
blob + fdfee83678e75f525652dfe78f6e533877c1915d
--- changelog.md
+++ changelog.md
# 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
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;
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);
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
}
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
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;
{
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)
__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
bool in_use;
uint32_t token;
struct sched_task *client;
+ struct sched_task *waiter;
struct ipc_vfs_response response;
bool ready;
};
slot = &vfs_state.slots[i];
slot->in_use = true;
slot->ready = false;
+ slot->waiter = NULL;
break;
}
}
/* 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;
++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=");
#endif
return -1;
}
- sched_yield();
+ if (spins < (spin_limit / 4)) {
+ sched_yield();
+ } else {
+ sched_block_current();
+ }
}
#ifdef LENIX_DEBUG
t_wait = __builtin_ia32_rdtsc();
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();
__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;
}