c - 如何拼接/dev/mem?

标签 c linux splice

我有一个外部 FPGA 设备,它通过 PCIe 将大量数据转储到保留的(使用引导加载程序参数)连续内存区域。该内存区域将始终从同一位置开始。

我现在想尽快通过 UDP 转储该数据。我不关心检查这些数据,所以没有必要将它带入用户空间。因此,我的研究表明使用零拷贝是最快/最好的方法。

我正在尝试 int memFd = open("/dev/mem", O_RDONLY);,然后在 sendfile 中使用 memFdsplice 函数调用,但这些都失败了。

花了几天时间,但我终于在 sendfile 源代码中看到输入文件描述符必须是一个常规文件(据我所知,手册页中遗漏了一个令人沮丧的细节), /dev/mem 不是常规文件。不管怎样,我又四处看了看,现在确信 splice 是我想要使用的调用。

但是,这也失败了,错误号为 14-EFAULT,意思是“地址错误”(同样令人沮丧的是,splice 手册页中未提及此错误代码)。我查看了 splice 的源代码,可以看到几次返回 EFAULT 的地方,但我只是看不出我传递的参数是如何导致问题的。

我的简化、无错误检查代码如下;

 int filedes[2];
 int memFd = open("/dev/mem", O_RDONLY);
 int fileFd = open("myTestFile.txt", O_RDONLY);
 loff_t offset = START_OF_MEM_REGION;
 int sockFd = ConfigureMySocket();

 pipe(filedes);  // this returns 0, so the pipes are good

 int ret = splice(memFd, &offset, filedes[1], NULL, 128, SPLICE_F_MOVE); // this fails with EFAULT
 //int ret = splice(memFd, NULL, filedes[1], NULL, 128, 0); // this also fails with EFAULT
 //int ret = splice(fileFd, NULL, filedes[1], NULL, 128, 0); // this works just fine

 // this is never reached because the splice call above hangs. If I run the
 // fileFd splice call instead this works just fine
 ret = splice(filedes[0], NULL, sockFd, NULL, 128, 0);

我的系统信息:

  • 在 ARM 架构上运行 linux 3.1.10 的嵌入式设备
  • 以根用户身份运行
  • 内核未使用 CONFIG_STRICT_DEVMEM 编译

其他有趣的事实:

  • 我有一个 2.6 linux CentOS 虚拟机,这段代码在 ~1MB 的偏移量下工作正常。但是,此内核是使用 CONFIG_STRICT_DEVMEM 编译的,因此我将 1MB 的限制归因于此。
  • 我可以很好地mmap 到内存区域并查看 FPGA 正在写入的数据。

我的问题是:

  1. 使用 splice 是执行此操作的正确方法吗?有人认为有更好的方法吗?
  2. 如果 splice 是正确的,那么有人知道这里会发生什么吗?是否有一个内核编译器标志阻止它工作?我正在阅读 splice.c 的源代码,但它不是 3.1.10 版本,所以也许有什么变化?无论哪种方式,看到这项工作在 VM 中运行良好,但在嵌入式环境中运行良好,都是令人遗憾的。

编辑:我已经从 kernal.org 下载了 3.1.10 源代码,不幸的是,我发现与我在 free-electrons.com 上看到的不同版本没有重大差异。在我看来,所有拼接代码都在/fs/splice.c 中。 do_splice(...) 必须是要执行的代码。我对 splice 的第一次调用(使用 memFdfiledes[1])应该下降到 if (opipe) {... 在这里你可以看到如果 copy_from_usercopy_to_user 失败返回 EFAULT ..这些怎么会失败?我的 &offset 变量没有任何问题,因为如果这是 NULL 我会得到同样的错误,或者如果我将 fileFd 替换为没有错误memFd 的位置。同样有趣的是,如果我用 0(要写入的字节数)替换 128,则没有错误。返回 EFAULT 的地方,我只是看不到文件描述符是如何影响该逻辑的,除非 EFAULT 被一些更深层次的函数调用返回。 .

这些是 splice.c

的片段
SYSCALL_DEFINE6(splice, int, fd_in, loff_t __user *, off_in,
        int, fd_out, loff_t __user *, off_out,
        size_t, len, unsigned int, flags)
{
    long error;
    struct file *in, *out;
    int fput_in, fput_out;

    if (unlikely(!len))
        return 0;

    error = -EBADF;
    in = fget_light(fd_in, &fput_in);
    if (in) {
        if (in->f_mode & FMODE_READ) {
            out = fget_light(fd_out, &fput_out);
            if (out) {
                if (out->f_mode & FMODE_WRITE)
                    error = do_splice(in, off_in,
                              out, off_out,
                              len, flags);
                fput_light(out, fput_out);
            }
        }

        fput_light(in, fput_in);
    }

    return error;
}

static long do_splice(struct file *in, loff_t __user *off_in,
              struct file *out, loff_t __user *off_out,
              size_t len, unsigned int flags)
{
    struct pipe_inode_info *ipipe;
    struct pipe_inode_info *opipe;
    loff_t offset, *off;
    long ret;

    ipipe = get_pipe_info(in);
    opipe = get_pipe_info(out);

    if (ipipe && opipe) {
        if (off_in || off_out)
            return -ESPIPE;

        if (!(in->f_mode & FMODE_READ))
            return -EBADF;

        if (!(out->f_mode & FMODE_WRITE))
            return -EBADF;

        /* Splicing to self would be fun, but... */
        if (ipipe == opipe)
            return -EINVAL;

        return splice_pipe_to_pipe(ipipe, opipe, len, flags);
    }

    if (ipipe) {
        if (off_in)
            return -ESPIPE;
        if (off_out) {
            if (!(out->f_mode & FMODE_PWRITE))
                return -EINVAL;
            if (copy_from_user(&offset, off_out, sizeof(loff_t)))
                return -EFAULT;
            off = &offset;
        } else
            off = &out->f_pos;

        ret = do_splice_from(ipipe, out, off, len, flags);

        if (off_out && copy_to_user(off_out, off, sizeof(loff_t)))
            ret = -EFAULT;

        return ret;
    }

    if (opipe) {
        if (off_out)
            return -ESPIPE;
        if (off_in) {
            if (!(in->f_mode & FMODE_PREAD))
                return -EINVAL;
            if (copy_from_user(&offset, off_in, sizeof(loff_t)))
                return -EFAULT;
            off = &offset;
        } else
            off = &in->f_pos;

        ret = do_splice_to(in, off, opipe, len, flags);

        if (off_in && copy_to_user(off_in, off, sizeof(loff_t)))
            ret = -EFAULT;

        return ret;
    }

    return -EINVAL;
}

最佳答案

mmap 内存区域,然后使用常规的writevmsplice

关于c - 如何拼接/dev/mem?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35420286/

相关文章:

c - 安全使用 MultiByteToWideChar

c++ - 在 Linux 上的 C++ 无限循环中捕获内存不足的错误 bad_alloc()

linux - 我可以有一个仅在代码使用时才需要的动态库吗?

javascript splice() 索引问题

C程序在内部使用fork时迭代太多

c - 如何在 C 中将单词作为变量?

c - 将整个结构数组传递给 C 中的 Pthread_Create

linux - 使用 bash 脚本批量重命名 FTP 服务器上的文件

javascript - JSONArray 上的 Splice 方法

javascript - 删除 javascript 数组中的特定对象