Введення в DPDK: архітектура і принцип роботи

DPDK

За останні кілька років тема продуктивності мережевого стека Linux набула особливої актуальності. Це цілком зрозуміло: обсяги переданих по мережі даних і відповідні навантаження ростуть не  дням, а  годинах.

І навіть широке поширення мережевих карт 10GE не вирішує проблеми: в самому ядрі Linux є безліч «вузьких місць», які перешкоджають швидкій обробці пакетів.


Робляться численні спроби ці «вузькі місця» обійти. Техніки, що використовуються для обходу, так і називаються   kernel bypass (з коротким оглядом можна ознайомитися, наприклад, тут). Вони дозволяють повністю виключити мережевий стек Linux процесу обробки пакетів і зробити так, щоб додаток, що працює в користувацькому просторі, взаємодіяло з мережевим пристроєм безпосередньо. Про одному з таких рішень   Intel DPDK (Data Plane Development Kit)   і хотіли б поговорити в сьогоднішньої статті.

Про DPDK існує безліч публікацій, в тому числі і на російською мовою (див., наприклад: 1, 2 і 3). Серед цих публікацій є і досить непогані, але не відповідають на найголовніше питання: як саме відбувається обробка пакетів з використанням DPDK? З яких етапів складається шлях пакета від мережного пристрою до користувачу?

Саме на ці питання ми і спробуємо відповісти. Щоб знайти відповіді, нам довелося проробити величезну роботу: у офіційній документації ми всієї потрібної інформації не знайшли, то нам довелося ознайомитися з масою додаткових матеріалів та поринути в вивчення джерел… Втім, про все по порядку. І перш ніж говорити про DPDK і про те, які проблеми він допомагає вирішити, нам потрібно згадати, як здійснюється обробка пакетів в Linux. З ми і почнемо.

Обробка пакетів в Linux: основні етапи

Отже, коли пакет надходить на мережеву карту, він спочатку потрапляє в спеціальну кільцеву структуру,   приймальню чергу (receive queue або просто RX). Звідти він копіюється в основну пам'ять з допомогою механізму DMA   Direct Memory Access.

Після цього потрібно повідомити системі про появу нового пакету і передати дані далі, в спеціально виділений буфер (Linux виділяє такі буфери для кожного пакета). Для цієї мети в Linux використовується механізм переривань: переривання генерується всякий раз, коли новий пакет надходить в систему. Потім пакет ще потрібно передати в користувальницьке простір.

Одне «вузьке місце» вже очевидно: чим більше пакетів доводиться обробляти, тим більше на це йде ресурсів, що негативно позначається на роботі системи у загалом.

