> -----Original Message----- > From: Christoph Hellwig <hch@xxxxxxxxxxxxx> > Sent: Wednesday, June 4, 2025 12:02 AM > To: Christian König <christian.koenig@xxxxxxx> > Cc: Christoph Hellwig <hch@xxxxxxxxxxxxx>; wangtao > <tao.wangtao@xxxxxxxxx>; sumit.semwal@xxxxxxxxxx; kraxel@xxxxxxxxxx; > vivek.kasireddy@xxxxxxxxx; viro@xxxxxxxxxxxxxxxxxx; brauner@xxxxxxxxxx; > hughd@xxxxxxxxxx; akpm@xxxxxxxxxxxxxxxxxxxx; amir73il@xxxxxxxxx; > benjamin.gaignard@xxxxxxxxxxxxx; Brian.Starkey@xxxxxxx; > jstultz@xxxxxxxxxx; tjmercier@xxxxxxxxxx; jack@xxxxxxx; > baolin.wang@xxxxxxxxxxxxxxxxx; linux-media@xxxxxxxxxxxxxxx; dri- > devel@xxxxxxxxxxxxxxxxxxxxx; linaro-mm-sig@xxxxxxxxxxxxxxxx; linux- > kernel@xxxxxxxxxxxxxxx; linux-fsdevel@xxxxxxxxxxxxxxx; linux- > mm@xxxxxxxxx; wangbintian(BintianWang) <bintian.wang@xxxxxxxxx>; > yipengxiang <yipengxiang@xxxxxxxxx>; liulu 00013167 > <liulu.liu@xxxxxxxxx>; hanfeng 00012985 <feng.han@xxxxxxxxx> > Subject: Re: [PATCH v4 0/4] Implement dmabuf direct I/O via > copy_file_range > > On Tue, Jun 03, 2025 at 05:55:18PM +0200, Christian König wrote: > > On 6/3/25 16:28, Christoph Hellwig wrote: > > > On Tue, Jun 03, 2025 at 04:18:22PM +0200, Christian König wrote: > > >>> Does it matter compared to the I/O in this case? > > >> > > >> It unfortunately does, see the numbers on patch 3 and 4. > > > > > > That's kinda weird. Why does the page table lookup tage so much > > > time compared to normal I/O? > > > > I have absolutely no idea. It's rather surprising for me as well. > > > > The user seems to have a rather slow CPU paired with fast I/O, but it still > looks rather fishy to me. > > > > Additional to that allocating memory through memfd_create() is *much* > slower on that box than through dma-buf-heaps (which basically just uses > GFP and an array). > > Can someone try to reproduce these results on a normal system before > we're building infrastructure based on these numbers? Here's my test program. If anyone's interested, please help test it? Regards, Wangtao. [PATCH] Add dmabuf direct I/O zero-copy test program Compare latency and throughput of file read/write for memfd, udmabuf+memfd, udmabuf, and dmabuf buffers. memfd supports buffer I/O and direct I/O via read/write, sendfile, and splice user APIs. udmabuf/dmabuf only support buffer I/O via read/write, lacking direct I/O, sendfile, and splice support. Previous patch added dmabuf's copy_file_range callback, enabling buffer/direct I/O file copies for udmabuf/dmabuf. u+memfd represents using memfd-created udmabuf with memfd's user APIs for file copying. usage: dmabuf-dio [file_path] [size_MB] Signed-off-by: wangtao <tao.wangtao@xxxxxxxxx> --- tools/testing/selftests/dmabuf-heaps/Makefile | 1 + .../selftests/dmabuf-heaps/dmabuf-dio.c | 617 ++++++++++++++++++ 2 files changed, 618 insertions(+) create mode 100644 tools/testing/selftests/dmabuf-heaps/dmabuf-dio.c diff --git a/tools/testing/selftests/dmabuf-heaps/Makefile b/tools/testing/selftests/dmabuf-heaps/Makefile index 9e7e158d5fa3..beb6b3e55e17 100644 --- a/tools/testing/selftests/dmabuf-heaps/Makefile +++ b/tools/testing/selftests/dmabuf-heaps/Makefile @@ -2,5 +2,6 @@ CFLAGS += -static -O3 -Wl,-no-as-needed -Wall $(KHDR_INCLUDES) TEST_GEN_PROGS = dmabuf-heap +TEST_GEN_PROGS += dmabuf-dio include ../lib.mk diff --git a/tools/testing/selftests/dmabuf-heaps/dmabuf-dio.c b/tools/testing/selftests/dmabuf-heaps/dmabuf-dio.c new file mode 100644 index 000000000000..eae902a27f29 --- /dev/null +++ b/tools/testing/selftests/dmabuf-heaps/dmabuf-dio.c @@ -0,0 +1,617 @@ +#include <linux/dma-heap.h> +#include <linux/dma-buf.h> +#include <linux/udmabuf.h> +#include <sys/mman.h> +#include <sys/sendfile.h> +#include <sys/ioctl.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <asm/unistd.h> +#include <time.h> +#include <errno.h> + +#ifndef TEMP_FAILURE_RETRY +#define TEMP_FAILURE_RETRY(exp) ({ \ + __typeof__(exp) _rc; \ + do { \ + _rc = (exp); \ + } while (_rc == -1 && errno == EINTR); \ + _rc; }) +#endif + +#if 1 +int memfd_create(const char *name, unsigned flags) +{ + return syscall(__NR_memfd_create, name, flags); +} + +ssize_t copy_file_range(int fd_in, off_t *off_in, int fd_out, off_t *off_out, + size_t len, unsigned flags) +{ + return syscall(__NR_copy_file_range, fd_in, off_in, fd_out, off_out, len, flags); +} +#endif + +int alloc_memfd(size_t size) +{ + int memfd = memfd_create("ubuf", MFD_ALLOW_SEALING); + if (memfd < 0) + return -1; + + int ret = fcntl(memfd, F_ADD_SEALS, F_SEAL_SHRINK); + if (ret < 0) + return -1; + ret = TEMP_FAILURE_RETRY(ftruncate(memfd, size)); + if (ret < 0) + return -1; + return memfd; +} + +int alloc_udmabuf(size_t size, int memfd) +{ + static int udev_fd = -1; + if (udev_fd < 0) { + udev_fd = open("/dev/udmabuf", O_RDONLY); + if (udev_fd < 0) + return -1; + } + + struct udmabuf_create uc = {0}; + uc.memfd = memfd; + uc.offset = 0; + uc.size = size; + int buf_fd = TEMP_FAILURE_RETRY(ioctl(udev_fd, UDMABUF_CREATE, &uc)); + if (buf_fd < 0) + return -1; + + return buf_fd; +} + +int alloc_dmabuf(size_t size) +{ + static int heap_fd = -1; + + struct dma_heap_allocation_data heap_data = { 0 }; + heap_data.len = size; // length of data to be allocated in bytes + heap_data.fd_flags = O_RDWR | O_CLOEXEC; // permissions for the memory to be allocated + + if (heap_fd < 0) { + heap_fd = open("/dev/dma_heap/system", O_RDONLY); + if (heap_fd < 0) + return -1; + } + + int ret = TEMP_FAILURE_RETRY(ioctl(heap_fd, DMA_HEAP_IOCTL_ALLOC, &heap_data)); + if (ret < 0) { + return -1; + } + if (heap_data.fd < 0) + return -1; + + return heap_data.fd; +} + +static inline long times_us_duration(struct timespec *ts_start, struct timespec *ts_end) +{ + long long start = ts_start->tv_sec * 1000000 + ts_start->tv_nsec / 1000; + long long end = ts_end->tv_sec * 1000000 + ts_end->tv_nsec / 1000; + return end - start; +} + +static inline long time_us2ms(long us) +{ + return (us + 1000 - 1) / 1000; +} + +void drop_pagecaches(int file_fd, loff_t offset, size_t len) +{ + if (file_fd >= 0 && len > 0) { + posix_fadvise(file_fd, offset, len, POSIX_FADV_DONTNEED); + return; + } + + int fd = open("/proc/sys/vm/drop_caches", O_WRONLY | O_CLOEXEC); + if (fd < 0) { + printf("open drop_caches failed %d\n", errno); + return; + } + write(fd, "3", 1); + close(fd); +} + +const size_t SIZ_MB = 1024 * 1024; +const size_t DMABUF_SIZE_MAX = SIZ_MB * 32; + +static inline unsigned char test_data_value(unsigned int val) +{ + return val % 253; +} + +void test_fill_data(unsigned char* ptr, unsigned int val, size_t sz, bool fast) +{ + if (sz > 0 && fast) { + ptr[0] = test_data_value(val); + ptr[sz / 2] = test_data_value(val + sz / 2); + ptr[sz - 1] = test_data_value(val + sz - 1); + return; + } + for (size_t i = 0; i < sz; i++) { + ptr[i] = test_data_value(val + i); + } +} + +bool test_check_data(unsigned char* ptr, unsigned int val, size_t sz, bool fast) +{ + if (sz > 0 && fast) { + if (ptr[0] != test_data_value(val)) + return false; + if (ptr[sz / 2] != test_data_value(val + sz / 2)) + return false; + if (ptr[sz - 1] != test_data_value(val + sz - 1)) + return false; + return true; + } + for (size_t i = 0; i < sz; i++) { + if (ptr[i] != test_data_value(val + i)) + return false; + } + return true; +} + +enum mem_buf_type { + BUF_MEMFD, + BUF_UDMA_MEMFD, + BUF_UDMABUF, + BUF_DMABUF, + BUF_TYPE_MAX, +}; + +enum copy_io_type { + IO_MAP_READ_WRITE, + IO_SENDFILE, + IO_SPLICE, + IO_COPY_FILE_RANGE, + IO_TYPE_MAX, +}; + +static const char *mem_buf_type_descs[BUF_TYPE_MAX] = { + "memfd", "u+memfd", "udmabuf", "dmabuf", +}; + +static const char *io_type_descs[IO_TYPE_MAX] = { + "R/W", "sendfile", "splice", "c_f_r", +}; + +struct mem_buf_st { + enum mem_buf_type buf_type_; + int io_fd_; + int mem_fd_; + int buf_fd_; + size_t buf_len_; + unsigned char *buf_ptr_; +}; + +struct mem_buf_tc { + enum mem_buf_type buf_type_; + enum copy_io_type io_type_; + int file_fd_; + bool direct_io_; + size_t io_len_; + long times_create_; + long times_data_; + long times_io_; + long times_close_; +}; + +void membuf_deinit(struct mem_buf_st *membuf) +{ + if (membuf->buf_ptr_ != NULL && membuf->buf_ptr_ != MAP_FAILED) + munmap(membuf->buf_ptr_, membuf->buf_len_); + membuf->buf_ptr_ = NULL; + if (membuf->mem_fd_ > 0) + close(membuf->mem_fd_); + if (membuf->buf_fd_ > 0) + close(membuf->buf_fd_); + membuf->mem_fd_ = -1; + membuf->buf_fd_ = -1; +} + +bool membuf_init(struct mem_buf_st *membuf, size_t len, enum mem_buf_type buf_type) +{ + int map_fd = -1; + + membuf->mem_fd_ = -1; + membuf->buf_fd_ = -1; + membuf->buf_len_ = len; + membuf->buf_ptr_ = NULL; + if (buf_type <= BUF_UDMABUF) { + membuf->mem_fd_ = alloc_memfd(len); + if (membuf->mem_fd_ < 0) { + printf("alloc memfd %zd failed %d\n", len, errno); + return false; + } + map_fd = membuf->mem_fd_; + if (buf_type > BUF_MEMFD) { + membuf->buf_fd_ = alloc_udmabuf(len, membuf->mem_fd_); + if (membuf->buf_fd_ < 0) { + printf("alloc udmabuf %zd failed %d\n", len, errno); + return false; + } + if (buf_type == BUF_UDMABUF) + map_fd = membuf->buf_fd_; + } + } else { + membuf->buf_fd_ = alloc_dmabuf(len); + if (membuf->buf_fd_ < 0) { + printf("alloc dmabuf %zd failed %d\n", len, errno); + return false; + } + map_fd = membuf->buf_fd_; + } + membuf->io_fd_ = map_fd; + membuf->buf_ptr_ = (unsigned char *)mmap(NULL, len, + PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0); + if (membuf->buf_ptr_ == MAP_FAILED) { + printf("fd %d map %zd failed %d\n", map_fd, len, errno); + membuf->buf_ptr_ = NULL; + return false; + } + return true; +} + +ssize_t membuf_read_write(const struct mem_buf_st *membuf, int file_fd, + loff_t off, bool is_read) +{ + if (!membuf->buf_ptr_) + return -1; + lseek(file_fd, off, SEEK_SET); + if (is_read) + return read(file_fd, membuf->buf_ptr_, membuf->buf_len_); + else + return write(file_fd, membuf->buf_ptr_, membuf->buf_len_); +} + +ssize_t membuf_sendfile(const struct mem_buf_st *membuf, int file_fd, + loff_t off, bool is_read) +{ + int mem_fd = membuf->io_fd_; + size_t buf_len = membuf->buf_len_; + + if (mem_fd < 0) + return -__LINE__; + + lseek(mem_fd, 0, SEEK_SET); + lseek(file_fd, off, SEEK_SET); + if (is_read) + return sendfile(mem_fd, file_fd, NULL, buf_len); + else + return sendfile(file_fd, mem_fd, NULL, buf_len); +} + +ssize_t membuf_splice(const struct mem_buf_st *membuf, int file_fd, + loff_t off, bool is_read) +{ + size_t len = 0, out_len = 0, buf_len = membuf->buf_len_; + int mem_fd = membuf->io_fd_; + int fd_in = file_fd, fd_out = mem_fd; + ssize_t ret = 0; + static int s_pipe_fds[2] = { -1, -1}; + + if (mem_fd < 0) + return -__LINE__; + + lseek(mem_fd, 0, SEEK_SET); + lseek(file_fd, off, SEEK_SET); + if (s_pipe_fds[0] < 0) { + const int pipe_size = SIZ_MB * 32; + int pipe_fds[2]; + ret = pipe(pipe_fds); + if (ret < 0) + return -__LINE__; + ret = fcntl(pipe_fds[1], F_SETPIPE_SZ, pipe_size); + if (ret < 0) + return -__LINE__; + ret = fcntl(pipe_fds[0], F_GETPIPE_SZ, pipe_size); + if (ret != pipe_size) + return -__LINE__; + s_pipe_fds[0] = pipe_fds[0]; + s_pipe_fds[1] = pipe_fds[1]; + } + + if (!is_read) { + fd_in = mem_fd; + fd_out = file_fd; + } + + while (buf_len > len) { + ret = splice(fd_in, NULL, s_pipe_fds[1], NULL, buf_len - len, SPLICE_F_NONBLOCK); + if (ret <= 0) + break; + len += ret; + do { + ret = splice(s_pipe_fds[0], NULL, fd_out, NULL, len - out_len, 0); + if (ret <= 0) + break; + out_len += ret; + } while (out_len < len); + } + return out_len > 0 ? out_len : ret; +} + +ssize_t membuf_cfr(const struct mem_buf_st *membuf, int file_fd, loff_t off, + bool is_read) +{ + loff_t mem_pos = 0; + loff_t file_pos = off; + size_t out_len = 0, buf_len = membuf->buf_len_; + int mem_fd = membuf->io_fd_; + int fd_in = file_fd, fd_out = mem_fd; + loff_t pos_in = file_pos, pos_out = mem_pos; + ssize_t ret = 0; + + if (mem_fd < 0) + return -__LINE__; + + lseek(mem_fd, mem_pos, SEEK_SET); + lseek(file_fd, file_pos, SEEK_SET); + + if (!is_read) { + fd_in = mem_fd; + fd_out = file_fd; + pos_in = mem_pos; + pos_out = file_pos; + } + + while (buf_len > out_len) { + ret = copy_file_range(fd_in, &pos_in, fd_out, &pos_out, buf_len - out_len, 0); + if (ret <= 0) + break; + out_len += ret; + } + return out_len > 0 ? out_len : ret; +} + +ssize_t membuf_io(const struct mem_buf_st *membuf, int file_fd, loff_t off, + bool is_read, enum copy_io_type io_type) +{ + ssize_t ret = 0; + if (io_type == IO_MAP_READ_WRITE) { + ret = membuf_read_write(membuf, file_fd, off, is_read); + } else if (io_type == IO_SENDFILE) { + ret = membuf_sendfile(membuf, file_fd, off, is_read); + } else if (io_type == IO_SPLICE) { + ret = membuf_splice(membuf, file_fd, off, is_read); + } else if (io_type == IO_COPY_FILE_RANGE) { + ret = membuf_cfr(membuf, file_fd, off, is_read); + } else + return -1; + if (ret < 0) + printf("membuf_io io failed %d\n", errno); + return ret; +} + +const char *membuf_tc_desc(const struct mem_buf_tc *tc) +{ + static char buf[32]; + snprintf(buf, sizeof(buf), "%s %s %s", mem_buf_type_descs[tc->buf_type_], + tc->direct_io_ ? "direct" : "buffer", io_type_descs[tc->io_type_]); + return buf; +} + +bool test_membuf(struct mem_buf_tc *tc, loff_t pos, size_t file_len, + size_t buf_siz, bool is_read, bool clean_pagecaches) +{ + loff_t off = pos, file_end = pos + file_len; + int file_fd = tc->file_fd_; + int i = 0, buf_num; + struct mem_buf_st *membufs; + struct timespec ts_start, ts_end; + ssize_t ret; + + if (buf_siz > file_len) + buf_siz = file_len; + buf_num = (file_len + buf_siz - 1) / buf_siz; + membufs = (struct mem_buf_st *)malloc(sizeof(*membufs) * buf_num); + if (!membufs) + return false; + + memset(membufs, 0, sizeof(*membufs) * buf_num); + drop_pagecaches(-1, 0, 0); + for (i = 0; i < buf_num && off < file_end; i++, off += buf_siz) { + if (buf_siz > file_end - off) + buf_siz = file_end - off; + + if (clean_pagecaches) + drop_pagecaches(file_fd, off, buf_siz); + + clock_gettime(CLOCK_MONOTONIC, &ts_start); + if (!membuf_init(&membufs[i], buf_siz, tc->buf_type_)) { + printf("alloc %s %d failed\n", membuf_tc_desc(tc), i); + break; + } + clock_gettime(CLOCK_MONOTONIC, &ts_end); + tc->times_create_ += times_us_duration(&ts_start, &ts_end); + + clock_gettime(CLOCK_MONOTONIC, &ts_start); + if (!membufs[i].buf_ptr_) { + printf("map %s %d failed\n", membuf_tc_desc(tc), i); + break; + } + if (!is_read) + test_fill_data(membufs[i].buf_ptr_, off + 1, buf_siz, true); + clock_gettime(CLOCK_MONOTONIC, &ts_end); + tc->times_data_ += times_us_duration(&ts_start, &ts_end); + + clock_gettime(CLOCK_MONOTONIC, &ts_start); + ret = membuf_io(&membufs[i], file_fd, off, is_read, tc->io_type_); + if (ret < 0 || ret != buf_siz) { + printf("membuf_io %s %d rw %zd ret %zd failed %d\n", + membuf_tc_desc(tc), i, buf_siz, ret, errno); + break; + } + clock_gettime(CLOCK_MONOTONIC, &ts_end); + tc->times_io_ += times_us_duration(&ts_start, &ts_end); + + clock_gettime(CLOCK_MONOTONIC, &ts_start); + if (!test_check_data(membufs[i].buf_ptr_, off + 1, buf_siz, true)) { + printf("check data %s %d failed\n", membuf_tc_desc(tc), i); + break; + } + clock_gettime(CLOCK_MONOTONIC, &ts_end); + tc->times_data_ += times_us_duration(&ts_start, &ts_end); + + if (clean_pagecaches) + drop_pagecaches(file_fd, off, buf_siz); + } + + clock_gettime(CLOCK_MONOTONIC, &ts_start); + for (i = 0; i < buf_num; i++) { + membuf_deinit(&membufs[i]); + } + clock_gettime(CLOCK_MONOTONIC, &ts_end); + tc->times_close_ += times_us_duration(&ts_start, &ts_end); + drop_pagecaches(-1, 0, 0); + tc->io_len_ = off - pos; + return off - pos >= file_end; +} + +bool prepare_init_file(int file_fd, loff_t off, size_t file_len) +{ + struct mem_buf_st membuf = {}; + ssize_t ret; + + ftruncate(file_fd, off + file_len); + + if (!membuf_init(&membuf, file_len, BUF_MEMFD)) + return false; + + test_fill_data(membuf.buf_ptr_, off + 1, file_len, false); + ret = membuf_io(&membuf, file_fd, off, false, IO_MAP_READ_WRITE); + membuf_deinit(&membuf); + + return ret >= file_len; +} + +bool prepare_file(const char *filepath, loff_t off, size_t file_len) +{ + ssize_t file_end; + bool suc = true; + int flags = O_RDWR | O_CLOEXEC | O_LARGEFILE | O_CREAT; + int file_fd = open(filepath, flags, 0660); + if (file_fd < 0) { + printf("open %s failed %d\n", filepath, errno); + return false; + } + + file_end = (size_t)lseek(file_fd, 0, SEEK_END); + if (file_end < off + file_len) + suc = prepare_init_file(file_fd, off, file_len); + + close(file_fd); + return suc; +} + +void test_membuf_cases(struct mem_buf_tc *test_cases, int test_count, + loff_t off, size_t file_len, size_t buf_siz, + bool is_read, bool clean_pagecaches) +{ + char title[64]; + long file_MB = (file_len + SIZ_MB - 1) / SIZ_MB; + long buf_MB = (buf_siz + SIZ_MB - 1) / SIZ_MB; + long base_io_time, io_times; + long base_io_speed, io_speed; + struct mem_buf_tc *tc; + int n; + + for (n = 0; n < test_count; n++) { + test_membuf(&test_cases[n], off, file_len, buf_siz, is_read, clean_pagecaches); + } + + snprintf(title, sizeof(title), "%ldx%ldMB %s %4ldMB", + (file_MB + buf_MB - 1) / buf_MB, buf_MB, + is_read ? "Read" : "Write", file_MB); + + base_io_time = test_cases[0].times_io_ ?: 1; + base_io_speed = test_cases[0].io_len_ / base_io_time ? : 1000; + + printf("| %-23s|%8s|%8s|%8s|%8s| I/O%%\n", title, + "Creat-ms", "Close-ms", "I/O-ms", "I/O-MB/s"); + printf("|---------------------------|--------|--------|--------|--------|-----\n"); + for (n = 0; n < test_count; n++) { + tc = &test_cases[n]; + io_times = tc->times_io_; + io_speed = io_times > 0 ? tc->io_len_ / io_times : 0; + + printf("|%2d) %23s| %6ld | %6ld | %6ld | %6ld |%4ld%%\n", n + 1, + membuf_tc_desc(tc), time_us2ms(tc->times_create_), + time_us2ms(tc->times_close_), time_us2ms(io_times), + io_speed, io_speed * 100 / base_io_speed); + } + return; +} + +void test_all_membufs(const char *filepath, loff_t off, size_t file_len, size_t buf_siz, + bool is_read, bool clean_pagecaches) +{ + int buffer_fd; + int direct_fd; + + buffer_fd = open(filepath, O_RDWR | O_CLOEXEC | O_LARGEFILE); + direct_fd = open(filepath, O_RDWR | O_CLOEXEC | O_LARGEFILE | O_DIRECT); + if (buffer_fd < 0 || direct_fd < 0) { + printf("buffer_fd %d direct_fd %d\n", buffer_fd, direct_fd); + return; + } + + struct mem_buf_tc test_cases[] = { + {BUF_MEMFD, IO_MAP_READ_WRITE, buffer_fd, false}, + {BUF_MEMFD, IO_MAP_READ_WRITE, direct_fd, true}, + {BUF_UDMA_MEMFD, IO_MAP_READ_WRITE, buffer_fd, false}, + {BUF_UDMA_MEMFD, IO_MAP_READ_WRITE, direct_fd, true}, + {BUF_UDMA_MEMFD, IO_SENDFILE, buffer_fd, false}, + {BUF_UDMA_MEMFD, IO_SENDFILE, direct_fd, true}, + {BUF_UDMA_MEMFD, IO_SPLICE, buffer_fd, false}, + {BUF_UDMA_MEMFD, IO_SPLICE, direct_fd, true}, + {BUF_UDMABUF, IO_MAP_READ_WRITE, buffer_fd, false}, + {BUF_DMABUF, IO_MAP_READ_WRITE, buffer_fd, false}, + {BUF_UDMABUF, IO_COPY_FILE_RANGE, buffer_fd, false}, + {BUF_UDMABUF, IO_COPY_FILE_RANGE, direct_fd, true}, + {BUF_DMABUF, IO_COPY_FILE_RANGE, buffer_fd, false}, + {BUF_DMABUF, IO_COPY_FILE_RANGE, direct_fd, true}, + }; + + test_membuf_cases(test_cases, sizeof(test_cases) / sizeof(test_cases[0]), + off, file_len, buf_siz, is_read, clean_pagecaches); + close(buffer_fd); + close(direct_fd); +} + +void usage(void) +{ + printf("usage: dmabuf-dio [file_path] [size_MB]\n"); + return; +} + +int main(int argc, char *argv[]) +{ + const char *file_path = "/data/membuf.tmp"; + size_t file_len = SIZ_MB * 1024; + + if (argc > 1) + file_path = argv[1]; + if (argc > 2) + file_len = atoi(argv[2]) * SIZ_MB; + if (file_len < 0) + file_len = SIZ_MB * 1024; + if (!prepare_file(file_path, 0, file_len)) { + usage(); + return -1; + } + + test_all_membufs(file_path, 0, file_len, SIZ_MB * 32, true, true); + return 0; +} --