2016-02-15 71 views
0

我有一個外部FPGA設備,通過PCIe將大量數據轉儲到保留(使用引導加載程序參數)連續內存區域。該內存區域將始終在相同的位置開始。如何拼接/ dev/mem?

我現在想要儘可能快地通過UDP轉儲數據。我不在乎檢查這些數據,因此不需要將其帶入用戶空間。因此,我的研究表明使用零拷貝是最快/最好的方法。

我試圖int memFd = open("/dev/mem", O_RDONLY);,然後用memFdsendfilesplice函數調用,但是這些都失敗了。

過了幾天,但我終於在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 root身份運行用戶
  • 嵌入式設備
  • 內核沒有用CONFIG_STRICT_DEVMEM

其他編譯有趣的事實:

  • 我有一個2.6 Linux的CentOS虛擬機,這個代碼工作正常,偏移量爲〜1MB。然而,這個內核是用CONFIG_STRICT_DEVMEM編譯的,所以我把1MB的限制歸因於此。
  • 我可以mmap到內存區就好了,看看FPGA寫的數據。

我的問題是:

  1. 是使用splice正確的方式做到這一點?有人認爲有更好的方法嗎?
  2. 如果splice是對的,任何人都知道這裏會發生什麼?有沒有內核編譯器標誌阻止這個工作?我正在閱讀splice.c的源代碼,但它不是3.1.10版本,所以可能有所改變?無論哪種方式,在虛擬機中看到這項工作很好,但在嵌入式環境中卻看不到。

編輯:我已經從kernal.org下載了3.1.10源代碼,很遺憾,看到我在free-electrons.com上看到的與其他版本沒有什麼大的區別。在我看來,所有拼接代碼都位於/fs/splice.c中。do_splice(...)必須是被執行的代碼。我的第一個電話splice(使用memFdfiledes[1])應該下降到if (opipe) { ...在這裏你可以看到EFAULT返回,如果copy_from_usercopy_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將網卡?這在你的ARM上可能是可行的。你必須(實際上)爲在FPGA中實現的NIC設備驅動程序,如果你正在做UDP,那麼不會那麼糟糕。如果UDP的速度比FPGA的數據速率更快,那麼它只會是一件好事......通過像你這樣的CPU內存去做會增加時間。 – bazza

+0

@bazza這是一個好主意,但是爲fpga編寫固件的人不想設置DMA控制器。我們也試圖擺脫內核驅動程序空間,以節省複雜性。如果我們走到最後,速度太慢,我們仍然有時間/金錢,這將是一個好主意。我們期望FPGA速度最快,而其他一切都比較慢,因此數據將會在某處丟失。 – yano

+0

根據您確切的系統架構,不一定必須是內核驅動程序。這僅僅是FPGA對CPU控制之外的NIC的影響。很好地丟棄數據可能會很棘手。除此之外,我想你需要花費時間在FPGA中完成所有工作,而不是根據需要花費時間來完成Linux。你可以看看內核旁路,這至少可以讓你自己做數據包,而不是試圖通過內核自己的網絡堆棧傳遞數據。 – bazza

回答

0

mmap內存區域,然後使用正則writevmsplice

+0

謝謝,使用普通的'write'確實有效,而且這樣做的方式我會解決這個問題,但是根據我讀過的'splice'應該更快(速度將是非常重要的),所以理想情況下,我希望得到這個工作。同時在這一點上我很好奇這是什麼問題 – yano

+1

我建議你分析它,並比較哪兩個vmsplice或寫)更好的價格(vmsplice是與o上的內存頁面一起操作的拼接的對應物ne結束而不是管道)。 – datenwolf

+0

這是它自己的問題,但是如果我用'memmap'保留內存,那是什麼空間?我不會調用該用戶或內核空間。系統空間?據我可以告訴Linux不知道它(在'dmesg'中沒有提及),除了Linux不會在'/ proc/iomem'中聲明這一點。真的,我很驚訝,我可以''映射到它。我得到的是,從我讀過的關於'vmsplice'的文章中,它聽起來像是將用戶空間內存移動到內核空間。如果這要求從系統空間 - >用戶空間 - >內核空間移動這些數據,'mmap,write'似乎是最好的。 – yano