dma_mmap_coherent () 매핑 메모리의 제로 카피 사용자 공간 TCP 전송


14

하나의 칩에 2 개의 ARMv7 코어가있는 FPGA 인 Cyclone V SoC에서 Linux 5.1을 실행하고 있습니다. 내 목표는 외부 인터페이스에서 많은 데이터를 수집하고 TCP 소켓을 통해이 데이터를 스트림하는 것입니다. 여기서의 문제는 데이터 전송률이 매우 높고 GbE 인터페이스를 포화시키는 데 근접 할 수 있다는 것입니다. write()소켓에 대한 호출을 사용하는 실제 구현이 있지만 55MB / s에서 최고입니다. 이론적 GbE 한계의 약 절반입니다. 처리량을 높이기 위해 제로 복사 TCP 전송을 시도하고 있지만 벽에 부딪 치고 있습니다.

FPGA에서 데이터를 Linux 사용자 공간으로 가져 오기 위해 커널 드라이버를 작성했습니다. 이 드라이버는 FPGA의 DMA 블록을 사용하여 많은 양의 데이터를 외부 인터페이스에서 ARMv7 코어에 연결된 DDR3 메모리로 복사합니다. 드라이버 는 dma_alloc_coherent()with를 사용하여 프로브 할 때이 메모리를 연속 된 1MB 버퍼의 무리로 할당 하고 GFP_USER, mmap()파일 을 구현 하고 사전 할당 된 버퍼를 /dev/사용하여 애플리케이션에 주소를 반환 하여 사용자 공간 애플리케이션에이를 노출합니다 dma_mmap_coherent().

여태까지는 그런대로 잘됐다; 사용자 공간 응용 프로그램에 유효한 데이터가 표시되고 여유 공간이있는> 360MB / s에서 처리량이 충분합니다 (외부 인터페이스는 상한을 실제로 볼 수있을만큼 빠르지 않습니다).

무 복사 TCP 네트워킹을 구현하기 위해 첫 번째 접근 방식은 SO_ZEROCOPY소켓 에서 사용 하는 것입니다.

sent_bytes = send(fd, buf, len, MSG_ZEROCOPY);
if (sent_bytes < 0) {
    perror("send");
    return -1;
}

그러나이 결과는 send: Bad address입니다.

조금 인터넷 검색을 한 후, 두 번째 접근 방식은 파이프를 사용하고 splice()다음 과 vmsplice()같습니다.

ssize_t sent_bytes;
int pipes[2];
struct iovec iov = {
    .iov_base = buf,
    .iov_len = len
};

pipe(pipes);

sent_bytes = vmsplice(pipes[1], &iov, 1, 0);
if (sent_bytes < 0) {
    perror("vmsplice");
    return -1;
}
sent_bytes = splice(pipes[0], 0, fd, 0, sent_bytes, SPLICE_F_MOVE);
if (sent_bytes < 0) {
    perror("splice");
    return -1;
}

그러나 결과는 동일 vmsplice: Bad address합니다.

내가 가리키는 (또는 없는 ) 데이터를 인쇄하는 함수 호출 vmsplice()또는 send()함수를 대체하면 모든 것이 잘 작동합니다. 따라서 사용자 공간에서 데이터에 액세스 할 수 있지만 / 호출로 처리 할 수없는 것 같습니다.bufsend() MSG_ZEROCOPYvmsplice()send(..., MSG_ZEROCOPY)

내가 여기서 무엇을 놓치고 있습니까? 커널 드라이버에서 얻은 사용자 공간 주소로 zero-copy TCP 전송을 사용하는 방법이 dma_mmap_coherent()있습니까? 사용할 수있는 다른 방법이 있습니까?

최신 정보

그래서 sendmsg() MSG_ZEROCOPY커널 의 경로를 조금 더 깊이 파고 들었고 결국 실패하는 호출은 get_user_pages_fast()입니다. 이 호출은 에서 설정된 플래그를 찾기 -EFAULT때문에 반환 됩니다. 페이지가 사용하는 사용자 공간에 매핑 할 때이 플래그는 분명히 설정 또는 . 다음 접근 방법은 이 페이지에 다른 방법을 찾는 것 입니다.check_vma_flags()VM_PFNMAPvmaremap_pfn_range()dma_mmap_coherent()mmap

답변:


8

내 질문에 업데이트를 게시했을 때 근본적인 문제는 zerocopy 네트워킹이 매핑 된 메모리 remap_pfn_range()( dma_mmap_coherent()후드에서도 사용됨)에 대해 작동하지 않는다는 것입니다 . 그 이유는이 유형의 메모리 ( VM_PFNMAP플래그 세트가있는)에 struct page*필요한 각 페이지와 연관된 형식의 메타 데이터가 없기 때문입니다.

