Як ядро ​​керує пам’яттю

Раніше ми побачили, як організована віртуальна пам'ять процесу. Тепер розглянемо механізми, завдяки яким ядро ​​керує пам'яттю. Звернемося до нашої програми:

сторінки

У Windows, блок EPROCESS - це грубо кажучи щось середнє між структурами task_struct і mm_struct. Аналогом дескриптора області віртуальної пам'яті є Virtual Address Descriptor або VAD, інформація про ці дескриптори зберігається в AVL-дереві. Знаєте що найсмішніше при порівнянні Windows та Linux? Це те, що відмінностей якраз не так багато.

Захист пам'яті здійснюється на посторінковій основі, оскільки сторінка - це найменший "шматочок" пам'яті, для якого можна виставити прапори "U/S" та "R/W". Варто однак враховувати, що теоретично дві різні віртуальні сторінки, що мають набір прапорів, що відрізняються, можуть відповідати одній і тій же фізичній сторінці. Зауважте, у форматі page table запису не передбачені прапори, пов'язані із забороною виконання коду. Саме тому класичний x86-пейджинг ніяк не перешкоджає виконанню коду в стеку, що полегшує експлуатацію вразливостей, в основі яких переповнення буфера в стеку (невиконані стеки все одно схильні до уразливостей, в даному випадку використовується техніка return-to-libc та інші прийоми). Відсутність no-execute прапора також свідчить про інший важливий аспект: прапори доступу, що містяться в дескрипторі VMA, не завжди мають прямі відповідності в системі захисту, що реалізується процесором, і можуть лише відповідати цій системі більшою чи меншою мірою. Образно кажучи, ядро ​​робить все, що в його силах, але зрештою архітектура процесора накладає свої обмеження на те, що можна продати.

ядро

Ядрі Linux веде облік кожного page-фрейму за допомогою спеціальногодескриптора та кількох прапорів. Взяті разом ці дескриптори описують всю оперативну пам'ять комп'ютера; у кожний момент часу відомий точний стан будь-якого page-фрейму. В основі управління фізичною пам'яттю лежить алгоритм Buddy memory allocation. Таким чином, page-фрейм вважається вільним, якщо він доступний для виділення з погляду Buddy-алгоритму. Виділений під використання page-фрейм може бути "анонімним" (у такому випадку він містить дані програми) або може перебувати в т.зв. сторінковий кеш (page cache) і зберігати порцію даних з деякого файлу або блокового пристрою. Існують і інші, більш екзотичні варіанти використання page-фреймів, але не давайте їх зараз чіпати. У Windows є аналогічна структура для обліку page-фреймів і називається база даних Page Frame Number. А тепер, давайте зібрамо воєдино всі ці концепції - області віртуальної пам'яті (VMA), page table-записи та page-фрейми - і подивимося як це працює. Далі йде приклад купи в user space програми:

керує

Прямокутники з блакитним фоном позначають віртуальні сторінки, що знаходяться в межах віртуальної пам'яті. Стрілки позначають page table-записи, за допомогою яких віртуальні сторінки "меппуються" в page-фреми (фізичні сторінки). Деякі віртуальні сторінки не мають стрілок; це означає, що у відповідних їм page table записах прапор присутності встановлений у 0. Причиною тому може бути те, що дані віртуальні страйки можливо ще не використовувалися разу або тому, що відповідні їм фізичні сторінки були вивантажені в своп. У будь-якому випадку, спроба доступу до цих сторінок призведе до page fault, навіть незважаючи на те, що віртуальні сторінки знаходяться в межах деякої VMA. Може здатися дивним, щоіснує подібного роду різночитання – сторінки в межах VMA, проте доступ до них є невалідним – але так дійсно часто відбувається.

VMA є свого роду “контракт” між програмою та ядром. Ви просите ядро ​​виконати якусь дію (наприклад, виділити пам'ять або замеппувати файл), ядро ​​каже “без проблем” і створює нову чи оновлює існуючу VMA. Але ядро ​​при цьому не поспішає виконувати саме прохання, натомість ядро ​​відкладе безпосереднє виконання запитаної дії до того моменту, поки не станеться page fault. Так, виходить, що ядро ​​- це такий собі "ледачий негідник"; це є основним принципом управління віртуальною пам'яттю. Даний принцип застосовується в більшості ситуацій – деякі з них можуть бути цілком знайомими, деякі – несподіваними, але загальне правило такого, що VMA лише фіксує, про що було домовлено, тоді як page table-записи відображають те, що безпосередньо було зроблено лінивим ядром. Ці дві структури разом беруть участь у управлінні пам'яттю програми; обидві структури відіграють певну роль при обробці page fault, вивільненні пам'яті, вивантаженні сторінок у своп і т.д. Розглянемо простий випадок виділення пам'яті:

ядро

Припустимо, VMA таки знайшлася. Подальша обробка page fault така – ядро ​​дивиться на вміст page table запису та тип VMA. У прикладі, page table запис свідчить у тому, що сторінки у пам'яті немає. Більше того, наш запис абсолютно порожній (складається з одних нулів), і в Linux це означає, що відповідна віртуальна сторінка взагалі ще жодного разу не була замеппірована. Оскільки ми маємо справу з "анонімною" VMA, то всі подальші дії будуть пов'язані тільки з оперативною пам'яттю і для обробки цієї ситуації викликаєтьсяфункція do_anonymous_page(). Ця функція здійснює виділення page-фрейму і меппирует до нього віртуальну сторінку шляхом внесення потрібних даних у page table запис.

Справа могла й інакше. Page table запис для вивантаженої у своп сторінки, наприклад, має прапор присутності встановлений нуль, але решта запису не порожня. Інші біти зберігають інформацію про знаходження сторінки у свопі. Функція do_swap_page() зчитує вміст цієї сторінки з диска та завантажує сторінку в оперативну пам'ять – це тип page fault називають major fault.

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