SafeAPI Backend POSIX
POSIX/Linux OAL backend for safeAPIFreamwork
Loading...
Searching...
No Matches
sapi_posix_backend_task.c
Go to the documentation of this file.
1#define _POSIX_C_SOURCE 200809L
2
29
30#include <errno.h>
31#include <pthread.h>
32#include <signal.h>
33#include "safeapi/oal/memory/sapi_mem_util.h"
34#include <time.h>
35
36typedef struct
37{
38 pthread_t thread;
39 sapi_task_config_t config;
40 volatile sig_atomic_t running;
41 volatile sig_atomic_t should_stop;
42 sapi_task_handle_t self_handle;
44
45/* C99-compatible static assert: posix_task_state_t must fit inside
46 * sapi_task_storage_t's reserved bytes. */
47typedef char posix_task_storage_fits_[(sizeof(posix_task_state_t) <= sizeof(sapi_task_storage_t)) ? 1 : -1];
48
49#if !defined(__linux__)
50static void ms_to_timespec(sapi_duration_ms_t ms, struct timespec *out_ts)
51{
52 out_ts->tv_sec = (time_t)(ms / 1000U);
53 out_ts->tv_nsec = (long)((ms % 1000U) * 1000000L);
54}
55#endif
56
57#if defined(__linux__)
58static void timespec_add_ms(struct timespec *ts, sapi_duration_ms_t ms)
59{
60 ts->tv_sec += (time_t)(ms / 1000U);
61 ts->tv_nsec += (long)((ms % 1000U) * 1000000L);
62 if (ts->tv_nsec >= 1000000000L)
63 {
64 ts->tv_sec += 1;
65 ts->tv_nsec -= 1000000000L;
66 }
67}
68#endif
69
74static void apply_priority_best_effort(pthread_t thread, uint32_t priority)
75{
76 struct sched_param sched;
77 int min_prio = sched_get_priority_min(SCHED_FIFO);
78 int max_prio = sched_get_priority_max(SCHED_FIFO);
79 int wanted;
80
81 if ((min_prio < 0) || (max_prio < 0))
82 {
83 return;
84 }
85
86 wanted = (int)priority;
87 if (wanted < min_prio)
88 {
89 wanted = min_prio;
90 }
91 if (wanted > max_prio)
92 {
93 wanted = max_prio;
94 }
95
96 sapi_mem_set(&sched, 0, sizeof(sched));
97 sched.sched_priority = wanted;
98 (void)pthread_setschedparam(thread, SCHED_FIFO, &sched);
99}
100
101static void *task_thread_main(void *arg)
102{
104
105 if (state->config.period_ms == 0U)
106 {
107 /* Run-once task: entry() always runs exactly once. should_stop is
108 * NOT consulted here - it exists to interrupt a *periodic* task
109 * between cycles. A one-shot task has no "between cycles" to
110 * interrupt, and a caller cannot meaningfully cancel work that
111 * hasn't started yet: sapi_task_start() has already committed to
112 * running entry() once. suspend()/destroy() on a run-once task
113 * therefore just blocks (pthread_join) until this single call
114 * completes, rather than racing to skip it.
115 *
116 * (Previously this branch also checked should_stop==0 before
117 * calling entry(), which created a genuine race: if suspend()/
118 * destroy() ran on another thread before this thread was first
119 * scheduled by the OS, should_stop could already be 1 here and
120 * entry() would be silently skipped - the task would appear to
121 * "do nothing" with no error reported anywhere.) */
122 state->config.entry(state->config.user_ctx);
123 }
124 else
125 {
126#if defined(__linux__)
127 struct timespec next_wake_ts;
128
129 if (clock_gettime(CLOCK_MONOTONIC, &next_wake_ts) != 0)
130 {
131 next_wake_ts.tv_sec = 0;
132 next_wake_ts.tv_nsec = 0;
133 }
134
135 while (state->should_stop == 0)
136 {
137 int rc;
138 timespec_add_ms(&next_wake_ts, state->config.period_ms);
139 do
140 {
141 rc = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_wake_ts, NULL);
142 } while ((rc == EINTR) && (state->should_stop == 0));
143
144 if (state->should_stop != 0)
145 {
146 break;
147 }
148
149 state->config.entry(state->config.user_ctx);
150 }
151#else
152 struct timespec period_ts;
153
154 ms_to_timespec(state->config.period_ms, &period_ts);
155
156 while (state->should_stop == 0)
157 {
158 struct timespec remaining = period_ts;
159 int rc;
160
161 do
162 {
163 rc = nanosleep(&remaining, &remaining);
164 } while ((rc != 0) && (errno == EINTR) && (state->should_stop == 0));
165
166 if (state->should_stop != 0)
167 {
168 break;
169 }
170
171 state->config.entry(state->config.user_ctx);
172 }
173#endif
174 }
175
176 state->running = 0;
177 return NULL;
178}
179
180static sapi_status_t backend_create(sapi_task_storage_t *storage, const sapi_task_config_t *config,
181 sapi_task_handle_t *out_handle)
182{
183 posix_task_state_t *state;
184
185 if ((storage == NULL) || (config == NULL) || (out_handle == NULL) || (config->entry == NULL))
186 {
187 return SAPI_STATUS_INVALID_PARAM;
188 }
189
190 state = (posix_task_state_t *)(void *)storage;
191 sapi_mem_set(state, 0, sizeof(*state));
192 state->config = *config;
193 state->self_handle = (sapi_task_handle_t)(void *)storage;
194 *out_handle = state->self_handle;
195 return SAPI_STATUS_OK;
196}
197
198static sapi_status_t backend_suspend(sapi_task_handle_t handle)
199{
200 posix_task_state_t *state = (posix_task_state_t *)(void *)handle;
201
202 if (state == NULL)
203 {
204 return SAPI_STATUS_INVALID_PARAM;
205 }
206 if (state->running != 0)
207 {
208 state->should_stop = 1;
209 (void)pthread_join(state->thread, NULL);
210 state->running = 0;
211 }
212 return SAPI_STATUS_OK;
213}
214
215static sapi_status_t backend_start(sapi_task_handle_t handle)
216{
217 posix_task_state_t *state = (posix_task_state_t *)(void *)handle;
218 sapi_status_t suspend_status;
219 pthread_attr_t attr;
220 pthread_attr_t *attr_ptr = NULL;
221
222 if (state == NULL)
223 {
224 return SAPI_STATUS_INVALID_PARAM;
225 }
226
227 /* Starting an already-running task means "restart from the top" -
228 * see file header. */
229 suspend_status = backend_suspend(handle);
230 if (suspend_status != SAPI_STATUS_OK)
231 {
232 return suspend_status;
233 }
234
235 state->should_stop = 0;
236
237 if (state->config.stack_size > 0U)
238 {
239 if (pthread_attr_init(&attr) == 0)
240 {
241 (void)pthread_attr_setstacksize(&attr, state->config.stack_size);
242 attr_ptr = &attr;
243 }
244 }
245
246 if (pthread_create(&state->thread, attr_ptr, task_thread_main, state) != 0)
247 {
248 if (attr_ptr != NULL)
249 {
250 (void)pthread_attr_destroy(attr_ptr);
251 }
252 return SAPI_STATUS_INTERNAL_ERROR;
253 }
254 state->running = 1;
255
256 apply_priority_best_effort(state->thread, state->config.priority);
257
258 if (attr_ptr != NULL)
259 {
260 (void)pthread_attr_destroy(attr_ptr);
261 }
262 return SAPI_STATUS_OK;
263}
264
265static sapi_status_t backend_destroy(sapi_task_handle_t handle)
266{
267 return backend_suspend(handle);
268}
269
270static const sapi_task_backend_t s_posix_task_backend = { backend_create, backend_start, backend_suspend,
271 backend_destroy };
272
273const sapi_task_backend_t *sapi_posix_backend_task(void)
274{
275 return &s_posix_task_backend;
276}
Real POSIX/Linux backend for every OAL service (ADR-018).
const sapi_task_backend_t * sapi_posix_backend_task(void)
The POSIX sapi_task backend (one pthread per task).
static void apply_priority_best_effort(pthread_t thread, uint32_t priority)