| 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 | 425 | void* pthread_entry_wrapper(void* wrapper_pointer) { | |
| 30 | 425 | FW_ASSERT(wrapper_pointer != nullptr); | |
| 31 | // Both downcasts are safe because we know the types | ||
| 32 | 425 | Os::Task::TaskRoutineWrapper& wrapper = *reinterpret_cast<Os::Task::TaskRoutineWrapper*>(wrapper_pointer); | |
| 33 | #if defined(POSIX_THREADS_ENABLE_NAMES) && POSIX_THREADS_ENABLE_NAMES | ||
| 34 | 425 | auto handle = reinterpret_cast<Os::Posix::Task::PosixTaskHandle*>(wrapper.m_task.getHandle()); | |
| 35 | 425 | 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 | 425 | (void)set_task_name(pthread_self(), handle->m_name); | |
| 39 | #endif | ||
| 40 | 425 | wrapper.run(&wrapper); | |
| 41 | 425 | 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 | 425 | int set_task_name(pthread_t thread, char* name) { | |
| 138 | 425 | FW_ASSERT(name != nullptr); | |
| 139 | 425 | 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 | 425 | name[Os::Posix::Task::PosixTaskHandle::PTHREAD_NAME_LENGTH - 1] = '\0'; | |
| 147 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 425 times.
|
425 | status = pthread_setname_np(thread, name); |
| 148 | #endif | ||
| 149 | 425 | return status; | |
| 150 | } | ||
| 151 | |||
| 152 | 425 | Os::Task::Status PosixTask::create(const Os::Task::Arguments& arguments, | |
| 153 | const PosixTask::PermissionExpectation permissions) { | ||
| 154 | 425 | int pthread_status = PosixTaskHandle::SUCCESS; | |
| 155 | 425 | PosixTaskHandle& handle = this->m_handle; | |
| 156 | 425 | const bool expect_permission = (permissions == EXPECT_PERMISSION); | |
| 157 | // Initialize and clear pthread attributes | ||
| 158 | 425 | pthread_attr_t attributes; | |
| 159 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 425 times.
|
425 | (void)memset(&attributes, 0, sizeof(attributes)); |
| 160 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 425 times.
|
425 | pthread_status = pthread_attr_init(&attributes); |
| 161 | // Setting the stack size requires no special permission | ||
| 162 |
1/4✗ Branch 2 not taken.
✓ Branch 3 taken 425 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
425 | if ((arguments.m_stackSize != Os::Task::TASK_DEFAULT) && (pthread_status == PosixTaskHandle::SUCCESS)) { |
| 163 | ✗ | pthread_status = set_stack_size(attributes, arguments); | |
| 164 | } | ||
| 165 |
1/6✗ Branch 2 not taken.
✓ Branch 3 taken 425 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
425 | 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 |
1/6✗ Branch 2 not taken.
✓ Branch 3 taken 425 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
425 | 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 |
1/1✓ Branch 10 taken 425 times.
|
425 | (void)Fw::StringUtils::string_copy(handle.m_name, arguments.m_name.toChar(), sizeof(handle.m_name)); |
| 176 | #endif | ||
| 177 | |||
| 178 |
1/2✓ Branch 0 taken 425 times.
✗ Branch 1 not taken.
|
425 | if (pthread_status == PosixTaskHandle::SUCCESS) { |
| 179 | pthread_status = | ||
| 180 |
2/4✗ Branch 3 not taken.
✓ Branch 4 taken 425 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 425 times.
|
425 | 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 |
1/2✓ Branch 0 taken 425 times.
✗ Branch 1 not taken.
|
425 | if (pthread_status == PosixTaskHandle::SUCCESS) { |
| 184 | 425 | handle.m_is_valid = true; | |
| 185 | } | ||
| 186 | |||
| 187 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 425 times.
|
425 | (void)pthread_attr_destroy(&attributes); |
| 188 |
1/1✓ Branch 1 taken 425 times.
|
850 | return Posix::posix_status_to_task_status(pthread_status); |
| 189 | } | ||
| 190 | |||
| 191 | 425 | void PosixTask::onStart() {} | |
| 192 | |||
| 193 | 425 | Os::Task::Status PosixTask::start(const Arguments& arguments) { | |
| 194 | 425 | FW_ASSERT(arguments.m_routine != nullptr); | |
| 195 | |||
| 196 | // Try to create thread with assuming permissions | ||
| 197 | 425 | Os::Task::Status status = this->create(arguments, PermissionExpectation::EXPECT_PERMISSION); | |
| 198 | // Failure due to permission automatically retried | ||
| 199 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 425 times.
|
425 | 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 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 425 times.
|
425 | } 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 | 425 | return status; | |
| 221 | } | ||
| 222 | |||
| 223 | 425 | Os::Task::Status PosixTask::join() { | |
| 224 | 425 | Os::Task::Status status = Os::Task::Status::JOIN_ERROR; | |
| 225 |
2/4✗ Branch 3 not taken.
✓ Branch 4 taken 425 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 425 times.
|
425 | if (not this->m_handle.m_is_valid) { |
| 226 | ✗ | status = Os::Task::Status::INVALID_HANDLE; | |
| 227 | } else { | ||
| 228 | 425 | int stat = ::pthread_join(this->m_handle.m_task_descriptor, nullptr); | |
| 229 |
1/2✓ Branch 0 taken 425 times.
✗ Branch 1 not taken.
|
425 | status = (stat == PosixTaskHandle::SUCCESS) ? Os::Task::Status::OP_OK : Os::Task::Status::JOIN_ERROR; |
| 230 | } | ||
| 231 | 425 | return status; | |
| 232 | } | ||
| 233 | |||
| 234 | 425 | TaskHandle* PosixTask::getHandle() { | |
| 235 | 425 | 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 | 5728 | Os::Task::Status PosixTask::_delay(const Fw::TimeInterval& interval) { | |
| 249 | 5728 | Os::Task::Status task_status = Os::Task::OP_OK; | |
| 250 | 5728 | timespec sleep_interval; | |
| 251 |
1/1✓ Branch 4 taken 5728 times.
|
5728 | sleep_interval.tv_sec = interval.getSeconds(); |
| 252 |
1/1✓ Branch 4 taken 5728 times.
|
5728 | sleep_interval.tv_nsec = interval.getUSeconds() * 1000; |
| 253 | |||
| 254 | 5728 | timespec remaining_interval; | |
| 255 | 5728 | remaining_interval.tv_sec = 0; | |
| 256 | 5728 | remaining_interval.tv_nsec = 0; | |
| 257 | |||
| 258 | while (true) { | ||
| 259 |
1/1✓ Branch 1 taken 5728 times.
|
5728 | int status = nanosleep(&sleep_interval, &remaining_interval); |
| 260 | // Success, return ok | ||
| 261 |
1/2✓ Branch 0 taken 5728 times.
✗ Branch 1 not taken.
|
5728 | if (0 == status) { |
| 262 | 5728 | 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 | 11456 | return task_status; | |
| 276 | } | ||
| 277 | |||
| 278 | } // end namespace Task | ||
| 279 | } // end namespace Posix | ||
| 280 | } // end namespace Os | ||
| 281 |