GCC Code Coverage Report


Directory: Os/
File: Posix/Task.cpp
Date: 2026-09-03 21:15:10
Exec Total Coverage
Lines: 12 141 8.5%
Functions: 1 13 7.7%
Branches: 4 94 4.3%

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