하나의 칩에 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()
함수를 대체하면 모든 것이 잘 작동합니다. 따라서 사용자 공간에서 데이터에 액세스 할 수 있지만 / 호출로 처리 할 수없는 것 같습니다.buf
send()
MSG_ZEROCOPY
vmsplice()
send(..., MSG_ZEROCOPY)
내가 여기서 무엇을 놓치고 있습니까? 커널 드라이버에서 얻은 사용자 공간 주소로 zero-copy TCP 전송을 사용하는 방법이 dma_mmap_coherent()
있습니까? 사용할 수있는 다른 방법이 있습니까?
최신 정보
그래서 sendmsg()
MSG_ZEROCOPY
커널 의 경로를 조금 더 깊이 파고 들었고 결국 실패하는 호출은 get_user_pages_fast()
입니다. 이 호출은 에서 설정된 플래그를 찾기 -EFAULT
때문에 반환 됩니다. 페이지가 사용하는 사용자 공간에 매핑 할 때이 플래그는 분명히 설정 또는 . 다음 접근 방법은 이 페이지에 다른 방법을 찾는 것 입니다.check_vma_flags()
VM_PFNMAP
vma
remap_pfn_range()
dma_mmap_coherent()
mmap
답변
내 질문에 업데이트를 게시했을 때 근본적인 문제는 zerocopy 네트워킹이 매핑 된 메모리 remap_pfn_range()
( dma_mmap_coherent()
후드에서도 사용됨)에 대해 작동하지 않는다는 것입니다 . 그 이유는이 유형의 메모리 ( VM_PFNMAP
플래그 세트가있는)에 struct page*
필요한 각 페이지와 연관된 형식의 메타 데이터가 없기 때문입니다.
용액을하는 방식으로 메모리를 할당하는 struct page*
S가 되는 메모리와 연관된.
메모리를 할당하는 데 필요한 워크 플로는 다음과 같습니다.
struct page* page = alloc_pages(GFP_USER, page_order);
연속적인 물리적 메모리 블록을 할당하는 데 사용 합니다. 여기서 할당 될 연속 페이지 수는로 지정됩니다2**page_order
.- 을 호출하여 고차 / 복합 페이지를 0 차 페이지로 분할하십시오
split_page(page, page_order);
. 이것은 이제 항목struct page* page
이있는 배열이 되었음을 의미2**page_order
합니다.
이제 이러한 지역을 DMA에 제출하려면 (데이터 수 신용) :
dma_addr = dma_map_page(dev, page, 0, length, DMA_FROM_DEVICE);
dma_desc = dmaengine_prep_slave_single(dma_chan, dma_addr, length, DMA_DEV_TO_MEM, 0);
dmaengine_submit(dma_desc);
전송이 완료된 DMA에서 콜백을 받으면이 메모리 블록의 소유권을 CPU로 다시 전송하기 위해 리전을 매핑 해제해야합니다. 캐시가 오래된 데이터를 읽지 않도록 캐시를 처리합니다.
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.
커널에 임의의 양의 인접한 물리적 페이지를 할당하기위한 인터페이스가 있으면 이러한 문제를 쉽게 해결할 수 있습니다. 왜 존재하지 않는지 모르겠지만 왜 이것이 사용할 수 없는지 / 구현하는 방법을 파헤쳐 야하는 위의 문제를 찾지 못했습니다 🙂
답변
아마 이것은 alloc_pages에 2의 제곱 페이지 번호가 필요한 이유를 이해하는 데 도움이 될 것입니다.
Linux 커널은 자주 사용되는 페이지 할당 프로세스를 최적화하고 외부 조각화를 줄이기 위해 CPU 당 페이지 캐시와 버디 할당기를 개발하여 메모리를 할당합니다 (다른 할당 기인 슬랩이 있습니다. 페이지).
CPU 당 페이지 캐시는 한 페이지 할당 요청을 처리하는 반면, 친구 할당 기는 각각 2 ^ {0-10} 개의 물리적 페이지를 포함하는 11 개의 목록을 유지합니다. 이 목록은 페이지를 할당하고 비울 때 잘 수행되며 물론 2의 제곱 버퍼를 요청한다는 전제입니다.