cuDDL
Loading...
Searching...
No Matches
io_ring.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <atomic>
4#include <cerrno>
5#include <cstdint>
6#include <memory>
7
8#if defined(__linux__) && __has_include(<linux/io_uring.h>)
9 #include <linux/io_uring.h>
10 #include <sys/mman.h>
11 #include <sys/syscall.h>
12 #include <unistd.h>
13#endif
14
15namespace cuddl::detail {
16
17#if defined(__linux__) && __has_include(<linux/io_uring.h>) && defined(__NR_io_uring_setup)
21class io_ring {
22 public:
23 io_ring(io_ring const&) = delete;
24 io_ring& operator=(io_ring const&) = delete;
25 ~io_ring() {
26 if (sqes_ != nullptr) ::munmap(sqes_, sqes_bytes_);
27 if (cq_ != nullptr) ::munmap(cq_, cq_bytes_);
28 if (sq_ != nullptr) ::munmap(sq_, sq_bytes_);
29 ::close(fd_);
30 }
31
34 [[nodiscard]] static std::unique_ptr<io_ring> make(unsigned depth) {
35 io_uring_params params{};
36 int const fd = static_cast<int>(::syscall(__NR_io_uring_setup, depth, &params));
37 if (fd < 0) return nullptr;
38 std::unique_ptr<io_ring> ring(new io_ring(fd));
39 if (!ring->map(params)) return nullptr;
40 return ring;
41 }
42
45 void write(int fd, void const* data, uint32_t bytes, uint64_t offset, uint64_t user_data) {
46 auto const tail = std::atomic_ref(*sq_tail_).load(std::memory_order_relaxed);
47 auto const index = tail & sq_mask_;
48 auto& sqe = sqes_[index];
49 sqe = io_uring_sqe{};
50 sqe.opcode = IORING_OP_WRITE;
51 sqe.fd = fd;
52 sqe.addr = reinterpret_cast<uint64_t>(data);
53 sqe.len = bytes;
54 sqe.off = offset;
55 sqe.user_data = user_data;
56 sq_array_[index] = index;
57 std::atomic_ref(*sq_tail_).store(tail + 1, std::memory_order_release);
58 ++queued_;
59 }
60
64 template <typename OnComplete>
65 [[nodiscard]] int submit_and_wait(unsigned wait_for, OnComplete&& on_complete) {
66 unsigned seen = 0;
67 while (true) {
68 seen += reap(on_complete);
69 if (queued_ == 0 && seen >= wait_for) return 0;
70 auto const waiting = seen >= wait_for ? 0U : 1U;
71 auto const entered = ::syscall(
72 __NR_io_uring_enter, fd_, queued_, waiting, IORING_ENTER_GETEVENTS, nullptr, 0
73 );
74 if (entered < 0) {
75 // A full completion queue refuses submissions until completions are reaped.
76 if (errno == EINTR || errno == EAGAIN || errno == EBUSY) continue;
77 return errno;
78 }
79 queued_ -= static_cast<unsigned>(entered);
80 }
81 }
82
83 private:
84 explicit io_ring(int fd) : fd_(fd) {}
85
86 [[nodiscard]] bool map(io_uring_params const& params) {
87 auto const region = [&](size_t bytes, off_t offset) -> void* {
88 void* const mapped = ::mmap(
89 nullptr, bytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, fd_, offset
90 );
91 return mapped == MAP_FAILED ? nullptr : mapped;
92 };
93 sq_bytes_ = params.sq_off.array + params.sq_entries * sizeof(uint32_t);
94 cq_bytes_ = params.cq_off.cqes + params.cq_entries * sizeof(io_uring_cqe);
95 sqes_bytes_ = params.sq_entries * sizeof(io_uring_sqe);
96 sq_ = static_cast<char*>(region(sq_bytes_, IORING_OFF_SQ_RING));
97 cq_ = static_cast<char*>(region(cq_bytes_, IORING_OFF_CQ_RING));
98 sqes_ = static_cast<io_uring_sqe*>(region(sqes_bytes_, IORING_OFF_SQES));
99 if (sq_ == nullptr || cq_ == nullptr || sqes_ == nullptr) return false;
100 sq_tail_ = reinterpret_cast<uint32_t*>(sq_ + params.sq_off.tail);
101 sq_mask_ = *reinterpret_cast<uint32_t*>(sq_ + params.sq_off.ring_mask);
102 sq_array_ = reinterpret_cast<uint32_t*>(sq_ + params.sq_off.array);
103 cq_head_ = reinterpret_cast<uint32_t*>(cq_ + params.cq_off.head);
104 cq_tail_ = reinterpret_cast<uint32_t*>(cq_ + params.cq_off.tail);
105 cq_mask_ = *reinterpret_cast<uint32_t*>(cq_ + params.cq_off.ring_mask);
106 cqes_ = reinterpret_cast<io_uring_cqe*>(cq_ + params.cq_off.cqes);
107 return true;
108 }
109
110 template <typename OnComplete>
111 unsigned reap(OnComplete& on_complete) {
112 auto head = std::atomic_ref(*cq_head_).load(std::memory_order_relaxed);
113 auto const tail = std::atomic_ref(*cq_tail_).load(std::memory_order_acquire);
114 unsigned const count = tail - head;
115 for (; head != tail; ++head) {
116 auto const& cqe = cqes_[head & cq_mask_];
117 on_complete(cqe.user_data, cqe.res);
118 }
119 std::atomic_ref(*cq_head_).store(head, std::memory_order_release);
120 return count;
121 }
122
123 int fd_;
124 char* sq_ = nullptr;
125 char* cq_ = nullptr;
126 io_uring_sqe* sqes_ = nullptr;
127 size_t sq_bytes_ = 0, cq_bytes_ = 0, sqes_bytes_ = 0;
128 uint32_t* sq_tail_ = nullptr;
129 uint32_t* sq_array_ = nullptr;
130 uint32_t* cq_head_ = nullptr;
131 uint32_t* cq_tail_ = nullptr;
132 io_uring_cqe* cqes_ = nullptr;
133 uint32_t sq_mask_ = 0, cq_mask_ = 0;
134 unsigned queued_ = 0;
135};
136#else
138class io_ring {
139 public:
140 [[nodiscard]] static std::unique_ptr<io_ring> make(unsigned) {
141 return nullptr;
142 }
143 void write(int, void const*, uint32_t, uint64_t, uint64_t) {}
144 template <typename OnComplete>
145 [[nodiscard]] int submit_and_wait(unsigned, OnComplete&&) {
146 return ENOSYS;
147 }
148};
149#endif
150
151} // namespace cuddl::detail
Stand-in where the build has no io_uring headers: never available.
Definition io_ring.hpp:138
void write(int, void const *, uint32_t, uint64_t, uint64_t)
Definition io_ring.hpp:143
int submit_and_wait(unsigned, OnComplete &&)
Definition io_ring.hpp:145
static std::unique_ptr< io_ring > make(unsigned)
Definition io_ring.hpp:140
auto const count
Definition kernels.cuh:1031