Реферат: Побудова надійних операційних систем, що допускають наявність ненадійних драйверів пристроїв

У наступних підрозділах ми пояснимо, чому застосування цих підходів дозволяє підвищити надійність. Ми також порівняємо вплив деяких класів помилок на нашу систему з тим, як вони впливають на монолітні системи, такі як Windows, Linux і BSD. У розд. 6 ми порівняємо наш підхід до підвищення надійності з іншими ідеями, пропонованими в літературних джерелах.

Скорочення числа помилок в ядрі

Нашої першою лінією захисту є дуже невелике ядро. Добре відомо, що в більшому за обсягом коді міститься більша кількість помилок, і тому чим менше ядро, тим менше в ньому помилок. Якщо в якості нижньої оцінки використати 6 помилок на 1000 рядків виконуваного коду [27], то за наявності 3800 рядків виконуваного коду в ядрі буде присутній, як мінімум, 22 помилки. Крім того, 3800 рядків коду (менше 100 сторінок лістингу, включаючи заголовки та коментарі) – це досить мало, щоб весь цей код міг зрозуміти один чоловік; це істотно підвищує шанси на те, що з часом всі помилки вдасться знайти.

На відміну від цього, в ядрі монолітної системи, такий як Linux, розміром в 2.5 мільйона рядків виконуваного коду, ймовірно, повинно міститися не менше 6 * 2500 = 15,000 помилок. Крім того, за наявності системи з декількох мільйонів рядків ні одна людина не може прочитати весь вихідний код і повністю зрозуміти, як він працює, що зменшує шанси на знаходження всіх помилок.

Зниження потенційного впливу помилок

Звичайно, зменшення розміру ядра не призводить до скорочення обсягу всього коду системи. При цьому всього лише велика частина системи починає працювати в режимі користувача. Однак саме це зміна надає глибоке вплив на надійність. У коду ядра є можливість повного доступу до всього, що може робити машина. Помилки в ядрі можуть призводити до випадкової ініціалізації введення-виведення, виконання неправильного вводу-виводу, пошкодження таблиць розподілу пам'яті та іншим речам, які не можуть зробити непривілейованих програми, які виконуються в режимі користувача.

Тому ми не стверджуємо, що переведення більшої частини операційної системи в призначений для користувача режим призводить до скорочення загальної кількості наявних помилок. Ми стверджуємо лише те, що ефект прояви помилки при виконанні програми в режимі користувача є менш руйнівним, ніж той, який проявляється при виконанні програми в режимі ядра. Наприклад, аудіо-драйвер, що виконуються в режимі користувача, при спробі використання невірного покажчика насильно завершується сервером процесів, аудіоапаратура перестає працювати, але на іншу частину системи це не впливає.

Для порівняння розглянемо вплив помилки в аудіо-драйвері, що виконуються в режимі ядра. Цей драйвер може ненавмисно перезаписати в стеку адресу повернення зі своєї процедури і зробити при виконанні повернення довільний перехід в монолітне ядро. Цей перехід може привести до коду управління пам'яттю, викликаючи руйнування ключових структур даних, таких як таблиці сторінок і списки вільних і зайнятих ділянок пам'яті. Монолітні системи в цьому відношенні є дуже крихкими і легко руйнуються при прояві помилки.

Відновлення після збоїв

Сервери і драйвери запускаються і контролюються системним процесом, званим сервером реінкарнації. Якщо контрольований процес непередбачених чи аварійних чином завершується, це негайно розпізнається, оскільки сервер процесів оповіщає сервер реінкарнації про завершення сервера або драйвера, і процес автоматично перезавантажиться. Крім того, сервер реінкарнації періодично опитує всі сервери і драйвери на предмет їхнього стану. Якщо який-небудь з цих процесів не відповідає правильним чином протягом встановленого інтервалу часу, то сервер реінкарнації насильно завершує і перезапускає погано провідні себе сервери та драйвери. Оскільки дуже багато помилок введення-виведення бувають нестійкими, що проявляються при рідко виникають тимчасових співвідношеннях, синхронізаційних глухий кут і т.д., простий перезапуск драйвера усуває проблему.

Збій драйвера має наслідки і для файлової системи. Можуть бути втрачені невиконані запити вводу-виводу, і в деяких випадках інформація про помилку вводу-виводу доводиться до відома програми. Однак у багатьох випадках можливе повне відновлення. Більш докладне обговорення сервера реінкарнації і надійності на рівні додатків наводиться в розд. 4.

У монолітних системах зазвичай відсутня можливість виявлення збійних драйверів «на льоту», хоча є дані про деякі дослідження в цій області [25]. Тим не менше, заміна на льоту ядерного драйвера є складною справою, оскільки до часу заміни він може утримувати ядерні блокування або знаходитися в критичному ділянці.

Обмеження зловживань переповнювання буфера

Відомо, що переповнення буферів є рясним джерелом помилок, наявністю яких інтенсивно користуються віруси і черв'яки. Хоча наша розробка спрямована радше на боротьбу з помилками, а не із зловмисними кодом, деякі засоби нашої системи надають захист від певних видів зловживань. Оскільки наше ядро є мінімальним, і в ньому використовується тільки статичне розміщення даних, виникнення проблеми малоймовірно в найбільш чутливої частини системи. Якщо переповнення буферу трапляється в одному з користувацьких процесів, то проблема не є надто серйозною, оскільки сервери і драйвери, що виконуються в режимі користувача, володіють обмеженими можливостями.

