Зменшуємо час відгуку додатків в linux, для системного адміністратора

Працюючи на комп'ютері, нам постійно доводиться стикатися з різного роду затримками. Спробуй одночасно Компільо ядро, слухати музику, качати фільми по сітці і набирати текст в OpenOffice.org. Я впевнений, що рано чи пізно ти зіткнешся з тим, що знаки в Writer стануть з'являтися через пару секунд після того, як ти натиснеш на кнопки клавіатури, так і програвач почне постійно заїкатися. Як же можна зменшити час відгуку додатків в Linux - операційній системі поділу часу. де всі процеси рівні (ну, майже)? Адже вона розроблялася з упором на оптимізацію системної продуктивності в цілому, і про забезпечення обмеженого часу відповіді додатків мови не йшло.

Використовуємо підручні засоби

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

Для того щоб культурно розподілити ресурси і нікого при цьому не обділити, в будь-якій системі є своя підсистема управління процесами. що працює за принципом «кожному за здібностями, кожному за працею». Процес може працювати в двох режимах: в режимі ядра (kernel mode) і в призначеному для користувача режимі (user mode). де він виконує прості інструкції, які не потребують особливих «системних» даних. Але коли такі послуги знадобляться, процес перейде в режим ядра, хоча інструкції як і раніше будуть виконуватися від імені процесу. Все це зроблено спеціально, щоб захистити робочий простір ядра від призначеного для користувача процесу. Решта процеси або готуються до запуску, чекаючи, коли планувальник їх вибере, або знаходяться в режимі сну (asleep), чекаючи недоступного на даний момент часу ресурсу. З останнім все просто. Коли надходить сигнал з підконтрольного пристрою, процес оголошує себе TASK_RUNNING і стає в чергу готовності до запуску. Якщо він має вищий пріоритет, то ядро ​​перемикається на його виконання.

Але є ще одна заковика. При наданні процесу системних ресурсів відбувається так зване перемикання контексту (context switch). що зберігає образ пов'язаних з поточною діяльністю (на що, до речі, теж потрібен якийсь час, тому латентність навіть в ідеальному випадку не дорівнюватиме нулю). Так ось перемикання контексту, коли процес знаходиться в режимі ядра, може привести до краху всієї системи. Тому високопріоритетних процесу доведеться терпляче почекати моменту переходу в режим завдання, а це може статися в двох випадках: робота зроблена або необхідний ресурс недоступний. Тобто, щоб забезпечити менший час відгуку, необхідно звести до мінімуму число ядерних завдань. Але за таке рішення доводиться платити загальної стабільністю і «вагою» коду. У мікроядром це, до речі, реалізовано значно краще - є базовий мінімальний набір, інше навішується модульно, як на новорічну ялинку, що забезпечує універсальність і дозволяє конструювати системи під конкретні завдання.

Що стосується планування процесів, то воно зав'язане на пріоритеті. Планувальник просто вибирає наступний процес, який має найвищий пріоритет. При цьому менш пріоритетний процес, що виконується в той момент, може навіть повністю не відпрацювати свій квант до кінця. Кожен процес має два види пріоритету: відносний (p-> nice, за замовчуванням до 100 рівнів пріоритетів), що встановлюється під час запуску програми, і поточний, на підставі якого і відбувається планування. Значення поточного пріоритету не є фіксованим, а обчислюється динамічно і безпосередньо залежить від nice. Попередньо встановлена ​​користувачем, може перебувати в межах від -20 до +19, при цьому з додатком з більш високим пріоритетом відповідає значення -20, а +10 (за замовчуванням) і вище вважаються вже фонової завданнями. Наприклад, для запуску програми з більш високим, ніж зазвичай, пріоритетом, робимо так:

$ Sudo nice --20 mplayer

А з більш низьким:

$ Sudo nice -20 job # 038;

Щоб змінити відносний пріоритет процесу, слід використовувати ідентифікатор процесу, а не назву:

$ Sudo renice --20 PID

Поточний пріоритет залежить від nice і часу використання системних ресурсів. Він перераховується з кожним тиком і під час виходу з режиму ядра. У різних системах це відбувається за своїми формулами, в найпростішому випадку пріоритет просто ділиться на 2 і при досягненні нульового значення повністю перераховується наново (відновлюється). Такий механізм дозволяє отримати свого часу і низькопріоритетним додатків, але в підсумку високопріоритетні отримують більшу його частину.

Всі ці тонкощі використовувалися в різних патчах реалізації lowlatency для ядра 2.4. Замінялися не тільки алгоритми перерахунку поточного пріоритету, а й всі можливі константи (наприклад, межа nice збільшували до 256), крім того, встановлювався таймер з частотою аж до 1000 HZ.

