скачать рефераты
  RSS    

Меню

Быстрый поиск

скачать рефераты

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

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

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

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

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

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

Ця схема проста і елегантна, вона дозволяє відокремити сервери і драйвери від ядра дозволяє замінювати їх простим чином, що сприяє модульності системи. Хоча тут потрібно до чотирьох повідомлень, вони передаються дуже швидко (в межах 500 наносекунд на повідомлення в залежності від ЦП). Якщо і відправник, і одержувач готові до комунікації, те ядро копіює повідомлення прямо з буфера відправник у буфер одержувача без його переміщення в адресний простір ядра. Крім того, число копіювань даних є точно таким же, як в монолітній системі: диск поміщає дан прямо в буферний кеш файлового сервера, та є одне копіювання з цього кеша в адресний простір користувацького процесу.

Принципи розробки

Перш ніж перейти до докладного розгляду властивостей надійності нашої системи, коротко обговоримо принципи розробки, якими ми керувалися у прагненні до надійності:

1.         Простота.

2.         Модульність.

3.         Найменша авторизація.

4.         Відмовостійкість.

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

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

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

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

5. Властивості надійності

Ми вважаємо, що в нашій розробці надійність системи підвищується в порівнянні з усіма іншими існуючими операційними системами за рахунок застосування трьох важливих підходів:

Зменшується кількість критичних збоїв.

Скорочується обсяг шкоди, яка може бути заподіяна будь-який помилкою.

Можна відновити після поширених збоїв.

У наступних підрозділах ми пояснимо, чому застосування цих підходів дозволя підвищити надійність. Ми також порівняємо вплив деяких класів помилок на нашу систему з тим, як вони впливають на монолітні системи, такі як 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, які дозволяється використовувати даному процесу.

Страницы: 1, 2, 3, 4


Новости

Быстрый поиск

Группа вКонтакте: новости

Пока нет

Новости в Twitter и Facebook

  скачать рефераты              скачать рефераты

Новости

скачать рефераты

© 2010.