Лекції з WinApi

Тема №3 Архітектура пам'яті в Win32® API

3. Архітектура пам'яті в Win32® API
 3.1. Адресний простір процесу

У Win32® API використовується плоска 32-розрядна модель пам'яті. Кожному процесу виділяється “особисте” (private) ізольований адресний простір, розмір якого складає 4Gb. Цей простір розбивається на регіони, небагато відмінні для Windows’95 і Windows NT. У загальному для тієї й іншої системи можна сказати, що нижні 2Gb ці простори відведені процесу для вільного використання, а верхні 2Gb зарезервовані для використання операційною системою.

  1. Регіони в адресному просторі процесу Windows’95

  1. Регіони в адресному просторі процесу Windows® NT™

Код системи Windows NT краще захищений від процесу користувача, чим код Windows’95. Це обумовлює велику стійкість ОС до помилок у прикладній програмі.

  1. Приклад

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

/* Win95Killer.cpp */

#include <windows.h>

#include <iostream.h>

void main()

{ // Системна область. Старший Gb адресного простору

DWORD SystemAreaAddress = 0xC0000000 ;

while(1) {

try { // Спроба запису

ZeroMemory( (LPVOID) SystemAreaAddress, 0x1000 ) ;

}

catch(...) { // помилка запису

cout << "Exception: " << hex << SystemAreaAddress << endl;

}

SystemAreaAddress += 0x1000 ; // Перехід до наступного сторінці (4K)

}

}

Оскільки в Windows’95 відсутен захист сторінок системної області, то програма знищує цю область, що приводить до краху системи. Windows NT захищає системну область і, тому, “витримує натиск” програми.

3. Архітектура пам'яті в Win32® API.
 3.2. Керування віртуальною пам'яттю. VMM.

VMM(Virtual Memory Manager) - частина операційної системи, що займається керуванням віртуальною пам'яттю.

У Win32 використовується сторінкова організація пам'яті. Розмір сторінок для платформ Intel і MIPS складає 4K. Розмір сторінок для DEC Alpha складає 8K.

Схема сторінкового перетворення в процесорах Intel докладно вивчалася в курсі “Програмування мовою ассемблера. Частина 3”. Win32 використовує двоступінчасту схему сторінкового перетворення, підтримувану процесорами 386, 486, Pentium. Додаткові схеми, що підтримує процесор Pentium Pro, не використовуються.

Сторінкова організація пам"яті

Кожному процесу призначається свій каталог сторінок. Саме тому адресний простір кожного процесу ізольовано.

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

  • Сторінка існує і знаходиться в пам'яті
  • Сторінка існує і вивантажена на диск
  • Сторінка не існує

При цьому VMM використовує наступний алгоритм організації доступу до даних:

Алгоритм роботи менеджера віртуальної пам"яті

Виділення пам'яті процесу означає виділення її у файлі підкачування.

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

3. Архітектура пам'яті в Win32® API.
 3.3. Архітектура інтерфейсів (API) керування пам'яттю.

Диспетчер керування пам'яттю (VMM) є складовою частиною ядра операційної системи. Додатка не можуть одержати до нього прямий доступ. Для керування пам'яттю прикладним програмам надаються різні інтерфейси (API).

Керування пам"ятю

Virtual Memory API - набір функцій, що дозволяють додатку працювати з віртуальним адресним простором: призначати фізичні сторінки блоку адрес і звільняти їх, встановлювати атрибути захисту. Докладно розглядається в розділі 4.4.

Memory Mapped File API - набір функцій, що дозволяє працювати з файлами, відображуваними в пам'ять. Новий механізм, наданий Win32® API для роботи з файлами і взаємодії процесів. Докладно розглядається в розділі 4.5.

Heap Memory API - набір функцій, що дозволяють працювати з областями пам'яті, що розподіляються динамічно, (купами). Докладно розглядається в розділі 4.6.

Local, Global Memory API - набір функцій роботи з пам'яттю, сумісних з 16-бітної Windows. Варто уникати їхнього використання.

CRT Memory API - функції стандартної бібліотеки мови “З” періоду виконання (runtime).

