Linux C++ 利用 io_uring 技术批量读取 tun 文件描述符的数据。

以下是参考的实现代码,IO_URING 操作必须要进行按页大小对齐(仅在O_DIRECT直接I/O下),不能是非对称的,一般大多数操作系统页大小为:4KB。

批量读取、writev 批量简写。 

                    static constexpr int MTU = ITap::Mtu;

                    struct io_uring ring;
                    memset(&ring, 0, sizeof(ring));

                    struct iovec tun_write_iov_data[PPP_TUN_ENTRIES_PACKET_SIZE];
                    Byte packets[PPP_TUN_ENTRIES_PACKET_SIZE][MTU];

                    SsmtThreadLocalTls& tls = ssmt_tls_;
                    tls.tun_write_iov_data = tun_write_iov_data;

                    if (io_uring_queue_init(PPP_TUN_ENTRIES_PACKET_SIZE, &ring, 0) < 0) {
                        Dispose();
                        return false;
                    }

                    bool any = false;
                    for (int i = 0; i < PPP_TUN_ENTRIES_PACKET_SIZE; i++) {
                        struct io_uring_sqe* sqe = io_uring_get_sqe(&ring);
                        if (NULL == sqe) {
                            goto LABEL_exit;
                        }

                        io_uring_prep_read(sqe, tun_fd, packets[i], MTU, 0);
                        sqe->user_data = (__u64)&packets[i][0];
                    }

                    io_uring_submit(&ring);
                    while (!disposed_) {
                        struct io_uring_cqe* cqes[PPP_TUN_ENTRIES_PACKET_SIZE];
                        __u32 count = io_uring_peek_batch_cqe(&ring, cqes, PPP_TUN_ENTRIES_PACKET_SIZE);
                        if (count == 0) {
                            int err = io_uring_wait_cqe(&ring, &cqes[0]);
                            if (err == -EINTR) {
                                continue;
                            }
                            elif(err >= 0) {
                                count = 1;
                            }
                            else {
                                break;
                            }
                        }
                        
                        ssmt_tls_.tun_wirte_iov_size = 0;
                        for (__u32 i = 0; i < count; i++) {
                            struct io_uring_cqe* cqe = cqes[i];
                            assert(NULL != cqe);

                            Byte* packet = (Byte*)cqe->user_data;
                            if (int bytes_transferred = cqe->res; bytes_transferred > 0) {
                                PacketInputEventArgs e{ packet, bytes_transferred };
                                tls.tun_fd_ = tun_fd;
                                OnInput(e);
                            }

                            struct io_uring_sqe* sqe = io_uring_get_sqe(&ring);
                            assert(NULL != sqe);

                            io_uring_prep_read(sqe, tun_fd, packet, MTU, 0);
                            sqe->user_data = (__u64)packet;
                        
                            io_uring_cqe_seen(&ring, cqe);
                        }

                        tls.tun_fd_ = -1;
                        io_uring_submit(&ring);
                        
                        if (tls.tun_wirte_iov_size > 0) {
                            int err = writev(tun_fd, tun_write_iov_data, tls.tun_wirte_iov_size);
                            for (int i = 0; i < tls.tun_wirte_iov_size; i++) {
                                struct iovec& iov = tls.tun_write_iov_data[i];
                                Mfree(iov.iov_base);
                            }

                            tls.tun_wirte_iov_size = 0;
                            if (err < 0) {
                                break;
                            }
                        }
                    }

                LABEL_exit:
                    io_uring_queue_exit(&ring);
                    Dispose();
                    return any;

write 批量收集或超限写出:

        bool TapLinux::Output(const void* packet, int packet_size) noexcept {
            if (NULL == packet || packet_size < 1) {
                return false;
            }

            int disposed = disposed_.load();
            if (disposed != FALSE) {
                return false;
            }
            
            // https://man7.org/linux/man-pages/man2/write.2.html
            int tun = static_cast<int>(reinterpret_cast<std::intptr_t>(GetHandle()));
            if (Ssmt()) {
                SsmtThreadLocalTls& tls = ssmt_tls_;
                int fd = tls.tun_fd_;
                if (fd != -1) {
                    tun = fd;

#if defined(BOOST_ASIO_HAS_IO_URING)
                    void* packet_copy = Malloc(packet_size);
                    if (NULL == packet_copy) {
                        return false;
                    }

                    struct iovec& iov = tls.tun_write_iov_data[tls.tun_wirte_iov_size++];
                    memcpy(packet_copy, packet, packet_size);

                    iov.iov_base = packet_copy;
                    iov.iov_len = packet_size;

                    if (tls.tun_wirte_iov_size >= PPP_TUN_ENTRIES_PACKET_SIZE) {
                        int err = writev(fd, tls.tun_write_iov_data, tls.tun_wirte_iov_size);
                        for (int i = 0; i < tls.tun_wirte_iov_size; i++) {
                            struct iovec& iov = tls.tun_write_iov_data[i];
                            Mfree(iov.iov_base);
                        }

                        tls.tun_wirte_iov_size = 0;
                        if (err < 0) {
                            return false;
                        }
                    }
#endif
                }
            }

            ssize_t bytes_transferred = ::write(tun, (void*)packet, (size_t)packet_size);
            return bytes_transferred > -1;
        }