diff --git a/CMakeLists.txt b/CMakeLists.txt index a0cde9a..9e47d9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -201,6 +201,7 @@ set(linyaps-box_LIBRARY_SOURCE src/linyaps_box/utils/mkdir.h src/linyaps_box/utils/mknod.cpp src/linyaps_box/utils/mknod.h + src/linyaps_box/utils/mman.cpp src/linyaps_box/utils/platform.cpp src/linyaps_box/utils/platform.h src/linyaps_box/utils/process.cpp diff --git a/src/linyaps_box/io/forwarder.cpp b/src/linyaps_box/io/forwarder.cpp index 2a254b1..08eec28 100644 --- a/src/linyaps_box/io/forwarder.cpp +++ b/src/linyaps_box/io/forwarder.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -50,14 +50,14 @@ auto Forwarder::pull() -> Status last_pull_again = false; while (!rb->full()) { - auto vecs = rb->get_write_vecs(); - const auto iov_cnt = (vecs[1].iov_len > 0) ? 2U : 1U; + auto *ptr = rb->get_write_ptr(); + const utils::span span(ptr, rb->free_space()); std::size_t bytes_read{ 0 }; - auto status = src_.fd->read_vecs({ vecs.data(), iov_cnt }, bytes_read); + auto status = src_.fd->read_span(span, bytes_read); if (bytes_read > 0) { - rb->advance_tail(bytes_read); + rb->advance_head(bytes_read); } if (status == utils::file_descriptor::IOStatus::TryAgain) { @@ -91,14 +91,15 @@ auto Forwarder::push() -> Status last_push_again = false; while (!rb->empty()) { - auto vecs = rb->get_read_vecs(); - const auto iov_cnt = (vecs[1].iov_len > 0) ? 2U : 1U; + auto *ptr = rb->get_read_ptr(); + const utils::span span(ptr, rb->size()); std::size_t bytes_write{ 0 }; - auto status = dst_.fd->write_vecs({ vecs.data(), iov_cnt }, bytes_write); + + auto status = dst_.fd->write_span(span, bytes_write); if (bytes_write > 0) { - rb->advance_head(bytes_write); + rb->advance_tail(bytes_write); } if (status == utils::file_descriptor::IOStatus::TryAgain) { diff --git a/src/linyaps_box/io/forwarder.h b/src/linyaps_box/io/forwarder.h index 9a5667a..ee342cd 100644 --- a/src/linyaps_box/io/forwarder.h +++ b/src/linyaps_box/io/forwarder.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -53,7 +53,7 @@ class Forwarder std::reference_wrapper poller; FdContext src_; FdContext dst_; - utils::ring_buffer_ptr rb; + utils::ring_buffer::ptr rb; }; } // namespace linyaps_box::io diff --git a/src/linyaps_box/utils/close_range.cpp b/src/linyaps_box/utils/close_range.cpp index 1adf278..f683fdb 100644 --- a/src/linyaps_box/utils/close_range.cpp +++ b/src/linyaps_box/utils/close_range.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -37,7 +37,7 @@ void close_range_fallback(uint first, uint last, int flags) throw std::system_error(errno, std::system_category(), "opendir /proc/self/fd"); } - auto close_dir = make_defer([dir]() noexcept { + auto close_dir = linyaps_box::utils::make_defer([dir]() noexcept { if (closedir(dir) < 0) { LINYAPS_BOX_WARNING() << "closedir /proc/self/fd failed: " << strerror(errno) << ", but this may not be a problem"; diff --git a/src/linyaps_box/utils/defer.h b/src/linyaps_box/utils/defer.h index 0bdc5d9..59c95ff 100644 --- a/src/linyaps_box/utils/defer.h +++ b/src/linyaps_box/utils/defer.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -9,6 +9,8 @@ #include #include +namespace linyaps_box::utils { + // Type constraints for defer, the cleanup function should not throw exceptions template constexpr bool compatible_defer = std::is_nothrow_invocable_r_v; @@ -99,3 +101,5 @@ auto make_errdefer(Fn &&fn) noexcept { return defer, defer_policy::on_error>(std::forward(fn)); } + +} // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/mman.cpp b/src/linyaps_box/utils/mman.cpp new file mode 100644 index 0000000..1ca26a9 --- /dev/null +++ b/src/linyaps_box/utils/mman.cpp @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "mman.h" + +#include + +namespace linyaps_box::utils { +auto memfd_create(const char *name, unsigned int flags) -> file_descriptor +{ + auto fd = ::memfd_create(name, flags); + if (fd == -1) { + throw std::system_error(errno, std::system_category(), "memfd_create failed"); + } + + return file_descriptor{ fd }; +} + +auto mmap(void *addr, + std::size_t length, + int prot, + int flags, + std::optional> fd, + off_t offset) -> std::byte * +{ + auto *result = static_cast(::mmap(addr, + length, + prot, + flags, + fd.has_value() ? fd.value().get().get() : -1, + offset)); + if (result == MAP_FAILED) { + throw std::system_error(errno, std::system_category(), "mmap failed"); + } + + return result; +} + +auto munmap(std::byte *addr, std::size_t length) -> void +{ + auto ret = ::munmap(addr, length); + if (ret == -1) { + throw std::system_error(errno, std::system_category(), "munmap failed"); + } +} + +} // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/mman.h b/src/linyaps_box/utils/mman.h new file mode 100644 index 0000000..e0e2e06 --- /dev/null +++ b/src/linyaps_box/utils/mman.h @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include "linyaps_box/utils/file_describer.h" + +#include + +namespace linyaps_box::utils { +auto memfd_create(const char *name, unsigned int flags) -> file_descriptor; +auto mmap(void *addr, + std::size_t length, + int prot, + int flags, + std::optional> fd, + off_t offset) -> std::byte *; +auto munmap(std::byte *addr, std::size_t length) -> void; +} // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/platform.cpp b/src/linyaps_box/utils/platform.cpp index 5d00c2e..3b3ec9b 100644 --- a/src/linyaps_box/utils/platform.cpp +++ b/src/linyaps_box/utils/platform.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -95,4 +95,26 @@ auto get_path_max(const std::filesystem::path &fs_dir) noexcept -> std::size_t return static_cast(max); } +auto get_page_size() noexcept -> std::size_t +{ + static const auto page_size = []() noexcept -> std::size_t { + errno = 0; + const auto sz = ::sysconf(_SC_PAGESIZE); + + if (sz == -1) { + if (errno != 0) { + auto saved_errno = errno; + LINYAPS_BOX_WARNING() << "Failed to get page size: " << ::std::strerror(saved_errno) + << ", defaulting to 4096"; + } + + return 4096; + } + + return static_cast(sz); + }(); + + return page_size; +} + } // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/platform.h b/src/linyaps_box/utils/platform.h index 3ce5896..d121983 100644 --- a/src/linyaps_box/utils/platform.h +++ b/src/linyaps_box/utils/platform.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -10,4 +10,5 @@ namespace linyaps_box::utils { auto str_to_signal(std::string_view str) -> int; auto str_to_rlimit(std::string_view str) -> int; auto get_path_max(const std::filesystem::path &fs_dir) noexcept -> std::size_t; +auto get_page_size() noexcept -> std::size_t; } // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/ringbuffer.cpp b/src/linyaps_box/utils/ringbuffer.cpp index f60e91f..9b850c2 100644 --- a/src/linyaps_box/utils/ringbuffer.cpp +++ b/src/linyaps_box/utils/ringbuffer.cpp @@ -1,74 +1,75 @@ -// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later #include "linyaps_box/utils/ringbuffer.h" +#include "linyaps_box/utils/defer.h" +#include "linyaps_box/utils/log.h" +#include "linyaps_box/utils/mman.h" +#include "linyaps_box/utils/platform.h" + +#include +#include + +#include +#include + namespace linyaps_box::utils { -ring_buffer_deleter::ring_buffer_deleter(std::size_t total_size) noexcept - : total_size{ total_size } +auto ring_buffer::deleter::operator()(ring_buffer *rb) const noexcept -> void { -} + if (rb == nullptr) { + return; + } -auto ring_buffer_deleter::operator()(ring_buffer *rb) const -> void -{ rb->~ring_buffer(); - ::operator delete(rb, - total_size, - std::align_val_t(compat::hardware_constructive_interference_size)); -} - -auto ring_buffer::create(std::size_t requested_capacity) - -> std::unique_ptr -{ - requested_capacity = std::max(requested_capacity, 2); - std::size_t capacity = 1; - while (capacity < requested_capacity) { - capacity <<= 1U; + try { + munmap(reinterpret_cast(rb), total_size); // NOLINT + } catch (const std::system_error &e) { + LINYAPS_BOX_ERR() << "Failed to munmap ring buffer: " << e.what(); + assert(false); } - - const auto total_size = sizeof(ring_buffer) + capacity; - auto *mem = ::operator new(total_size, - std::align_val_t(compat::hardware_constructive_interference_size)); - auto *rb = new (mem) ring_buffer(capacity); - return { rb, ring_buffer_deleter(total_size) }; } -auto ring_buffer::get_read_vecs() const noexcept -> iov_view +auto ring_buffer::create(std::size_t requested_capacity) -> ptr { - if (empty()) { - return {}; - } + const auto page_size = utils::get_page_size(); + auto meta_size = (sizeof(ring_buffer) + page_size - 1) & ~(page_size - 1); - const auto *base = data_ptr(); - if (tail_ > head_) { - return { { { const_cast(base + head_), tail_ - head_ }, // NOLINT - { nullptr, 0 } } }; + auto cap = page_size; + while (cap < requested_capacity) { + cap <<= 1U; } - return { { { const_cast(base + head_), capacity_ - head_ }, // NOLINT - { const_cast(base), tail_ } } }; // NOLINT -} + auto total_vma = meta_size + (2 * cap); -auto ring_buffer::get_write_vecs() const noexcept -> iov_view -{ - if (full()) { - return {}; + auto fd = memfd_create("linyaps_box_io_buffer", MFD_CLOEXEC); + if (::ftruncate(fd.get(), meta_size + cap) == -1) { + throw std::system_error(errno, std::system_category(), "ftruncate failed"); } - const auto *base = data_ptr(); - const auto space = capacity_ - size() - 1; + auto *addr = mmap(nullptr, total_vma, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, std::nullopt, 0); + auto mem_guard = utils::make_errdefer([addr, total_vma]() noexcept { + munmap(addr, total_vma); + }); - if (tail_ >= head_) { - const auto first_part = std::min(space, capacity_ - tail_); - const auto second_part = space - first_part; - return { { { const_cast(base + tail_), first_part }, // NOLINT - { const_cast(base), second_part } } }; // NOLINT - } + mmap(addr, + meta_size + cap, + PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_FIXED | MAP_POPULATE, + fd, + 0); + + auto *mirror_addr = static_cast(addr) + meta_size + cap; + mmap(mirror_addr, cap, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, meta_size); + + auto *data_base = static_cast(addr) + meta_size; + auto *rb = new (addr) ring_buffer(cap, data_base); - return { { { const_cast(base + tail_), space }, { nullptr, 0 } } }; // NOLINT + auto deleter = ring_buffer::deleter{ .total_size = total_vma }; + return { rb, std::move(deleter) }; } } // namespace linyaps_box::utils diff --git a/src/linyaps_box/utils/ringbuffer.h b/src/linyaps_box/utils/ringbuffer.h index c170dd1..306dbbf 100644 --- a/src/linyaps_box/utils/ringbuffer.h +++ b/src/linyaps_box/utils/ringbuffer.h @@ -1,16 +1,10 @@ -// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later #pragma once -#include -#include #include -#include - -#include -#include namespace linyaps_box::compat { #ifdef __cpp_lib_hardware_interference_size @@ -26,73 +20,73 @@ constexpr std::size_t hardware_destructive_interference_size = 64; namespace linyaps_box::utils { -class ring_buffer; - -struct ring_buffer_deleter -{ - explicit ring_buffer_deleter(std::size_t total_size) noexcept; - - auto operator()(ring_buffer *rb) const -> void; - -private: - std::size_t total_size{ 0 }; -}; - class alignas(compat::hardware_constructive_interference_size) ring_buffer { - using iov_view = std::array; - public: - enum class Status : uint8_t { Success, TryAgain, Full, Empty, Finished }; + struct deleter + { + auto operator()(ring_buffer *rb) const noexcept -> void; + std::size_t total_size; + }; + + using ptr = std::unique_ptr; ring_buffer(const ring_buffer &) = delete; ring_buffer &operator=(const ring_buffer &) = delete; - ring_buffer(ring_buffer &&) = default; - ring_buffer &operator=(ring_buffer &&) = default; + ring_buffer(ring_buffer &&) = delete; + ring_buffer &operator=(ring_buffer &&) = delete; ~ring_buffer() = default; - static auto create(std::size_t requested_capacity) - -> std::unique_ptr; - - [[nodiscard]] auto get_read_vecs() const noexcept -> iov_view; + static auto create(std::size_t requested_capacity) -> ptr; - auto advance_head(std::size_t n) noexcept -> void { head_ = (head_ + n) & mask_; } + [[nodiscard]] auto get_read_ptr() const noexcept -> const std::byte * + { + return data_ptr_ + (tail_ & mask_); + } - [[nodiscard]] auto get_write_vecs() const noexcept -> iov_view; + [[nodiscard]] auto get_read_ptr() noexcept -> std::byte * + { + return data_ptr_ + (tail_ & mask_); + } - auto advance_tail(std::size_t n) noexcept -> void { tail_ = (tail_ + n) & mask_; } + [[nodiscard]] auto get_write_ptr() const noexcept -> const std::byte * + { + return data_ptr_ + (head_ & mask_); + } - [[nodiscard]] auto empty() const noexcept -> bool { return head_ == tail_; } + [[nodiscard]] auto get_write_ptr() noexcept -> std::byte * + { + return data_ptr_ + (head_ & mask_); + } - [[nodiscard]] auto full() const noexcept -> bool { return ((tail_ + 1) & mask_) == head_; } + auto advance_head(std::size_t n) noexcept -> void { head_ += n; } - [[nodiscard]] auto capacity() const noexcept -> std::size_t { return capacity_ - 1; } + auto advance_tail(std::size_t n) noexcept -> void { tail_ += n; } - [[nodiscard]] auto size() const noexcept -> std::size_t { return (tail_ - head_) & mask_; } + [[nodiscard]] auto size() const noexcept -> std::size_t { return head_ - tail_; } -private: - explicit ring_buffer(std::size_t cap) - : capacity_(cap) - , mask_(cap - 1) + [[nodiscard]] auto free_space() const noexcept -> std::size_t { + return (mask_ + 1) - (head_ - tail_); } - [[nodiscard]] auto data_ptr() const noexcept -> const std::byte * - { - return reinterpret_cast(this + 1); // NOLINT - } + [[nodiscard]] auto empty() const noexcept -> bool { return head_ == tail_; } + + [[nodiscard]] auto full() const noexcept -> bool { return (head_ - tail_) == (mask_ + 1); } + + [[nodiscard]] auto capacity() const noexcept -> std::size_t { return mask_ + 1; } - auto data_ptr() noexcept -> std::byte * +private: + explicit ring_buffer(std::size_t cap, std::byte *data_base) + : data_ptr_(data_base) + , mask_(cap - 1) { - return reinterpret_cast(this + 1); // NOLINT } + std::byte *data_ptr_; std::size_t head_{ 0 }; std::size_t tail_{ 0 }; - std::size_t capacity_; std::size_t mask_; }; -using ring_buffer_ptr = std::unique_ptr; - } // namespace linyaps_box::utils