জিরো-কপি ব্যবহারকারী-স্পেস টিসিপি dma_mmap_coherent () ম্যাপযুক্ত মেমরি প্রেরণ


14

আমি একটি ঘূর্ণিঝড় ভি এসসিতে লিনাক্স 5.1 চালাচ্ছি, এটি একটি চিপায় দুটি এআরএমভি 7 কোর সহ একটি এফপিজিএ। আমার লক্ষ্য হ'ল বাহ্যিক ইন্টারফেস থেকে প্রচুর ডেটা সংগ্রহ করা এবং কোনও টিসিপি সকেটের মাধ্যমে এই ডেটা স্ট্রিমের অংশ (অংশ)। এখানে চ্যালেঞ্জটি হ'ল ডেটা হার খুব বেশি এবং জিবিই ইন্টারফেসটি স্যাচুরেট করার কাছাকাছি আসতে পারে। আমার একটি কার্যকরী বাস্তবায়ন রয়েছে যা write()সকেটে কেবল কলগুলি ব্যবহার করে তবে এটি 55MB / s এ শীর্ষে রয়েছে; প্রায় তাত্ত্বিক জিবিই সীমা অর্ধেক। থ্রিপুট বাড়ানোর জন্য আমি এখন শূন্য-অনুলিপি টিসিপি ট্রান্সমিশনটি পাওয়ার চেষ্টা করছি, তবে আমি একটি প্রাচীরটি আঘাত করছি।

লিনাক্স ব্যবহারকারী-স্পেসে এফপিজিএ থেকে ডেটা পেতে, আমি একটি কার্নেল ড্রাইভার লিখেছি written এই ড্রাইভারটি এপিপিজিএ-তে একটি ডিএমএ ব্লক ব্যবহার করে এআরএমভি c কোরের সাথে সংযুক্ত ডিডিআর 3 মেমরির একটি বহিরাগত ইন্টারফেস থেকে প্রচুর পরিমাণে ডেটা অনুলিপি করতে। ড্রাইভার এই মেমরিটি একত্রে 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

কিছুক্ষণ গুগল করার পরে, আমার দ্বিতীয় পদ্ধতিটি ছিল একটি পাইপ ব্যবহার করা এবং তার 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*গুলি করছে মেমরির সঙ্গে যুক্ত।

যে কর্মপ্রবাহটি এখন মেমরি বরাদ্দ করতে আমার পক্ষে কাজ করে তা হ'ল:

  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

যদিও এটি কাজ করে, দুটি বিষয় আছে যা এই পদ্ধতির সাথে আমার ভালভাবে বসে না:

  • আপনি কেবল এই পদ্ধতির সাহায্যে পাওয়ার-অফ-2-আকারের বাফারগুলি বরাদ্দ করতে পারেন, যদিও আপনি 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

সম্ভবত এটি আপনাকে বুঝতে বুঝতে সাহায্য করবে কেন কেন বরাদ্দ_ পৃষ্ঠাগুলির জন্য 2 পৃষ্ঠার পাওয়ার সংখ্যা থাকা দরকার?

পৃষ্ঠ বরাদ্দকরণ প্রক্রিয়াটি (এবং বাহ্যিক খণ্ডগুলি হ্রাস করতে), যা ঘন ঘন নিযুক্ত থাকে, অপ্টিমাইজ করতে লিনাক্স কার্নেল প্রতি-সিপিইউ পৃষ্ঠার ক্যাশে এবং বন্ধু-বরাদ্দকে মেমরি বরাদ্দ করতে বিকশিত করে (মেমরির বরাদ্দের জন্য আরও একটি বরাদ্দকারী, স্ল্যাব রয়েছে, যা একটির চেয়ে ছোট পৃষ্ঠা)।

প্রতি সিপিইউ পৃষ্ঠার ক্যাশে এক পৃষ্ঠার বরাদ্দ অনুরোধটি পরিবেশন করে, যখন বন্ধু-বরাদ্দকারী ১১ টি তালিকা রাখে, যার প্রতিটি যথাক্রমে 2 {{0-10} দৈহিক পৃষ্ঠা থাকে। এই তালিকাগুলি যখন বরাদ্দ করা এবং বিনামূল্যে পৃষ্ঠাগুলি ভাল সঞ্চালিত হয় এবং অবশ্যই এর ভিত্তিটি আপনি 2-আকারের বাফারটির জন্য অনুরোধ করছেন।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.