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 
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)) {
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
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 
192 
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");
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 
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 
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.
241  FW_ASSERT(false);
242 }
243 
245  FW_ASSERT(false);
246 }
247 
249  Os::Task::Status task_status = Os::Task::OP_OK;
250  timespec sleep_interval;
251  sleep_interval.tv_sec = interval.getSeconds();
252  sleep_interval.tv_nsec = interval.getUSeconds() * 1000;
253 
254  timespec remaining_interval;
255  remaining_interval.tv_sec = 0;
256  remaining_interval.tv_nsec = 0;
257 
258  while (true) {
259  int status = nanosleep(&sleep_interval, &remaining_interval);
260  // Success, return ok
261  if (0 == status) {
262  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  return task_status;
276 }
277 
278 } // end namespace Task
279 } // end namespace Posix
280 } // end namespace Os
Status _delay(const Fw::TimeInterval &interval) override
delay the current task
Definition: Task.cpp:248
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
int set_priority_params(pthread_attr_t &attributes, const Os::Task::Arguments &arguments)
Definition: Task.cpp:79
void suspend(SuspensionType suspensionType) override
suspend the task given the suspension type
Definition: Task.cpp:240
static void log(const char *format,...)
log a formated string with supplied arguments
Definition: Logger.cpp:21
static constexpr int SUCCESS
Definition: Task.hpp:26
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:31
PermissionExpectation
Enumeration of permission expectations.
Definition: Task.hpp:41
pthread_t m_task_descriptor
Posix task descriptor.
Definition: Task.hpp:29
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:234
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:25
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:29
int set_stack_size(pthread_attr_t &attributes, const Os::Task::Arguments &arguments)
Definition: Task.cpp:44
U32 getUSeconds() const
static const int SCHED_POLICY
Definition: Task.cpp:22
Expect that you hold necessary permissions.
Definition: Task.hpp:42
Status join() override
block until the task has ended
Definition: Task.cpp:223
Status start(const Arguments &arguments) override
start the task
Definition: Task.cpp:193
FwTaskPriorityType m_priority
Definition: Task.hpp:98
int set_cpu_affinity(pthread_attr_t &attributes, const Os::Task::Arguments &arguments)
Definition: Task.cpp:114
void resume() override
resume a suspended task
Definition: Task.cpp:244
void onStart() override
perform required task start actions
Definition: Task.cpp:191
#define FW_ASSERT(...)
Definition: Assert.hpp:14
U32 getSeconds() const
int set_task_name(pthread_t thread, char *name)
Definition: Task.cpp:137