2016-02-15 4 views
0

У меня есть внешнее устройство 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.

Мои вопросы:

  1. Пользуется splice правильный способ сделать это? Кто-то думает, что есть лучший способ?
  2. Если 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; 
} 
+1

Почему не DMA непосредственно к NIC? Вероятно, это возможно на вашей ARM. Вы должны иметь (по сути) драйвер устройства для сетевого адаптера, реализованный в FPGA, не так уж плохо, если вы делаете UDP. И было бы хорошо, если бы UDP был быстрее скорости передачи данных вашего FPGA ... Переход через память процессора, как вы делаете, - это добавить время. – bazza

+0

@bazza Это хорошая идея, однако парень, пишущий прошивку для fpga, не хочет настраивать контроллер DMA. Мы также стараемся не вмешиваться в пространство ядра, просто чтобы сэкономить на сложности. Если мы доберемся до конца, и это слишком медленно, и у нас все еще есть время/деньги, это будет хорошей идеей. Мы ожидаем, что FPGA будет самым быстрым, а все остальное будет медленнее, поэтому данные будут куда-то падать. – yano

+0

В зависимости от вашей точной архитектуры системы необязательно должен быть драйвер ядра. Это было бы просто что-то, что FPGA делает для NIC вне контроля процессора. Однако падение данных может быть сложным. Отложив это в сторону, я думаю, вам нужно торговать временем, затраченным на выполнение всего этого в FPGA, и время, затраченное на то, чтобы свести Linux к вашим потребностям. Вы можете взглянуть на обход ядра, который, по крайней мере, позволит вам сделать сам пакет, вместо того, чтобы пытаться передавать данные через собственный сетевой стек ядра. – bazza

ответ

0

mmap области памяти, а затем использовать обычный write или vmsplice

+0

Спасибо, используя регулярную 'write', делает работу, и то, как это происходит, я соглашусь на это, но на основе того, что я прочитал' splice' должен быть быстрее (скорость будет очень важна), поэтому в идеале я бы хотел, чтобы это работало. Также на данный момент мне чертовски интересно, в чем проблема. – yano

+1

Я предлагаю вам просмотреть профиль и сравнить, какой из них (vmsplice или write) лучше (vmsplice - это аналог сращивания, который работает с страницами памяти на o ne end вместо трубы). – datenwolf

+0

Это его собственный вопрос, но если я зарезервирую память с помощью 'memmap', какое пространство это? Я бы не назвал это пространство пользователя или ядра. Системное пространство? Насколько я могу судить, Linux не знает об этом (не упоминается в 'dmesg'), кроме того, что Linux не будет считать это очевидным в'/proc/iomem'. На самом деле я очень удивлен, что могу «умереть». То, что я получаю, - это то, что я читал о 'vmsplice', похоже, что его целью является перемещение памяти пространства пользователя в пространство ядра. Если это требует переместить эти данные из системного пространства -> пространства пользователя -> пространства ядра, 'mmap, write' выглядит лучше всего. – yano

Смежные вопросы