Коротко про процеси в Linux
Трохи теорії
Вперше поняття “процес” з’явилося в операційній системі Multics — одній з перших систем з поділом часу. Процес — програма, що виконується і якій виділено процесорний час. Сама по собі програма процесом не є.
Кілька процесів можуть використовувати одну програму або ті ж ресурси типу відкритих файлів або адресного простору.
У процеси входять сегменти даних, в яких є такі змінні:
- набір ресурсів (відкриті файли та сигнали, які очікують обробки);
- адресний простір;
- кількість потоків виконання.
Потоки виконання здійснюють операції всередині процесу. У них є лічильник команд, стек їх виконання і набір регістрів. Ядро системи працює з потоками, а не з процесами.
Для роботи процесів потрібні 2 віртуальних ресурси: процесор і пам’ять. Перший змушує процес думати, що він використовує всю систему, а друга — що всю фізичну пам’ять. Потоки користуються однією на всіх віртуальною пам’яттю, а віртуальні процесори можуть створюватися на кожен з них.
Щоб описати роботу процесу, є кілька моделей. Найпростіша складається з 3 стадій:
- виконання: активна стадія, коли у процесу є всі ресурси та він виконується;
- очікування: пасивна стадія, коли процес не виконується без потрібної події, наприклад, введення даних;
- готовність: пасивна стадія, при якій процес не виконується через незалежні від нього зовнішні причини.
Є модель складніше, в якій станів вже 5. Нові — народження і смерть процесу:
- народження: пасивний стан, при якому самого процесу немає, але вже формується структура під його діяльність;
- смерть: пасивний стан, коли процесу вже немає, але його структура ще є в списку; такі процеси називаються зомбі.
Які дії можуть відбуватися з процесами?
- створення: перехід від народження до готовності;
- знищення: від виконання до смерті;
- відновлення: від готовності до виконання;
- зміна пріоритету: від виконання до готовності;
- блокування: від виконання до очікування;
- пробудження: від очікування до готовності;
- запуск або вибір: від готовності до виконання.
Що потрібно системі для створення процесу?
- дати процесу ім’я;
- внести дані про процес в загальний список;
- призначити процесу пріоритет;
- створити блок управління ним;
- виділити процесу ресурси.
Як живе процес?
Створення процесу
Процеси завжди створюються якимось іншим процесом: вони діляться на батьківські (parent) і дочірні (child). У всіх процесів є параметри PID (Process ID — ідентифікатор) і PPID (Parent Process ID — ідентифікатор батьківського процесу).
У Linux процес створюється через системний виклик fork(): він повністю копіює батьківський процес і робить дочірній. Той, що породжує залишиться активним, а породжений починає працювати з місця повернення з системного виклику.
Далі вони вибудовуються в деревоподібну структуру на чолі з процесом init (PID=1). Наступний за ним процес — fork(2). Він повністю ідентичний init(1), але повертає йому свій PID і бере собі значення 0, а PPID змінюється на PID батька. За системними ресурсами батько і дитина теж ідентичні, але це не зовсім так для пам’яті.
У Linux працює функція copy-on-write. Сторінки пам’яті доступні батьківському і дочірньому процесу в режимі read-only. Якщо якийсь із процесів змінює дані на сторінці пам’яті, створюється копія сторінки, в якій ці зміни й відбуваються. Незмінена сторінка прив’язується до протилежного процесу і переходить в статус read-write.
Готовність процесу
Як тільки fork(2) виконається, процес переходить в статус “готовий”. Він очікує своєї черги, поки планувальник не виділить йому час в процесорі.
Щоб ресурси пропорційно і пріоритетно виділялися на кожен процес, в системі є планувальник процесів. Коли закінчиться квант часу процесу, що виконується, планувальник переведе наш процес зі стадії готовності в стадію виконання.
Виконання процесу
І ось планувальник виділив процесу квант часу. Статус зміниться на “виконується”. Процесор працює з ним або весь квант часу або частину часу, яку буде працювати з іншим процесом, якщо ви скористаєтеся системним викликом sched_yield.
Очікування процесу
Припустимо, процесу потрібне введення даних для роботи або, навпаки, необхідно їх вивести. На цьому моменті він перейде в стадію очікування. Після того як ви зробите ключову дію, процес перейде в стадію готовності.
У процесу Linux є такий варіант очікування, в якому він стає нечутливим до сигналів переривання. Поки процес не вийде з очікування, всі сигнали, що надходять, стануть в чергу. Ядро Linux самостійно вибирає в яке “очікування” перевести процес.
Зупинка процесу
Якщо вам потрібно призупинити процес, це можна зробити через сигнал SIGSTOP. Після цього він перейде в очікування і не вийде з цього стану, поки не отримає сигнал SIGCONT (відновити роботу) або SIGKILL (померти). До цього інші сигнали стануть в чергу.
Завершення процесу
Процеси не закінчуються самі по собі, а роблять запит системі через системний виклик _exit. Або система завершить їх через помилки. Якщо повернути число з функції main(), все одно спрацює виклик _exit. Хоч аргумент виклику прийме значення int, як код повернення буде використаний менший байт числа.
Процеси-зомбі
Якщо процес завершився, ядро системи фіксує дані про це і робить його “зомбі”. Це стан, коли процес помер, але пам’ять про нього залишилася в ядрі. А ще “зомбі” ігнорує SIGKILL: те, що мертве, померти не може.
Забуття процесу
Тепер нам потрібно забрати інформацію про процес-зомбі з ядра системи через спеціальні системні виклики, але зараз не про них. Цю інформацію можна втиснути в дані типу int. Щоб отримати код повернення і відомості про причини завершення процесу, потрібно використовувати макроси зі станиці man waitpid(2).
Іноді батьківський процес завершується раніше, ніж дочірній. Батьком в цій ситуації стане init, який скористається викликом wait(2) в потрібний час.
Коли батько забере інформацію про смерть дочірнього процесу, ядро зітре інформацію про дитину, а замість нього з’явиться інший процес.
Висновок
Зрозуміло, що далеко не всім потрібна інформація про те, як працює ядро їхньої операційної системи. Але щоб розуміти інструмент, з яким ти працюєш кожен день, потрібно знати всі нюанси його пристрою. А по-іншому – ніяк.