Дані пакета, як вже було сказано вище, зберігаються в спеціально виділеному буфері, або, кажучи точніше   в структурі sk_buff. Ця структура виділяється для кожного пакета і звільняється, коли пакет потрапляє в користувальницьке простір. На цю операцію витрачається дуже багато циклів шини (тобто циклів, що передають дані з CPU основну пам'ять).

З структурою sk_buff є ще один проблемний момент: мережевий стек Linux спочатку намагалися зробити так, щоб він був сумісний з як можна великою кількістю протоколів. Метадані всіх цих протоколів включені і в структуру sk_buff, але для обробки конкретного пакета вони можуть бути просто не потрібні. З-за надмірної складності структури обробка сповільнюється.

Ще одним фактором, що негативно впливає на продуктивність, є перемикання контексту. Коли додатком, запущеного користувацькому просторі, потрібно прийняти або відправити пакет, воно робить системний виклик, і відбувається перемикання контексту в режим ядра, а    назад в користувальницький режим. Це пов'язане з відчутними витратами системних ресурсів.

Щоб вирішити частину описаних вище проблем, ядро Linux починаючи з версії ядра 2.6 був доданий так званий NAPI (New API, який метод переривань поєднується з методом опитування. Розглянемо коротко, як це працює.

Спочатку мережева карта працює в режимі переривань, але як тільки пакет надходить на мережний інтерфейс, вона реєструє себе в poll-списку і відключає переривання. Система періодично перевіряє список на наявність нових пристроїв і забирає пакети для подальшої обробки. Як тільки пакети оброблені, карта буде видалена з списку, а переривання включаться знову.

Ми описали процес обробки пакетів дуже побіжно. З більш детальним описом можна ознайомитися, наприклад, в циклі статей в блозі компанії Private Internet Access. Однак навіть короткого розгляду достатньо, щоб побачити проблеми, з-за яких швидкість обробки пакетів сповільнюється. У наступному розділі ми опишемо, як ці проблеми вирішуються з допомогою DPDK.

DPDK: як це працює

У загальних рисах

Розглянемо наступну ілюстрацію:

DPDK

Зліва представлений процес обробки пакетів «традиційним» способом, а праворуч   використанням DPDK. Як бачимо, у другому випадку ядро не задіяно взагалі: взаємодія з мережевою картою здійснюється через спеціалізовані драйвери і бібліотеки.

Якщо ви вже читали про DPDK або маєте хоча б невеликий досвід роботи з ним, то знаєте, що порти мережевої карти, на які буде надходити трафік, потрібно взагалі вивести з-під управління Linux — це робиться за допомогою команди dpdk_nic_bind (або dpdk-devbind), а в більш ранніх версіях) — ./dpdk_nic_bind.py.

Як відбувається передача портів під управління DPDK? У кожного драйвера в Linux є так звані bind — і unbind-файли. Є вони і у драйвера мережевої карти:

ls /sys/bus/pci/drivers/ixgbe
bind module new_id remove_id uevent unbind


Щоб відкріпити пристрій від драйвера, потрібно записати номер шини цього пристрою в unbind-файл. Відповідно для передачі пристрою під управління іншого драйвера потрібно записати номер шини в його bind-файл. Більш докладно про це можна прочитати в цієї статті.

Інструкції з інсталяції DPDK зазначається, що порти потрібно передати під управління драйвера vfio_pci, igb_uio або uio_pci_generic.
Всі ці драйвери (докладно розбирати їх особливості в рамках цієї статті ми не будемо; зацікавлених читачів відсилаємо до статей на kernel.org: 1 і 2) роблять можливим взаємодію з пристроями в користувацькому просторі. Звичайно, в їх склад входить і модуль ядра, але його функції зводяться до ініціалізації пристроїв і надання PCI-інтерфейсу.
Всю подальшу роботу з організації спілкування програми з мережевою картою бере на в себе вхідний DPDK драйвер PMD (скорочення від poll mode driver).

Для роботи з DPDK необхідно також налаштувати великі сторінки пам'яті (hugepages). Це потрібно, щоб виділяти великі регіони пам'яті і записувати у них дані. Можна сказати, що hugepages в DPDK виконують ту ж роль, що механізм DMA в традиційній обробці пакетів.

Всі нюанси ми більш детально обговоримо нижче, а поки коротко опишемо основні стадії обробки пакетів з використанням DPDK:

  1. Надійшли пакети потрапляють в кільцевий буфер (його пристрій ми розберемо наступному розділі). Додаток періодично перевіряє цей буфер на наявність нових пакетів.
  2. Якщо в буфері є нові дескриптори пакетів, додаток звертається до буферам пакетів DPDK, що знаходяться в спеціально виділеному пулі пам'яті, через покажчики в дескрипторах пакетів.
  3. Якщо в кільцевому буфері немає жодних пакетів, то додаток опитує знаходяться під управлінням DPDK мережеві пристрої, а потім знову звертається до кільцю.


Розглянемо внутрішнє пристрій DPDK більш детально.

EAL: абстракція оточення

EAL (Environment Abstraction Layer, рівень абстракції оточення) — це центральне поняття DPDK.

EAL — це набір програмних інструментів, які забезпечують роботу DPDK в конкретному програмному оточенні і під управлінням певної операційної системи. У офіційному репозиторії DPDK бібліотеки і драйвери, які входять в склад EAL, зберігаються в директорії rte_eal.

У цієї директорії зберігаються драйвери і бібліотеки для Linux і BSD-систем. Є також набори заголовних файлів для різних процесорних архітектур: ARM, x86, TILE64, PPC64.

До програмами, що входять в EAL, ми звертаємося при складанні DPDK з вихідного коду:

make config T=x86_64-native-linuxapp-gcc


У цій команді, як не важко здогадатися, ми вказуємо, що DPDK потрібно зібрати для архітектури x86_84, OC Linux.

Саме EAL забезпечує «прив'язку» DPDK до додатків. Всі програми, які використовують DPDK (див. приклади тут), обов'язково включають входять в склад EAL відмінності файли.
Перерахуємо найбільш вживані з з них:

  • rte_lcore.h   функції керування процесорними ядрами та сокетами;
  • rte_memory.h   функції управління пам'яттю;
  • rte_pci.h   функції, що забезпечують інтерфейс доступу до адресного простору PCI;
  • rte_debug.h   функції трасування і налагодження (логгирование, dump_stack і інші);
  • rte_interrupts.h   функції обробці переривань.


Більш детально про пристрої та функції EAL можна прочитати в документації.

Управління чергами: бібліотека rte_ring

Як ми вже говорили вище, пакет, що надійшов на мережеву карту, потрапляє в приймальню чергу, яка являє собою кільцевий буфер. У DPDK новоприбулі пакети теж поміщаються в чергу, реалізовану на базі бібліотеки rte_ring. Всі наведені нижче опису цієї бібліотеки засновані на посібник розробника, а також коментарях до вихідного коду.

При розробці rte_ring за основу була взята реалізація кільцевого буфера для FreeBSD.Якщо ви заглянете в джерело зверніть увагу на такий коментар: Derived from FreeBSD's bufring.c.

Чергу являє собою кільцевий буфер без блокувань, організований за принципом FIFO (First In, First Out). Кільцевий буфер — це таблиця покажчиків на збережені в пам'яті об'єкти. Всі покажчики поділяються на чотири типи: prod_tail, prod_head, cons_tail, cons_head.

Prod і cons — це скорочення від producer (виробник) і consumer(споживач). Виробником (producer) називається процес, який записує дані в буфер поточний момент, а споживачем   процес, який в поточний момент дані з буфера забирає.

Хвостом (tail) називається місце, куди в поточний момент здійснюється запис у кільцевий буфер. Місце, звідки, в поточний момент здійснюється зчитування з буфера, називається головою (head).

Образно кажучи, сенс операції постановки в черга і виведення з черзі полягає в те, щоб поміняти голову і хвіст місцями. Наприклад, при додаванні нового об'єкта в чергу в підсумку все повинно вийти так, що вказівник ring->prod_tail буде вказувати на  місце, куди раніше вказував ring->prod_head.
Тут ми наводимо лише короткий опис; більш докладно про сценарії роботи кільцевого буфера можна прочитати в посібник розробника на сайті DPDK.

З переваг такого підходу до управління чергами слід виділити, по-перше, більш високу швидкість запису буфер. По-друге, при виконанні операцій масової постановки в черга і масового виведення з черзі промахи кешу мають місце набагато рідше, тому що покажчики зберігаються в таблиці.

Недоліком реалізації кільцевого буфера у DPDK є фіксований розмір, який неможливо збільшити «на льоту». Крім того, на роботу з кільцевою структурою витрачається набагато більше пам'яті, ніж на з зв'язаним списком: кільці завжди використовується максимально можлива кількість покажчиків.

Управління пам'яттю: бібліотека rte_mempool

Ми вже говорили вище, що для роботи DPDK потрібні великі сторінки пам'яті (HugePages). У інструкціях з встановлення рекомендується створювати HugePages розміром 2 мегабайта.

Ці сторінки об'єднуються в сегменти, які потім діляться на зони. У зони вже містяться об'єкти, які створюються додатками або іншими бібліотеками   наприклад, черги і буфери пакетів.

До числа таких об'єктів належать і пули пам'яті, які створює бібліотека rte_mempool. Це пули об'єктів фіксованого розміру, які використовують rte_ring для зберігання вільних об'єктів можуть бути ідентифіковані за унікального імені.

Для поліпшення продуктивності можуть використовуватися техніки вирівнювання пам'яті.

Незважаючи на те, що доступ до вільним об'єктів організований на бази кільцевого буфера без блокувань, витрати системних ресурсів можуть бути дуже великими. До кільцю мають доступ кілька процесорних ядер і всякий раз, коли ядро звертається до кільцю, потрібно здійснювати операцію порівняння з обміном (compare and set, CAS).

Щоб кільце не стало «вузьким місцем», кожне ядро отримує додатковий локальний кеш пулі пам'яті. Ядро має повний доступ до кешу вільних об'єктів допомогою механізму блокування. Коли кеш заповнюється або звільняється повністю, пул пам'яті обмінюється даними з кільцем. Таким чином забезпечується доступ ядра до часто використовуваних елементів.

Управління буферами: бібліотека rte_mbuf

У мережевому стеку Linux, як це вже було зазначено вище, для представлення всіх мережевих пакетів використовується структура sk_buff. У DPDK для цієї мети використовується структура rte_mbuf, описана в заголовочном файлі rte_mbuf.h.

Підхід до управління буферами в DPDK у чому нагадує той, що використовується у FreeBSD: замість однієї великої структури sk_buff   багато буферів rte_mbuf невеликого розміру. Буфери створюються до запуску додатку, що використовує DPDK, і зберігаються у пулах пам'яті (для виділення пам'яті використовується бібліотека rte_mempool).

