Как эффективно передать буфер с поддержкой огромных страниц на устройство BM DMA в Linux?

Мне нужно предоставить огромный кольцевой буфер (несколько ГБ) для устройства DMA PCIe, управляющего шиной, реализованного в FPGA.

Буферы не должны резервироваться во время загрузки. Следовательно, буфер может быть несмежным.

Устройство поддерживает операцию рассеяния-сбора (SG), но из соображений производительности адреса и длины последовательных непрерывных сегментов буфера хранятся внутри FPGA. Поэтому использование стандартных страниц размером 4 КБ неприемлемо (на каждый 1 ГБ буфера приходится до 262144 сегментов).

Правильное решение должно выделить буфер, состоящий из огромных страниц размером 2 МБ, в пользовательском пространстве (уменьшив максимальное количество сегментов в 512 раз). Виртуальный адрес буфера должен быть передан драйверу ядра через ioctl. Затем следует вычислить адреса и длину сегментов и записать их в ПЛИС.

Теоретически я мог бы использовать get_user_pages для создайте список страниц, а затем вызовите sg_alloc_table_from_pages для получения списка SG, подходящего для программирования механизма прямого доступа к памяти в FPGA. К сожалению, в этом подходе я должен подготовить промежуточный список структур страниц длиной 262144 страниц на 1 ГБ буфера. Этот список хранится в ОЗУ, а не в ПЛИС, поэтому с ним меньше проблем, но в любом случае было бы неплохо его избежать.

На самом деле мне не нужно сохранять сопоставление страниц для ядра, так как огромные страницы защищены от выгрузки и сопоставлены для приложения пользовательского пространства, которое будет обрабатывать полученные данные.

Итак, я ищу функцию sg_alloc_table_from_user_hugepages, которая могла бы взять такой адрес пользовательского пространства буфера памяти на основе огромных страниц и передать его непосредственно в правильный список разброса, не выполняя ненужного и потребляющего память сопоставления для ядра. Конечно, такая функция должна проверять, действительно ли буфер состоит из огромных страниц.

Я нашел и прочитал следующие сообщения: (A) , (B), но не смог не найти хороший ответ. Есть ли какой-нибудь официальный способ сделать это в текущем ядре Linux?


person wzab    schedule 18.03.2021    source источник


Ответы (1)


На данный момент у меня очень неэффективное решение на основе get_user_pages_fast:

   int sgt_prepare(const char __user *buf, size_t count, 
               struct sg_table * sgt, struct page *** a_pages,
               int * a_n_pages)
   {
       int res = 0;
       int n_pages;
       struct page ** pages = NULL;
       const unsigned long offset = ((unsigned long)buf) & (PAGE_SIZE-1);
       //Calculate number of pages
       n_pages = (offset + count + PAGE_SIZE - 1) >> PAGE_SHIFT;
       printk(KERN_ALERT "n_pages: %d",n_pages);
       //Allocate the table for pages
       pages = vzalloc(sizeof(* pages) * n_pages);
       printk(KERN_ALERT "pages: %p",pages);
       if(pages == NULL) {
           res = -ENOMEM;
           goto sglm_err1;
       }
       //Now pin the pages
       res = get_user_pages_fast(((unsigned long)buf & PAGE_MASK), n_pages, 0, pages); 
       printk(KERN_ALERT "gupf: %d",res);   
       if(res < n_pages) {
           int i;
           for(i=0; i<res; i++)
               put_page(pages[i]);
           res = -ENOMEM;
           goto sglm_err1;
       }
       //Now create the sg-list
       res = sg_alloc_table_from_pages(sgt, pages, n_pages, offset, count, GFP_KERNEL);
       printk(KERN_ALERT "satf: %d",res);   
       if(res < 0)
           goto sglm_err2;
       *a_pages = pages;
       *a_n_pages = n_pages;
       return res;
   sglm_err2:
       //Here we jump if we know that the pages are pinned
       {
           int i;
           for(i=0; i<n_pages; i++)
               put_page(pages[i]);
       }
   sglm_err1:
       if(sgt) sg_free_table(sgt);
       if(pages) kfree(pages);
       * a_pages = NULL;
       * a_n_pages = 0;
       return res;
   }
   
   void sgt_destroy(struct sg_table * sgt, struct page ** pages, int n_pages)
   {
       int i;
       //Free the sg list
       if(sgt->sgl)
           sg_free_table(sgt);
       //Unpin pages
       for(i=0; i < n_pages; i++) {
           set_page_dirty(pages[i]);
           put_page(pages[i]);
       }
   }

Функция sgt_prepare создает структуру sg_table sgt, которую я могу использовать для создания отображения прямого доступа к памяти. Я проверил, что он содержит количество записей, равное количеству используемых огромных страниц.

К сожалению, для этого требуется, чтобы список страниц был создан (выделен и возвращен с помощью аргумента указателя a_pages) и сохранялся до тех пор, пока используется буфер.

Поэтому мне очень не нравится это решение. Теперь у меня есть 256 огромных страниц по 2 МБ, используемых в качестве буфера DMA. Это означает, что я должен создавать и хранить ненужные структуры страниц размером 128*1024. Я также трачу 512 МБ адресного пространства ядра на ненужное отображение ядра.

Интересный вопрос: можно ли хранить a_pages только временно (пока не будет создан sg-list)? Теоретически это должно быть возможно, так как страницы все еще заблокированы...

person wzab    schedule 12.04.2021
comment
Согласно источникам ядра elixir.bootlin.com /linux/v5.11.13/source/include/linux/ страница структуры имеет длину от 56 до 80 байт. Мне нужно создать одну такую ​​структуру для каждой страницы в массиве a_pages. Это означает, что у меня было до 2% накладных расходов. - person wzab; 14.04.2021