F´ Flight Software - C/C++ Documentation
A framework for building embedded system applications to NASA flight quality standards.
Task.cpp
Go to the documentation of this file.
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)) {
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
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
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 
217 
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");
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 
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 
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.
266  FW_ASSERT(false);
267 }
268 
270  FW_ASSERT(false);
271 }
272 
274  Os::Task::Status task_status = Os::Task::OP_OK;
275  timespec sleep_interval;
276  sleep_interval.tv_sec = interval.getSeconds();
277  sleep_interval.tv_nsec = interval.getUSeconds() * 1000;
278 
279  timespec remaining_interval;
280  remaining_interval.tv_sec = 0;
281  remaining_interval.tv_nsec = 0;
282 
283  while (true) {
284  int status = nanosleep(&sleep_interval, &remaining_interval);
285  // Success, return ok
286  if (0 == status) {
287  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  return task_status;
301 }
302 
303 } // end namespace Task
304 } // end namespace Posix
305 } // end namespace Os
Status _delay(const Fw::TimeInterval &interval) override
delay the current task
Definition: Task.cpp:273
static constexpr FwSizeType TASK_DEFAULT
Definition: Task.hpp:41
TaskHandle * getHandle() override
return the underlying task handle (implementation specific)
Definition: Task.cpp:187
Task handle representation.
Definition: Task.hpp:33
Operation succeeded.
Definition: Os.hpp:27
PlatformSizeType FwSizeType
const char * toChar() const
Convert to a C-style char*.
Definition: TaskString.hpp:45
#define PRI_FwSizeType
static constexpr FwTaskPriorityType TASK_PRIORITY_NON_REALTIME
Sentinel priority: run the task under SCHED_OTHER (non-realtime) rather than SCHED_RR.
Definition: Task.hpp:48
int set_priority_params(pthread_attr_t &attributes, const Os::Task::Arguments &arguments)
Definition: Task.cpp:80
void suspend(SuspensionType suspensionType) override
suspend the task given the suspension type
Definition: Task.cpp:265
static void log(const char *format,...)
log a formated string with supplied arguments
Definition: Logger.cpp:21
static constexpr int SUCCESS
Definition: Task.hpp:27
int set_non_realtime_params(pthread_attr_t &attributes)
Definition: Task.cpp:115
static constexpr FwTaskPriorityType TASK_PRIORITY_DEFAULT
Definition: Task.hpp:47
Task::Status posix_status_to_task_status(int posix_status)
Definition: error.cpp:147
message sent/received okay
Definition: Task.hpp:50
bool m_is_valid
Is the above descriptor valid.
Definition: Task.hpp:32
PermissionExpectation
Enumeration of permission expectations.
Definition: Task.hpp:42
pthread_t m_task_descriptor
Posix task descriptor.
Definition: Task.hpp:30
char * string_copy(char *destination, const char *source, FwSizeType num)
copy string with null-termination guaranteed
Definition: StringUtils.cpp:7
TaskHandle * getHandle() override
return the underlying task handle (implementation specific)
Definition: Task.cpp:259
static void run(void *task_pointer)
run the task routine wrapper
Definition: Task.cpp:29
static constexpr FwSizeType PTHREAD_NAME_LENGTH
Length of pthread name.
Definition: Task.hpp:26
Wrapper for task routine that ensures onStart() is called once the task actually begins.
Definition: Task.hpp:213
Task & m_task
Reference to owning task.
Definition: Task.hpp:227
const Os::TaskString m_name
Definition: Task.hpp:95
void * pthread_entry_wrapper(void *wrapper_pointer)
Definition: Task.cpp:30
int set_stack_size(pthread_attr_t &attributes, const Os::Task::Arguments &arguments)
Definition: Task.cpp:45
U32 getUSeconds() const
static const int SCHED_POLICY
Definition: Task.cpp:22
Expect that you hold necessary permissions.
Definition: Task.hpp:43
Status join() override
block until the task has ended
Definition: Task.cpp:248
Status start(const Arguments &arguments) override
start the task
Definition: Task.cpp:218
FwTaskPriorityType m_priority
Definition: Task.hpp:98
int set_cpu_affinity(pthread_attr_t &attributes, const Os::Task::Arguments &arguments)
Definition: Task.cpp:135
void resume() override
resume a suspended task
Definition: Task.cpp:269
void onStart() override
perform required task start actions
Definition: Task.cpp:216
static const int SCHED_POLICY_NON_REALTIME
Definition: Task.cpp:23
#define FW_ASSERT(...)
Definition: Assert.hpp:14
U32 getSeconds() const
int set_task_name(pthread_t thread, char *name)
Definition: Task.cpp:158