용액을하는 방식으로 메모리를 할당하는 struct page*S가 되는 메모리와 연관된.

메모리를 할당하는 데 필요한 워크 플로는 다음과 같습니다.

  1. struct page* page = alloc_pages(GFP_USER, page_order);연속적인 물리적 메모리 블록을 할당하는 데 사용 합니다. 여기서 할당 될 연속 페이지 수는로 지정됩니다 2**page_order.
  2. 을 호출하여 고차 / 복합 페이지를 0 차 페이지로 분할하십시오 split_page(page, page_order);. 이것은 이제 항목 struct page* page이있는 배열이 되었음을 의미 2**page_order합니다.

이제 이러한 지역을 DMA에 제출하려면 (데이터 수 신용) :

  1. dma_addr = dma_map_page(dev, page, 0, length, DMA_FROM_DEVICE);
  2. dma_desc = dmaengine_prep_slave_single(dma_chan, dma_addr, length, DMA_DEV_TO_MEM, 0);
  3. dmaengine_submit(dma_desc);

전송이 완료된 DMA에서 콜백을 받으면이 메모리 블록의 소유권을 CPU로 다시 전송하기 위해 리전을 매핑 해제해야합니다. 캐시가 오래된 데이터를 읽지 않도록 캐시를 처리합니다.

  1. dma_unmap_page(dev, dma_addr, length, DMA_FROM_DEVICE);

이제 우리가 구현하고 싶을 때 mmap()실제로 vm_insert_page()할당해야 할 것은 우리가 미리 할당 한 모든 0 페이지를 반복적 으로 호출 하는 것입니다.

static int my_mmap(struct file *file, struct vm_area_struct *vma) {
    int res;
...
    for (i = 0; i < 2**page_order; ++i) {
        if ((res = vm_insert_page(vma, vma->vm_start + i*PAGE_SIZE, &page[i])) < 0) {
            break;
        }
    }
    vma->vm_flags |= VM_LOCKED | VM_DONTCOPY | VM_DONTEXPAND | VM_DENYWRITE;
...
    return res;
}

파일이 닫히면 페이지를 비우는 것을 잊지 마십시오.

for (i = 0; i < 2**page_order; ++i) {
    __free_page(&dev->shm[i].pages[i]);
}

mmap()이 방법을 구현 하면 소켓 sendmsg()에서 MSG_ZEROCOPY플래그 와 함께이 버퍼를 사용할 수 있습니다 .

이것이 효과가 있지만,이 접근법으로 나와 잘 어울리지 않는 두 가지가 있습니다.

  • 이 방법을 사용하면 2 크기의 버퍼 만 할당 할 수 있지만 alloc_pages크기를 변경하여 다양한 크기의 하위 버퍼로 구성된 크기 버퍼를 가져 오는 순서를 줄여 필요한 횟수만큼 호출하는 논리를 구현할 수 있습니다. 그런 다음 이러한 버퍼를에 묶고 mmap()DMA에 스 캐터 수집 ( sg) 호출이 아닌 스 캐터 수집 ( ) 호출로 논리가 필요합니다 single.
  • split_page() 문서에서 말합니다 :
 * Note: this is probably too low level an operation for use in drivers.
 * Please consult with lkml before using this in your driver.

커널에 임의의 양의 인접한 물리적 페이지를 할당하기위한 인터페이스가 있으면 이러한 문제를 쉽게 해결할 수 있습니다. 왜 존재하지 않는지 모르겠지만 왜 이것이 사용할 수 없는지 / 구현하는 방법을 파헤쳐 야하는 위의 문제를 찾지 못했습니다 :-)


2

아마 이것은 alloc_pages에 2의 제곱 페이지 번호가 필요한 이유를 이해하는 데 도움이 될 것입니다.

Linux 커널은 자주 사용되는 페이지 할당 프로세스를 최적화하고 외부 조각화를 줄이기 위해 CPU 당 페이지 캐시와 버디 할당기를 개발하여 메모리를 할당합니다 (다른 할당 기인 슬랩이 있습니다. 페이지).

CPU 당 페이지 캐시는 한 페이지 할당 요청을 처리하는 반면, 친구 할당 기는 각각 2 ^ {0-10} 개의 물리적 페이지를 포함하는 11 개의 목록을 유지합니다. 이 목록은 페이지를 할당하고 비울 때 잘 수행되며 물론 2의 제곱 버퍼를 요청한다는 전제입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.