GCC Code Coverage Report


Directory: Os/
File: Posix/Task.cpp
Date: 2026-09-23 21:11:56
Exec Total Coverage
Lines: 12 156 7.7%
Functions: 1 14 7.1%
Branches: 4 112 3.6%

Line Branch Exec Source
1 // ======================================================================
2 // \title Os/Posix/Task.cpp
3 // \brief implementation of Posix implementation of Os::Task
4 // ======================================================================
5 #include <pthread.h>
6 #include <unistd.h>
7 #include <cerrno>
8 #include <climits>
9 #include <cstring>
10
11 #include "Fw/Logger/Logger.hpp"
12 #include "Fw/Types/Assert.hpp"
13 #include "Fw/Types/StringUtils.hpp"
14 #include "Os/Posix/Task.hpp"
15 #include "Os/Posix/error.hpp"
16 #include "Os/Task.hpp"
17
18 namespace Os {
19 namespace Posix {
20 namespace Task {
21 std::atomic<bool> PosixTask::s_permissions_reported(false);
22 static const int SCHED_POLICY = SCHED_RR;
23 static const int SCHED_POLICY_NON_REALTIME = SCHED_OTHER;
24
25 typedef void* (*pthread_func_ptr)(void*);
26
27 // Forward declaration
28 int set_task_name(pthread_t thread, char* name);
29
30 ✗ void* pthread_entry_wrapper(void* wrapper_pointer) {
31 ✗ FW_ASSERT(wrapper_pointer != nullptr);
32 // Both downcasts are safe because we know the types
33 ✗ Os::Task::TaskRoutineWrapper& wrapper = *reinterpret_cast<Os::Task::TaskRoutineWrapper*>(wrapper_pointer);
34 #if defined(POSIX_THREADS_ENABLE_NAMES) && POSIX_THREADS_ENABLE_NAMES
35 ✗ auto handle = reinterpret_cast<Os::Posix::Task::PosixTaskHandle*>(wrapper.m_task.getHandle());
36 ✗ FW_ASSERT(handle != nullptr);
37 // Task name is on a best effort basis. Use pthread_self() since the handle's task
38 // descriptor is written by pthread_create concurrently with this thread's start.
39 ✗ (void)set_task_name(pthread_self(), handle->m_name);
40 #endif
41 ✗ wrapper.run(&wrapper);
42 ✗ return nullptr;
43 }
44
45 ✗ int set_stack_size(pthread_attr_t& attributes, const Os::Task::Arguments& arguments) {
46 ✗ int status = PosixTaskHandle::SUCCESS;
47 ✗ FwSizeType stack = arguments.m_stackSize;
48 // Check for stack size multiple of page size or skip when the function
49 // is unavailable.
50 #ifdef _SC_PAGESIZE
51 ✗ long page_size = sysconf(_SC_PAGESIZE);
52 #else
53 long page_size = -1; // Force skip and warning
54 #endif
55 ✗ if (page_size <= 0) {
56 ✗ Fw::Logger::log("[WARNING] %s could not determine page size %s. Skipping stack-size check.\n",
57 ✗ const_cast<CHAR*>(arguments.m_name.toChar()), strerror(errno));
58 ✗ } else if ((stack % static_cast<FwSizeType>(page_size)) != 0) {
59 // Round-down to nearest page size multiple
60 ✗ FwSizeType rounded = (stack / static_cast<FwSizeType>(page_size)) * static_cast<FwSizeType>(page_size);
61 ✗ Fw::Logger::log("[WARNING] %s stack size of %" PRI_FwSizeType
62 " is not multiple of page size %ld, rounding to %" PRI_FwSizeType "\n",
63 ✗ const_cast<CHAR*>(arguments.m_name.toChar()), stack, page_size, rounded);
64 ✗ stack = rounded;
65 }
66
67 // Clamp invalid stack sizes
68 ✗ if (stack <= static_cast<FwSizeType>(PTHREAD_STACK_MIN)) {
69 ✗ Fw::Logger::log(
70 "[WARNING] %s stack size of %" PRI_FwSizeType " is too small, clamping to %" PRI_FwSizeType "\n",
71 ✗ const_cast<CHAR*>(arguments.m_name.toChar()), stack, static_cast<FwSizeType>(PTHREAD_STACK_MIN));
72 ✗ stack = static_cast<FwSizeType>(PTHREAD_STACK_MIN);
73 }
74 // Clamping above guarantees a valid minimum stack size
75 ✗ FW_ASSERT(stack >= static_cast<FwSizeType>(PTHREAD_STACK_MIN), static_cast<FwAssertArgType>(stack));
76 ✗ status = pthread_attr_setstacksize(&attributes, static_cast<size_t>(stack));
77 ✗ return status;
78 }
79
80 ✗ int set_priority_params(pthread_attr_t& attributes, const Os::Task::Arguments& arguments) {
81 ✗ const FwSizeType min_priority = static_cast<FwSizeType>(sched_get_priority_min(SCHED_POLICY));
82 ✗ const FwSizeType max_priority = static_cast<FwSizeType>(sched_get_priority_max(SCHED_POLICY));
83 ✗ int status = PosixTaskHandle::SUCCESS;
84 ✗ FwSizeType priority = arguments.m_priority;
85 // Clamp to minimum priority
86 ✗ if (priority < min_priority) {
87 ✗ Fw::Logger::log("[WARNING] %s low task priority of %" PRI_FwSizeType " clamped to %" PRI_FwSizeType "\n",
88 ✗ const_cast<CHAR*>(arguments.m_name.toChar()), priority, min_priority);
89 ✗ priority = min_priority;
90 }
91 // Clamp to maximum priority
92 ✗ else if (priority > max_priority) {
93 ✗ Fw::Logger::log("[WARNING] %s high task priority of %" PRI_FwSizeType " clamped to %" PRI_FwSizeType "\n",
94 ✗ const_cast<CHAR*>(arguments.m_name.toChar()), priority, max_priority);
95 ✗ priority = max_priority;
96 }
97
98 // Clamping above guarantees the priority is within the policy's valid range
99 ✗ FW_ASSERT(priority >= min_priority && priority <= max_priority, static_cast<FwAssertArgType>(priority));
100
101 // Set attributes required for priority
102 ✗ status = pthread_attr_setschedpolicy(&attributes, SCHED_POLICY);
103 ✗ if (status == PosixTaskHandle::SUCCESS) {
104 ✗ status = pthread_attr_setinheritsched(&attributes, PTHREAD_EXPLICIT_SCHED);
105 }
106 ✗ if (status == PosixTaskHandle::SUCCESS) {
107 ✗ sched_param schedParam;
108 ✗ (void)memset(&schedParam, 0, sizeof(sched_param));
109 ✗ schedParam.sched_priority = static_cast<int>(priority);
110 ✗ status = pthread_attr_setschedparam(&attributes, &schedParam);
111 }
112 ✗ return status;
113 }
114
115 ✗ int set_non_realtime_params(pthread_attr_t& attributes) {
116 // Lowest priority of the non-realtime policy (0 on Linux). Set explicitly: otherwise the creating thread's
117 // (possibly realtime) priority is inherited and rejected by pthread_create when combined with SCHED_OTHER.
118 ✗ const int priority = sched_get_priority_min(SCHED_POLICY_NON_REALTIME);
119 ✗ int status = (priority >= 0) ? PosixTaskHandle::SUCCESS : errno;
120 ✗ if (status == PosixTaskHandle::SUCCESS) {
121 ✗ status = pthread_attr_setschedpolicy(&attributes, SCHED_POLICY_NON_REALTIME);
122 }
123 ✗ if (status == PosixTaskHandle::SUCCESS) {
124 ✗ status = pthread_attr_setinheritsched(&attributes, PTHREAD_EXPLICIT_SCHED);
125 }
126 ✗ if (status == PosixTaskHandle::SUCCESS) {
127 ✗ sched_param schedParam;
128 ✗ (void)memset(&schedParam, 0, sizeof(sched_param));
129 ✗ schedParam.sched_priority = priority;
130 ✗ status = pthread_attr_setschedparam(&attributes, &schedParam);
131 }
132 ✗ return status;
133 }
134
135 ✗ int set_cpu_affinity(pthread_attr_t& attributes, const Os::Task::Arguments& arguments) {
136 ✗ int status = 0;
137 // pthread_attr_setaffinity_np is a non-POSIX function. Notably, it is not available on musl.
138 // Limit its use to builds that involve glibc, on Linux, with _GNU_SOURCE defined.
139 // That's the circumstance in which we expect this feature to work.
140 #if defined(TGT_OS_TYPE_LINUX) && defined(__GLIBC__) && defined(_GNU_SOURCE)
141 ✗ const FwSizeType affinity = arguments.m_cpuAffinity;
142 // CPU_SET is undefined for indices at or beyond CPU_SETSIZE
143 ✗ FW_ASSERT(affinity < static_cast<FwSizeType>(CPU_SETSIZE), static_cast<FwAssertArgType>(affinity));
144 ✗ cpu_set_t cpu_set;
145 ✗ CPU_ZERO(&cpu_set);
146 ✗ CPU_SET(static_cast<int>(affinity), &cpu_set);
147
148 // According to the man-page this function sets errno rather than returning an error status like other functions
149 ✗ status = pthread_attr_setaffinity_np(&attributes, sizeof(cpu_set_t), &cpu_set);
150 ✗ status = (status == PosixTaskHandle::SUCCESS) ? status : errno;
151 #else
152 Fw::Logger::log("[WARNING] %s setting CPU affinity is only available with GNU pthreads\n",
153 const_cast<CHAR*>(arguments.m_name.toChar()));
154 #endif
155 ✗ return status;
156 }
157
158 ✗ int set_task_name(pthread_t thread, char* name) {
159 ✗ FW_ASSERT(name != nullptr);
160 ✗ int status = 0;
161 // pthread_setname_np is a non-POSIX function.
162 // Limit its use to builds that involve glibc, on Linux, with _GNU_SOURCE defined.
163 // That's the circumstance in which we expect this feature to work.
164 #if defined(TGT_OS_TYPE_LINUX) && defined(__GLIBC__) && defined(_GNU_SOURCE) && defined(POSIX_THREADS_ENABLE_NAMES) && \
165 POSIX_THREADS_ENABLE_NAMES
166 // Force safe name usage
167 ✗ name[Os::Posix::Task::PosixTaskHandle::PTHREAD_NAME_LENGTH - 1] = '\0';
168 ✗ status = pthread_setname_np(thread, name);
169 #endif
170 ✗ return status;
171 }
172
173 ✗ Os::Task::Status PosixTask::create(const Os::Task::Arguments& arguments,
174 const PosixTask::PermissionExpectation permissions) {
175 ✗ int pthread_status = PosixTaskHandle::SUCCESS;
176 ✗ PosixTaskHandle& handle = this->m_handle;
177 ✗ const bool expect_permission = (permissions == EXPECT_PERMISSION);
178 // Initialize and clear pthread attributes
179 ✗ pthread_attr_t attributes;
180 ✗ (void)memset(&attributes, 0, sizeof(attributes));
181 ✗ pthread_status = pthread_attr_init(&attributes);
182 // Setting the stack size requires no special permission
183 ✗ if ((arguments.m_stackSize != Os::Task::TASK_DEFAULT) && (pthread_status == PosixTaskHandle::SUCCESS)) {
184 ✗ pthread_status = set_stack_size(attributes, arguments);
185 }
186 // Non-realtime scheduling requires no special permission; realtime priorities do
187 ✗ if ((arguments.m_priority == PosixTask::TASK_PRIORITY_NON_REALTIME) &&
188 (pthread_status == PosixTaskHandle::SUCCESS)) {
189 ✗ pthread_status = set_non_realtime_params(attributes);
190 ✗ } else if ((arguments.m_priority != Os::Task::TASK_PRIORITY_DEFAULT) && (expect_permission) &&
191 (pthread_status == PosixTaskHandle::SUCCESS)) {
192 ✗ pthread_status = set_priority_params(attributes, arguments);
193 }
194 ✗ if ((arguments.m_cpuAffinity != Os::Task::TASK_DEFAULT) && (expect_permission) &&
195 (pthread_status == PosixTaskHandle::SUCCESS)) {
196 ✗ pthread_status = set_cpu_affinity(attributes, arguments);
197 }
198 #if defined(POSIX_THREADS_ENABLE_NAMES) && POSIX_THREADS_ENABLE_NAMES
199 // Copy the name before the thread starts, since the new thread reads it
200 ✗ (void)Fw::StringUtils::string_copy(handle.m_name, arguments.m_name.toChar(), sizeof(handle.m_name));
201 #endif
202
203 ✗ if (pthread_status == PosixTaskHandle::SUCCESS) {
204 pthread_status =
205 ✗ pthread_create(&handle.m_task_descriptor, &attributes, pthread_entry_wrapper, arguments.m_routine_argument);
206 }
207 // Successful execution of all precious steps will result in a valid task handle
208 ✗ if (pthread_status == PosixTaskHandle::SUCCESS) {
209 ✗ handle.m_is_valid = true;
210 }
211
212 ✗ (void)pthread_attr_destroy(&attributes);
213 ✗ return Posix::posix_status_to_task_status(pthread_status);
214 }
215
216 ✗ void PosixTask::onStart() {}
217
218 ✗ Os::Task::Status PosixTask::start(const Arguments& arguments) {
219 ✗ FW_ASSERT(arguments.m_routine != nullptr);
220
221 // Try to create thread with assuming permissions
222 ✗ Os::Task::Status status = this->create(arguments, PermissionExpectation::EXPECT_PERMISSION);
223 // Failure due to permission automatically retried
224 ✗ if (status == Os::Task::Status::ERROR_PERMISSION) {
225 ✗ if (not PosixTask::s_permissions_reported) {
226 ✗ Fw::Logger::log("\n");
227 ✗ Fw::Logger::log("[NOTE] Task Permissions:\n");
228 ✗ Fw::Logger::log("[NOTE]\n");
229 ✗ Fw::Logger::log(
230 "[NOTE] You have insufficient permissions to create a task with priority and/or cpu affinity.\n");
231 ✗ Fw::Logger::log("[NOTE] A task without priority and affinity will be created.\n");
232 ✗ Fw::Logger::log("[NOTE]\n");
233 ✗ Fw::Logger::log("[NOTE] There are three possible resolutions:\n");
234 ✗ Fw::Logger::log("[NOTE] 1. Use tasks without priority and affinity using parameterless start()\n");
235 ✗ Fw::Logger::log("[NOTE] 2. Run this executable as a user with task priority permission\n");
236 ✗ Fw::Logger::log("[NOTE] 3. Grant capability with \"setcap 'cap_sys_nice=eip'\" or equivalent\n");
237 ✗ Fw::Logger::log("\n");
238 ✗ PosixTask::s_permissions_reported = true;
239 }
240 // Fallback with no permission
241 ✗ status = this->create(arguments, PermissionExpectation::EXPECT_NO_PERMISSION);
242 ✗ } else if (status != Os::Task::Status::OP_OK) {
243 ✗ Fw::Logger::log("[ERROR] Failed to create task with status: %d", static_cast<int>(status));
244 }
245 ✗ return status;
246 }
247
248 ✗ Os::Task::Status PosixTask::join() {
249 ✗ Os::Task::Status status = Os::Task::Status::JOIN_ERROR;
250 ✗ if (not this->m_handle.m_is_valid) {
251 ✗ status = Os::Task::Status::INVALID_HANDLE;
252 } else {
253 ✗ int stat = ::pthread_join(this->m_handle.m_task_descriptor, nullptr);
254 ✗ status = (stat == PosixTaskHandle::SUCCESS) ? Os::Task::Status::OP_OK : Os::Task::Status::JOIN_ERROR;
255 }
256 ✗ return status;
257 }
258
259 ✗ TaskHandle* PosixTask::getHandle() {
260 ✗ return &this->m_handle;
261 }
262
263 // Note: not implemented for Posix threads. Must be manually done using a mutex or other blocking construct as there
264 // is no top-level pthreads support for suspend and resume.
265 ✗ void PosixTask::suspend(Os::Task::SuspensionType suspensionType) {
266 ✗ FW_ASSERT(false);
267 ✗ }
268
269 ✗ void PosixTask::resume() {
270 ✗ FW_ASSERT(false);
271 ✗ }
272
273 1 Os::Task::Status PosixTask::_delay(const Fw::TimeInterval& interval) {
274 1 Os::Task::Status task_status = Os::Task::OP_OK;
275 1 timespec sleep_interval;
276
1/1
✓ Branch 4 taken 1 times.
1 sleep_interval.tv_sec = interval.getSeconds();
277
1/1
✓ Branch 4 taken 1 times.
1 sleep_interval.tv_nsec = interval.getUSeconds() * 1000;
278
279 1 timespec remaining_interval;
280 1 remaining_interval.tv_sec = 0;
281 1 remaining_interval.tv_nsec = 0;
282
283 while (true) {
284
1/1
✓ Branch 1 taken 1 times.
1 int status = nanosleep(&sleep_interval, &remaining_interval);
285 // Success, return ok
286
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (0 == status) {
287 1 break;
288 }
289 // Interrupted, reset sleep and iterate
290 ✗ else if (EINTR == errno) {
291 ✗ sleep_interval = remaining_interval;
292 ✗ continue;
293 }
294 // Anything else is an error
295 else {
296 ✗ task_status = Os::Task::Status::DELAY_ERROR;
297 ✗ break;
298 }
299 ✗ }
300 2 return task_status;
301 }
302
303 } // end namespace Task
304 } // end namespace Posix
305 } // end namespace Os
306