简体   繁体   中英

How to splice /dev/mem?

I have an external FPGA device that is dumping vast amounts of data via PCIe to a reserved (using boot-loader parameters) contiguous memory region. This memory region will always start in the same location.

I now want to dump that data over UDP as quickly as possible. I don't care about examining this data so there is no need to bring it into user-space. As such, my research has indicated using zero-copy is the fastest/best way to do this.

I am trying to int memFd = open("/dev/mem", O_RDONLY); , then using memFd in sendfile and splice function calls, but these are failing.

It took a few days, but I finally saw in the sendfile source that the input file descriptor must be a regular file (a detail frustratingly left out of the man page as far as I can tell), and /dev/mem is not a regular file. Anyway, I looked around some more, and now am confident splice is the call I want to use.

However, this is failing as well with an errno of 14-EFAULT which means "bad address" (again frustratingly, this error code is not mentioned in the splice man page). I've looked over the source code for splice , and can see a few times where EFAULT is returned, but I just don't see how the arguments I'm passing are causing a problem.

My simplified, non-error checking code is below;

 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);

My system info:

  • embedded device running linux 3.1.10 on ARM architecture
  • running as root user
  • kernel was NOT compiled with CONFIG_STRICT_DEVMEM

Other fun facts:

  • I have a 2.6 linux CentOS virtual machine and this code works fine up to offsets of ~1MB. However, this kernel was compiled with CONFIG_STRICT_DEVMEM , so I attribute the 1MB limit to that.
  • I can mmap to the memory region just fine and see the data the FPGA is writing.

My questions are:

  1. Is using splice the right way to do this? Does someone think there's a better way?
  2. If splice is right, anybody have any idea what could be happening here? Could there be a kernel compiler flag preventing this from working? I was reading source code from splice.c , but it wasn't the 3.1.10 version, so perhaps something has changed? Either way, it's a bummer to see this work just fine in the VM but not in the embedded environment.

EDIT: I have downloaded the 3.1.10 source from kernal.org and unfortunately see no major differences from what I was looking at on free-electrons.com with a different version. Looks like to me all the splice code is in /fs/splice.c. do_splice(...) must be the code that gets executed. My first call to splice (using memFd and filedes[1] ) should be dropping down to if (opipe) { ... here you can see that EFAULT is returned if copy_from_user or copy_to_user fail .. how could these be failing? There can't be anything wrong with my &offset variable since I get the same error if this is NULL or no error if I substitute fileFd in place of memFd . Also something of interest,, there are no errors if I replace 128 with 0 (number of bytes to write). The places where EFAULT is returned, I just don't see how the file descriptor even factors into that logic,, unless EFAULT is getting returned by some deeper function calls...

These are the snippets from 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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM