การส่ง TCP พื้นที่ผู้ใช้ที่ไม่มีการคัดลอกของหน่วยความจำที่แมป dma_mmap_coherent ()


14

ฉันใช้ Linux 5.1 บน Cyclone V SoC ซึ่งเป็น FPGA ที่มีสอง ARMv7 cores ในหนึ่งชิป เป้าหมายของฉันคือการรวบรวมข้อมูลจำนวนมากจากอินเทอร์เฟซภายนอกและสตรีม (ส่วนหนึ่ง) ข้อมูลนี้ผ่านทางซ็อกเก็ต TCP ความท้าทายที่นี่คืออัตราข้อมูลสูงมากและอาจเข้าใกล้อินเทอร์เฟซ GbE ที่อิ่มตัว ฉันมีการนำไปใช้งานที่ใช้การwrite()เรียกไปยังซ็อกเก็ต แต่มันใช้งานได้ที่ 55MB / s; ประมาณครึ่งหนึ่งของขีด จำกัด GbE ตามทฤษฎี ตอนนี้ฉันกำลังพยายามให้การส่ง TCP เป็นศูนย์คัดลอกเพื่อเพิ่มปริมาณงาน แต่ฉันชนกำแพง

เพื่อให้ได้ข้อมูลจาก FPGA ลงในพื้นที่ผู้ใช้ Linux ฉันได้เขียนไดรเวอร์เคอร์เนล ไดรเวอร์นี้ใช้บล็อก DMA ใน FPGA เพื่อคัดลอกข้อมูลจำนวนมากจากอินเทอร์เฟซภายนอกไปยังหน่วยความจำ DDR3 ที่ต่ออยู่กับแกน ARMv7 จัดสรรโปรแกรมควบคุมหน่วยความจำนี้เป็นพวงของบัฟเฟอร์ 1MB ต่อเนื่องกันเมื่อตรวจสอบโดยใช้dma_alloc_coherent()กับGFP_USERและ exposes เหล่านี้ไปยังโปรแกรมประยุกต์ userspace โดยการใช้mmap()ไฟล์ใน/dev/และกลับมาอยู่กับการประยุกต์ใช้dma_mmap_coherent()ในบัฟเฟอร์ preallocated

จนถึงตอนนี้ดีมาก; แอปพลิเคชันพื้นที่ผู้ใช้เห็นข้อมูลที่ถูกต้องและปริมาณงานมีมากเกินพอที่> 360MB / s พร้อมพื้นที่ว่าง (อินเทอร์เฟซภายนอกไม่เร็วพอที่จะดูว่าขอบเขตบนคืออะไร)

ในการใช้เครือข่าย TCP แบบ zero-copy แนวทางแรกของฉันคือใช้SO_ZEROCOPYกับซ็อกเก็ต:

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

send: Bad addressอย่างไรก็ตามผลนี้

หลังจาก googling สักพักวิธีที่สองของฉันคือใช้ไพพ์และ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)โทรจะไม่สามารถจัดการได้

ฉันหายไปนี่อะไร มีวิธีการใช้การส่ง TCP เป็นศูนย์สำเนาด้วยที่อยู่พื้นที่ผู้ใช้ที่ได้รับจากเคอร์เนลไดรเวอร์ผ่านdma_mmap_coherent()? มีวิธีอื่นที่ฉันสามารถใช้ได้หรือไม่?

UPDATE

ดังนั้นผมจึงนกพิราบบิตลึกเข้าไปในsendmsg() MSG_ZEROCOPYเส้นทางใน kernel get_user_pages_fast()และโทรที่ล้มเหลวในที่สุดคือ ผลตอบแทนที่สายนี้-EFAULTเพราะcheck_vma_flags()พบชุดธงในVM_PFNMAP vmaตั้งค่าสถานะนี้เห็นได้ชัดว่าเมื่อหน้าเว็บที่มีการแมปลงในพื้นที่ของผู้ใช้โดยใช้หรือremap_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 จึงต้องใช้หมายเลขหน้า power-of-2

เพื่อปรับกระบวนการจัดสรรเพจให้เหมาะสม (และลดการแตกแฟรกเมนต์ภายนอก) ซึ่งมีส่วนร่วมอยู่บ่อยๆเคอร์เนล Linux พัฒนาแคชเพจต่อซีพียูและ Buddy-allocator เพื่อจัดสรรหน่วยความจำ หน้า).

แคชหน้าต่อซีพียูทำหน้าที่ร้องขอการจัดสรรแบบหน้าเดียวในขณะที่ buddy-allocator เก็บ 11 รายการแต่ละหน้ามีฟิสิคัล 2 ^ {0-10} หน้าตามลำดับ รายการเหล่านี้ทำงานได้ดีเมื่อมีการจัดสรรและเพิ่มเพจให้ฟรีและแน่นอนว่าสถานที่ตั้งคือคุณกำลังร้องขอบัฟเฟอร์ที่มีขนาดเท่ากันหมด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.