Для того щоб вказати на додаток, яке вимагає особливої ​​уваги з боку процесора, можна використовувати і закладений в специфікації POSIX real-time виклик SCHED_FIFO (щось на зразок переходу в режим «м'якого» реального часу). Подібний результат досягається при використанні викликів SCHED_RR, CAP_IPC_LOCK, CAP_SYS_NICE або через підміну значення sys_sched_get_priority_max - функції, що повертає максимальний real-time пріоритет. Саме використання SCHED_FIFO призводить до того, що програвач xmms, запущений під root, практично не заїкається навіть при позамежних навантаженнях на систему.

Основною проблемою real-time є можливість захоплення ресурсів у низькопріоритетного процесу. особливо якщо він виконується в режимі ядра. Адже навіть на перемикання контексту витрачається деякий час. Сотні розробників по всьому світу намагалися застосувати мільярдів технологій: від можливості переривання під час виконання в ядерному режимі (preemptible kernel, викладали ядро) до тимчасового успадкування (inherit) пріоритету реального часу низькопріоритетним додатком. щоб він міг скоріше закінчити критичнийрозділ коду і віддати управління.

Тема вивантажується ядра зацікавила громадськість ще в період панування гілки 2.2. Лінус Торвальдс сказав, що real-time - погана ідея, і до пори preemptible реалізовувалося виключно за допомогою патчів. Але вже при підготовці гілки 2.6 в вихідний код була додана можливість зробити ядро ​​вивантажувати (PREEMPT_RT). Preemptible-kernel реалізується, як правило, у вигляді другого ядра. Якщо процес звертається до нього з запитом, основна система фактично блокується на час його виконання. Виповнюється все це в вигляді завантаження модуля, який підміняє / перехоплює найбільш критичні функції, здатні привести до затримок. Але не все так просто. У своєму інтерв'ю один з інженерів MontaVista (компанії-розробника одного з real-time рішень на базі Linux) заявив, що в ядрі 2.6 близько 11 000 ділянок коду просто неможливо зробити preemptible.

В інтернеті, якщо гарненько пошукати, можна знайти достатню кількість різноманітних патчів, що дозволяють реалізувати режим реального часу в Linux, але, як правило, більша частина проектів вже застаріла і пропонує зміни до ядра 2.4. Наприклад, KURT-Linux (www.ittc.ku.edu/kurt) і RT-Linux (www.rtlinux.org). Обидві Лінукса використовують схожі технології, і суб'єктивно відмінностей в їх роботі не помітно, але в інтернеті хвалять все кому не лінь саме RT-Linux. Знайти комп'ютери під її управлінням можна на генераторах «Токамак», в лікарнях Перу, на супутниках NASA і в симуляторах F111-C. До речі, якщо в Ubuntu ввести sudo apt-cache search real-time, то ти виявиш наявність пакета зі старою 3.1pre3-3 версією RT-Linux.

патчим ядро

На стандартному ядрі запускаємо утиліту rt-test. тільки запустивши, ми отримуємо значення 0,125 мс, при збільшенні навантаження воно зростає до 15,402 мс. Слід звернути увагу на параметр Criteria, який дорівнює 100 мікросекунд. У нашому випадку результат тесту - FAIL, тобто до real-time ще далеко. Ставимо lowlatency-ядро - звичайне ядро, але з таймером 1000 HZ і зменшеним часом відгуку:

$ Sudo apt-get install linux-lowlatency

Перезавантажуємося і запускаємо rt-test ще раз.

$ Sudo rt-test all

Стартове значення латентності тепер одно 0,073 мс, а максимальна - 2,907 мс. Вже краще. Хоча Criteria як і раніше FAIL, але музичка в Amarok'е при пристойній завантаженні системи більше не переривається.

З усіх рішень по реалізації системи реального часу до сьогоднішнього дня дійшло тільки одне, пропоноване Інго Молнаром. Ходили чутки про те, що випускаються ним патчі (www.kernel.org/pub/linux/kernel/projects/rt) будуть включені в ядро ​​2.6.22, але поки цього не сталося. Для установки rt шанувальникам Fedora досить ввести yum install kernel-rt, іншим доведеться трохи покомпіліровать. Качаємо і застосовуємо патч до свого ядру:

$ Wget -c www.kernel.org/pub/linux/kernel/projects/rt/older/patch-2.6.22.1-rt9
$ Tar xjvf linux-2.6.22.1.tar.bz2
$ Cd linux-2.6.22.1
$ Sudo patch -p1

При конфігуруванні виявиться ряд додаткових параметрів. Серед них Tickless System (Dynamic Ticks) (NO_HZ) - змінюваний тик; High Resolution Timer Support (HIGH_RES_TIMERS) - таймер з високою роздільною здатністю. У секції Preemption Mode нас цікавить Complete Preemption (Real-Time) (PREEMPT_RT), хоча доступні ще No Forced Preemption (Server) (PREEMPT_NONE), Voluntary Kernel Preemption (Desktop) (PREEMPT_VOLUNTARY), Preemptible Kernel (Low-Latency Desktop) (PREEMPT_DESKTOP ). У секції «Block layer - Default I / O scheduler» з'явився планувальник повністю справедливою черзі CFS.

Після перезавантаження системи, ввівши dmesg, можна побачити, що ядро ​​стало PREEMPT RT, таймер годин працює нестабільно ( «Clocksource tsc unstable»), а ps aux показує наявність великої кількості нових процесів. Але нас більше цікавить результат роботи rt-test. Так ось всі наші хитрощі призвели до того, що максимальне значення латентності тепер не перевищує 0,07 мс. Вуаля, тест пройдено!