Part III: Kernel Services ######################### POSIX Thread ============ POSIX Threads (:c:type:`pthread_t`) represent a schedulable execution unit in QuantumRT. Each thread executes independently with its own stack and priority, managed by the scheduler. The kernel must be configured with configuration option :c:macro:`QRT_CFG_THREAD_LIMIT` with non-zero value. Thread Creation --------------- Threads are created using :c:func:`pthread_create()`. Thread attributes such as stack size, priority, and detach state can be configured using :c:struct:`pthread_attr_t` before creation. Thread Lifecycle ---------------- Threads can be joined (:c:func:`pthread_join()`), detached (:c:func:`pthread_detach()`), or cancelled (:c:func:`pthread_cancel()`). QuantumRT supports both deferred and asynchronous cancellation. Cleanup handlers can be registered with :c:func:`pthread_cleanup_push()` and :c:func:`pthread_cleanup_pop()` to ensure resources are released when a thread is cancelled or exits. :c:macro:`QRT_CFG_CLEANUP_LIMIT` is used to configure the number of cleanup handlers per thread. Thread Attributes ----------------- Attributes define initial parameters for a thread: :c:func:`pthread_attr_init()`, :c:func:`pthread_attr_setstacksize()`, and related APIs control thread configuration. Thread Stack Allocation ----------------------- Threads require a dedicated stack for execution. The stack can be allocated either: - **Statically** with :c:func:`pthread_attr_setstack()` - **Dynamically** with :c:func:`pthread_attr_setstacksize()`. Default behavior if stack is not set explicitly. If the stack size is not explicitly set, a default size, which must be configured with :c:macro:`QRT_CFG_THREAD_STACK_DEFAULT`, is used. If dynamic memory allocation is used, thread stack pool must be configured with sufficient memory with :c:macro:`QRT_CFG_STACK_POOL_SIZE`. .. note:: - ARMv6-M and ARMv7-M Stack base address must be stack size aligned. - ARMv8-M Stack base address must be 32-byte aligned if memory protection is enabled. .. note:: - Stack size must be power of two if memory protection is enabled. - Stack size must be power of two if dynamic allocation is used. POSIX Semaphore =============== POSIX semaphores provide a mechanism for signaling between threads and managing access to shared resources. They can be created using :c:func:`sem_init()` and destroyed with :c:func:`sem_destroy()`. Threads can wait on a semaphore using :c:func:`sem_trywait()` / :c:func:`sem_wait()` / :c:func:`sem_timedwait()` and signal a semaphore using :c:func:`sem_post()`. The current value of a semaphore can be queried with :c:func:`sem_getvalue()` Part of the semaphore API is available from deferred call context. .. note:: Named semaphores are not supported. Semaphores can be enabled by setting configuration option :c:macro:`QRT_CFG_SEMAPHORE_ENABLE`. POSIX Thread Mutex ================== POSIX Thread Mutexes provide a mechanism for mutual exclusion, allowing threads to safely access shared resources without data corruption. Mutexes can be created using :c:func:`pthread_mutex_init()` and destroyed with :c:func:`pthread_mutex_destroy()`. Mutex attributes can be configured using :c:struct:`pthread_mutexattr_t` before creation. Threads can lock a mutex using :c:func:`pthread_mutex_trylock()` / :c:func:`pthread_mutex_lock()` / :c:func:`pthread_mutex_timedlock()` and unlock a mutex using :c:func:`pthread_mutex_unlock()`. Mutexes can be enabled by setting configuration option :c:macro:`QRT_CFG_MUTEX_ENABLE`. Priority Inversion ------------------ POSIX Thread Mutexes support priority inheritance and priority ceiling protocols to mitigate priority inversion issues. These protocols can be configured on mutex creation using :c:func:`pthread_mutexattr_setprotocol()` with the values :c:macro:`PTHREAD_PRIO_INHERIT`, :c:macro:`PTHREAD_PRIO_PROTECT` or :c:macro:`PTHREAD_PRIO_NONE`. Deadlocks --------- A deadlock occurs when multiple threads hold each other's locks and these threads attempt to acquire each other's locks with infinite timeout. Recommended Practices to Avoid Deadlocks: - Always acquire multiple mutexes in a consistent global order across all threads. - Use try-lock mechanisms (:c:func:`pthread_mutex_trylock()`) to attempt acquiring locks without blocking indefinitely. - Implement timeout mechanisms when acquiring locks to prevent indefinite waiting. .. note:: The kernel does not provide built-in deadlock detection or prevention. Semaphore vs. Mutex ------------------- Although semaphores and mutexes are both used for synchronization, they have key differences: .. table:: Comparison between semaphore and mutex :widths: 25 35 40 +-------------------+---------------------------+--------------------------------------+ | Feature | Semaphore | Mutex | +===================+===========================+======================================+ | Ownership | No ownership (any thread | Has ownership (only the locking | | | can signal) | thread can release) | +-------------------+---------------------------+--------------------------------------+ | Use Case | Synchronization & | Exclusive resource access | | | multi-resource control | | +-------------------+---------------------------+--------------------------------------+ | Priority | No built-in handling | Supports priority inheritance and | | Inversion | | priority ceiling | +-------------------+---------------------------+--------------------------------------+ POSIX Message Queue =================== POSIX Message Queues provide a mechanism for threads to communicate by sending and receiving messages in a FIFO manner. Message queues support multiple producers and consumers, allowing threads to exchange data safely and efficiently. A message queue is created using :c:func:`mq_open()` and destroyed using :c:func:`mq_close()` and :c:func:`mq_unlink()`. Messages are sent to the queue using :c:func:`mq_send()` / :c:func:`mq_timedsend()` and received using :c:func:`mq_receive()` / :c:func:`mq_timedreceive()`. Message queues dynamically allocate memory from the kernel heap for messages. The kernel heap must be configured with sufficient memory with :c:macro:`QRT_CFG_KERNEL_HEAP_SIZE`. Message queues can be enabled by setting configuration option :c:macro:`QRT_CFG_MESSAGE_QUEUE_ENABLE`, and setting configuration option :c:macro:`QRT_CFG_MESSAGE_QUEUE_LIMIT` with non-zero value. POSIX Timer =========== POSIX Timers provide a mechanism for executing callback functions at specified intervals or after a certain delay. Timers can be created using :c:func:`timer_create()` and deleted with :c:func:`timer_delete()`. Timers can be started with :c:func:`timer_settime()` and stopped with :c:func:`timer_delete()`. When a timer expires, it invokes a user-defined callback function. Software timers can be enabled by setting configuration option :c:macro:`QRT_CFG_TIMER_ENABLE` and setting configuration option :c:macro:`QRT_CFG_TIMER_LIMIT` with non-zero value. System Call Extension ===================== System Call Extensions are designed for invoking custom system calls in kernel context when memory protection is enabled. System Call Extensions are registered with :c:func:`qrt_syscall_register()` and invoked with :c:func:`qrt_syscall_invoke()`. System Call Extensions can be enabled by setting configuration option :c:macro:`QRT_CFG_SYSCALL_EXT_ENABLE` and setting configuration option :c:macro:`QRT_CFG_SYSCALL_EXT_LIMIT` with non-zero value. Deferred Call ============= QuantumRT supports deferred calls, allowing ISRs and callbacks to split workload between urgent and less urgent work. Deferred calls are registered with :c:func:`qrt_defercall()`. Deferred calls are executed in order they were pushed and only after all interrupt handlers have completed. System calls are not directly available from ISR context. Instead, ISRs must use the deferred call to invoke system calls. Deferred call can safely invoke the following system calls: * :c:func:`sem_post()` * :c:func:`sem_trywait()` * :c:func:`sem_getvalue()` Deferred Calls can be enabled by setting configuration option :c:macro:`QRT_CFG_DEFER_LIMIT` with non-zero value.