libxtos-rt
Linux scheduling, affinity, locked memory, and single-producer single-consumer frame queues.
On this page
Policy and permissions
xtos_rt_set_scheduler(policy, priority) calls sched_setscheduler(0, ...) for the calling thread.
SCHED_OTHER requires priority=0; this is not a nice value.
SCHED_FIFO/RR usually use 1–99, with larger static priority; query sched_get_priority_min/max for the actual range.
Do not use this interface for SCHED_DEADLINE, which has a different sched_setattr contract.
| Policy | Semantics | Prerequisites and errors |
|---|---|---|
| SCHED_OTHER | Time-sharing; suitable for device-free examples, not real-time latency. | priority=0; subject to system security policy. |
| SCHED_FIFO | Equal priorities do not time-slice; block or yield to avoid monopolizing a CPU. | Needs CAP_SYS_NICE or eligible RLIMIT_RTPRIO; insufficient permission usually gives EPERM. |
| SCHED_RR | Round-robin at equal priority; still above ordinary policy. | Same permissions as FIFO; invalid policy or priority gives EINVAL. |
Success is 0 and failure is -1. The implementation checks the range, sets EINVAL for an out-of-range priority, then calls sched_*; errno may also come from the system call. Containers, CPU cgroups, and security policy can restrict setup even for uid 0. The example uses SCHED_OTHER and needs no real-time privilege.
Affinity and apply
struct xtos_rt_config {
int policy;
int priority;
int cpu;
};
int xtos_rt_apply(const struct xtos_rt_config *config);
int xtos_rt_set_scheduler(int policy, int priority);
int xtos_rt_set_affinity(int cpu);
int xtos_rt_get_current(int *policy, int *priority);cpu is a logical CPU ID, not an index in an allowed list. XTOS_RT_NO_CPU (-1) skips affinity only in apply; direct set_affinity(-1) returns -1/EINVAL. set_affinity accepts 0≤cpu<CPU_SETSIZE and sets a single-CPU mask. An offline CPU, a CPU outside the cpuset, or an empty intersection can yield EINVAL; CPU 0 is not portable.
apply requires non-NULL config, sets scheduling first, then optional affinity.
If the second step fails, the first remains applied; there is no rollback.
Save the original policy and affinity for recovery. get_current requires two non-NULL output pointers and returns the calling thread’s policy and static priority, not affinity; read affinity with sched_getaffinity.
Configuration functions return 0/-1 and retain no caller pointers.
Configure inside the worker that will run.
Locked memory
| Interface | Parameters and ownership | Result and errors |
|---|---|---|
| lock_memory(address,length) | address is non-NULL and length is positive valid bytes; caller owns the memory. | 0/-1; NULL or zero gives EINVAL; other errors come from mlock. |
| alloc_locked(length) | length>0; posix_memalign page-aligns before mlock; memory is uninitialized. | Pointer or NULL; EINVAL, ENOMEM, ENOSYS, or mlock errno. |
| free_locked(address,length) | Use only the original alloc_locked pointer and length, not stack, subrange, or freed storage. | munlock then free; frees even if munlock fails; do not retry after -1. |
mlock is limited by RLIMIT_MEMLOCK; CAP_IPC_LOCK changes the privilege condition.
Limits, unmapped ranges, or resource exhaustion can give ENOMEM; policy can give EPERM/EAGAIN.
Allocate, clear, and touch memory during startup.
Unlock lock_memory ranges with munlock; free_locked cannot release arbitrary addresses.
Queue internals, stacks, temporaries, and future allocations are not locked automatically.
SPSC queue
struct xtos_sample_frame {
uint64_t timestamp_ns;
uint64_t sequence;
uint32_t channels;
uint32_t sample_rate_hz;
uint32_t quality_flags;
const int32_t *samples_nv;
};
struct xtos_spsc_queue *xtos_spsc_create(size_t capacity, uint32_t channels);
int xtos_spsc_push(struct xtos_spsc_queue *queue,
const struct xtos_sample_frame *frame);
int xtos_spsc_pop(struct xtos_spsc_queue *queue,
struct xtos_sample_frame *frame,
int32_t *samples_nv, size_t samples_capacity);
void xtos_spsc_destroy(struct xtos_spsc_queue *queue);SPSC requires one producer for every push and one consumer for every pop; sequential single-thread use is valid. Multiple acquisition threads cannot share push, and multiple workers cannot share pop. Head, tail, and slot contents use acquire/release publication; there is no MPMC arbitration.
create requires capacity>0 and channels>0; capacity counts complete frames, not bytes, and need not be a power of two. Each slot allocates its channel array at creation; invalid values give EINVAL and allocation failure usually ENOMEM. push/pop do not allocate. Stop both endpoints before destruction. push checks non-NULL pointers, matching channels, and nonzero timestamp_ns, but not time order, sequence continuity, nonzero sample_rate_hz, or quality_flags; samples_nv must expose at least channels readable elements.
Queue returns and lifetime
| Condition | Result | Handling |
|---|---|---|
| push success | 0; metadata and samples are copied into the slot. | The source array may be reused after return. |
| push full | -1/EAGAIN; rejects the new frame without overwriting old frames. | Record drops and sequence gaps; avoid unbounded high-priority spinning. |
| invalid push | -1/EINVAL; rejected_invalid increases for a valid queue. | Fix channels, timestamp, or pointers. |
| pop success | 0; samples_capacity≥channels, measured in elements. | frame.samples_nv points to caller output. |
| empty/invalid pop | Empty is -1/EAGAIN; NULL or short output is -1/EINVAL. | Do not use unwritten frame or output after failure. |
get_stats reads pushed, popped, rejected_full, rejected_invalid, and overwritten atomics independently, not as a consistent cross-field snapshot; the current reject-on-full path does not increment overwritten.
NULL stats is ignored; size/capacity return 0 for a NULL queue.
Concurrent size is not a reservation or synchronization primitive; use push/pop results.
Sources and tests are sdk/libxtos-rt/include/xtos/{rt,spsc}.h, src/{rt,spsc}.c, tests/test_rt.c, and tests/test_spsc.c.
Confirm C11 atomic lock-freedom on the target.