शून्य-प्रति उपयोगकर्ता-स्पेस टीसीपी dma_mmap_coherent () मैप की गई स्मृति को भेजें


14

मैं एक चक्रवात V SoC पर लिनक्स 5.1 चला रहा हूं, जो एक चिप में दो ARMv7 कोर के साथ एक FPGA है। मेरा लक्ष्य एक बाहरी इंटरफ़ेस और टीसीपी सॉकेट के माध्यम से इस डेटा के प्रवाह (भाग) से बहुत सारे डेटा को इकट्ठा करना है। यहां चुनौती यह है कि डेटा दर बहुत अधिक है और GbE इंटरफ़ेस को संतृप्त करने के करीब आ सकती है। मेरे पास एक कार्यशील कार्यान्वयन है जो सिर्फ write()सॉकेट के लिए कॉल का उपयोग करता है , लेकिन यह 55 एमबी / एस में सबसे ऊपर है; लगभग आधा सैद्धांतिक जीबीई सीमा। मैं अब थ्रूपुट को बढ़ाने के लिए शून्य-प्रतिलिपि टीसीपी ट्रांसमिशन प्राप्त करने की कोशिश कर रहा हूं, लेकिन मैं एक दीवार मार रहा हूं।

FPGA से डेटा को लिनक्स यूजर-स्पेस में लाने के लिए, मैंने कर्नेल ड्राइवर लिखा है। यह ड्राइवर एक बाहरी इंटरफ़ेस से ARMv7 कोर से जुड़ी DDR3 मेमोरी में बड़ी मात्रा में डेटा की प्रतिलिपि बनाने के लिए FPGA में DMA ब्लॉक का उपयोग करता है। चालक इस मेमोरी को सन्निहित 1MB बफ़र्स के एक समूह के रूप में आवंटित करता है, जब उपयोग करने की संभावना dma_alloc_coherent()होती है GFP_USER, और उन्हें उपयोक्ता अनुप्रयोग में mmap()एक फाइल पर लागू करके और उपयोक्ता के बफ़र पर /dev/प्रयोग करके एक पते पर वापस dma_mmap_coherent()भेज देता है।

अब तक सब ठीक है; उपयोगकर्ता-स्पेस एप्लिकेशन वैध डेटा देख रहा है और थ्रूपुट कमरे के अतिरिक्त> 360MB / s पर कमरे के अतिरिक्त (बाहरी इंटरफ़ेस वास्तव में ऊपरी सीमा क्या है यह देखने के लिए पर्याप्त तेज़ नहीं है) से अधिक है।

शून्य-प्रतिलिपि टीसीपी नेटवर्किंग को लागू करने के लिए, मेरा पहला दृष्टिकोण 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)कॉल इसे संभालने में असमर्थ हैं।

मुझे यहां क्या समझ नहीं आ रहा है? क्या कर्नेल ड्राइवर से प्राप्त उपयोगकर्ता-स्थान पते के साथ शून्य-प्रतिलिपि टीसीपी भेजने का उपयोग करने का कोई तरीका है dma_mmap_coherent()? क्या कोई अन्य तरीका है जिसका मैं उपयोग कर सकता हूं?

अपडेट करें

इसलिए मैं sendmsg() MSG_ZEROCOPYकर्नेल में पथ में थोड़ा गहरा काम करता हूं , और अंततः विफल होने वाली कॉल है get_user_pages_fast()। यह कॉल लौटाता है -EFAULTक्योंकि ध्वज सेट को check_vma_flags()ढूँढता VM_PFNMAPहै vma। यह ध्वज स्पष्ट रूप से तब सेट किया जाता है जब पृष्ठों का उपयोग करके उपयोगकर्ता स्थान में मैप किया जाता है remap_pfn_range()या dma_mmap_coherent()। मेरा अगला तरीका mmapइन पन्नों को खोजने का है ।

जवाबों:


8

जैसा कि मैंने अपने प्रश्न में एक अपडेट में पोस्ट किया है, अंतर्निहित समस्या यह है कि ज़ीरोकॉपी नेटवर्किंग मेमोरी के लिए काम नहीं करती है 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

अब ऐसे क्षेत्र को डीएमए (डेटा रिसेप्शन के लिए) में जमा करना है:

  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);

जब हमें डीएमए से एक कॉलबैक मिलता है कि हस्तांतरण समाप्त हो गया है, तो हमें मेमोरी के इस ब्लॉक के स्वामित्व को सीपीयू में स्थानांतरित करने के लिए क्षेत्र को अनमैप करना होगा, जो कि कैश का ख्याल रखता है यह सुनिश्चित करने के लिए कि हम बासी डेटा नहीं पढ़ रहे हैं:

  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 ध्वज के ।

हालांकि यह काम करता है, दो चीजें हैं जो इस दृष्टिकोण के साथ मेरे साथ अच्छी तरह से नहीं बैठती हैं:

  • आप इस विधि के साथ केवल पावर-ऑफ-साइज़ बफ़र्स आवंटित कर सकते हैं, हालांकि आप alloc_pagesअलग-अलग आकारों के उप-बफ़र्स से बने किसी भी आकार के बफर को प्राप्त करने के लिए घटते हुए आदेशों के साथ आवश्यकतानुसार कॉल करने के लिए तर्क को लागू कर सकते हैं। इसके बाद इन बफ़र्स को एक साथ बाँधने के लिए mmap()और डीएमए को एक साथ जोड़ने के लिए कुछ तर्क की आवश्यकता होगी, 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

शायद इससे आपको यह समझने में मदद मिलेगी कि क्यों आवंटन_पृष्ठों को एक शक्ति -२ पृष्ठ संख्या की आवश्यकता है।

पृष्ठ आवंटन प्रक्रिया को अनुकूलित करने के लिए (और बाहरी टुकड़ों को कम करने के लिए), जो अक्सर लगी रहती है, लिनक्स कर्नेल प्रति मेमोरी को आवंटित करने के लिए प्रति-सीपीयू पेज कैश और मित्र-आवंटनकर्ता विकसित करता है (स्मृति आवंटन की सेवा करने के लिए एक और आवंटनकर्ता, स्लैब है, जो मेमोरी से अधिक है पृष्ठ)।

प्रति-सीपीयू पेज कैश एक-पेज आवंटन अनुरोध का कार्य करता है, जबकि मित्र-आवंटनकर्ता क्रमशः 11 सूचियाँ रखता है, जिनमें से प्रत्येक में 2 ^ {0-10} भौतिक पृष्ठ होते हैं। आवंटन और नि: शुल्क पृष्ठों के दौरान ये सूचियाँ अच्छा प्रदर्शन करती हैं, और निश्चित रूप से, आधार यह है कि आप एक पावर ऑफ़ 2-आकार के बफर का अनुरोध कर रहे हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.