Крім того, в нашій системі виконується тільки код, розташований в сегментах тексту, які доступні тільки з читання. Хоча це не запобігає можливість переповнення буфера, ускладнюється можливість зловживання, оскільки надлишкові дані, що знаходяться в стеці або купі, неможливо виконати як код. Цей захисний механізм є виключно важливим, оскільки він запобігає зараження вірусами і черв'яками та виконання їх власного коду. Сценарій найгіршого випадку змінюється від взяття безпосереднього управління до перезапису адреси повернення в стеку та виконання деякої існуючої бібліотечної процедури. Найбільш відомий приклад такої ситуації часто називають атакою шляхом «повернення в libc» («return-to-libc»), і цей спосіб атаки вважається набагато більш складним, ніж виконання коду в стеці або купі.

На відміну від цього, в монолітних системах купуються повноваження супер, якщо переповнення буферу відбувається в будь-якій частині операційної системи. Більш того, в багатьох монолітних системах допускається виконання коду в стеці або купі, що істотно спрощує зловживання переповнювання буфера.

Забезпечення надійного IPC

Добре відомою проблемою механізмів обміну повідомленнями є управління буферами, але в нашому варіанті комунікаційних примітивів ми повністю уникаємо цієї проблеми. У нашому механізмі синхронної передачі повідомлень використовуються рандеву, в результаті чого усувається потреба в буферизації і управлінні буферами, а також відсутня проблема вичерпання ресурсів. Якщо одержувач не очікує повідомлення, то примітив SEND блокує відправника. Аналогічно, примітив RECEIVE блокує процес, якщо немає повідомлення, що очікує свого отримання. Це означає, що для заданого процесу в таблиці процесів у будь-який час повинен зберігатися єдиний вказівник на буфер повідомлення.

На додаток до цього, у нас є механізм асинхронної передачі повідомлень NOTIFY, який також не є чутливим до вичерпання ресурсів. Повідомлення є типізовані, і для кожного процесу зберігається тільки один біт для кожного типу. Хоча обсяг інформації, яку можна передати таким чином, обмежений, цей підхід був обраний з-за своєї надійності.

До речі, зауважимо, що у своєму IPC ми уникаємо переповнювання буфера шляхом обмеження засобів комунікації короткими повідомленнями фіксованої довжини. Повідомлення є об'єднанням декількох типізованих форматів повідомлень, так що розмір автоматично вибирається компілятором, як розмір найбільшого допустимого типу повідомлень, який залежить від розміру цілих чисел і покажчиків. Цей механізм передачі повідомлень використовується для всіх запитів і відповідей.

Обмеження IPC

IPC – це потужний механізмом, який потребує строгого контролі. Оскільки наш механізм передачі повідомлень є синхронним, процес, що виконує примітив IPC, блокується, поки обидва учасника не стануть готовими. Користувальницький процес може легко зловживати цим властивістю для завішування системних процесів шляхом посилки запиту без очікування відповіді. Тому є інший примітив IPC SENDREC, що комбінує в одному виклик SEND і RECEIVE. Він блокує відправника до отримання відповіді на запит. З метою захисту операційної системи цей примітив є єдиним, який можна використовувати звичайним користувачам. Насправді, в ядрі для кожного процесу підтримується бітовий масив для обмеження примітивів IPC, які дозволяється використовувати даному процесу.

Крім того, в ядрі підтримується бітовий масив, що визначає, з якими драйверами і серверами може взаємодіяти даний процес. Ця маска посилки повідомлень являє собою механізм, що запобігає безпосередню посилку повідомлень драйверам від користувацьких процесів. Натомість цього, їм дозволяється спілкуватися тільки з серверами, що забезпечують POSIX-дзвінки. Однак маска посилки повідомлень використовується також і для запобігання посилки (непередбаченого) повідомлення, скажімо, від драйвера клавіатури аудіо-драйверу. Знову шляхом суворої інкапсуляції можливостей кожного процесу ми можемо в значній мірі запобігти поширенню неминучих помилок в драйверах і їх вплив на інші частини системи.

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

Уникання тупиків

Оскільки за замовчуванням для IPC використовуються синхронні виклики SEND і RECEIVE, можуть виникати тупики, коли два або більше число процесів одночасно намагаються обмінюватися повідомленнями, і всі процеси блокуються в очікуванні один одного. Тому ми ретельно розробляли протокол уникнення тупиків, що приписує часткове, що сходить впорядкування повідомлень.

Впорядкування повідомлень приблизно відповідає розбивка на рівні, описаного в розд. 2.2. Наприклад, звичайним користувальницьким процесам дозволяється тільки посилати повідомлення з використанням примітиву SENDREC серверів, які реалізують інтерфейс POSIX, а ці сервери можуть запитувати сервіси від драйверів, які, у свою чергу, можуть виробляти виклики ядра. Однак для асинхронних подій, таких як переривання і таймери, потрібні п

К-во Просмотров: 124
Бесплатно скачать Реферат: Побудова надійних операційних систем, що допускають наявність ненадійних драйверів пристроїв