RTE OSAdapter POSIX 0.1.0 build 41b3eb3
POSIX/Linux OAL OSAdapter for Platform_RTE
Loading...
Searching...
No Matches
rte_posix_osadapter_task.c
Go to the documentation of this file.
1#define _POSIX_C_SOURCE 200809L
2
120
121#include <errno.h>
122#include <pthread.h>
123#include <sched.h>
124#include <signal.h>
125#include <stdbool.h>
126#include "rte/oal/memory/rte_mem_util.h"
127
128typedef struct
129{
130 pthread_t thread;
131 rte_task_config_t config;
132 bool joinable; /* controlling thread only, see file header */
133 volatile sig_atomic_t should_stop; /* the only field both threads write, see file header */
134 rte_task_handle_t self_handle;
136
137/* C99-compatible static assert: posix_task_state_t must fit inside
138 * rte_task_storage_t's reserved bytes. */
139typedef char posix_task_storage_fits_[(sizeof(posix_task_state_t) <= sizeof(rte_task_storage_t)) ? 1 : -1];
140
141/* The thread-specific-data key of the self check (file header): created once per process by
142 * create_task_key() through the pthread_once() in task_key_ready(), never deleted. s_task_key_ready is
143 * written only inside pthread_once() and read after it returned. */
144static pthread_key_t s_task_key;
145static bool s_task_key_ready = false;
146
152static bool set_fifo_attributes(pthread_attr_t *attr, uint32_t priority)
153{
154 bool applied = false;
155 int min_prio = sched_get_priority_min(SCHED_FIFO);
156 int max_prio = sched_get_priority_max(SCHED_FIFO);
157
158 if ((min_prio >= 0) && (max_prio >= min_prio))
159 {
160 struct sched_param sched;
161 int wanted;
162
163 if (priority < (uint32_t)min_prio)
164 {
165 wanted = min_prio;
166 }
167 else if (priority > (uint32_t)max_prio)
168 {
169 wanted = max_prio;
170 }
171 else
172 {
173 wanted = (int)priority;
174 }
175
176 rte_mem_set(&sched, 0, sizeof(sched));
177 sched.sched_priority = wanted;
178 if (pthread_attr_setinheritsched(attr, PTHREAD_EXPLICIT_SCHED) == 0)
179 {
180 if (pthread_attr_setschedpolicy(attr, SCHED_FIFO) == 0)
181 {
182 if (pthread_attr_setschedparam(attr, &sched) == 0)
183 {
184 applied = true;
185 }
186 }
187 }
188 }
189 return applied;
190}
191
194static void create_task_key(void)
195{
196 s_task_key_ready = (pthread_key_create(&s_task_key, NULL) == 0);
197}
198
201static bool task_key_ready(void)
202{
203 static pthread_once_t key_once = PTHREAD_ONCE_INIT;
204 bool ready = false;
205
206 if (pthread_once(&key_once, create_task_key) == 0)
207 {
208 ready = s_task_key_ready;
209 }
210 return ready;
211}
212
217{
218 struct timespec deadline;
219 bool running = (posix_period_init(&deadline) == RTE_STATUS_OK);
220 bool clock_failed = !running;
221
222 while (running && (state->should_stop == 0))
223 {
224 posix_period_advance(&deadline, state->config.period_ms);
225 if (posix_period_sleep_until(&deadline, &state->should_stop) != RTE_STATUS_OK)
226 {
227 clock_failed = true;
228 running = false;
229 }
230 else if (state->should_stop == 0)
231 {
232 state->config.entry(state->config.user_ctx);
233 }
234 else
235 {
236 running = false;
237 }
238 }
239
240 if (clock_failed)
241 {
242 /* OSA rule R3/R6: no monotonic time base - never pace from a guessed time 0. Report once; the controlling
243 * thread still joins this thread. */
244 POSIX_OSA_REPORT_RT_ATTRIBUTE("task monotonic clock failed - task stopped");
245 }
246}
247
249static void run_task(posix_task_state_t *state)
250{
251 if (state->config.period_ms == 0U)
252 {
253 /* Run-once task: entry() runs exactly once per start (once the
254 * self-check mark is set, see file header). should_stop is
255 * NOT consulted here - it exists to interrupt a *periodic* task
256 * between cycles. A one-shot task has no "between cycles" to
257 * interrupt, and a caller cannot meaningfully cancel work that
258 * hasn't started yet: rte_task_start() has already committed to
259 * running entry() once. suspend()/destroy() on a run-once task
260 * therefore just blocks (pthread_join) until this single call
261 * completes, rather than racing to skip it (and joins the
262 * thread even after entry() has returned).
263 *
264 * (Previously this branch also checked should_stop==0 before
265 * calling entry(), which created a genuine race: if suspend()/
266 * destroy() ran on another thread before this thread was first
267 * scheduled by the OS, should_stop could already be 1 here and
268 * entry() would be silently skipped - the task would appear to
269 * "do nothing" with no error reported anywhere.) */
270 state->config.entry(state->config.user_ctx);
271 }
272 else
273 {
274 run_periodic_task(state);
275 }
276}
277
280static void *task_thread_main(void *arg)
281{
283
284 if (pthread_setspecific(s_task_key, state) == 0)
285 {
286 run_task(state);
287 }
288 else
289 {
290 /* OSA rule R2/R6: no self check possible - an entry calling suspend/start on its own task could not be told
291 * apart from another thread. Report and call no entry; the controlling thread still joins this thread. */
292 POSIX_OSA_REPORT_RT_ATTRIBUTE("task thread-specific data not set - task not run");
293 }
294 /* The controlling thread learns that this thread ended from pthread_join(). */
295 return NULL;
296}
297
300static bool is_task_thread(const posix_task_state_t *state)
301{
302 return (pthread_getspecific(s_task_key) == (const void *)state);
303}
304
308static rte_status_t join_task_thread(posix_task_state_t *state)
309{
310 rte_status_t status = RTE_STATUS_OK;
311
312 if (state->joinable)
313 {
314 posix_period_wake_all(&state->should_stop);
315 if (pthread_join(state->thread, NULL) == 0)
316 {
317 state->joinable = false;
318 }
319 else
320 {
321 status = RTE_STATUS_INTERNAL_ERROR;
322 }
323 }
324 return status;
325}
326
330static rte_status_t create_task_thread(posix_task_state_t *state)
331{
332 rte_status_t status = RTE_STATUS_INTERNAL_ERROR;
333 pthread_attr_t attr;
334
335 if (pthread_attr_init(&attr) == 0)
336 {
337 bool inherit_fallback = false;
338 int rc = -1;
339
340 if (state->config.stack_size > 0U)
341 {
342 if (pthread_attr_setstacksize(&attr, state->config.stack_size) != 0)
343 {
344 /* OSA rule R1: fixed stack size - report that the configured size was refused. */
345 POSIX_OSA_REPORT_RT_ATTRIBUTE("task stack size not applied");
346 }
347 }
348
349 if (set_fifo_attributes(&attr, state->config.priority))
350 {
351 rc = pthread_create(&state->thread, &attr, task_thread_main, state);
352 if (rc == EPERM)
353 {
354 /* No privilege for the explicit SCHED_FIFO attributes; no thread was created. */
355 inherit_fallback = true;
356 }
357 }
358 else
359 {
360 inherit_fallback = true;
361 }
362
363 if (inherit_fallback)
364 {
365 /* OSA rule R1/R6: static real-time priority not applied - report once per start, then create the thread
366 * with PTHREAD_INHERIT_SCHED (ADR-018 fallback, stack size kept; see file header for Linux vs macOS). */
367 POSIX_OSA_REPORT_RT_ATTRIBUTE("task SCHED_FIFO priority not applied");
368 rc = -1;
369 if (pthread_attr_setinheritsched(&attr, PTHREAD_INHERIT_SCHED) == 0)
370 {
371 rc = pthread_create(&state->thread, &attr, task_thread_main, state);
372 }
373 }
374
375 if (rc == 0)
376 {
377 status = RTE_STATUS_OK;
378 }
379 (void)pthread_attr_destroy(&attr);
380 }
381 return status;
382}
383
384static rte_status_t osadapter_create(rte_task_storage_t *storage, const rte_task_config_t *config,
385 rte_task_handle_t *out_handle)
386{
387 posix_task_state_t *state;
388 bool ready;
389
390 if ((storage == NULL) || (config == NULL) || (out_handle == NULL) || (config->entry == NULL))
391 {
392 return RTE_STATUS_INVALID_PARAM;
393 }
394 ready = task_key_ready();
395 if (ready)
396 {
397 ready = posix_period_wake_ready();
398 }
399 if (!ready)
400 {
401 /* The self-check key or the period wake object could not be created (file header): no task without them;
402 * storage left untouched. */
403 return RTE_STATUS_INTERNAL_ERROR;
404 }
405
406 state = (posix_task_state_t *)(void *)storage;
407 rte_mem_set(state, 0, sizeof(*state));
408 state->config = *config;
409 state->self_handle = (rte_task_handle_t)(void *)storage;
410 *out_handle = state->self_handle;
411 return RTE_STATUS_OK;
412}
413
414static rte_status_t osadapter_suspend(rte_task_handle_t handle)
415{
416 posix_task_state_t *state = (posix_task_state_t *)(void *)handle;
417 rte_status_t status;
418
419 if (state == NULL)
420 {
421 return RTE_STATUS_INVALID_PARAM;
422 }
423 /* Set (and the waiting thread woken) before the self check, so a periodic task's loop ends whichever branch is
424 * taken. On the join branch join_task_thread() sets it and wakes once more, which is harmless. */
425 posix_period_wake_all(&state->should_stop);
426 if (is_task_thread(state))
427 {
428 /* Self-call rule (file header): the task thread cannot join itself. It ends after this entry call; the next
429 * start/suspend/destroy from another thread joins it. */
430 status = RTE_STATUS_OK;
431 }
432 else
433 {
434 status = join_task_thread(state);
435 }
436 return status;
437}
438
439static rte_status_t osadapter_start(rte_task_handle_t handle)
440{
441 posix_task_state_t *state = (posix_task_state_t *)(void *)handle;
442 rte_status_t status;
443
444 if (state == NULL)
445 {
446 return RTE_STATUS_INVALID_PARAM;
447 }
448
449 if (is_task_thread(state))
450 {
451 /* Self-call rule (file header): a restart from the task's own entry would leave this thread running next to
452 * the new one. Refused, nothing changed. */
453 status = RTE_STATUS_NOT_SUPPORTED;
454 }
455 else
456 {
457 /* Starting a task whose thread exists (running, or a run-once task whose entry has returned) means "restart
458 * from the top" - see file header: join the old thread before starting a fresh one. */
459 status = join_task_thread(state);
460 if (status == RTE_STATUS_OK)
461 {
462 state->should_stop = 0;
463 status = create_task_thread(state);
464 if (status == RTE_STATUS_OK)
465 {
466 state->joinable = true;
467 }
468 }
469 }
470 return status;
471}
472
473static rte_status_t osadapter_destroy(rte_task_handle_t handle)
474{
475 return osadapter_suspend(handle);
476}
477
478static const rte_osadapter_task_t s_posix_task_osadapter = { osadapter_create, osadapter_start, osadapter_suspend,
479 osadapter_destroy };
480
481const rte_osadapter_task_t *rte_posix_osadapter_task(void)
482{
483 return &s_posix_task_osadapter;
484}
Real POSIX/Linux OSAdapter for every OAL service (ADR-018).
const rte_osadapter_task_t * rte_posix_osadapter_task(void)
The POSIX rte_task OSAdapter (one pthread per task).
static rte_status_t osadapter_create(rte_ipc_storage_t *storage, const rte_ipc_config_t *config, rte_ipc_handle_t *out_handle)
rte_status_t posix_period_init(struct timespec *out_deadline)
Starts a period: reads CLOCK_MONOTONIC into the deadline.
void posix_period_wake_all(volatile sig_atomic_t *should_stop)
Requests a stop of a periodic thread and wakes it from its wait.
bool posix_period_wake_ready(void)
Creates the process-wide wake object on the first call; true once it exists.
rte_status_t posix_period_sleep_until(const struct timespec *deadline, const volatile sig_atomic_t *should_stop)
Waits until the absolute CLOCK_MONOTONIC deadline, or until a stop is requested.
void posix_period_advance(struct timespec *deadline, rte_duration_ms_t period_ms)
Moves the deadline one period later, keeping tv_nsec in [0, 1 s).
Private helper: pacing of a periodic thread by an absolute CLOCK_MONOTONIC deadline (OSA rule R3),...
Private helper: reports a real-time attribute this OSAdapter could not apply (OSA rules R1/R2/R6).
static void create_task_key(void)
static rte_status_t join_task_thread(posix_task_state_t *state)
static bool set_fifo_attributes(pthread_attr_t *attr, uint32_t priority)
static bool is_task_thread(const posix_task_state_t *state)
static void * task_thread_main(void *arg)
static void run_task(posix_task_state_t *state)
static bool task_key_ready(void)
static void run_periodic_task(posix_task_state_t *state)
static rte_status_t create_task_thread(posix_task_state_t *state)