tin  1.5.9
linux.h
Go to the documentation of this file.
1 // Copyright (c) 2026 Tinverse LLC. All rights reserved.
2 // SPDX-License-Identifier: LicenseRef-Tinverse-Commercial
3 
10 
11 #pragma once
12 
13 #include <atomic>
14 #include <cerrno>
15 #include <chrono>
16 #include <cstdio>
17 #include <cstring>
18 #include <pthread.h>
19 #include <sched.h>
20 #include <sys/epoll.h>
21 #include <sys/eventfd.h>
22 #include <sys/mman.h>
23 #include <sys/utsname.h>
24 #include <thread>
25 #include <time.h>
26 #include <type_traits>
27 #include <unistd.h>
28 
29 #include "tsm/chrono_ticks.h"
30 #include "tsm/runtime/executor.h"
31 
32 namespace tsm::detail {
33 
40 struct linux_syscalls
41 {
42  static int clock_gettime(clockid_t clock_id, struct timespec* ts) noexcept
43  {
44  return ::clock_gettime(clock_id, ts);
45  }
46 
47  static int nanosleep(struct timespec const* request,
48  struct timespec* remaining) noexcept
49  {
50  return ::nanosleep(request, remaining);
51  }
52 
53  static FILE* fopen(char const* path, char const* mode) noexcept
54  {
55  return std::fopen(path, mode);
56  }
57 
58  static int fgetc(FILE* file) noexcept
59  {
60  return std::fgetc(file);
61  }
62 
63  static int fclose(FILE* file) noexcept
64  {
65  return std::fclose(file);
66  }
67 
68  static int uname(struct utsname* kernel) noexcept
69  {
70  return ::uname(kernel);
71  }
72 
73  static pthread_t pthread_self() noexcept
74  {
75  return ::pthread_self();
76  }
77 
78  static int pthread_setschedparam(pthread_t thread,
79  int policy,
80  struct sched_param const* param) noexcept
81  {
82  return ::pthread_setschedparam(thread, policy, param);
83  }
84 
85  static int pthread_setaffinity_np(pthread_t thread,
86  std::size_t cpusetsize,
87  cpu_set_t const* cpuset) noexcept
88  {
89  return ::pthread_setaffinity_np(thread, cpusetsize, cpuset);
90  }
91 
92  static int mlockall(int flags) noexcept
93  {
94  return ::mlockall(flags);
95  }
96 
97  static void perror(char const* message) noexcept
98  {
99  std::perror(message);
100  }
101 
102  static int eventfd(unsigned int initval, int flags) noexcept
103  {
104  return ::eventfd(initval, flags);
105  }
106 
107  static int epoll_create1(int flags) noexcept
108  {
109  return ::epoll_create1(flags);
110  }
111 
112  static int epoll_ctl(int epfd,
113  int op,
114  int fd,
115  struct epoll_event* event) noexcept
116  {
117  return ::epoll_ctl(epfd, op, fd, event);
118  }
119 
120  static int epoll_wait(int epfd,
121  struct epoll_event* events,
122  int maxevents,
123  int timeout) noexcept
124  {
125  return ::epoll_wait(epfd, events, maxevents, timeout);
126  }
127 
128  static ssize_t read(int fd, void* buffer, std::size_t count) noexcept
129  {
130  return ::read(fd, buffer, count);
131  }
132 
133  static ssize_t write(int fd, void const* buffer, std::size_t count) noexcept
134  {
135  return ::write(fd, buffer, count);
136  }
137 
138  static int close(int fd) noexcept
139  {
140  return ::close(fd);
141  }
142 };
143 
146 struct AccurateClock
147 {
148  using duration = std::chrono::nanoseconds;
149  using period = duration::period;
150  using rep = duration::rep;
151  using time_point = std::chrono::time_point<AccurateClock>;
152 
153  static constexpr bool is_steady = true;
154 
155  static time_point now() noexcept
156  {
157  struct timespec ts;
158  linux_syscalls::clock_gettime(CLOCK_MONOTONIC, &ts);
159  auto duration_since_epoch = std::chrono::seconds(ts.tv_sec) +
160  std::chrono::nanoseconds(ts.tv_nsec);
161  return time_point(duration(duration_since_epoch));
162  }
163 };
164 
170 template<typename Clock = std::chrono::steady_clock,
171  typename Duration = typename Clock::duration>
172 struct Timer
173 {
174  using ClockType = Clock;
175  using DurationType = Duration;
176  void start()
177  {
178  start_time_ = Clock::now();
179  started_ = true;
180  }
181 
182  Duration elapsed() const
183  {
184  if (!started_) {
185  return Duration(0);
186  }
187  auto now = Clock::now();
188  auto interval = now - start_time_;
189  return std::chrono::duration_cast<Duration>(interval);
190  }
191 
192  template<typename ToDuration = Duration>
193  Duration elapsed(ToDuration since) const
194  {
195  auto now = Clock::now();
196  if constexpr (std::is_same<ToDuration, Duration>::value) {
197  return std::chrono::duration_cast<Duration>(now - since);
198  } else {
199  return std::chrono::duration_cast<ToDuration>(now - since);
200  }
201  }
202 
203  template<typename ToDuration>
204  ToDuration elapsed() const
205  {
206  return std::chrono::duration_cast<ToDuration>(elapsed());
207  }
208 
209  bool started() const
210  {
211  return started_;
212  }
213  void reset()
214  {
215  start_time_ = Clock::now();
216  }
217  void stop()
218  {
219  started_ = false;
220  }
221 
222  protected:
223  typename Clock::time_point start_time_;
224  bool started_{ false };
225 };
226 
227 template<typename Clock = std::chrono::steady_clock,
228  typename Duration = typename Clock::duration>
229 struct IntervalTimer : public Timer<Clock, Duration>
230 {
231  Duration interval()
232  {
233  if (!this->started()) {
234  this->start();
235  return Duration(0);
236  }
237  auto now = Clock::now();
238  auto interval = now - this->start_time_;
239  this->start_time_ = now;
240  return std::chrono::duration_cast<Duration>(interval);
241  }
242 };
243 
248 template<typename Clock = AccurateClock,
249  typename Duration = typename Clock::duration,
250  typename Sys = linux_syscalls>
251 struct PeriodicSleepTimer : public Timer<Clock, Duration>
252 {
253  PeriodicSleepTimer(Duration period = Duration(1))
254  : period_(period)
255  {
256  }
257  void start()
258  {
259  Timer<Clock, Duration>::start();
260  }
261 
262  void wait()
263  {
264  auto remaining = period_ - Timer<Clock, Duration>::elapsed();
265  struct timespec ts;
266  ts.tv_sec =
267  std::chrono::duration_cast<std::chrono::seconds>(remaining).count();
268  ts.tv_nsec =
269  std::chrono::duration_cast<std::chrono::nanoseconds>(remaining)
270  .count();
271 
272  struct timespec remaining_ts;
273  while (Sys::nanosleep(&ts, &remaining_ts) != 0 && errno == EINTR) {
274  ts = remaining_ts;
275  }
276  Timer<Clock, Duration>::reset();
277  }
278 
279  Duration get_period() const
280  {
281  return period_;
282  }
283 
284  protected:
285  Duration period_;
286 };
287 
289 template<typename Sys>
290 struct basic_realtime_configurator
291 {
292  basic_realtime_configurator() = default;
293  basic_realtime_configurator(int priority, std::array<int, 4> affinity)
294  : PROCESS_PRIORITY(priority)
295  , CPU_AFFINITY(affinity)
296  {
297  }
298 
299  static bool running_realtime_kernel()
300  {
301  if (auto* realtime = Sys::fopen("/sys/kernel/realtime", "r")) {
302  int value = Sys::fgetc(realtime);
303  Sys::fclose(realtime);
304  return value == '1';
305  }
306 
307  struct utsname kernel
308  {};
309  if (Sys::uname(&kernel) != 0) {
310  return false;
311  }
312  return std::strstr(kernel.release, "-rt") != nullptr ||
313  std::strstr(kernel.version, "PREEMPT_RT") != nullptr;
314  }
315 
316  void config_realtime_thread()
317  {
318  if (!running_realtime_kernel()) {
319  return;
320  }
321 
322  struct sched_param param;
323  param.sched_priority = PROCESS_PRIORITY - 3;
324  const auto sched_error =
325  Sys::pthread_setschedparam(Sys::pthread_self(), SCHED_RR, &param);
326  if (sched_error != 0 && sched_error != EPERM) {
327  Sys::perror("pthread_setschedparam");
328  }
329 
330  cpu_set_t cpuset;
331  CPU_ZERO(&cpuset);
332  for (auto cpu : CPU_AFFINITY) {
333  CPU_SET(cpu, &cpuset);
334  }
335 
336  if (Sys::pthread_setaffinity_np(
337  Sys::pthread_self(), sizeof(cpu_set_t), &cpuset) != 0) {
338  Sys::perror("sched_setaffinity");
339  }
340 
341  if (Sys::mlockall(MCL_CURRENT | MCL_FUTURE) == -1) {
342  Sys::perror("mlockall failed");
343  }
344  }
345 
346  template<typename Fn>
347  std::thread real_time_thread(Fn fn)
348  {
349  return std::thread([this, fn] {
350  this->config_realtime_thread();
351  fn();
352  });
353  }
354 
355  std::thread make_real_time(std::thread&& t)
356  {
357  config_realtime_thread();
358  return std::move(t);
359  }
360 
361  protected:
362  int PROCESS_PRIORITY{ 98 };
363  std::array<int, 4> CPU_AFFINITY{ 0, 1, 2, 3 };
364 };
365 
366 using RealtimeConfigurator = basic_realtime_configurator<linux_syscalls>;
367 
373 struct host_thread_policy
374 {
375  void configure_current_thread() {}
376 };
377 
378 template<typename TickPeriod = std::chrono::milliseconds>
381 
386 template<typename Sys = linux_syscalls>
387 struct basic_host_realtime_policy : basic_realtime_configurator<Sys>
388 {
389  using basic_realtime_configurator<Sys>::basic_realtime_configurator;
390 
391  void configure_current_thread()
392  {
393  this->config_realtime_thread();
394  }
395 };
396 
397 using host_realtime_policy = basic_host_realtime_policy<linux_syscalls>;
398 
399 template<typename ThreadPolicy, typename Sys, typename... Tasks>
400 class basic_thread_executor
401 {
402  public:
403  explicit basic_thread_executor(Tasks&... tasks)
404  : ready_(tasks...)
405  {
406  event_fd_ = Sys::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
407  epoll_fd_ = Sys::epoll_create1(EPOLL_CLOEXEC);
408  if (event_fd_ >= 0 && epoll_fd_ >= 0) {
409  epoll_event event{};
410  event.events = EPOLLIN;
411  event.data.fd = event_fd_;
412  static_cast<void>(
413  Sys::epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, event_fd_, &event));
414  }
415  }
416 
417  basic_thread_executor(basic_thread_executor const&) = delete;
418  basic_thread_executor& operator=(basic_thread_executor const&) = delete;
419 
420  ~basic_thread_executor()
421  {
422  stop();
423  if (event_fd_ >= 0) {
424  Sys::close(event_fd_);
425  }
426  if (epoll_fd_ >= 0) {
427  Sys::close(epoll_fd_);
428  }
429  }
430 
431  void start()
432  {
433  if (worker_.joinable()) {
434  return;
435  }
436  stop_requested_.store(false);
437  worker_ = std::thread([this] {
438  policy_.configure_current_thread();
439  while (!stop_requested_.load()) {
440  // The worker drains cooperative work to quiescence, then waits
441  // for an OS wake event. HSM dispatch remains runtime-owned.
442  if (ready_.run_ready() == 0U) {
443  wait_for_work();
444  }
445  }
446  });
447  }
448 
449  void stop()
450  {
451  stop_requested_.store(true);
452  wake();
453  if (worker_.joinable()) {
454  worker_.join();
455  }
456  }
457 
458  void wake()
459  {
460  if (event_fd_ < 0) {
461  return;
462  }
463  // eventfd collapses multiple wakes into a counter. That is sufficient
464  // because ready work is drained until no runtime reports progress.
465  std::uint64_t value = 1U;
466  const auto written = Sys::write(event_fd_, &value, sizeof(value));
467  static_cast<void>(written);
468  }
469 
470  void wake_from_isr()
471  {
472  wake();
473  }
474 
475  void wait_for_work()
476  {
477  if (epoll_fd_ < 0 || event_fd_ < 0) {
478  std::this_thread::sleep_for(std::chrono::milliseconds(1));
479  return;
480  }
481  // The timeout keeps stop() bounded even if the wake file descriptor was
482  // not created, while the normal path wakes immediately through eventfd.
483  epoll_event event{};
484  const int ready = Sys::epoll_wait(epoll_fd_, &event, 1, 1);
485  if (ready > 0 && event.data.fd == event_fd_) {
486  std::uint64_t value{};
487  while (Sys::read(event_fd_, &value, sizeof(value)) > 0) {}
488  }
489  }
490 
491  [[nodiscard]] bool step()
492  {
493  return ready_.step();
494  }
495 
496  std::size_t run_ready()
497  {
498  return ready_.run_ready();
499  }
500 
501  std::size_t tick(tsm::tick_rep elapsed_ticks = 1U)
502  {
503  auto resumed = ready_.tick(elapsed_ticks);
504  if (resumed != 0U) {
505  wake();
506  }
507  return resumed;
508  }
509 
510  std::size_t tick(tsm::tick_count elapsed_ticks)
511  {
512  return tick(elapsed_ticks.count());
513  }
514 
515  void start_all()
516  {
517  ready_.start_all();
518  }
519 
520  template<auto Entry, std::size_t Instance = 0U>
521  [[nodiscard]] spawn_result start()
522  {
523  auto result = ready_.template start<Entry, Instance>();
524  if (result == spawn_result::started) {
525  wake();
526  }
527  return result;
528  }
529 
530  template<auto Entry, typename... Args>
531  [[nodiscard]] spawn_result spawn(Args&&... args)
532  {
533  auto result = ready_.template spawn<Entry>(std::forward<Args>(args)...);
534  if (result == spawn_result::started) {
535  wake();
536  }
537  return result;
538  }
539 
540  [[nodiscard]] runtime::task_spawner<basic_thread_executor> spawner()
541  {
542  return runtime::task_spawner<basic_thread_executor>{ *this };
543  }
544 
545  template<auto Entry, std::size_t Instance = 0U>
546  [[nodiscard]] task_status task_status() const
547  {
548  return ready_.template task_status<Entry, Instance>();
549  }
550 
551  template<auto Entry, std::size_t Instance = 0U>
552  [[nodiscard]] task_failure_reason task_failure_reason() const
553  {
554  return ready_.template task_failure_reason<Entry, Instance>();
555  }
556 
557  template<auto Entry, std::size_t Instance = 0U>
558  [[nodiscard]] bool cancel() noexcept
559  {
560  const bool cancelled = ready_.template cancel<Entry, Instance>();
561  if (cancelled) {
562  wake();
563  }
564  return cancelled;
565  }
566 
567  void cancel_all() noexcept
568  {
569  ready_.cancel_all();
570  wake();
571  }
572 
573  private:
574  ThreadPolicy policy_{};
575  tsm::runtime::cooperative_executor<Tasks...> ready_;
576  std::thread worker_{};
577  std::atomic_bool stop_requested_{ true };
578  int event_fd_{ -1 };
579  int epoll_fd_{ -1 };
580 };
581 
582 template<typename... Tasks>
583 class thread_executor
584  : public basic_thread_executor<host_thread_policy, linux_syscalls, Tasks...>
585 {
586  using base =
587  basic_thread_executor<host_thread_policy, linux_syscalls, Tasks...>;
588 
589  public:
590  using base::base;
591 };
592 
593 template<typename... Tasks>
594 thread_executor(Tasks&...) -> thread_executor<Tasks...>;
595 
596 template<typename... Tasks>
598  : public basic_thread_executor<host_realtime_policy, linux_syscalls, Tasks...>
599 {
600  using base =
601  basic_thread_executor<host_realtime_policy, linux_syscalls, Tasks...>;
602 
603  public:
604  using base::base;
605 };
606 
607 template<typename... Tasks>
609 
610 template<typename... Tasks>
611 using task_executor = thread_executor<Tasks...>;
612 
613 } // namespace tsm::detail
Helpers for converting measured durations into explicit HSM ticks.
Definition: chrono_ticks.h:137
Definition: executor.h:294
Target-independent executors for tsm runtimes.
tsm::sleep_ticks_awaitable sleep_for(tick_domain< TickPeriod > domain, Duration duration) noexcept
Definition: chrono_ticks.h:105
Definition: linux.h:32
realtime_thread_executor(Tasks &...) -> realtime_thread_executor< Tasks... >
thread_executor(Tasks &...) -> thread_executor< Tasks... >
thread_executor< Tasks... > task_executor
Definition: linux.h:611
basic_realtime_configurator< linux_syscalls > RealtimeConfigurator
Definition: linux.h:366
basic_host_realtime_policy< linux_syscalls > host_realtime_policy
Definition: linux.h:397
task_spawner< Executor > spawner
Definition: executor.h:150
::tsm::runtime_policy< Topology, Storage, Scheduler, Overflow, Transport > policy
Definition: policy.h:119
task_failure_reason
Definition: coroutine.h:170
task_status
Definition: coroutine.h:160
spawn_result
Definition: coroutine.h:181
TSM_TICK_REP tick_rep
Definition: ticks.h:35
Definition: coroutine.h:1162
Strong value type for semantic scheduler ticks.
Definition: ticks.h:54
constexpr tick_rep count() const noexcept
Definition: ticks.h:73