Два останніх набори функцій у дійсному курсі не розглядаються.

3. Архітектура пам'яті в Win32® API.
 3.4. Робота додатків з віртуальною пам'яттю.

Блок адрес в адресному просторі процесу може знаходитися в одному з трьох станів

  1. Виділений (committed) - блоку адрес призначені фізична пам'ять або частина файлу підкачування.
  2. Зарезервований (reserved) - блок адрес позначений як зайнятий, але фізична пам'ять не розподілена.
  3. Вільний (free) - блок адрес не виділений і не зарезервований.

Резервування і виділення пам'яті виробляється блоками. Початкова адреса блоку повинна бути вирівняна на границю 64K (округляється вниз), а розмір кратний розміру сторінки (округляється нагору). При виділенні пам'ять обнуляється.

Для резервування регіону пам'яті в адресному просторі чи процесу виділення її використовується функція VirtualAlloc, а для звільнення - функція VirtualFree.

LPVOID VirtualAlloc(

LPVOID lpAddress, // address of region to reserve or commit

DWORD dwSize, // size of region

DWORD flAllocationType, // type of allocation

DWORD flProtect // type of access protection

);

Функція повертає адресу виділеного чи регіону NULL у випадку невдачі

Параметри:

lpAddress - адреса, по якому треба чи зарезервувати виділити пам'ять. Якщо цей параметр дорівнює NULL, то система самостійно вибирає місце в адресному просторі процесу;

dwSize - розмір виділюваного регіону;

flAllocationType - тип розподілу пам'яті;

MEM_RESERVE

Резервує блок адрес без виділення пам'яті

MEM_COMMIT

Відображає раніше зарезервований блок адрес на фізичну чипам'ять файл підкачування. (Виділяє пам'ять). Може бути комбінований із прапором MEM_RESERVE для одночасного резервування і виділення.

MEM_TOP_DOWN

Виділяє пам'ять по найбільшій можливій адресі. Має сенс тільки при lpAddress = NULL. У Windows’95 ігнорується.

flProtect - тип захисту доступу виділюваного регіону;

PAGE_READONLY

допускається тільки читання

PAGE_READWRITE

допускається читання і запис

PAGE_EXECUTE

допускається тільки виконання

PAGE_EXECUTE_READ

допускається виконання і читання

PAGE_EXECUTE_READWRITE

допускається виконання читання і запис

PAGE_GUARD

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

PAGE_NOCACHE

Забороняє кэширування сторінок. Може бути корисний при розробці драйверів пристроїв (наприклад, дані у відеобуфер повинні листуватися відразу, без кэширування)

BOOL VirtualFree(

LPVOID lpAddress, // address of region of committed pages

DWORD dwSize, // size of region

DWORD dwFreeType // type of free operation

);

Повертає TRUE у випадку успіху і FALSE - у випадку невдачі.

Параметри:

lpAddress - адреса регіону, якім треба звільнити.

dwSize - розмір регіону, що звільняється;

dwFreeType - тип звільнення.

MEM_DECOMMIT

Звільнити виділену пам'ять

MEM_RELEASE

Звільнити зарезервований регіон. При використанні цього прапора параметр dwSize повинний бути дорівнює нулю.

Виділені сторінки можна заблокувати в пам'яті, тобто заборонити їхнє витиснення у файл підкачування. Для цих цілей служить пари функцій VirtualLock і VirtualUnlock. Процесу не дозволяється блокувати більш 30 сторінок.

Для зміни атрибутів захисту регіонів використовуються функції VirtualProtect і VirtualProtectEx. Причому, перша дозволяє змінювати атрибути захисту в адресному просторі поточного процесу, а друга -довільного.

Функції VirtualQuery і VirtualQueryEx дозволяють визначити статус зазначеного регіону адрес.

3. Архітектура пам'яті в Win32® API.
 3.5. Файли, проецируємі в пам'ять.