Крім власне даних пакета кожен буфер містить і метадані (тип повідомлення, довжина, адресу початку сегменту даних). Буфер також містить вказівник на наступний буфер. Це потрібно для роботи з пакетами, що містять велику кількість даних   цьому випадку пакети можна об'єднувати (так само, як це робиться в FreeBSD — докладніше про це можна прочитати, наприклад, тут).

Інші бібліотеки: короткий огляд

У попередніх розділах ми описали лише самі основні бібліотеки DPDK. Але є ще безліч інших бібліотек, розповісти про яких рамках однієї статті навряд чи можливо. Тому ми обмежимося лише коротким оглядом.

З допомогою бібліотеки LPM DPDK реалізується алгоритм Longest Prefix Match (LPM), використовуваний для пересилання пакетів в залежно від  IPv4-адреси. Основні функції цієї бібліотеки полягають у додавання і видалення IP-адрес, а також пошуку нового адреси використанням LPM-алгоритму.

Для IPv6 адрес аналогічна функціональність реалізована на базі бібліотеки LPM6.

У інших бібліотеках схожа функціональність реалізована з допомогою хеш-функцій. З допомогою rte_hash можна здійснювати пошук за великого набору записів з використанням унікального ключа. Цю бібліотеку можна використовувати, наприклад, для класифікації і розподілу пакетів.

Бібліотека rte_timer забезпечує асинхронне виконання функцій. Таймер може виконуватися як один раз, так і періодично.

Висновок

У цій статті ми спробували розповісти про внутрішній устрій і принципи роботи DPDK. Спробували, але не розповіли до кінця   тема ця настільки складна і обширна, що однієї статті явно не вистачить. Тому чекайте продовження: в наступній статті ми більш докладно поговоримо про практичних аспектах використання DPDK.

У коментарях ми з задоволенням відповімо на всі ваші запитання. А  кого-то з вас є досвід використання DPDK, то будемо вдячні за будь-які зауваження і доповнення.

Для всіх, хто хоче дізнатися більше, наводимо корисні посилання по темі:

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.