futex.hpp 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. /*
  2. * Distributed under the Boost Software License, Version 1.0.
  3. * (See accompanying file LICENSE_1_0.txt or copy at
  4. * http://www.boost.org/LICENSE_1_0.txt)
  5. *
  6. * Copyright (c) 2020-2025 Andrey Semashev
  7. */
  8. /*!
  9. * \file atomic/detail/futex.hpp
  10. *
  11. * This header defines wrappers around futex syscall.
  12. *
  13. * http://man7.org/linux/man-pages/man2/futex.2.html
  14. * https://man.openbsd.org/futex
  15. */
  16. #ifndef BOOST_ATOMIC_DETAIL_FUTEX_HPP_INCLUDED_
  17. #define BOOST_ATOMIC_DETAIL_FUTEX_HPP_INCLUDED_
  18. #include <boost/atomic/detail/config.hpp>
  19. #ifdef BOOST_HAS_PRAGMA_ONCE
  20. #pragma once
  21. #endif
  22. #if defined(__linux__)
  23. #include <sys/syscall.h>
  24. #if defined(SYS_futex)
  25. #define BOOST_ATOMIC_DETAIL_SYS_FUTEX SYS_futex
  26. #elif defined(SYS_futex_time64)
  27. // On some 32-bit targets (e.g. riscv32) SYS_futex is not defined and instead SYS_futex_time64 is implemented,
  28. // which is equivalent to SYS_futex but uses 64-bit time_t.
  29. #define BOOST_ATOMIC_DETAIL_SYS_FUTEX SYS_futex_time64
  30. #define BOOST_ATOMIC_DETAIL_FUTEX_TIME64
  31. #elif defined(__NR_futex)
  32. // Some Android NDKs (Google NDK and older Crystax.NET NDK versions) don't define SYS_futex.
  33. #define BOOST_ATOMIC_DETAIL_SYS_FUTEX __NR_futex
  34. #endif
  35. #elif defined(__OpenBSD__)
  36. // OpenBSD provides futex(2) function wrapper since OpenBSD 6.2 (https://man.openbsd.org/OpenBSD-6.2/futex.2).
  37. // It has also removed syscall(2) interface:
  38. // https://github.com/openbsd/src/commit/cafeb892b121ee89c39c2b940e8ccd6950f50009
  39. #include <sys/param.h>
  40. #include <cerrno>
  41. #if OpenBSD >= 201711
  42. #define BOOST_ATOMIC_DETAIL_OPENBSD_FUTEX
  43. #endif // OpenBSD >= 201711
  44. #elif defined(__NETBSD__) || defined(__NetBSD__)
  45. #include <sys/syscall.h>
  46. #if defined(SYS___futex)
  47. // NetBSD defines SYS___futex, which has slightly different parameters. Basically, it has decoupled timeout and val2 parameters:
  48. // int __futex(int *addr1, int op, int val1, const struct timespec *timeout, int *addr2, int val2, int val3);
  49. // https://ftp.netbsd.org/pub/NetBSD/NetBSD-current/src/sys/sys/syscall.h
  50. // http://bxr.su/NetBSD/sys/kern/sys_futex.c
  51. #define BOOST_ATOMIC_DETAIL_SYS_FUTEX SYS___futex
  52. #define BOOST_ATOMIC_DETAIL_NETBSD_FUTEX
  53. #endif // defined(SYS___futex)
  54. #endif
  55. #if defined(BOOST_ATOMIC_DETAIL_SYS_FUTEX) || defined(BOOST_ATOMIC_DETAIL_OPENBSD_FUTEX)
  56. #if defined(__linux__)
  57. #include <linux/futex.h>
  58. #else
  59. #include <sys/futex.h>
  60. #endif
  61. #include <time.h> // timespec
  62. #include <cstdint>
  63. #include <boost/atomic/detail/intptr.hpp>
  64. #include <boost/atomic/detail/header.hpp>
  65. #define BOOST_ATOMIC_DETAIL_HAS_FUTEX
  66. // Note: On Android, futex.h is lacking many definitions, but the actual Linux kernel supports the API in full.
  67. #if defined(FUTEX_WAIT_BITSET)
  68. #define BOOST_ATOMIC_DETAIL_FUTEX_WAIT_BITSET FUTEX_WAIT_BITSET
  69. #elif defined(__ANDROID__)
  70. #define BOOST_ATOMIC_DETAIL_FUTEX_WAIT_BITSET 9
  71. #endif
  72. #if defined(FUTEX_PRIVATE_FLAG)
  73. #define BOOST_ATOMIC_DETAIL_FUTEX_PRIVATE_FLAG FUTEX_PRIVATE_FLAG
  74. #elif defined(__ANDROID__)
  75. #define BOOST_ATOMIC_DETAIL_FUTEX_PRIVATE_FLAG 128
  76. #else
  77. #define BOOST_ATOMIC_DETAIL_FUTEX_PRIVATE_FLAG 0
  78. #endif
  79. #if defined(FUTEX_CLOCK_REALTIME)
  80. #define BOOST_ATOMIC_DETAIL_FUTEX_CLOCK_REALTIME FUTEX_CLOCK_REALTIME
  81. #elif defined(__ANDROID__)
  82. #define BOOST_ATOMIC_DETAIL_FUTEX_CLOCK_REALTIME 256
  83. #endif
  84. namespace boost {
  85. namespace atomics {
  86. namespace detail {
  87. #if defined(BOOST_ATOMIC_DETAIL_FUTEX_TIME64)
  88. //! An equivalent of `timespec` that uses 64-bit members when the userland `timespec` is 32-bit
  89. struct futex_timespec
  90. {
  91. std::int64_t tv_sec;
  92. std::int64_t tv_nsec;
  93. futex_timespec() = default;
  94. explicit futex_timespec(::timespec ts) noexcept :
  95. tv_sec(ts.tv_sec), tv_nsec(ts.tv_nsec)
  96. {}
  97. };
  98. #else // defined(BOOST_ATOMIC_DETAIL_FUTEX_TIME64)
  99. using futex_timespec = ::timespec;
  100. #endif // defined(BOOST_ATOMIC_DETAIL_FUTEX_TIME64)
  101. //! Invokes an operation on the futex
  102. BOOST_FORCEINLINE int futex_invoke(void* addr1, int op, unsigned int val1, const futex_timespec* timeout = nullptr, void* addr2 = nullptr, unsigned int val3 = 0u) noexcept
  103. {
  104. #if defined(BOOST_ATOMIC_DETAIL_OPENBSD_FUTEX)
  105. return ::futex
  106. (
  107. static_cast< volatile std::uint32_t* >(addr1),
  108. op,
  109. static_cast< int >(val1),
  110. timeout,
  111. static_cast< volatile std::uint32_t* >(addr2)
  112. );
  113. #elif defined(BOOST_ATOMIC_DETAIL_NETBSD_FUTEX)
  114. // Pass 0 in val2.
  115. return ::syscall(BOOST_ATOMIC_DETAIL_SYS_FUTEX, addr1, op, val1, timeout, addr2, 0u, val3);
  116. #else
  117. return ::syscall(BOOST_ATOMIC_DETAIL_SYS_FUTEX, addr1, op, val1, timeout, addr2, val3);
  118. #endif
  119. }
  120. //! Invokes an operation on the futex
  121. BOOST_FORCEINLINE int futex_invoke(void* addr1, int op, unsigned int val1, unsigned int val2, void* addr2 = nullptr, unsigned int val3 = 0u) noexcept
  122. {
  123. #if defined(BOOST_ATOMIC_DETAIL_OPENBSD_FUTEX)
  124. return ::futex
  125. (
  126. static_cast< volatile std::uint32_t* >(addr1),
  127. op,
  128. static_cast< int >(val1),
  129. reinterpret_cast< const futex_timespec* >(static_cast< atomics::detail::uintptr_t >(val2)),
  130. static_cast< volatile std::uint32_t* >(addr2)
  131. );
  132. #elif defined(BOOST_ATOMIC_DETAIL_NETBSD_FUTEX)
  133. // Pass nullptr in timeout.
  134. return ::syscall(BOOST_ATOMIC_DETAIL_SYS_FUTEX, addr1, op, val1, static_cast< void* >(nullptr), addr2, val2, val3);
  135. #else
  136. return ::syscall(BOOST_ATOMIC_DETAIL_SYS_FUTEX, addr1, op, val1, static_cast< atomics::detail::uintptr_t >(val2), addr2, val3);
  137. #endif
  138. }
  139. //! Checks that the value \c pval is \c expected and blocks
  140. BOOST_FORCEINLINE int futex_wait(void* pval, unsigned int expected, int flags) noexcept
  141. {
  142. int res = futex_invoke(pval, FUTEX_WAIT | flags, expected);
  143. #if defined(OpenBSD) && (OpenBSD < 202111)
  144. // In older OpenBSD versions, futex(2) returned error code directly instead of setting errno and returning -1.
  145. // This was fixed in OpenBSD 7.0 (https://github.com/openbsd/src/commit/3288ea8fbfe504db25b57dd18b664a1aa377e4bf).
  146. // This primarily affects FUTEX_WAIT. For FUTEX_WAKE and FUTEX_REQUEUE the returned value may be positive
  147. // on successful completion of the call and there seem to be no errors that can be returned. Other functions
  148. // are not supported on OpenBSD 7.0 and older.
  149. if (res > 0)
  150. {
  151. errno = res;
  152. res = -1;
  153. }
  154. #endif // defined(OpenBSD) && (OpenBSD < 202111)
  155. return res;
  156. }
  157. //! Checks that the value \c pval is \c expected and blocks until timeout
  158. BOOST_FORCEINLINE int futex_wait_for(void* pval, unsigned int expected, futex_timespec const& timeout, int flags) noexcept
  159. {
  160. int res = futex_invoke(pval, FUTEX_WAIT | flags, expected, &timeout);
  161. #if defined(OpenBSD) && (OpenBSD < 202111)
  162. // See the comment in futex_wait
  163. if (res > 0)
  164. {
  165. errno = res;
  166. res = -1;
  167. }
  168. #endif // defined(OpenBSD) && (OpenBSD < 202111)
  169. return res;
  170. }
  171. #if defined(BOOST_ATOMIC_DETAIL_FUTEX_WAIT_BITSET)
  172. //! Checks that the value \c pval is \c expected and blocks until timeout
  173. BOOST_FORCEINLINE int futex_wait_until(void* pval, unsigned int expected, futex_timespec const& timeout, int flags) noexcept
  174. {
  175. return futex_invoke(pval, BOOST_ATOMIC_DETAIL_FUTEX_WAIT_BITSET | flags, expected, &timeout, nullptr, ~static_cast< unsigned int >(0u));
  176. }
  177. #endif // defined(BOOST_ATOMIC_DETAIL_FUTEX_WAIT_BITSET)
  178. //! Wakes the specified number of threads waiting on the futex
  179. BOOST_FORCEINLINE int futex_signal(void* pval, int flags, unsigned int count = 1u) noexcept
  180. {
  181. return futex_invoke(pval, FUTEX_WAKE | flags, count);
  182. }
  183. //! Wakes all threads waiting on the futex
  184. BOOST_FORCEINLINE int futex_broadcast(void* pval, int flags) noexcept
  185. {
  186. return futex_signal(pval, flags, (~static_cast< unsigned int >(0u)) >> 1u);
  187. }
  188. //! Wakes the wake_count threads waiting on the futex pval1 and requeues up to requeue_count of the blocked threads onto another futex pval2
  189. BOOST_FORCEINLINE int futex_requeue
  190. (
  191. void* pval1,
  192. void* pval2,
  193. int flags,
  194. unsigned int wake_count = 1u,
  195. unsigned int requeue_count = (~static_cast< unsigned int >(0u)) >> 1u
  196. ) noexcept
  197. {
  198. return futex_invoke(pval1, FUTEX_REQUEUE | flags, wake_count, requeue_count, pval2);
  199. }
  200. } // namespace detail
  201. } // namespace atomics
  202. } // namespace boost
  203. #include <boost/atomic/detail/footer.hpp>
  204. #endif // defined(BOOST_ATOMIC_DETAIL_SYS_FUTEX) || defined(BOOST_ATOMIC_DETAIL_OPENBSD_FUTEX)
  205. #endif // BOOST_ATOMIC_DETAIL_FUTEX_HPP_INCLUDED_