
- •Поняття операційної системи
- •Операційна система як розширена машина
- •Операційна система як менеджер ресурсів
- •Історія розвитку операційних систем
- •Перше покоління (1945-1955): електронні лампи і комутаційні панелі
- •Друге покоління (1955-1965): транзистори і системи пакетної обробки
- •Третє покоління (1965-1980): інтегральні схеми і багатозадачність
- •Четверте покоління (з 1980 року по наші дні): персональні комп'ютери
- •Історія minix 3
- •Основні концепції
- •Процеси
- •Оболонка
- •Системні виклики
- •Системні виклики для управління процесами
- •Системні виклики для управління сигналами
- •Системні виклики для управління файлами
- •Системні виклики для управління каталогами
- •Системні виклики для захисту
- •Системні виклики для управління часом
- •Структура операційної системи
- •1.5.2. Багаторівневі системи
- •1.5.3. Віртуальні машини
- •1.5.4. Екзоядра
- •1.5.5. Модель клієнт-сервер
- •2.1.1. Модель процесів
- •2.1.2. Створення процесів
- •2.1.3. Завершення процесів
- •2.1.4. Ієрархії процесів
- •2.1.5. Стани процесів
- •2.1.6. Реалізація процесів
- •2.1.7. Програмні потоки
- •2.2. Взаємодія між процесами
- •5.1. Файли
- •5.1.1. Іменування файлів
- •5.1.2. Структура файлу
- •5.1.3. Типи файлів
- •5.1.4. Доступ до файлів
- •5.1.5. Атрибути файлів
- •5.1.6. Операції з файлами
- •5.2. Каталоги
- •5.2.1. Прості каталоги
- •5.2.2. Ієрархічні системи каталогів
- •5.2.3. Шляхи
- •5.2.4. Операції з каталогами
- •5.3. Реалізація файлової системи
- •5.3.1. Структура файлової системи
- •5.3.2. Реалізація файлів
- •5.3.4. Організація дискового простору
Системні виклики для управління процесами
Перша група викликів у табл. 1.1 управляє процесами. Почнемо
розгляд з виклику fork. Системний виклик fork (розгалуження) є
єдиним способом створення нового процесу в MINIX 3. Він створює точну копію
вихідного процесу, включаючи дескриптори файлу, регістри і т. п. Після виклику
fork вихідний процес і його копія (батьківський і дочірній процеси)
розвиваються окремо один від одного. Всі змінні мають однакові величини
під час виклику fork, але як тільки батьківські дані скопійовані для
створення дочірнього процесу, наступні зміни в одному з них вже не впливають
на інший. (Текст програми, який не змінюється, розподіляється між
батьківським і дочірнім процесами.) Виклик fork повертає величину, рівну
нулю в дочірньому процесі і рівну ідентифікатору дочірнього процесу в
батьківському. Використовуючи повернутий ідентифікатор процесу (Process IDentifier, PID),
два процеси можуть з'ясувати, який з них батьківський, а який - дочірній.
У більшості випадків після виклику fork дочірньому процесу необхідно
виконати програмний код, відмінний від коду батьківського процесу. Розглянемо
приклад оболонки. Вона читає команди з терміналу, запускає дочірній процес,
чекає, поки дочірній процес виконає команду, і читає наступну команду
після завершення роботи дочірнього процесу. Чекаючи, поки дочірній процес
закінчить роботу, батьківський процес виконує системний виклик waitpid,
який очікує завершення дочірнього процесу (або всіх дочірніх процесів,
якщо їх на даний момент декілька). Виклик waitpid може чекати закінчення
якогось певного дочірнього процесу або будь-якого дочірнього
процесу, для цього потрібно задати перше параметри виклику рівним -1. Коли виклик
waitpid виконаний, покажчик, що задається другим параметром statloc,
покаже статус завершення дочірнього процесу (нормальне або аварійне
завершення і вихідне значення). Третій параметр визначає різні
необов'язкові настройки. Виклик waitpid замінює раніше використовувати виклик wait,
який на даний момент застарів і підтримується лише для зворотної сумісності.
Тепер розглянемо, як виклик fork використовується оболонкою. Коли друкується
команда, оболонка створює дочірній процес, який повинен виконати
команду користувача. Він робить це за допомогою системного виклику execve,
заміняє весь його образ пам'яті файлом, зазначеним у першому параметрі.
(Фактично самим системним викликом є exec, але кілька різних
бібліотечних процедур викликають його з різними параметрами і незначно
відмінними іменами. Ми тут скористаємося ними як системними виклику
викликами.) Вельми спрощена оболонка, що ілюструє використання команд
fork, waitpid і exec, показана в лістингу 1.1.
Лістинг 1.1. Усічена оболонка. Тут і далі в книзі передбачається,
що значення TRUE одно 1
# Define TRUE I
while (TRUE) {/ * вічний цикл * /
type_prompt (); / * друк запрошення на екрані * /
read_command (command, parameters); / * читати вхідні дані з терміналу * /
if (fork () 1 = 0) {/ * запускає дочірній процес * /
/ * Код батьківського процесу * /
waitpid (-l, & status, 0); / * чекати закінчення дочірнього процесу * /
} Else {
/ * Код дочірнього процесу * /
execve (command, parameters, 0); / * виконання command * /
}
}
У самому загальному випадку у команди exec є три параметри: ім'я виконуваного
файла, покажчик на масив аргументів і покажчик на масив змінних оточення
оточення. Ці параметри ми коротко обговоримо надалі. Різні бібліотечні
програми, включаючи execl, execv, execle і execve, дозволяють опускати
параметри або визначати їх іншими способами. У книзі ми скористаємося
назвою exec для того, щоб представити системний виклик, що викликається усіма
цими процедурами.
Розглянемо наступну команду:
ср filel file2
Ця команда використовується для копіювання файлу filel в файл f ile2. Після
створення оболонкою дочірнього процесу останній знаходить і виконує файл
ср і передає йому імена вихідного та цільового файлів.
Основний модуль програми ср (як і більшість інших головних програм
на мові С) містить визначення:
main (argc, argv, envp)
У цьому визначенні в параметр argc входить кількість записів в командному
рядку, включаючи ім'я програми. Наприклад, для показаної рядка параметр
argc дорівнює 3.
Другий параметр argv є покажчиком на масив покажчиків. Елемент г
масиву вказує на г-у запис в командному рядку. У нашому прикладі параметр
argv [0] повинен вказувати на рядок ср, a argv [1] і argv [2] - на рядки
filel і file2 відповідно.
Третій параметр функції main з ім'ям envp є покажчиком на масив
строкових змінних оточення вигляді ім'я = величина, які використовують
для передачі програмі такої інформації, як тип терміналу або ім'я
домашнього каталогу. У лістингу 1.1 третій параметр дорівнює нулю, оскільки дочірньому
процесу нічого не передається.
Якщо команда exec здається складною, не засмучуйтеся, тому що це - один з
найбільш складних системних викликів в POSIX. Всі інші набагато
простіше. В якості ще одного прикладу розглянемо виклик exit, процеси повинні
використовувати його при завершенні роботи. У нього є всього один параметр, статус
виходу, що змінюється від 0 до 255. Він повертається батьківському процесові
через змінну statloc в системному виклику waitpid. Молодший байт цієї
змінної містить значення статусу виходу, який дорівнює 0 при нормальному
завершення роботи і ненульове значення при завершенні помилково. Старший
байт містить статус завершення дочірнього процесу (від 0 до 255). Наприклад:
n = waitpid (-l, & status, options);
Якщо батьківський процес виконає цю команду, то його робота буде
припинена до завершення дочірнього процесу. Якщо дочірній процес завершиться,
скажімо, через виклик exit з параметром 4, то коли батьківський процес
продовжить роботу, п міститиме PID дочірнього процесу, a statloc -
значення 0x0400 (в мові С прийнято писати символи Ох перед шістнадцятковим
числами, ця угода постійно використовується в книзі).
У MINIX 3 під процеси відводиться частина пам'яті, яка, в свою чергу,
ділиться на три сегменти: тексту (код програми), даних (змінні) і стека.
Сегмент даних росте знизу вгору, а стек збільшується зверху вниз, як
показано на рис. 1.9. Між ними існує частина невикористаного адресного
простору. Стек автоматично займає таку частину цієї ділянки пам'яті,
яку необхідно, але розширення сегмента даних виконується явним
чином. Для цього використовується спеціальний системний виклик brk, що задає новий
адреса для межі сегменту даних. Ця адреса може бути як більше
поточного значення (сегмент зростає), так і менше (сегмент зменшується). Але, звичайно
ж, ця адреса має бути менше, ніж покажчик стека, тому що в противному
випадку дані і стек можуть перекриватися, що неприпустимо.
Для зручності програміста пропонується бібліотечна процедура sbrk,
також змінює розмір сегмента даних. Її єдиний параметр вказує, на
скільки повинен бути збільшений розмір сегмента (щоб зменшити сегмент,
потрібно передавати негативні значення). Процедура працює так: виклики brk
визначається поточний розмір сегмента, потім обчислюється новий розмір, після
чого робиться ще один системний виклик, запитувач необхідну
кількість байтів. Обидва виклику (brk і sbrk) не відносяться до стандарту POSIX. Для
динамічного виділення пам'яті програмісти можуть використовувати бібліотечну
процедуру malloc. Оскільки прямо цю процедуру застосовують дуже рідко,
її стандартизацію порахували недоцільною.
Наступний системний виклик для роботи з процесами, getpid, є заодно
і найпростішим з них. Цей виклик просто повертає ідентифікатор викликав
його процесу. Зверніть увагу на те, що при виклику fork значення
ідентифікатора дочірнього процесу отримує тільки батьківський процес. Якщо
дочірньому процесу буде потрібно дізнатися власний ідентифікатор процесу, йому
доведеться використовувати виклик getpid. Виклик getgrp повертає ідентифікатор групи
процесів, в яку входить процес, який зробив виклик. Виклик sets id відкриває
новий сеанс і встановлює ідентифікатор групи процесів рівним
ідентифікатору процесу, який зробив виклик. Сеанси відносяться до необов'язковою
функції POSIX, званої управлінням завданнями і в MINIX 3 не
реалізованої, тому дана функція надалі не згадується.
Останній системний виклик з цієї групи, ptrace, використовується налаштувальник
для управління налагоджують програмою. Він дозволяє налаштувальнику звертатися
до пам'яті налаштовуємого процесу, а також керувати ним іншими способами.