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:
CONFIG_STRICT_DEVMEM
Other fun facts:
CONFIG_STRICT_DEVMEM
, so I attribute the 1MB limit to that. mmap
to the memory region just fine and see the data the FPGA is writing. My questions are:
splice
the right way to do this? Does someone think there's a better way? 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
内存区域,然后使用正则write
或vmsplice
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.