У меня есть внешнее устройство FPGA, которое сбрасывает огромные объемы данных через PCIe в зарезервированную (используя параметры загрузочного загрузчика) смежную область памяти. Эта область памяти всегда будет запускаться в том же месте.Как соединить/dev/mem?
Теперь я хочу сбросить эти данные по UDP как можно быстрее. Мне не нужно изучать эти данные, поэтому нет необходимости вводить их в пространство пользователя. Таким образом, мои исследования показали, что использование нулевой копии - это самый быстрый/лучший способ сделать это.
Я пытаюсь int memFd = open("/dev/mem", O_RDONLY);
, а затем с помощью memFd
в sendfile
и splice
вызовов функций, но это не удается.
Прошло несколько дней, но я, наконец, увидел в 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);
Моя система информации:
- встроенное устройство работает Linux 3.1.10 на ARM архитектуры
- работает как корневой пользователь
- ядро было не компилируется с
CONFIG_STRICT_DEVMEM
Другое интересные факты:
- У меня есть виртуальная машина 2.6 linux CentOS, и этот код работает отлично до смещения ~ 1 МБ. Однако это ядро было скомпилировано с
CONFIG_STRICT_DEVMEM
, поэтому я приписываю этому пределу 1 МБ. - Я могу
mmap
в область памяти просто отлично и видеть данные, которые записывает FPGA.
Мои вопросы:
- Пользуется
splice
правильный способ сделать это? Кто-то думает, что есть лучший способ? - Если
splice
прав, никто не знает, что здесь может быть? Может ли быть флаг компилятора ядра, препятствующий этому работать? Я читал исходный код отsplice.c
, но это была не версия 3.1.10, поэтому, возможно, что-то изменилось? В любом случае, это облом, чтобы увидеть эту работу просто отлично в VM, но не во встроенной среде.
EDIT: Я загрузил источник 3.1.10 с сайта kernal.org и, к сожалению, не вижу существенных отличий от того, что я искал на free-electrons.com, с другой версией. Похоже на то, что весь код сращивания находится в /fs/splice.c.do_splice(...)
должен быть кодом, который выполняется. Мой первый вызов splice
(с использованием memFd
и filedes[1]
) должны упасть до if (opipe) {
... здесь вы можете увидеть, что EFAULT
возвращается, если copy_from_user
или copy_to_user
неудачу .. как бы это терпит неудачу? Не может быть ничего плохого в моей переменной &offset
, так как я получаю ту же ошибку, если это NULL
или нет ошибки, если я заменю fileFd
вместо memFd
. Также что-то интересное, ошибок нет, если я заменю 128 на 0 (количество байтов для записи). Места, где 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;
}
Почему не DMA непосредственно к NIC? Вероятно, это возможно на вашей ARM. Вы должны иметь (по сути) драйвер устройства для сетевого адаптера, реализованный в FPGA, не так уж плохо, если вы делаете UDP. И было бы хорошо, если бы UDP был быстрее скорости передачи данных вашего FPGA ... Переход через память процессора, как вы делаете, - это добавить время. – bazza
@bazza Это хорошая идея, однако парень, пишущий прошивку для fpga, не хочет настраивать контроллер DMA. Мы также стараемся не вмешиваться в пространство ядра, просто чтобы сэкономить на сложности. Если мы доберемся до конца, и это слишком медленно, и у нас все еще есть время/деньги, это будет хорошей идеей. Мы ожидаем, что FPGA будет самым быстрым, а все остальное будет медленнее, поэтому данные будут куда-то падать. – yano
В зависимости от вашей точной архитектуры системы необязательно должен быть драйвер ядра. Это было бы просто что-то, что FPGA делает для NIC вне контроля процессора. Однако падение данных может быть сложным. Отложив это в сторону, я думаю, вам нужно торговать временем, затраченным на выполнение всего этого в FPGA, и время, затраченное на то, чтобы свести Linux к вашим потребностям. Вы можете взглянуть на обход ядра, который, по крайней мере, позволит вам сделать сам пакет, вместо того, чтобы пытаться передавать данные через собственный сетевой стек ядра. – bazza