Part III: Kernel Services ######################### POSIX Thread ============ POSIX Threads (:c:type:`pthread_t`) represent a schedulable execution unit. Each thread executes independently with its own stack and priority, managed by the scheduler. Threads can be used by including ``pthread.h``. Threads are always enabled and the kernel must be configured with configuration option :c:macro:`QRT_CFG_THREAD_LIMIT` with non-zero value. Thread Attributes ----------------- Threads are created with the default attributes if no attributes are specified. The default attributes are defined as: - Detach state: :c:macro:`PTHREAD_CREATE_JOINABLE` - Cancellation state: :c:macro:`PTHREAD_CANCEL_ENABLE` - Cancellation type: :c:macro:`PTHREAD_CANCEL_DEFERRED` - Stack size: :c:macro:`QRT_CFG_THREAD_STACK_DEFAULT` - Scheduling policy: :c:macro:`SCHED_RR` - Scheduling priority: lowest priority (1) - Timeslice: :c:macro:`QRT_CFG_TIME_SLICE_MS` - Privilege level: :c:macro:`PTHREAD_UNPRIVILEGED_NP` To customize thread attributes before passing it to :c:func:`pthread_create()` and a :c:struct:`pthread_attr_t` object must be initialized with :c:func:`pthread_attr_init()`. The :c:struct:`pthread_attr_t` object can then be modified with: - Stack: :c:func:`pthread_attr_setstack()` / :c:func:`pthread_attr_getstack()` - Stack size (if stack not set): :c:func:`pthread_attr_setstacksize()` / :c:func:`pthread_attr_getstacksize()` - Scheduling policy: :c:func:`pthread_attr_setschedpolicy()` / :c:func:`pthread_attr_getschedpolicy()` - Scheduling priority: :c:func:`pthread_attr_setschedparam()` / :c:func:`pthread_attr_setschedparam()` - Detach state: :c:func:`pthread_attr_setdetachstate()` / :c:func:`pthread_attr_getdetachstate()` - Scheduling attribute inheritance: :c:func:`pthread_attr_setinheritsched()` / :c:func:`pthread_attr_getinheritsched()` - Uninitialize the attribute object: :c:func:`pthread_attr_destroy()` QuantumRT also provides extensions to configure aditional thread attributes: - Timeslice: :c:func:`pthread_attr_settimeslice_np()` / :c:func:`pthread_attr_gettimeslice_np()` - Privilege level: :c:func:`pthread_attr_setprivilege_np()` / :c:func:`pthread_attr_getprivilege_np()` - Thread name: :c:func:`pthread_attr_setname_np()` / :c:func:`pthread_attr_getname_np()` 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()`. 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. Thread Lifecycle ---------------- Threads are created using :c:func:`pthread_create()` and are exited with :c:func:`pthread_exit()`. Cancelling a thread with :c:func:`pthread_cancel()` allows it to be terminated from another thread. Both deferred and asynchronous cancellation are supported: - Deferred cancellation allows a thread to specify cancellation points where it can be safely cancelled. Cancel points include blocking system calls and explicit cancellation points with :c:func:`pthread_testcancel()`. - Asynchronous cancellation cancels a thread immediately. Threads can be joined with (:c:func:`pthread_join()`) allowing another thread to wait for its termination and retrieve its exit status. Joined threads resources are automatically released upon termination. Only deferred cancelability type threads can be joined. Detaching a thread with :c:func:`pthread_detach()` allows it to run independently. Detached threads resources are automatically released upon termination. Cleanup handlers are used to ensure resources are released when a thread is cancelled or exits. Cleanup handlers can be registered with :c:func:`pthread_cleanup_push()` and :c:func:`pthread_cleanup_pop()`. :c:macro:`QRT_CFG_CLEANUP_LIMIT` is used to configure the number of cleanup handlers per thread. Thread-Local Storage -------------------- POSIX Thread-Local Storage (TLS) provides a mechanism for threads to maintain their own private data. TLS keys can be created using :c:func:`pthread_key_create()` and deleted with :c:func:`pthread_key_delete()`. Threads can set and get their own specific data using :c:func:`pthread_setspecific()` and :c:func:`pthread_getspecific()`. POSIX Semaphore =============== POSIX semaphores provide a mechanism for signaling between threads and managing access to shared resources. Semaphores can be used by including ``semaphore.h``. Semaphores 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()` / :c:func:`sem_clockwait()` 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`. .. _pthread-mutex: 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 used by including ``pthread.h``. Mutexes can be created using :c:func:`pthread_mutex_init()` and destroyed with :c:func:`pthread_mutex_destroy()`. Mutexes are initialized with default attributes if no attributes are specified. The default attributes are defined as: - Protocol: :c:macro:`PTHREAD_PRIO_NONE` - Priority ceiling: 0 - Type: :c:macro:`PTHREAD_MUTEX_DEFAULT` To customize mutex attributes before passing it to :c:func:`pthread_mutex_init()` and a :c:struct:`pthread_mutexattr_t` object must be initialized with :c:func:`pthread_mutexattr_init()`. The :c:struct:`pthread_mutexattr_t` object can then be modified with: - Protocol: :c:func:`pthread_mutexattr_setprotocol()` / :c:func:`pthread_mutexattr_getprotocol()` - Priority ceiling: :c:func:`pthread_mutexattr_setprioceiling()` / :c:func:`pthread_mutexattr_getprioceiling()` - Type: :c:func:`pthread_mutexattr_settype()` / :c:func:`pthread_mutexattr_gettype()` - Uninitialize the attribute object: :c:func:`pthread_mutexattr_destroy()` Threads can lock a mutex using :c:func:`pthread_mutex_trylock()` / :c:func:`pthread_mutex_lock()` / :c:func:`pthread_mutex_timedlock()` / :c:func:`pthread_mutex_clocklock()` and unlock a mutex using :c:func:`pthread_mutex_unlock()`. Mutexes can be enabled by setting configuration option :c:macro:`QRT_CFG_MUTEX_ENABLE`. Each thread can hold a limited number of mutexes simultaneously, configured with :c:macro:`QRT_CFG_MUTEX_LIST_SIZE`. 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 one of the following values: - :c:macro:`PTHREAD_PRIO_INHERIT` - :c:macro:`PTHREAD_PRIO_PROTECT` - :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. - Use timed lock mechanisms (:c:func:`pthread_mutex_timedlock()`) to limit the wait time for acquiring a lock. .. note:: The kernel does not provide built-in deadlock detection or prevention. 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. Threads can be used by including ``mqueue.h``. 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 queue attributes can be configured with :c:func:`mq_setattr()` and retrieved with :c:func:`mq_getattr()`. Default attributes are used if no attributes are specified during creation. The default attributes are defined as: - mq_maxmsg: 10 - mq_msgsize: 256 bytes 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. Threads can be used by including ``time.h``. 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_settime()` by setting zero time. 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. POSIX Clock =========== POSIX Clocks provide a mechanism for retrieving and manipulating time values. Threads can be used by including ``time.h``. The kernel supports multiple clock sources, including: - :c:macro:`CLOCK_REALTIME`: System-wide real-time clock. - :c:macro:`CLOCK_MONOTONIC`: Monotonic clock that cannot be set and represents elapsed time since an unspecified starting point. Clock API provides functions for retrieving the current time with :c:func:`clock_gettime()`, setting the time with :c:func:`clock_settime()`. System Call Extension ===================== System Call Extensions are designed for invoking custom system calls in kernel context when memory protection is enabled. They enable unprivileged threads to access peripheral devices. Privileged threads can register System Call Extensions with :c:func:`qrt_syscall_register()`. System Call Extensions are 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 enabling :c:macro:`QRT_CFG_DEFER_ENABLE` and by setting configuration option :c:macro:`QRT_CFG_DEFER_LIMIT` with non-zero value.