Operating Systems: Three Easy Pieces. Part 3: Process API (переклад)

Введення в операційні системи

Привіт, Хабре! Хочу представити вашій увазі серію статей-перекладів однієї цікавої на мою думку літератури — OSTEP. У цьому матеріалі розглядається досить глибоко робота unix-подібних операційних систем, а саме робота з процесами, різними планувальниками, пам'яттю та іншими подібними компонентами, які складають сучасну ОС. Оригінал всіх матеріалів ви можете подивитись ось тут. Прошу врахувати, що переклад виконаний непрофесійно (досить вільно), але сподіваюся, загальний зміст я зберіг.

Лабораторні роботи з даного предмета можна знайти ось тут:

Інші частини:

А ще можете заглядати до мене на канал у телеграма =)

Алярм! до цієї лекції є лаба! дивись гітхаб

API обробки

Розглянемо приклад створення процесу у UNIX системі. Він відбувається через два системні виклики вилка () и exec ().

Виклик fork()

Operating Systems: Three Easy Pieces. Part 3: Process API (переклад)

Розглянемо програму, яка здійснює виклик fork(). Результат виконання буде наступним.

Operating Systems: Three Easy Pieces. Part 3: Process API (переклад)

Насамперед ми входимо у функцію main() і виконуємо виведення рядка на екран. Рядок містить ідентифікатор процесу який в оригіналі називається PID або процес identifier. Цей ідентифікатор застосовується в UNIX для того, щоб звертатися до процесу. Наступною командою буде здійснено виклик fork(). У цей момент створюється майже точна копія процесу. Для ОС це виглядає так, що в системі запущені нібито 2 копії однієї й тієї програми, які в свою чергу вийдуть з виконання функції fork(). Новостворені процес-нащадок (стосовно створив його процесу-батьку) вже не буде виконуватися, починаючи з функції main(). Слід пам'ятати, що процес-нащадок не є точною копією процесу-батька, зокрема він має власний адресний простір, власні регістри, свій покажчик на виконувані інструкції тощо. Таким чином, значення, яке повертається викликачеві функції fork() буде різним. Зокрема, процес-батько отримає як повернення значення PID процесу дитини, а дитина отримає значення 0. За цими кодами повернення надалі вже можна розділяти процеси і змушувати кожен з них виконувати свою роботу. У цьому виконання цієї програми не визначено суворо. Після поділу на 2 процеси ОС починає стежити за ними, так само, і планувати їхню роботу. У разі виконання на одноядерному процесорі роботу продовжить один із процесів, у даному випадку — батьківський, а потім управління отримає процес-нащадок. При повторному запуску ситуація може скластися інакше.

Виклик wait()

Operating Systems: Three Easy Pieces. Part 3: Process API (переклад)

Розглянемо таку програму. У цій програмі за рахунок наявності виклику почекати () процес-батько завжди чекатиме завершення роботи процесу-нащадка. У цьому випадку ми отримаємо певний висновок тексту на екран

Operating Systems: Three Easy Pieces. Part 3: Process API (переклад)

Виклик exec()

Operating Systems: Three Easy Pieces. Part 3: Process API (переклад)

Розглянемо виклик exec (). Цей системний виклик корисний, коли хочемо запустити зовсім іншу програму. Тут ми викликатимемо execvp() для запуску програми wc, що є програмою підрахунку слів. Що відбувається при виклику exec()? Цьому виклику передаються як аргументи ім'я файлу, що виконується, і деякі параметри. Після чого відбувається завантаження коду та статичних даних із цього виконуваного файлу та перезатирання власного сегмента з кодом. Інші ділянки пам'яті, такі як стек та купа переініціалізуються. Після цього ОС просто виконує програму, передаючи їй набір аргументів. Таким чином, ми не створювали новий процес, ми просто трансформували поточну запущену програму на іншу запущену програму. Після виконання виклику exec() у нащадку настає враження, що початкова програма начебто в принципі не запускалася.

Таке ускладнення запуску абсолютно нормально для shell оболонки Unix, і дозволяє цій оболонці виконувати код після виклику вилка (), але до виклику exec (). Прикладом такого коду може бути підстроювання оточення оболонки під потреби програми, що запускається, перед її безпосереднім запуском.

Склад — лише користувальницька програма. Вона показує вам рядок запрошення і чекає, поки ви в нього щось напишете. У більшості випадків якщо написати туди ім'я програми, shell знайде його місцезнаходження, викличе метод fork(), а потім щоб створити новий процес, викличе який-небудь з типів exec() і дочекається його виконання за допомогою виклику wait(). Коли процес-нащадок завершиться shell повернеться з виклику wait() і знову виведе рядок запрошення і буде чекати введення наступної команди.

Поділ fork() & exec() дозволяє shell робити такі речі, наприклад:
wc file > new_file.

У цьому прикладі виведення програми wc перенаправлено у файл. Спосіб, яким shell досягає цього досить простий - при створенні процесу-дитини перед викликом exec (), shell закриває стандартний потік виводу та відкриває файл новий_файл, таким чином, весь висновок з далі запущеної програми wc буде перенаправлено у файл замість екрана.

Unix pipe реалізовані схожим чином з різницею, що вони використовують виклик pipe(). У цьому випадку потік виведення процесу буде підключений до черги pipe, розташованої в ядрі, до якої буде приєднаний потік введення іншого процесу.

Джерело: habr.com

Додати коментар або відгук