The LK kernel provides a comprehensive set of synchronization primitives for coordinating access to shared resources and enabling communication between threads. These primitives are built on top of the wait queue system and provide different semantics for various synchronization patterns.
Most blocking primitives in LK are built upon wait queues (wait_queue_t) (with the exception of spinlocks), which provide the fundamental blocking and wakeup mechanisms:
Most code in the system will not use a wait queue directly, it acts as the building block for other primitives.
Mutexes provide exclusive access to shared resources with ownership semantics. See mutex.h implementation details.
typedef struct mutex { uint32_t magic; // Magic number for validation int count; // Contention counter thread_t *holder; // Currently owning thread wait_queue_t wait; // Wait queue for blocked threads } mutex_t;
void mutex_init(mutex_t *m); // Initialze a mutex to the default state. void mutex_destroy(mutex_t *m); status_t mutex_acquire_timeout(mutex_t *m, lk_time_t timeout); status_t mutex_acquire(mutex_t *m); // Same as above but with infinite timeout. status_t mutex_release(mutex_t *m); // Release the mutex, must be the holding thread. bool is_mutex_held(const mutex_t *m); // Is the mutex held by the current thread?
// Static initialization of the mutex. Equivalent to mutex_init(). mutex_t resource_lock = MUTEX_INITIAL_VALUE(resource_lock); void protected_function(void) { status_t result = mutex_acquire(&resource_lock); if (result == NO_ERROR) { // Critical section - exclusive access to resource access_shared_resource(); mutex_release(&resource_lock); } }
class Mutex { public: status_t acquire(lk_time_t timeout = INFINITE_TIME); status_t release(); bool is_held(); }; class AutoLock { public: explicit AutoLock(mutex_t *mutex); // RAII lock acquisition ~AutoLock(); // Automatic release void release(); // Early release };
Semaphores control access to a finite number of resources using a counter mechanism. See semaphore.h implementation details.
typedef struct semaphore { int magic; // Magic number for validation int count; // Available resource count wait_queue_t wait; // Wait queue for blocked threads } semaphore_t;
void sem_init(semaphore_t *sem, unsigned int value); void sem_destroy(semaphore_t *sem); status_t sem_wait(semaphore_t *sem); // Infinite timeout status_t sem_timedwait(semaphore_t *sem, lk_time_t timeout); status_t sem_trywait(semaphore_t *sem); // Non-blocking int sem_post(semaphore_t *sem, bool resched); // Signal availability
semaphore_t resource_pool; void init_resource_pool(void) { sem_init(&resource_pool, 5); // 5 available resources } void use_resource(void) { if (sem_wait(&resource_pool) == NO_ERROR) { // Use one resource use_shared_resource(); sem_post(&resource_pool, true); // Return resource } }
Events provide signaling mechanisms for thread coordination and notification. See event.h implementation details.
typedef struct event { int magic; // Magic number for validation bool signaled; // Current signal state uint flags; // Behavior flags wait_queue_t wait; // Wait queue for blocked threads } event_t;
void event_init(event_t *e, bool initial, uint flags); void event_destroy(event_t *e); status_t event_wait(event_t *e); // Infinite timeout status_t event_wait_timeout(event_t *e, lk_time_t timeout); int event_signal(event_t *e, bool reschedule); // Returns number of threads woken status_t event_unsignal(event_t *e); bool event_initialized(event_t *e);
event_t completion_event; void init_completion(void) { event_init(&completion_event, false, EVENT_FLAG_AUTOUNSIGNAL); } void wait_for_completion(void) { event_wait(&completion_event); // Blocks until signaled } void signal_completion(void) { event_signal(&completion_event, true); // Wake one waiter }
event_t ready_event; void init_ready_state(void) { event_init(&ready_event, false, 0); // No auto-unsignal } void wait_until_ready(void) { event_wait(&ready_event); // All waiters proceed when signaled } void set_ready(void) { event_signal(&ready_event, true); // Wake all waiters } void clear_ready(void) { event_unsignal(&ready_event); // Manual clear }
Ports provide message-passing communication channels between threads with buffering. See port.h implementation details.
typedef struct { char value[PORT_PACKET_LEN]; // Packet payload } port_packet_t; typedef struct { void *ctx; // Associated context port_packet_t packet; // Message data } port_result_t;
typedef enum { PORT_MODE_BROADCAST, // Multiple readers can connect PORT_MODE_UNICAST, // Single reader connection PORT_MODE_BIG_BUFFER // Larger internal buffer } port_mode_t;
void port_init(void); status_t port_create(const char *name, port_mode_t mode, port_t *port); status_t port_destroy(port_t port); status_t port_open(const char *name, void *ctx, port_t *port); status_t port_close(port_t port); status_t port_write(port_t port, const port_packet_t *pk, size_t count); status_t port_read(port_t port, port_result_t *result); status_t port_read_timeout(port_t port, port_result_t *result, lk_time_t timeout); // Port groups for multiplexed reading status_t port_group_create(port_t *group); status_t port_group_add(port_t group, port_t port); status_t port_group_remove(port_t group, port_t port); status_t port_group_read(port_t group, port_result_t *result); status_t port_group_read_timeout(port_t group, port_result_t *result, lk_time_t timeout);
// Producer thread void producer_thread(void *arg) { port_t write_port; port_create("data_channel", PORT_MODE_BROADCAST, &write_port); port_packet_t packet; // Fill packet with data fill_packet_data(&packet); port_write(write_port, &packet, 1); port_destroy(write_port); } // Consumer thread void consumer_thread(void *arg) { port_t read_port; port_open("data_channel", NULL, &read_port); port_result_t result; if (port_read_timeout(read_port, &result, 1000) == NO_ERROR) { // Process received data process_packet_data(&result.packet); } port_close(read_port); }
Spinlocks provide lightweight mutual exclusion for short critical sections. See spinlock.h implementation details.
Architecture-specific spinlock implementation with common interface:
typedef arch_spin_lock_t spin_lock_t;
void spin_lock_init(spin_lock_t *lock); void spin_lock(spin_lock_t *lock); // Assumes interrupts disabled int spin_trylock(spin_lock_t *lock); // Non-blocking attempt void spin_unlock(spin_lock_t *lock); bool spin_lock_held(spin_lock_t *lock); // Wrapper functions that disable and restore interrupts, saving interrupt state // into 'state'. arch_interrupt_saved_state_t spin_lock_irqsave(spin_lock_t *lock); void spin_unlock_irqrestore(spin_lock_t *lock, arch_interrupt_saved_state_t state);
spin_lock_t hardware_lock = SPIN_LOCK_INITIAL_VALUE; void access_hardware_register(void) { arch_interrupt_saved_state_t state = spin_lock_irqsave(&hardware_lock); // Brief critical section write_hardware_register(value); spin_unlock_irqrestore(&hardware_lock, state); }
class SpinLock { public: void lock(); int trylock(); void unlock(); bool is_held(); arch_interrupt_saved_state_t lock_irqsave(); void unlock_irqrestore(arch_interrupt_saved_state_t state); }; class AutoSpinLock { public: explicit AutoSpinLock(spin_lock_t *lock); // RAII with IRQ save ~AutoSpinLock(); void release(); }; class AutoSpinLockNoIrqSave { public: explicit AutoSpinLockNoIrqSave(spin_lock_t *lock); // RAII without IRQ save ~AutoSpinLockNoIrqSave(); void release(); };
The foundation primitive underlying all blocking synchronization: See wait.h implementation details.
void wait_queue_init(wait_queue_t *wait); void wait_queue_destroy(wait_queue_t *wait, bool reschedule); status_t wait_queue_block(wait_queue_t *wait, lk_time_t timeout); int wait_queue_wake_one(wait_queue_t *wait, bool reschedule, status_t error); int wait_queue_wake_all(wait_queue_t *wait, bool reschedule, status_t error); status_t thread_unblock_from_wait_queue(thread_t *t, status_t error);
The wait queues provide no mechanism to handle priority inversion at this time. All threads are woken in FIFO order.
Debug builds include assertions that will panic on:
When threads are unblocked, the scheduler automatically handles CPU wakeup:
Use Mutexes when:
Use Spinlocks when:
// Using semaphores semaphore_t empty_slots, full_slots; mutex_t buffer_lock; void producer(void) { sem_wait(&empty_slots); // Wait for space mutex_acquire(&buffer_lock); // Protect buffer add_to_buffer(data); mutex_release(&buffer_lock); sem_post(&full_slots, true); // Signal data available } void consumer(void) { sem_wait(&full_slots); // Wait for data mutex_acquire(&buffer_lock); // Protect buffer data = remove_from_buffer(); mutex_release(&buffer_lock); sem_post(&empty_slots, true); // Signal space available }
// Using events for completion notification event_t work_complete; void worker_thread(void) { // Perform work do_work(); // Signal completion event_signal(&work_complete, true); } void coordinator_thread(void) { // Start work start_work(); // Wait for completion event_wait(&work_complete); // Process results process_results(); }
// Using semaphores for resource pool semaphore_t resource_pool; mutex_t pool_lock; resource_t *resources[MAX_RESOURCES]; void init_pool(void) { sem_init(&resource_pool, MAX_RESOURCES); mutex_init(&pool_lock); // Initialize resource array } resource_t *acquire_resource(void) { if (sem_wait(&resource_pool) == NO_ERROR) { mutex_acquire(&pool_lock); resource_t *res = find_free_resource(); mark_resource_used(res); mutex_release(&pool_lock); return res; } return NULL; } void release_resource(resource_t *res) { mutex_acquire(&pool_lock); mark_resource_free(res); mutex_release(&pool_lock); sem_post(&resource_pool, true); }
All blocking primitives (except spinlocks) require:
thread_block()