
- •Поняття операційної системи
- •Операційна система як розширена машина
- •Операційна система як менеджер ресурсів
- •Історія розвитку операційних систем
- •Перше покоління (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. Організація дискового простору
Системні виклики для управління файлами
Багато системні виклики мають відношення до файлової системи. У цьому
розділі ми розглянемо виклики, що працюють з окремими файлами, а в наступному
розділі звернемося до викликів, які оперують каталогами або файлової
системою в цілому. Для створення нового файлу служить виклик creat (відповідь на
питання, чому саме creat, а не create, загубився в тумані часів). Його
параметри - ім'я файлу і права доступу, наприклад:
fd = creat ("abc", 0751);
Ця команда створить файл з ім'ям abc і встановить для нього права доступу 0751
(В С числа з ведучим нулем вважаються вісімковими). Молодші 9 біт цього
числа показують, що власник файлу має права доступу rwx G означає
права на читання, запис і виконання), члени групи власника мають права
на читання і виконання E), а інші користувачі - тільки на виконання A).
Виклик creat не тільки створює новий файл, але й відкриває його для запису
незалежно від зазначеного режиму. Подальша запис в файл проводиться через
його дескриптор fd, значення якого повертається викликом. Якщо виконати
виклик creat для існуючого файлу, то файл усікається до нульової довжини
(Якщо, звичайно, права доступу дозволяють це). Зараз виклик creat застарів і
підтримується для зворотної сумісності, замість нього потрібно використовувати
виклик open.
Щоб створити спеціальний файл, потрібно замість creat виконати виклик mknod.
Ось типовий приклад:
fd = mknod ("/ dev/ttyc2", 020744, 0x0402);
Ця команда створює файл з ім'ям / dev/ttyc2 (звичайно це ім'я відповідає
другою консоллю) і задає для нього права доступу 020 744 (спеціальний символьний
файл з правами rwxr - r -). Третій параметр складений з пари байтів, з
яких старший задає основний пристрій D), а молодший - вторинне
пристрій B). Основний пристрій може бути будь-яким, а вторинне для файлу
/ Dev/ttyc2 має дорівнювати 2. Робити виклик mknod може тільки
суперкористувач, в іншому випадку виникає помилка.
Щоб прочитати або записати файл, його спочатку потрібно відкрити за допомогою
виклику open. Для цього виклику вказується ім'я файлу, (задається
або абсолютний шлях файла, або посилання на робочий каталог) і код O_RDONLY,
O_WRONLY або O_RDWR, що означає, що файл відкривається для читання, записи
або того й іншого. Для створення нового файлу служить код O_CREAT.
Повертається дескриптор файлу потім можна вжити при читанні або запису.
Потім файл закривається за допомогою виклику close, який робить дескриптор
файлу доступним для подальшого виклику creat або open.
Найбільш часто використовуваними викликами, без сумніву, є read і write.
Виклик read ми вже обговорювали, write має ті ж самі параметри.
Незважаючи на те що більшість програм читає і записує файли шляхом
послідовного доступу, деяким прикладним програмам необхідна
можливість доступу до будь-якої випадково вибраної частини файлу. Пов'язаний з
кожним файлом покажчик містить поточну позицію у файлі. Коли читання
(Запис) здійснюється послідовно, він зазвичай вказує на байт, який
повинен бути прочитаний (записаний) наступним. Виклик lseek може змінити
значення позиції покажчика, так що наступний виклик read або write почне
операцію десь в іншій частині файлу.
У виклику lseek є три параметри: перший - це ідентифікатор файлу,
другий - позиція у файлі, а третій каже, чи є другий параметр позицією
у файлі щодо початку файлу (абсолютна позиція), щодо
поточної позиції або щодо кінця файлу. Виклик lseek повертає
абсолютну позицію у файлі після зміни покажчика.
Для кожного файлу MINIX зберігає такі дані: тип файлу (звичайний,
спеціальний, каталог і т. д.), розмір, час останньої зміни та іншу
інформацію. Програма може запросити цю інформацію через системні виклику
виклики statnfstat. Вони розрізняються лише тим, що перший з них вимагає
імені файлу, а другий покладається на дескриптор файлу і корисний для відкритих
файлів, особливо для файлів стандартного введення і виведення, імена яких не
завжди відомі. У обох викликів другий параметр вказує на структуру, куди
потрібно помістити інформацію. Ця структура показана в лістингу 1.2.
Лістинг 1.2. Структура, яка використовується для отримання інформації від системних викликів STAT
і FSTAT. У фактичному коді для деяких типів використовуються символічні імена
struct stat {
short st_dev; / * пристрій, якому належить i-вузол * /
unsigned short st_ino; / * номер inode * /
unsigned short st_mode; / * режим доступу * /
short st_nlink; / * число посилань на файл * /
short st_uid; / * ідентифікатор користувача * /
short st_gid; / * ідентифікатор групи * /
short st_rdev; / * основне і вторинне * /
/ * Пристрій для спеціальних файлів * /
long st_size; / * розмір файлу * /
long st_atime; / * час останнього звернення * /
long st_mtime; / * час останньої зміни * /
long st_ctime; / * час останньої зміни для i-вузла * /
};
При роботі з дескрипторами файлів іноді може виявитися корисним
системний виклик dup. Наприклад, розглянемо програму, яка закриває
стандартний висновок (дескриптор 1), підставляє в якості стандартного виводу інший
файл, викликає функцію, яка щось пише в цей файл, а потім
відновлює початковий стан. Якщо просто закрити стандартний висновок і відкрити
новий файл, то новий файл стане стандартним висновком (якщо використовується
стандартне введення, дескриптор якого дорівнює 0), але відновити стан
буде неможливо. Рішення дає наступна команда, в якій використовується
системний виклик dup:
fd = dupA);
При виконанні цієї команди в змінну fd поміщається новий дескриптор,
який буде відповідати тому ж файлу, що і стандартний висновок A).
Потім стандартний висновок можна закрити, після чого відкрити новий файл і
використовувати його. Коли знадобиться відновити початковий стан, потрібно
закрити дескриптор 1 і виконати код:
n = dup (fd);
В результаті самий менший з дескрипторів файлів, а саме 1, стане
відповідати тому ж файлу, що і f d. Нарешті, fd можна закрити, в результаті
ми повернемося до тієї ж ситуації, з якої почали.
У системного виклику dup є другий варіант, за допомогою якого можна пов'язати
невикористаний дескриптор з уже відкритим файлом. Він записується так:
dup2 (fd, fd2);
Тут fd - це дескриптор відкритого файлу, af d2 - не пов'язаний ні з яким
файлом дескриптор, який після виконання виклику буде посилатися на той
же файл. Таким чином, якщо fd посилається на стандартний ввід (дескриптор
дорівнює 0), а дескриптор f d2 дорівнює 4, то після виконання виклику і 0 і 4 будуть
відповідати стандартному вводу.
Як уже зазначалося, для забезпечення взаємодії між процесами в MINIX 3
можна використовувати канали. Наприклад:
cat filel file2 I sort
Коли користувач дає оболонці цю команду, то оболонка створює канал і
з'єднує стандартний висновок першого процесу з входом каналу, а стандартний
введення другого процесу з виходом каналу. Щоб створити канал, застосовується
системний виклик pipe, що повертає два дескриптора файлів: один для читання
з каналу, інший для запису в нього:
pipe (& fd [O]);
Тут fd - масив двох цілих чисел, fd [0] - дескриптор для читання, afd [1] -
дескриптор для запису. Як правило, після цього робиться виклик fork,
батьківський процес закриває дескриптор для читання, а дочірній процес -
дескриптор для запису (або навпаки), щоб один процес міг писати в канал,
а інший - читати з нього.
У лістингу 1.3 приведений каркас процедури, що створює два процеси таким
чином, що вихід першого з них передається через канал у другій (більше
реалістичний приклад забезпечував би перевірку помилок і обробку аргументів).
Спочатку створюється потік, потім робиться виклик fork, і батьківський процес
стає першим процесом в каналі, а дочірній процес - другим. Так як
запускаються файли, processl і process2, нічого не знають про те, що вони
з'єднуються каналом, для роботи програми необхідно, щоб стандартний
висновок першого процесу був з'єднаний зі стандартним вводом другого процесу
каналом. Спочатку батьківський процес закриває дескриптор для читання з
каналу. Потім він закриває стандартний висновок і робить виклик dup, після якого
стандартним висновком стає вхід каналу. Важливо розуміти, що виклик dup
завжди повертає найменший допустимий дескриптор, в даному випадку - 1.
Нарешті, вихідний дескриптор для запису в канал закривається.
Лістинг 1.3. Каркас процедури, що створює конвеєр з двох процесів
# Define STD_INPUT 0 / * Дескриптор файлу для стандартного вводу * /
# Define STD_OUTPUT I / * Дескриптор файлу для стандартного виводу * /
pipeline (processl, process2) / * Вказівники на імена програм * /
char * processl, * process2;
{
int fd [2];
pipe (& fd [0]); / * створити конвеєр * /
if (fork ()! = 0) {
/ * Ці висловлювання виконуються батьківським процесом * /
close (fd [0]); / * Процес 1 не потребує читанні з конвеєра * /
close (STD_OUTPUT); / * Підготовка нового стандартного виводу * /
dup (fd [l]); / * Зробити стандартним виводом пристрій fd [l] * /
close (fd [1]); / * Цей дескриптор файлу більше не потрібний * /
execl (processl, processl, 0);
} Else {
/ * Ці висловлювання виконуються процесом-спадкоємцем * /
close (fd [l]); / * Процес 2 не потребує записи в конвеєр * /
close (STD_INPUT); / * Підготовка нового стандартного вводу * /
dup (fd [0]); / * Зробити стандартним вводом пристрій fd [O] * /
close (fd [0]); / * Цей дескриптор файлу більше не потрібний * /
execl (process2, process2, 0);
}
}
Після виклику exec стартує процес, у якого дескриптори 0 і 2 залишилися без
змін, а дескриптор 1 відповідає запису в канал. Код дочірнього
процесу аналогічний. Значення параметра функції execl повторюється тому, що
першим її параметром має бути ім'я програми, а другим - значення
перший аргумент запускається програми, яке для більшості програм
має збігатися з ім'ям.
Наступний системний виклик, ioctl, потенційно застосовний до всіх
спеціальним файлів. Наприклад, він використовується драйверами блокових пристроїв, таких
як SCSI-драйвери для роботи з стрічковими накопичувачами і CD-ROM. Тим не
Проте він в основному застосовується до символьним спеціальним файлам,
особливо до терміналів. У стандарті POSIX визначено декілька функцій, які
транслюються бібліотекою у виклики ioctl. За допомогою функцій tcgetattr
і t з set at tr можна змінити характеристики терміналу, такі як корекція
помилок, режим і т. д. Ці функції використовують виклик ioctl.
Режим з обробкою (cooked mode) - це нормальний режим роботи терміналу,
в якому можливе видалення символів, клавіші Ctrl S і Ctrl Q
відповідно зупиняють і запускають висновок інформації на термінал, клавіші Ctrl D
задають кінець файлу, а клавіші Ctrl C генерують сигнал переривання. Натискання
клавіш Ctrl \ в цьому режимі генерує сигнал, за яким процес
примусово переривається, і виводиться дамп пам'яті.
У режимі без обробки (raw mode) вся ця додаткова обробка не
виконується, і символи передаються програмі безпосередньо. Більш того, в цьому режимі термінал не чекає закінчення введення рядка, а передає символи програмі
відразу. В такому режимі найчастіше працюють екранні редактори.
Режим з перериванням (cbreak mode) - проміжний між двома
попередніми. У цьому режимі при редагуванні не працюють клавіші стирання і
видалення, а також комбінація Ctrl D, але клавіші Ctrl S, Ctrl Q, Ctrl C і Ctrl \
працюють. Як і в режимі без обробки, символи передаються програмами відразу, не
чекаючи закінчення введення рядка (так як редагування рядків не працює,
не обов'язково чекати закінчення введення, тому що користувач не зможе
передумати і видалити введені символи, як в режимі з обробкою).
Режими з обробкою, без обробки і з перериванням в стандарті POSIX не
описані. Замість цього POSIX визначає канонічний режим (canonical mode),
відповідний режиму з обробкою. В ньому визначено одинадцять
спеціальних символів, і введення ведеться порядково. В неканонічному режимі (noncanonical mode) введення даних визначається мінімальним сприйманим кількістю
символів і часом (в десятих долях секунди). Стандарт POSIX дуже гнучкий
і визначає безліч різних прапорів, керуючи якими можна
наблизити неканонічний режим як до режиму без обробки, так і до режиму з
перериванням. Старі терміни більш змістовні, тому ми неформально
будемо їх дотримуватися.
У виклику ioctl є три параметра. Наприклад, виклик функції tcsetattr,
встановлює параметри терміналу, виглядає так:
ioctl (fd, TCSETS, & termios);
Перший параметр задає файл, другий - виконувану операцію, а третій -
адреса POSIX-структури, яка містить прапори і масив керуючих символів.
Інші коди операцій дозволяють відкласти зміни до завершення
виводу, вважати поточні значення параметрів і відкинути не повністю лічені
дані.
Системний виклик access дозволяє дізнатися, чи дозволено системою
обмеження доступу звернення до певного файлу. Цей виклик необхідний
тому, що деякі програми можуть працювати під іншим ідентифікатором
користувача. Для цього програмами використовується механізм SETUID,
який описаний далі.
Щоб привласнити файлу нове ім'я, застосовується системний виклик rename. Його
параметри задають старе і нове ім'я файлу.
Нарешті, для управління файлами служить виклик f cntl, у чомусь подібний
викликом ioctl (і обидва ці виклику - так звані брудні хакі). У цього
виклику є кілька параметрів, найважливіші з яких служать для
управління захопленням файлу. Викликом fcntl можна захоплювати і звільняти
окремі частини файлів, а також визначати, захоплений чи потрібну ділянку. Цей
виклик ніяк не визначає семантику захоплення файлу. Програми повинні самі
вирішувати, що робити.