Смекни!
smekni.com

Технології віртуалізації: вчора, сьогодні, завтра (стр. 2 из 8)

Ефективна схема? Цілком! Вона працює, вона зручна, доступна і зрозуміла, тому що багато любителі асемблера до цих пір із задоволенням нею користуються. Але довго вона не протрималася, оскільки, як легко здогадатися, особливою гнучкістю у використанні не відрізняється. Як ми з самого початку «наріжемо» пам'ять скибочками, - так воно в майбутньому і залишиться: виділимо занадто багато - якісь області залишаться невикористаними і простоюють; виділимо занадто мало - не зможемо в потрібний момент збільшити цей обсяг. Чи пам'ятають ще програмісти старі добрі DOS-Овские середовища розробки від Borland, де в опціях компілятора вказувалася «модель пам'яті», в якій визначався розмір і кількість використовуваних у програмі сегментів? І чи пам'ятають користувачі чудову утілітку mem і знамените Not enough memory, якими так радували око користувача ранні «персоналки»?

Одним словом, навіть у ті часи існували кращі рішення, і в наступному, першому по-справжньому сучасному поколінні x86-х процесорів (80386) слідом за процесорами Motorola і мейнфреймах з'явилася основа будь-яких сучасних багатозадачних ОС - віртуальна пам'ять. Про вдалість цієї розробки говорить хоча б те, що аж до переходу до 64-бітним розділами інструкцій «ядро» будь-яких x86 в точності відповідало стандарту IA-32 (Intel Architecture for 32-bit), введеному Intel для i386 (так що, в принципі, на «трешка» повинні працювати будь-які 32-бітові програми, не задіюються занадто сучасних функцій).

Віртуальна пам'ять (схема 3) - це логічний розвиток ідеї сегментованої пам'яті, коли ми переходимо від цілком конкретним чином перетворюються у фізичні «лінійних» адрес захищеного режиму x86 до абсолютно абстрактним «віртуальним» адресами. Адже, за великим рахунком, для працюючої на комп'ютері програми зовсім байдуже, що за «фізичні» осередку пам'яті вона використовує! Їй потрібний просто деякий діапазон адрес, за якими вона зможе зберігати свої дані, а що за цими «ціфіркамі» насправді криється, їй глибоко байдуже. Головне - щоб процесор знав, як ці абстрактні цифри (віртуальні адреси) переводити в цілком конкретні інструкції для контролера пам'яті (фізичні адреси).

Схема 3. Віртуальна пам'ять

Як це робиться на практиці? Вся доступна процесору фізична оперативна пам'ять розбивається на невеликі шматочки розміром 4 Кбайт або 4 Мбайт - «сторінки». При цьому використовується та ж схема, що й при розбивці фізичних адрес на адреси конкретної комірки пам'яті: молодші 12 або 22 біт віртуального адреси позначають зміщення цієї адреси від початку сторінки, а старші біти (від 10 до 50) - номер сторінки. Коли процесору потрібно обчислити фізичну адресу з віртуального, він просто розділяє віртуальний адресу на номер сторінки і зсув, заглядає в таблицю, де для кожного номера вказані координати початку сторінки у фізичній пам'яті, і додає до отриманої координаті зміщення (схема 4). Згадана табличка сторінок називається таблицею трансляції адрес віртуальної пам'яті (або просто таблицею трансляції), і розміщується вона у вигляді B-дерева в самій звичайної оперативної пам'яті, що дозволяє створювати без великої надлишковості як завгодно великі швидкодіючі таблиці трансляції. Працює це дерево, правда (як і все, пов'язане з оперативною пам'яттю), як і раніше не дуже швидко, і тому процесор кешує раніше певні відповідності «номер сторінки - запис у таблиці трансляції» в спеціальному сервері - буфері трансляції віртуальних адрес (Translation Look -aside Buffer, TLB).

Схема 4. Робота з віртуальною пам'яттю.

Деталі таблиці трансляції

Фраза про B-дерево може прозвучати страхітливо, але насправді ховається за цим не така вже й складна технологія. Двійковий номер віртуальної пам'яті, за все тією ж доброю традицією, «розрізається» на кілька шматочків невеликого розміру (по 10 біт): наприклад, 00000000001111111111010101010101 - перетворюється на 0000000000 + 1111111111 + 010101010101. Перша частина адреси - 0 - це «номер директорії», друга - 1023 - «номер сторінки», третя - 1365 - зміщення від початку сторінки.

