4. Завантаження та ініціалізація ядра ос
Код ядра зберігають в окремому файлі на диску, завантажувач повинен його знайти. Є різні підходи до реалізації завантаження ядра: воно може завантажитися у пам'ять повністю або частинами (при цьому одні частини можуть завантажувати інші за потребою), або у частково стиснутому стані (а після виконання деяких попередніх операцій бути розпакованим).
У разі одноетапного завантаження ядро завантажують завжди в реальному режимі. У разі двоетапного -режим завантаження залежить від того, чи перемикають завантажувач другого етапу в захищений режим.
Після завантаження ядра у пам'ять керування передають за адресою спеціальної процедури, що починає процес ініціалізації ядра, і виконуються такі дії, як опитування та ініціалізація устаткування (зазвичай ініціалізують все устаткування -навіть те, що було вже проініціалізовано BIOS), ініціалізація підсистем ядра, завантаження та ініціалізація необхідних драйверів (насамперед диска та відеокарти), монтування кореневої файлової системи. Точна послідовність дій різна для різних ОС.
5. Завантаження компонентів системи
Після того як ядро завантажилося у пам'ять, починається завантаження різних компонентів системи. До них належать додаткові драйвери (які не були потрібні під час завантаження), системні фонові процеси тощо. Більшу частину цієї роботи виконують у режимі користувача. У результаті до моменту, коли користувач може почати працювати із системою, у пам'яті, крім ядра, присутній набір процесів, потрібних для повноцінної роботи.
Коли система завантажилася повністю, вона звичайно відображає запрошення для входу користувача (якщо вона з'єднана із терміналом), а коли користувач успішно ввійде у систему, для нього ініціалізують програмне забезпечення, що організовує його сесію (наприклад, запускають копію командного інтерпретатора).
Особливості завантажувача Linux
Під час завантаження Linux використовують двоетапний завантажувач. Є кілька програмних продуктів, що реалізують такі завантажувачі, найвідоміший із них lilo (від linux loader). Він може бути встановлений як у MBR (замінивши там код, що завантажує перший сектор активного розділу), так і у завантажувальному секторі якогось (звичайно активного) розділу диска. Другий підхід є безпечнішим за умов, коли на комп'ютері встановлено кілька ОС у режимі мультизавантаження, оскільки деякі ОС можуть перезаписувати MBR за своєю ініціативою.
Перша частина lilo, записана у завантажувальний сектор або MBR, під час
свого виконання готує пам'ять і завантажує в неї другу частину. Та зчитує із диска двійкове відображення карти наявних на комп'ютері варіантів завантаження (різні ОС, різні установки Linux тощо) і пропонує користувачу вибрати один із них (за допомогою підказки «LILO boot:»). Зазначимо, що вихідну версію карти варіантів завантаження створює користувач (системний адміністратор) у вигляді звичайного текстового файла /etc/lilo.conf. Після кожної зміни карти необхідно обновлювати її відображення на диску, використовуване завантажувачем. Для цього
виконують команду (із правами root):
Після вибору користувачем одного із варіантів завантаження поведінка завантажувача залежить від характеру файлової системи для розділу. У разі вибору розділу з іншою ОС (наприклад, Windows) зчитують у пам'ять і виконують завантажувальний сектор цього розділу (тому за допомогою 1 і 1 о можна завантажити будь-яку ОС), якщо ж вибрано розділ із Linux, у пам'ять завантажують ядро системи (його адреса на диску міститься в карті варіантів завантаження). Після завантаження у пам'яті з'являється стиснуте ядро системи і придатний для виконання код двох функцій завантаження: setup () і startup_32(). Код завантажувача переходить до виконання функції setup (), починаючи ініціалізацію ядра.
Ініціалізація ядра
Перший етап ініціалізації ядра відбувається у реальному режимі процесора. Переважно здійснюється ініціалізація апаратних пристроїв (Linux не довіряє цього BIOS). Функція setup () визначає фізичний обсяг пам'яті у системі, ініціалізує клавіатуру, відеокарту, контролер жорсткого диска, деякі інші пристрої, перевизначає таблицю переривань, переводить процесор у захищений режим і передає управління функції startup_320, код якої також перебуває поза стиснутим ядром.
Функція startup_320 задає сегментні регістри і стек, розпаковує образ ядра і розташовує його у пам'яті. Далі виконують код розпакованого ядра, при цьому
керування спочатку дістає функція, яку також називають startup_320. Вона формує середовище виконання для першого потоку ядра Linux («процес 0»), створює його стек (із цього моменту вважають, що він є), вмикає підтримку сторінкової організації пам'яті, задає початкові (порожні) оброблювачі переривань, визначає модель процесора і переходить до виконання функції start_kerneK).
Функцію start_kernel () виконують у межах потоку ядра «процес 0», завер- шуючи ініціалізацію ядра. Вона доводить до робочого стану практично кожен
компонент ядра, зокрема:
ініціалізує таблиці сторінок і всі дескриптори сторінок;
остаточно ініціалізує таблицю переривань;
ініціалізує кусковий розподілювач пам'яті;
встановлює системні дату і час;
виконує код ініціалізації драйверів пристроїв;
робить доступною кореневу файлову систему (де розташовані файли, необхідні для завантаження системи).
Крім того, за допомогою функції kernel_thread() створюють потік ініціалізації («процес 1»), що виконує код функції init(). Цей потік створює інші потоки ядра і виконує програму /sbin/init, перетворюючись у перший у системі процес користувача і nit. Зазначимо, що для коректного завантаження іnit повинна бути доступна коренева файлова система із найважливішими розділюваними бібліотеками (каталог /lib має бути на тому самому розділі, що і кореневий каталог /).
На перетворенні цього потоку в і nit ініціалізація ядра завершена, функція
start_kernel () переходить у нескінченний цикл простою (idle loop), не займаючи
ресурсів процесора. Подальша ініціалізація системи відбувається під час виконання і nit.
Виконання процесу init
Процес іnit є предком усіх інших процесів у системі. Після запуску він читає свій конфігураційний файл /etc/inittab і запускає процеси, визначені в ньому. Набір процесів, які запускаються, залежить від дистрибутива Linux. Приклад виконання init під час завантаження системи Red Hat Linux наведено нижче.
Файл /etc/inittab визначає кілька рівнів роботи (runlevels). Кожен із них — це особлива програмна конфігурація системи, у якій може існувати тільки певна група процесів. Рівень роботи визначає режим функціонування ОС (однокористувацький, багатокористувацький, перезавантаження тощо). Стандартними рівнями роботи є рівні з 0 по 6. Ось найважливіші з них:
0 — завершення роботи (shutdown);
1 — однокористувацький режим (single user mode) — у ньому не дозволене виконання деяких фонових процесів, доступ до системи через мережу тощо;
3 — стандартний багатокористувацький режим (звичайно цей рівень задають за замовчуванням);
6 — перезавантаження (reboot).
Для деяких рівнів задано символьні синоніми (наприклад, для рівня 1 синонімом є рівень S). У файлі /etc/inittab визначено різні командні файли (із програмами, написаними мовою командного інтерпретатора, далі їх називатимемо скриптами), які повинні виконуватися для різних рівнів виконання.
Синтаксис рядка /etc/inittab такий:
ідентифікатор: рівень роботи: дія: програма
Перший скрипт, який запускає init, визначений у рядку /etc/inittab із дією, заданою ключовим словом sysinit. Точне його ім'я залежить від поставки Linux,
у Red Hat це /etc/rc.d/rc.sysinit. Його називають також стартовим скриптом. Ось
відповідний рядок /etc/inittab:
si::sysinit:/etc/rc.d/rc.sysinit
Стартовий скрипт налаштовує базові системні сервіси, зокрема:
час від часу перевіряє диски на помилки командою fsck;
завантажує модулі ядра для драйверів пристроїв, які не повинні бути завантажені раніше;
ініціалізує ділянку підкачування командою swapon;
монтує файлові системи відповідно до файла /etc/fstab.
У файлі /etc/fstab задано, яка файлова система має бути змонтована у який каталог кореневої файлової системи. Кожний рядок цього файла відповідає одній операції монтування і містить інформацію про тип файлової системи, розділ, де вона розташована, точку монтування тощо.
Крім стартового скрипта, у каталозі /etc/rc.d перебувають кілька інших скриптів:
/etc/rc.d/rc — викликають у разі зміни рівня виконання; як параметр він приймає номер рівня, звичайно запускає всі скрипти, що відповідають рівню (такі скрипти розглянемо трохи пізніше);
/etc/rc.d/rc.local - викликають останнім під час завантаження системи; він містить специфічні для конкретної машини команди (зазвичай не рекомендують поміщати такі команди у стартовий скрипт, оскільки у разі перевстановлення або відновлення системи той файл стирають, a rc.local — ні).
Запуск системних фонових процесів та ініціалізацію системних служб (наприклад, мережних інтерфейсів) виконують із використанням набору файлів і підкаталогів у каталозі /etc.
У каталозі /etc/rc.d/init.d зберігають набір індивідуальних стартових скриптів, відповідальних за керування різними фоновими процесами і службами. Наприклад, скрипт /etc/rc.d/init.d/httpd відповідає за керування веб-сервером Apache. Кожний стартовий скрипт має обробляти отримані як параметри ключові слова start і stop, запускаючи і зупиняючи відповідний процес.
# /etc/rc.d/init.d/httpd start
Стартові скрипти не запускаються безпосередньо процесом і nit під час завантаження системи. Для організації такого запуску в /etc/rc.d є набір каталогів (rcO.d. .. rc6.d), кожен із них містить символічні зв'язки, що вказують на стартові скрипти. У разі переходу на певний рівень виконання і nit запускає скрипт /etc/rc.d/rc, який переглядає всі зв'язки каталогу, що відповідає рівню, і виконує дії відповідно до їх імен.
Кожен зв'язок має деяке ім'я у форматі Кииім'я або Sn/гім'я (де пп — цифри, наприклад S70httpd), яке характеризується такими особливостями.
Якщо ім'я починається на S, то на цьому рівні служба має бути запущена (якщо вона не запущена, потрібно виконати відповідний стартовий скрипт із параметром start); на К — на цьому рівні служба не повинна бути запущена (якщо вона запущена, її потрібно зупинити, виконавши стартовий скрипт із параметром stop).
Число пп задає номер послідовності, що визначає порядок запуску або зупинки служб у разі переходу на рівень. Що більший пп, то пізніше виконається скрипт; важливо, щоб до цього часу вже були запущені всі служби, від яких залежить ця. Наприклад, ініціалізацію мережі потрібно виконувати якомога раніше, тому зв'язок, що вказує на скрипт, при цьому може бути такий: S20network.
Ім'я зв'язку завершують іменем стартового скрипта, на який він вказує.
Наприклад, коли і nit переходить на рівень виконання 3, усі зв'язки, що починаються на S у каталозі /etc/rc.d/rc3.d, буде запущено в порядку їхніх номерів, і для кожного запуску буде задано параметр start. Після виконання всіх скриптів вимоги до рівня виконання 3 задовольняться.
В /etc/inittab має бути заданий рівень виконання за замовчуванням, для чого потрібно включити у файл рядок із ключовим словом і nitdefault. Система завершить своє завантаження після переходу на цей рівень. Звичайно, це — рівень 3.
id:3:initdefau"lt:
Після запуску всіх скриптів для переходу на рівень за замовчуванням іnit завжди запускає спеціальну програму getty, що відповідає за зв'язок із користувачем через консоль і термінали (звичайно створюють кілька таких процесів — по одному на кожну консоль). Є різні реалізації цієї програми, у Linux популярними є agetty і mingetty. Саме getty видає підказку «login:».
Рядок у /etc/inittab, що задає запуск версії getty, має такий вигляд (ключове слово res pawn означає, що процес буде перезапущено, якщо він припиниться):
1:2345:respawn:/sbi n/mingetty ttyl
Після того як користувач ввів своє ім'я, getty викликає програму /bin/login, що запитує пароль (видавши підказку «password:»), перевіряє його та ініціалізує сесію користувача. У більшості випадків це зводиться до запуску для користувача копії командного інтерпретатора (звичайно bash) у його домашньому каталозі. У результаті користувач може розпочати роботу із системою.
Процес init залишається у пам'яті і після завантаження. Він відповідає за автоматичний перезапуск процесів (для цього потрібно прописати програму в /etc/inittab із дією respawn, як getty); init також стає предком для всіх процесів, чий безпосередній предок припинив свою роботу.
Як зазначалося, у разі перезавантаження або зупинки система також переходить на певні рівні виконання, і при цьому виконуються скрипти з /etc/rc.d/init.d через зв'язки в каталогах для цих рівнів (rcO.d для зупинки, rc6.d для перезавантаження; такі зв'язки починаються на К). Для того щоб розпочати перезавантаження або зупинити систему, використовують спеціальну програму /sbm/shutdown, доступну лише супер користувачу root. Консоль Linux також підтримує організацію перезавантаження від клавіатури натисканням на Ctrl+Alt+Del.
