Адресний простір
Навіщо це
Section titled “Навіщо це”Запустіть ту саму програму двічі й подивіться в обох процесах адресу однієї глобальної змінної. Вона виявиться однаковою, хоча в пам’яті це очевидно різні комірки.
Питання складніші. Як компілятор узагалі може зашити в код конкретну
адресу, якщо невідомо, куди програму завантажать? І чому процес, який
звернувся за чужою адресою, отримує SIGSEGV замість того, щоб прочитати
чужі дані?
На всі три відповідь одна: адреса, якою оперує програма, не є адресою в мікросхемі пам’яті.
Передумови. MMU і режими виконання (модуль 2), секції адресного простору (модуль 6).
Дві адреси
Section titled “Дві адреси”Логічну адресу бачить і використовує процес, і формує її процесор під час виконання інструкції. Фізичну отримує контролер пам’яті.
Між ними стоїть MMU, блок керування пам’яттю. Це апаратура, яка перетворює одну адресу на іншу при кожному зверненні до пам’яті. Не ядро й не компілятор, а саме апаратура, мільярди разів на секунду.
З цього випливає все інше. Два процеси можуть мати ту саму логічну адресу,
відображену на різні фізичні, і тому не бачать одне одного. Логічна адреса,
для якої відображення не задано, дає апаратний виняток, звідки й береться
SIGSEGV. А ядро може перемістити процес у пам’яті, просто змінивши
відображення, і процес цього навіть не помітить.
Найпростіший MMU: база і межа
Section titled “Найпростіший MMU: база і межа”Історично першим апаратним рішенням були два регістри. Базовий зберігав фізичну адресу початку області процесу, межовий — її розмір.
На кожному зверненні апаратура робила дві дії:
якщо логічна_адреса >= межа → апаратний винятокфізична_адреса = база + логічна_адресаОднієї перевірки й одного додавання вистачає, щоб отримати одразу дві речі. Захист, бо процес не дотягнеться за свою межу. І переміщуваність, бо достатньо змінити базу, і процес поїде, нічого не помітивши. Самі регістри доступні лише в режимі ядра, тож підняти собі межу процес не може.
Обмеження в цієї схеми одне, зате серйозне: уся пам’ять процесу мусить лежати суцільним шматком. Звідси й починаються проблеми.
Розподіл суцільними ділянками
Section titled “Розподіл суцільними ділянками”Якщо кожен процес потребує безперервної області, ядру доводиться її десь знаходити. Історично пробували два підходи.
Розділи фіксованого розміру ділять пам’ять наперед на N ділянок. Просто, але процес на 3 МБ у розділі на 8 МБ марнує п’ять мегабайтів. Такі втрати всередині виданої ділянки називають внутрішньою фрагментацією.
Розділи змінного розміру виділяють ділянку рівно за розміром запиту. Внутрішня фрагментація зникає, зате з’являється інша.
Зовнішня фрагментація
Section titled “Зовнішня фрагментація”Процеси приходять і йдуть, лишаючи по собі дірки різного розміру. Через якийсь час вільної пам’яті стає багато, тільки вся вона розсипана на шматки, менші за будь-який осмислений запит.
Куди тоді класти новий процес, якщо дірок кілька? Класичних стратегій три:
| Стратегія | Правило | На практиці |
|---|---|---|
| Перша відповідна | перша дірка, куди влізе | найшвидша, результат непоганий |
| Найкраща відповідна | найменша з достатніх | лишає багато крихітних непридатних дірок |
| Найгірша відповідна | найбільша | найгірша з трьох за всіма мірками |
Назва «найкраща відповідна» звучить переконливо, а виграшу не дає: відрізаючи від найменшої достатньої дірки, ця стратегія щоразу лишає залишок, непридатний ні для чого. За щільністю заповнення пам’яті вони з першою відповідною йдуть приблизно врівень, зате перша помітно швидша — їй не треба щоразу переглядати весь список дірок.
Є ще ущільнення — зсунути всі процеси впритул і зібрати вільне місце в одну область. Працює, але коштує копіювання всієї зайнятої пам’яті й можливе лише тоді, коли адреси прив’язуються під час виконання. Для системи з гігабайтами пам’яті це неприйнятно дорого.
Висновок, з якого починається наступний модуль
Section titled “Висновок, з якого починається наступний модуль”Зовнішня фрагментація виникає не через поганий алгоритм. Вона неминуча, поки діє вимога безперервності.
Отже, прибирати треба саму вимогу: дозволити пам’яті процесу лежати розкиданою по фізичній пам’яті, а адресний простір лишити суцільним тільки на вигляд. Механізм, який це робить, називається пейджингом.
Як це насправді в Linux
Section titled “Як це насправді в Linux”cat /proc/self/mapsВідображення логічних адрес поточного процесу. Ліворуч — діапазони логічних адрес, далі права доступу, а праворуч — що саме за ними стоїть. Фізичних адрес тут немає взагалі: процесу вони недоступні.
sh -c 'cat /proc/self/maps | head -3'; sh -c 'cat /proc/self/maps | head -3'Два запуски однієї програми дають різні базові адреси — це ASLR (модуль 16). Без нього адреси збігалися б.
cat /proc/meminfo | grep -E '^(MemTotal|MemFree|MemAvailable|Mapped)'MemTotal — фізична пам’ять. Сума логічних адресних просторів усіх
процесів може бути в рази більшою: адресний простір не є пам’яттю.
ps -eo pid,vsz,rss,comm --sort=-vsz | head -5VSZ — розмір логічного адресного простору, RSS — скільки з нього
фактично лежить у фізичній пам’яті. VSZ у десятки разів більший
за RSS — норма, а не витік.
Типові помилки розуміння
Section titled “Типові помилки розуміння”«Адреса у вказівнику — це місце в мікросхемі пам’яті». Це логічна адреса. Що їй відповідає фізично, знає тільки MMU, і відповідь цілком може змінитися між двома зверненнями.
«VSZ показує, скільки пам’яті займає процес». Показує він розмір
адресного простору, разом із тим, що ніколи не буде використане.
Споживання пам’яті — це RSS, та й той завищений через спільні бібліотеки.
«Внутрішня і зовнішня фрагментація — те саме». Внутрішня є невикористаним залишком усередині виданої ділянки. Зовнішня — вільною пам’яттю між ділянками, надто дрібною, щоб її використати.
«Найкраща відповідна — найкраща стратегія». Назва оманлива: за заповненням памʼяті вона не краща за першу відповідну, а часу забирає більше, бо щоразу переглядає весь список дірок.
«Фрагментацію можна перемогти кращим алгоритмом». Не можна, поки пам’ять процесу мусить бути суцільною. Перемогли її тим, що скасували саму вимогу суцільності.
Перевір себе
Лабораторна
Section titled “Лабораторна”A4 — власний розподільник пам’яті. Реалізація malloc поверх mmap
зі стратегіями «перша відповідна» і «найкраща відповідна», з вимірюванням
фрагментації на реальному профілі запитів. Різниця між теорією
і виміряним результатом тут зазвичай несподівана.
Джерела
Section titled “Джерела”- OSTEP: Address Spaces, Address Translation, Free Space Management
- Silberschatz, Operating System Concepts, розділ 9
man 5 proc— розділ про/proc/[pid]/mapsіsmaps