“Як і віртуальна пам'ять, проецируємі файли дозволяють резервувати регіон адресного простору і передавати йому фізичну пам'ять. Розходження між цими механізмами полягає в тому, що в останньому випадку фізична пам'ять не виділяється із системного сторінкового файлу, а береться з файлу, що вже знаходиться на диску. Як тільки файл спроецірован у пам'ять, до нього можна звертатися так, начебто він у неї цілком завантажений.” (Джеффри Ріхтер. Windows для професіоналів.)

Цей механізм має три застосування в Win32

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

1) Запуск файлів, що виконуються, і бібліотек, що динамічно зв'язуються.

При виконанні функції CreateProcess система звертається до VMM для виконання наступних дій

  1. Створити адресний простір процесу (розміром 4Gb)
  2. Резервувати в адресному просторі процесу регіон розміром, достатнім для розміщення файлу, що виконується. Початкова адреса регіону визначається в заголовку EXE-модуля. Звичайно він дорівнює 0x00400000, але може бути змінений при побудові файлу параметром /BASE компоновщика.
  3. Відобразити файл, що виконується, на зарезервований адресний простір. Тим самим VMM розподіляє фізичні сторінки не з файлу підкачування, а безпосередньо з EXE-модуля.
  4. У такий же спосіб відобразити на адресний простір процесу необхідні йому бібліотеки, що динамічно зв'язуються. Інформація про необхідні бібліотеки знаходиться в заголовку EXE-модуля. Бажане розташування регіону адрес описано усередині бібліотеки. Visual C++, наприклад, установлює за замовчуванням адреса 0x10000000. Ця адреса може так само змінюватися параметром /BASE компоновщика. Якщо при завантаженні з'ясовується, що даний регіон зайнятий, то система спробує перемістити бібліотеку в інший регіон адрес, на основі налагоджувальної інформації, що міститься в DLL-модулі. Однак ця операція знижує ефективність системи і, крім того, якщо налагуджувальна інформація вилучена при компонуванні бібліотеки параметром /FIXED, те завантаження стає взагалі неможливою.

При одночасному запуску декількох додатків Win32® відображає той самий файл, що виконується, і бібліотеки на адресні простори різних процесів. При цьому виникає проблема незалежного використання процесами статичних перемінних і областей даних. Крім того, зміна даних програмою, що виповнюється, не повинне приводити до зміни EXE-файлу. Win32® відкладає рішення цієї проблеми на максимально можливий термін (Lazy Evaluation). При цьому використовується класичний механізм відкладеного копіювання (copy-on-write - копіювання при спробі запису). Усі сторінки адресного простору процесу одержують атрибут захистуPAGE_WRITECOPY. При спробі запису в таку сторінку виникає виключення порушення захисту і VMM копіює сторінку для процесу, що звернувся. Надалі ця сторінка буде вивантажуватися у файл підкачування. Після копіювання відбувається рестарт команди, що викликала виключення.

2) Файли даних, проецируємі в пам'ять.

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

  1. Створюється об'єкт ядра “файл”. У колишній термінології це операція відкриття файлу. Для створення об'єкта “файл” використовується функція CreateFile, аналогічна функції open() з CRT-бібліотеки.
  2. За допомогою функції CreateFileMapping створюється об'єкт ядра “проецируємий файл”. При цьому використовується описувач файлу (handle), повернутий функцією CreateFile. Тепер файл готовий до проектування.
  3. Виробляється відображення об'єкта “проецируємий файл” чи його частини на адресний простір процесу. Для цього застосовується функція MapViewOfFile.

Для відкріплення файлу від адресного простору процесу використовується функція UnmapViewOfFile, а для знищення об'єктів “файл” і “проецируємый файл” - функція CloseHandle.

Загальна схема роботи з проецируванними файлами така:

HANDLE hFile, hFileMapping;

PVOID pMassive;

hFile = CreateFile( “File Name”, ... );

hFileMapping = CreateFileMapping( hFile, ... );

CloseHandle( hFile ) ;

pMassive = MapViewOfFile( hFileMapping, ... );

/* Робота з масивом pMassive */

UnmapViewOfFile( pMassive );

3) Взаємодія процесів через загальну область даних. Когерентність.

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

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

Когерентність

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

Робота з пам"ятю