Що далі з цим усім робити? У процесорі є спеціальний регістр під назвою CR3 (Control Register # 3), в якому записується «вказівник на таблицю трансляції» - фізичну адресу, по якому в пам'яті розташовується «таблиця директорій». Ця сама таблиця - це 1024 записи довжиною по 32 (або 64) біта, в яких записані фізичні адреси «таблиць сторінок», відповідних тій або іншій директорії. У нас директорія номер нуль, а тому процесор, декодує віртуальний адреса, обчислює суму регістру CR3 з нулем і отримує адресу потрібної йому «таблиці сторінок». Ось у цій таблиці (теж з 1024 записів завдовжки 32 або 64 біта) вже записані фізичні адреси почав сторінок, так що, додавши до початку таблиці сторінок номер сторінки (1023) - ми виходимо на запис, в якій знаходиться фізичну адресу початку потрібної нам сторінки. Залишається тільки додати до нього 1365 - зміщення - і шуканий фізичну адресу готовий. У разі 64-бітної організації пам'яті рівнів трансляції в цій схемі не два, а чотири; у разі трансляції зі сторінками розміром 4 Мбайт - останній рівень трансляції пропускається.

Навіщо взагалі знадобилася така складна схема і чому було не можна обмежитися однією таблицею трансляції? Вся справа в розмірі таблиць. Для 32-бітної адресації пам'яті і сторінок розміром 4 Мбайт необхідний розмір таблиці складає всього лише 4 або 8 Кбайт пам'яті, але для більш затребуваних 4 Кбайт - сторінок, і, ще гірше, для 64-бітної адресації пам'яті, необхідні розміри таблиці виходять набагато більшими - від 4-8 Мбайт до 8 Гбайт і навіть 8 Тбайт. За часів 386-х процесорів навіть 4 Мбайт для таблиці трансляції адрес однієї програми здавалося занадто великою цифрою (а, враховуючи, що на комп'ютері можуть бути одночасно запущені сотні програм, і для кожної потрібно мінімум по 4 Мбайт фізичної пам'яті - це і для сучасних систем надто багато), і тому вибір був зроблений на користь дворівневої трансляції, при якій трансляцію можна зупинити ще на «верхньому» рівні, вказавши для деяких записів у «таблиці директорій», що вони не відповідають жодним реальним фізичним адресами і прибравши, таким чином, необхідність у вказівці для цілого діапазону адрес записів у таблиці трансляції.

До речі, сегментація (в 32-бітних процесорах) навіть з віртуальною пам'яттю все одно зберігається. Тобто реальні адреси, що згадуються в програмі, спочатку перетворюються з урахуванням сегментів у «лінійні», а вже вони за допомогою таблиці трансляції - в реальні «фізичні» адреси.

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

Захист важливих ділянок оперативної пам'яті від перезапису.

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

Захист програм від вірусів.

Основа новомодних «антивірусних» технологій на кшталт Microsoft Data Execution Prevention, що забезпечують надійний захист комп'ютера від експлойтів, що використовують атаки типу «переповнювання буфера», - крихітний Битик в таблиці трансляції (No eXecute - NX в AMD, і eXecute Disable, XD - у Intel), що забороняє виконання машинного коду з певних ділянок пам'яті.

Захист операційної системи.

Спеціальний біт дозволяє визначити деякі ділянки оперативної пам'яті як «системні» і принципово недоступні звичайному додатком як для читання, так і для запису.

Ефективний менеджмент оперативної пам'яті.

Цілий ряд спеціальних бітів дозволяє операційній системі відслідковувати, з яких адресами програма читала або записувала дані, і визначити «глобальні» сторінки пам'яті, загальні для всіх програм у процесорі.

Але найголовніший біт в таблиці трансляції - це «нульовий» біт P - Present, що забезпечує власне

По-справжньому віртуальну пам'ять.

Насправді, призначення цього біта досить просте - якщо він «скинутий» (встановлено в 0), то будь-яке звернення до оперативної пам'яті за цією адресою викликає системну помилку (виключення), що називається Page Fault (# PF). Але скільки ж на основі цієї «простоти» вдається побудувати! Адже, по суті справи, P-морський прапор - це зазначення процесору, що для обробки звернення програми до даного адресою пам'яті потрібно звернутися за допомогою до операційної системи.

Судіть самі: найпростіше застосування P-прапора - це реалізація своп-файлу, що дозволяє використовувати жорсткий диск замість фізичної оперативної пам'яті. Ідея в тому, що ми можемо для деяких сторінок віртуальної пам'яті не ставити їм у відповідність ніякого фізичної адреси оперативної пам'яті, а «скинути» для відповідних записів у таблиці трансляції P-прапор і зберегти сторінку у файл на жорсткому диску. Якщо звернень до даної сторінки не відбувається - то все добре. Якщо відбувається - то генерується виключення # PF. По суті своїй процесор просто припиняє виконання поточної програми, і заглядає у свій «довідник щодо дій у нештатних ситуаціях» - спеціальна ділянка пам'яті, в якому прописано, яку підпрограму операційної системи викликати в тому чи іншому випадку. У повній відповідності зі стандартом, процесор викликає обробник виключення # PF - один з ключових фрагментів будь-якої операційної системи. Обробник (фактично - операційна система) - «дивиться» на виниклу ситуацію («програма така-то полізла в пам'ять за адресою такому-то і була зупинена тому що прапор Present був скинутий»), визначає, що цією адресою відповідає сторінка пам'яті, якої немає в оперативній пам'яті, але яка є на жорсткому диску - і починає діяти: