07. Механизмы управления виртуальной и динамически распределяемой памятью
Тема 7. Механизмы управления виртуальной и динамически распределяемой памятью
Введение и цели лекции
📌 Слайд 1: Механизмы управления виртуальной и динамически распределяемой памятью
- Курс: Системное программирование
- Тема 7 (4 академических часа)
- Лектор: каф. Системного программирования
Управление памятью — один из фундаментальных компонентов современных операционных систем общего назначения. Концепция виртуальной памяти решает три критические задачи вычислительных систем:
- Изоляция и безопасность: каждый процесс функционирует в собственном изолированном адресном пространстве, не имея физической возможности случайно или преднамеренно прочитать или модифицировать данные других процессов или привилегированного ядра ОС.
- Абстракция физической адресации: прикладной программист оперирует непрерывным плоским адресным пространством (до 128 ТБ / 256 ТБ в 64-разрядных системах), абстрагируясь от фрагментации, объема и физического расположения микросхем ОЗУ.
- Эффективное разделение данных и кода: механизм виртуальных страниц позволяет разделять между процессами исполняемый код разделяемых библиотек (DLL / shared objects) и организовывать высокоскоростное межпроцессное взаимодействие с нулевым копированием (zero-copy IPC) через проецирование файлов и разделяемые сегменты.
📌 Слайд 2: Учебные вопросы лекции
- Архитектура виртуальной памяти: страничная трансляция x86-64, MMU, TLB, регистр CR3.
- Жизненный цикл виртуальных страниц: состояния Free, Reserved, Committed.
- Аппаратный сбой страницы (Page Fault): типы сбоев и взаимодействие VMM с диском.
- Системный API виртуальной памяти:
VirtualAlloc,VirtualProtect,VirtualFree.- Иерархия распределения памяти: CRT-рантайм, системные кучи (
HeapAlloc), LFH.- Проецирование файлов на память (Memory-Mapped Files, MMF) и разделяемая память.
- Разделяемые сегменты данных в DLL (
#pragma data_seg).- Архитектура DLL: формат PE,
DllMain, таблицы EAT и IAT, механизмы связывания.
1. Архитектура виртуальной памяти и аппаратная трансляция адресов
📌 Слайд 3: Архитектура виртуальной памяти и адресация
- Виртуальное адресное пространство процесса (Virtual Address Space, VAS).
- Линейная адресация: пользовательский диапазон (User Space) и системный (System/Kernel Space).
- Страничная организация: страницы виртуальной памяти и физические страницы (Page Frames / PFN).
- Четырехуровневая трансляция в x86-64: PML4 $\to$ PDPT $\to$ PD $\to$ PT $\to$ Offset.
- Регистр процессора CR3 и буфер ассоциативной трансляции (TLB).
В современных защищенных операционных системах процессор всегда выполняет команды в режиме виртуальной адресации. Центральный процессор никогда не передает сгенерированный программой указатель напрямую на шину системной памяти; каждый адрес предварительно транслируется аппаратным блоком управления памятью (MMU — Memory Management Unit).
Канонический формат виртуального адреса x86-64
В классической 64-битной архитектуре x86-64 используется 48-битная каноническая виртуальная адресация (4-level paging), предоставляющая адресное пространство размером $2^{48} = 256$ ТБ:
- Пользовательское адресное пространство (User Space): нижняя половина диапазона (
0x0000'0000'0000'0000–0x0000'7FFF'FFFF'FFFF, 128 ТБ). Доступно коду процесса в Ring 3. - Адресное пространство ядра (Kernel/System Space): верхняя половина диапазона (
0xFFFF'8000'0000'0000–0xFFFF'FFFF'FFFF'FFFF, 128 ТБ). Доступно только в привилегированном режиме Ring 0. - Неканоническая дыра (Non-canonical hole): диапазон между 128 ТБ и $2^{64}-128$ ТБ, попытка обращения к которому вызывает исключение общей защиты (#GP — General Protection Fault).
Трансляция виртуального адреса в физический выполняется аппаратно через древовидную иерархию 4 уровней таблиц страниц:
- PML4 (Page Map Level 4): биты адреса [47:39] (9 бит, 512 записей по 8 байт). Базовый физический адрес таблицы PML4 текущего потока хранится в системном регистре процессора CR3. При переключении контекста потока другого процесса операционная система загружает в CR3 адрес PML4 нового процесса, мгновенно меняя все отображение памяти.
- PDPT (Page Directory Pointer Table): биты [38:30] (9 бит, указывает на область размером 1 ГБ).
- PD (Page Directory): биты [29:21] (9 бит, указывает на область размером 2 МБ).
- PT (Page Table): биты [20:12] (9 бит, указывает на 4-килобайтную страницу).
- Offset (Смещение в странице): биты [11:0] (12 бит, $2^{12} = 4096$ байт = 4 КБ).
Финальный физический адрес формируется конкатенацией базового физического номера страницы (PFN — Page Frame Number), извлеченного из записи таблицы страниц (PTE), и смещения: $$\text{Physical Address} = (\text{PFN} \ll 12) \mid \text{Offset}$$
📌 Слайд 4: Архитектура виртуальной памяти и трансляция адресов MMU
- Битовая декомпозиция адреса x86-64.
- Аппаратная трансляция MMU через регистр CR3 и кэш TLB.
- Жизненный цикл состояний страниц: Free, Reserved, Committed.

Буфер ассоциативной трансляции (TLB)
Поскольку 4-уровневая трансляция требует 4 последовательных обращений к памяти для чтения каждого указателя, процессор оснащен сверхбыстрым кэшем трансляций — TLB (Translation Lookaside Buffer):
- TLB Hit: адрес найден в кэше $\to$ трансляция занимает 1 такт процессора.
- TLB Miss: аппаратный блок Page Walker обходит иерархию таблиц в ОЗУ $\to$ задержка составляет от десятков до сотен тактов.
- Сброс TLB: при смене значения регистра CR3 (переключение процесса) процессор автоматически инвалидирует записи TLB (за исключением записей с установленным флагом Global Page, используемых для разделяемого ядра ОС).
2. Состояния страниц виртуальной памяти и обработка Page Fault
📌 Слайд 5: Состояния страниц памяти: Free, Reserved, Committed
- Free (Свободная): не выделена в виртуальном адресном пространстве, обращение вызывает Access Violation.
- Reserved (Зарезервированная): непрерывный блок адресов забронирован в VMM, но физическая память не назначена.
- Committed (Выделенная): за страницей закреплена физическая память в RAM или дисковом файле подкачки (pagefile).
- Механизм двухфазного выделения памяти для разреженных структур данных (Sparse Arrays).
Диспетчер виртуальной памяти (Virtual Memory Manager, VMM) Windows оперирует страницами, находящимися в одном из трех взаимоисключающих состояний:
- Free (Свободная): страница не привязана ни к какому объекту. Любая попытка чтения или записи по адресу из свободной страницы приводит к аппаратному исключению нарушения прав доступа (Access Violation, код исключения
0xC0000005в Windows / сигналSIGSEGVв Linux). - Reserved (Зарезервированная): диапазон виртуальных адресов резервируется в адресном пространстве процесса. Никакая физическая оперативная память и место в файле подкачки при этом не расходуются; VMM лишь гарантирует, что данный непрерывный диапазон адресов не будет занят другими вызовами
VirtualAllocили загрузчиком DLL. Чтение и запись по зарезервированным страницам запрещены. - Committed (Выделенная / Зафиксированная): за страницей закрепляется физическое хранилище. Операционная система гарантирует, что при обращении к этой странице данные будут находиться либо в оперативной памяти (RAM), либо в файле подкачки (
pagefile.sys).
Паттерн работы с разреженными массивами
Двухфазное выделение позволяет эффективно размещать гигантские разреженные структуры данных (например, хэш-таблицы или матрицы на 10 ГБ):
1// 1. Фаза резервирования: бронируем 1 ГБ адресов (0 байт физической RAM потрачено)
2void* pHugeArray = VirtualAlloc(NULL, 1024 * 1024 * 1024, MEM_RESERVE, PAGE_READWRITE);
3
4// 2. Фаза фиксации: по мере поступления данных выделяем только нужные страницы (например, 64 КБ)
5void* pChunk = VirtualAlloc(pHugeArray, 64 * 1024, MEM_COMMIT, PAGE_READWRITE);
6
7// 3. Работа с выделенным участком
8((int*)pChunk)[0] = 42;
📌 Слайд 6: Аппаратный сбой страницы (Page Fault)
- Аппаратное прерывание
#PF(Interrupt 14) процессора.- Проверка бита присутствия (
Present = 0) в записи PTE.- Hard Fault: страница отсутствует в RAM, требует блокирующего чтения с диска (высокая латентность $\sim 5$–$10$ мс).
- Soft Fault: страница находится в системном кэше (Standby / Modified list), восстановление PTE за доли микросекунды.
- Структура VAD (Virtual Address Descriptor) процесса в ядре ОС.
Когда поток выполняет инструкцию, обращающуюся к виртуальному адресу, MMU проверяет бит Present в соответствующей записи таблицы страниц PTE:
- Если
Present == 1: физическая страница находится в RAM, трансляция завершается успешно. - Если
Present == 0: процессор прерывает выполнение инструкции и генерирует исключение Page Fault (#PF).
Диспетчер памяти ядра перехватывает #PF и выполняет проверку по дереву дескрипторов виртуальных адресов процесса (VAD — Virtual Address Descriptor):
- Недопустимое обращение: адрес попадает в свободную страницу или нарушает права (например, запись в страницу с
PAGE_READONLY). Ядро транслирует прерывание в структурированное исключениеSTATUS_ACCESS_VIOLATION(или отправляет сигналSIGSEGV). - Soft Page Fault: страница была вытеснена из рабочего набора процесса (Working Set), но ее содержимое еще не затерто и хранится в оперативной памяти в списке ожидания (
Standby List) или списке измененных страниц (Modified List). Ядро просто восстанавливает битPresent = 1в PTE без обращения к диску. - Hard Page Fault: страница находится на дисковом накопителе (в файле подкачки или проецируемом файле программы). Поток переводится в состояние ожидания I/O, ядро считывает страницу с диска в свободный фрейм физической памяти, обновляет PTE и возобновляет инструкцию потока.
3. Системный API низкоуровневого управления виртуальной памятью
📌 Слайд 7: Системный API виртуальной памяти
- Функция
VirtualAlloc: параметрыflAllocationType(MEM_RESERVE,MEM_COMMIT,MEM_RESET).- Гранулярность распределения: выделение кратно размеру страницы (4 КБ), резервирование кратно гранулярности распределения системы (64 КБ).
- Функция
VirtualProtect: динамическое изменение прав доступа страниц (PAGE_READWRITE,PAGE_EXECUTE_READ).- Функция
VirtualFree: освобождение памяти (MEM_DECOMMITпротивMEM_RELEASE).- Функция
VirtualQuery: опрос атрибутов и состояний региона памяти (MEMORY_BASIC_INFORMATION).
В Windows NT прямой доступ к диспетчеру виртуальной памяти предоставляется семейством функций Virtual*:
1LPVOID VirtualAlloc(
2 LPVOID lpAddress, // Желаемый адрес (или NULL для выбора системой)
3 SIZE_T dwSize, // Размер региона в байтах
4 DWORD flAllocationType, // Тип: MEM_RESERVE, MEM_COMMIT, MEM_RESET
5 DWORD flProtect // Атрибуты защиты: PAGE_READWRITE, PAGE_EXECUTE_READ
6);
Гранулярность распределения (Allocation Granularity)
Функция VirtualAlloc подчиняется двум жестким системным ограничениям архитектуры:
- Размер страницы (Page Size): минимальный квант фиксации физической памяти (
MEM_COMMIT) составляет ровно 4 КБ (4096 байт). Если запросить 10 байт, система выделит целую страницу в 4096 байт. - Гранулярность распределения (Allocation Granularity): базовый адрес зарезервированного региона (
MEM_RESERVE) всегда выравнивается на границу 64 КБ (0x10000). Это историческое проектное решение процессоров с аппаратными кэшами позволяет минимизировать фрагментацию таблиц страниц.
Освобождение памяти: VirtualFree
1BOOL VirtualFree(
2 LPVOID lpAddress,
3 SIZE_T dwSize,
4 DWORD dwFreeType // MEM_DECOMMIT или MEM_RELEASE
5);
Различие типов освобождения критично:
MEM_DECOMMIT: переводит страницы из состоянияCommittedобратно вReserved. Физическая память освобождается, но виртуальные адреса остаются закрепленными за процессом. ПараметрdwSizeможет указывать произвольное число страниц.MEM_RELEASE: полностью освобождает зарезервированный регион, переводя страницы в состояниеFree. При этомlpAddressобязан указывать на базовый адрес региона, возвращенный при первичном резервировании, аdwSizeобязан быть строго равен 0.
4. Иерархия уровней распределения памяти: кучи процесса и CRT
📌 Слайд 8: Иерархия уровней управления памятью
- Проблема использования
VirtualAllocдля мелких объектов: внутреннее переполнение (Internal Fragmentation) и накладные расходы на системные вызовы.- Четырехуровневая модель:
- Языковой уровень / CRT:
malloc,free,new,delete.- Системные кучи Win32:
HeapCreate,HeapAlloc,HeapFree.- Виртуальная память:
VirtualAlloc,VirtualProtect,VirtualFree.- Ядро ОС и физическая RAM.
- Понятие дефолтной кучи процесса:
GetProcessHeap().
📌 Слайд 9: Иерархия уровней управления памятью в ОС
- Стек подсистем распределения: от CRT до контроллера памяти.
- Критерии выбора между VirtualAlloc, HeapAlloc и malloc.

Если процессу требуется выделить блок памяти размером 32 байта, вызов VirtualAlloc был бы катастрофически неэффективен:
- Система вынуждена выделить целую страницу в 4096 байт, что дает потерю более 99% памяти (внутренняя фрагментация).
- Вызов
VirtualAllocтребует переключения контекста процессора в режим ядра (Ring 0), что занимает сотни тактов.
Для эффективного управления миллионами разнородных мелких объектов в операционной системе реализован уровень кучи процесса (Heap).
📌 Слайд 10: Управление кучами процесса (Heap API)
- Системные функции:
HeapCreate,HeapAlloc,HeapFree,HeapDestroy.- Флаг синхронизации:
HEAP_NO_SERIALIZEдля однопоточных структур без мьютексов.- Изоляция данных: создание приватных куч для исключения фрагментации дефолтной кучи.
- Механизм Low Fragmentation Heap (LFH): кэширование предопределенных корзин (bins) мелких размеров блоков.
- Мгновенная очистка: вызов
HeapDestroyуничтожает все выделенные объекты разом.
Куча представляет собой менеджер памяти пользовательского режима, который запрашивает у диспетчера виртуальной памяти большие непрерывные блоки (сегменты по несколько мегабайт через VirtualAlloc), а затем нарезает их на блоки произвольного размера по запросам приложения.
1// Создание приватной изолированной кучи с начальным размером 1 МБ и безлимитным ростом (0)
2HANDLE hCustomHeap = HeapCreate(0, 1024 * 1024, 0);
3
4// Выделение 256 байт внутри созданной кучи
5void* pData = HeapAlloc(hCustomHeap, HEAP_ZERO_MEMORY, 256);
6
7// Освобождение блока
8HeapFree(hCustomHeap, 0, pData);
9
10// Полное уничтожение всей кучи и всех оставшихся блоков за один вызов
11HeapDestroy(hCustomHeap);
Преимущества создания собственных куч:
- Защита от фрагментации: интенсивное выделение и удаление временных мелких объектов в отдельной куче не фрагментирует основную кучу процесса (
GetProcessHeap()), в которой живут критические структуры CRT и системных библиотек. - Многопоточная производительность: передача флага
HEAP_NO_SERIALIZEпри работе из одного потока исключает накладные расходы на блокировки межпоточной синхронизации. - Бесплатная пакетная деаллокация: при завершении сложной задачи вызов
HeapDestroyмгновенно возвращает всю задействованную память операционной системе без необходимости поштучного вызоваfreeдля каждого узла графа или дерева.
5. Проецирование файлов на память (Memory-Mapped Files)
📌 Слайд 11: Проецирование файлов на память (MMF)
- Концепция Memory-Mapped Files: отображение содержимого файла непосредственно в виртуальные адреса процесса.
- Устранение двойной буферизации: доступ к файлу через обычные указатели в обход промежуточных системных буферов
ReadFile/WriteFile.- Ленивая подгрузка страниц: с диска считываются только те страницы, к которым реально обратился процесс.
- Когерентность дискового кэша: изменения, внесенные в память, автоматически сбрасываются ядром на диск (
FlushViewOfFile).
Проецирование файлов на виртуальное адресное пространство (Memory-Mapped Files, MMF) — один из наиболее мощных механизмов системного программирования. Он стирает границу между работой с оперативной памятью и дисковым файловым вводом-выводом.
Вместо циклического вызова ReadFile с выделением буфера и копированием данных из кэша ядра в память процесса, файл связывается с диапазоном виртуальных адресов. При обращении к указателю процессор сам считывает нужную страницу с диска через стандартный механизм аппаратного прерывания Page Fault.
📌 Слайд 12: Создание и отображение проекций
- Трехэтапный конвейер WinAPI:
- Открытие/создание файла:
CreateFile.- Создание объекта ядра «проекция»:
CreateFileMapping.- Отображение проекции в адресное пространство:
MapViewOfFile.- Закрытие ресурсов:
UnmapViewOfFile$\to$CloseHandle(hMap)$\to$CloseHandle(hFile).- Ограничения 32/64 бит: отображение файлов гигабайтного размера через скользящее окно (View Offset).
1// 1. Открытие физического файла на диске
2HANDLE hFile = CreateFileA("large_database.dat", GENERIC_READ | GENERIC_WRITE,
3 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
4
5// 2. Создание объекта ядра «Проекция файла»
6HANDLE hMapping = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);
7
8// 3. Отображение всего файла в виртуальное адресное пространство процесса
9char* pFileView = (char*)MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
10
11// Прямой доступ к данным файла через указатель в памяти:
12pFileView[0] = 'H';
13pFileView[1] = 'E';
14pFileView[2] = 'A';
15pFileView[3] = 'D';
16
17// 4. Принудительный сброс модифицированных страниц на накопитель
18FlushViewOfFile(pFileView, 4);
19
20// 5. Очистка ресурсов
21UnmapViewOfFile(pFileView);
22CloseHandle(hMapping);
23CloseHandle(hFile);
📌 Слайд 13: Межпроцессное взаимодействие (IPC) через MMF
- Использование файла подкачки вместо дискового файла: передача дескриптора
INVALID_HANDLE_VALUE.- Именованные объекты ядра: публикация имени в пространстве
Global\илиLocal\.- Нулевое копирование (Zero-Copy IPC): два независимых процесса получают разные виртуальные адреса, указывающие на одни и те же физические фреймы RAM.
- Необходимость внешней синхронизации: мьютексы и семафоры ядра.
Если при вызове CreateFileMapping вместо реального дескриптора файла передать INVALID_HANDLE_VALUE, операционная система создает блок разделяемой оперативной памяти (Shared Memory), резервируемый в системном файле подкачки:
1// Процесс A: Создает именованный блок памяти на 1 МБ
2HANDLE hMap = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE,
3 0, 1024 * 1024, "Global\\SharedBuffer");
4void* pSharedMemA = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
5
6// Процесс B: Открывает существующую проекцию по общему имени
7HANDLE hMapB = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, "Global\\SharedBuffer");
8void* pSharedMemB = MapViewOfFile(hMapB, FILE_MAP_ALL_ACCESS, 0, 0, 0);
Виртуальные адреса pSharedMemA и pSharedMemB в процессах A и B могут иметь совершенно разные числовые значения, но благодаря записям в PTE их адреса указывают на одни и те же физические фреймы оперативной памяти (PFN). Запись данных процессом A становится мгновенно доступна процессу B на аппаратной скорости оперативной памяти.
6. Разделяемые сегменты памяти в DLL
📌 Слайд 14: Разделяемые сегменты данных в DLL
- Поведение по умолчанию: механизм Copy-on-Write изолирует глобальные переменные DLL для каждого процесса.
- Директива создания именованной секции:
#pragma data_seg(".shared").- Обязательное условие: все переменные в секции должны быть явно инициализированы!
- Директива компоновщику: атрибуты секции
RWS(Read, Write, Shared).- Применение: подсчет запущенных экземпляров приложения, межпроцессные флаги.
По умолчанию, когда динамическая библиотека загружается в несколько процессов, операционная система использует для ее страниц данных механизм Copy-on-Write (копирование при записи): страницы являются общими до первой модификации, после чего ядро создает для пишущего процесса приватную физическую копию страницы.
Однако WinAPI позволяет разработчику явно объявить секцию данных разделяемой (Shared Section):
1// mylib.c в составе DLL
2#pragma data_seg(".shared")
3// Обязательная явная инициализация! Неинициализированные переменные попадут в .bss
4int g_InstanceCounter = 0;
5char g_SharedStateString[256] = "DefaultState";
6#pragma data_seg()
7
8// Указание компоновщику сделать секцию разделяемой (RWS: Read, Write, Shared)
9#pragma comment(linker, "/SECTION:.shared,RWS")
При загрузке такой DLL любым количеством процессов все они будут совместно использовать единственную физическую страницу памяти с переменными g_InstanceCounter и g_SharedStateString.
📌 Слайд 15: Проецирование файлов и разделяемая память IPC
- Архитектура MMF с физическим хранилищем (файлы / pagefile).
- Разделение физических страниц между адресными пространствами двух процессов.
- Механизм разделяемых сегментов DLL (
.shared).

7. Архитектура динамически подключаемых библиотек (DLL)
📌 Слайд 16: Динамически подключаемые библиотеки: концепция
- Назначение DLL (Dynamic-Link Library): модульность, разделение кода между процессами, независимое обновление компонентов.
- Экономия памяти: секция кода
.textDLL отображается какPAGE_EXECUTE_READи разделяется всеми процессами системы.- Структура формата PE (Portable Executable): заголовки DOS/PE, секции
.text,.data,.rdata,.rsrc,.reloc.- Базовый адрес загрузки (Image Base) и механизм релокации (ASLR / секция
.reloc).
Динамически подключаемые библиотеки (DLL в Windows, Shared Objects .so в Linux) представляют собой исполняемые PE-модули, содержащие код и ресурсы, которые могут использоваться одновременно несколькими независимыми процессами.
Главные преимущества DLL:
- Экономия физической оперативной памяти: код библиотеки (
.text), будучи неизменяемым, загружается в физическую память в единственном экземпляре и отображается в адресные пространства всех использующих ее процессов. - Модульность и плагинность: функциональность программы может расширяться добавлением новых модулей без перекомпиляции основного
.exe. - Обновляемость: исправление ошибок или оптимизация функций внутри DLL не требует пересборки зависимых клиентских приложений.
📌 Слайд 17: Внутренняя структура DLL: точка входа DllMain
- Сигнатура
BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID).- Причины вызова (
fdwReason):DLL_PROCESS_ATTACH,DLL_PROCESS_DETACH,DLL_THREAD_ATTACH,DLL_THREAD_DETACH.- Блокировка загрузчика (Loader Lock): критическая секция внутри вызова
DllMain.- Чего категорически нельзя делать в
DllMain: вызыватьLoadLibrary, создавать потоки, захватывать прикладные мьютексы (риск взаимной блокировки — Deadlock).
1BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) {
2 switch (fdwReason) {
3 case DLL_PROCESS_ATTACH:
4 // Вызывается при первичной загрузке DLL в адресное пространство процесса.
5 // Возврат FALSE прерывает загрузку всего процесса!
6 DisableThreadLibraryCalls(hinstDLL); // Оптимизация: отключает THREAD_ATTACH/DETACH
7 break;
8
9 case DLL_PROCESS_DETACH:
10 // Вызывается при выгрузке DLL (FreeLibrary или завершение процесса)
11 // lpReserved != NULL указывает, что процесс аварийно завершается
12 break;
13
14 case DLL_THREAD_ATTACH:
15 // Вызывается при создании каждого нового потока в процессе
16 break;
17
18 case DLL_THREAD_DETACH:
19 // Вызывается при корректном завершении потока (ExitThread)
20 break;
21 }
22 return TRUE;
23}
DllMain выполняется операционной системой под системной блокировкой Loader Lock. Внутри DllMain категорически запрещено: вызывать LoadLibrary/FreeLibrary, запускать новые потоки и ожидать их завершения (WaitForSingleObject), инициализировать подсистемы COM или графики. Нарушение этого правила приводит к гарантированному тупику потоков (Deadlock).📌 Слайд 18: Таблицы экспорта (EAT) и импорта (IAT)
- Таблица экспорта (Export Address Table, EAT): список имен функций, ординалов и их относительных адресов (RVA).
- Директива экспорта:
__declspec(dllexport)или сценарии описания модуля (.def-файлы).- Таблица импорта (Import Address Table, IAT): массив указателей на внешние функции, заполняемый загрузчиком при старте.
- Директива импорта:
__declspec(dllimport)генерирует прямой косвенный вызовcall [__imp_Function].
1// mathlib.h
2#ifdef MATHLIB_EXPORTS
3 #define MATHLIB_API __declspec(dllexport)
4#else
5 #define MATHLIB_API __declspec(dllimport)
6#endif
7
8extern "C" MATHLIB_API double CalculateIntegral(double a, double b, int steps);
Директива __declspec(dllimport) критически важна для оптимизации:
- Без
dllimportкомпилятор генерирует вызов статической заглушки (thunk), которая делает дополнительныйjmpпо таблице указателей. - С директивой
__declspec(dllimport)компилятор сразу генерирует прямую инструкцию косвенного вызоваcall qword ptr [__imp__CalculateIntegral], исключая накладные расходы на лишний переход.
📌 Слайд 19: Способы связывания: неявное vs явное
- Неявное (Load-Time / Implicit Linking):
- Требует заголовочный файл
.hи статическую библиотеку импорта.lib.- Загрузчик ОС автоматически подгружает DLL при старте программы.
- Ошибка: если DLL отсутствует, процесс завершается с ошибкой инициализации.
- Явное (Run-Time / Explicit Linking):
- Функции
LoadLibrary(LoadLibraryEx),GetProcAddress,FreeLibrary.- Библиотека импорта
.libне требуется, ручная проверка ошибок и плагинность.- В Linux: функции
dlopen,dlsym,dlclose(библиотекаlibdl).
1// Пример явной динамической загрузки модуля плагина
2typedef double (*CALC_FN)(double, double, int);
3
4HMODULE hModule = LoadLibraryA("mathlib.dll");
5if (hModule != NULL) {
6 CALC_FN pfnCalculate = (CALC_FN)GetProcAddress(hModule, "CalculateIntegral");
7 if (pfnCalculate != NULL) {
8 double result = pfnCalculate(0.0, 3.14159, 1000);
9 }
10 FreeLibrary(hModule); // Выгрузка DLL и декремент счетчика ссылок
11} else {
12 // Грамотная обработка отсутствия библиотеки в рантайме
13 ReportMissingPlugin("mathlib.dll");
14}
📌 Слайд 20: Внутренняя структура DLL и механизмы связывания
- Схема жизненного цикла
DllMain.- Таблицы EAT и IAT.
- Сравнение конвейера компиляции и связывания Load-Time против Run-Time.

8. Итоги и системные принципы управления памятью
📌 Слайд 21: Итоги и системные принципы управления памятью
- 4-уровневые таблицы x86-64 и регистр CR3 изолируют адресные пространства процессов.
- Двухфазное выделение (
VirtualAlloc): резервирование адресов без затрат физической ОЗУ.- Аппаратный сбой Page Fault реализует прозрачную ленивую подгрузку страниц с накопителя.
- Кучи процесса (Heap API) и LFH защищают систему от внутренней фрагментации памяти.
- Проекция файлов (MMF) обеспечивает высокоскоростной Zero-Copy IPC в разделяемой памяти.
- Динамические библиотеки (DLL) разделяют физический код и секции данных между процессами.
Контрольные вопросы и литература
📌 Слайд 22: Вопросы для самопроверки
- Как формируется физический адрес при 4-уровневой трансляции x86-64 и какова роль регистра CR3?
- Чем отличается страница в состоянии
Reservedот страницы в состоянииCommitted?- В чем различие между аппаратными сбоями Hard Page Fault и Soft Page Fault?
- Каковы правила выравнивания и гранулярности при вызовах
VirtualAlloc?- В чем разница между вызовами
VirtualFreeс флагамиMEM_DECOMMITиMEM_RELEASE?- Почему не рекомендуется использовать
VirtualAllocдля мелких объектов вместоHeapAlloc?- Как организовать межпроцессный обмен данными через
CreateFileMappingсINVALID_HANDLE_VALUE?- Что необходимо указать в коде DLL для создания разделяемой секции памяти (
#pragma data_seg)?- Какие операции запрещено выполнять внутри функции
DllMainиз-за блокировки Loader Lock?- Чем отличается неявное связывание через
.libот явной загрузки черезLoadLibrary/GetProcAddress?
📌 Слайд 23: Литература и рекомендуемые ресурсы
- Руссинович М., Соломон Д., Ионеску А. Внутреннее устройство Microsoft Windows. 7-е изд. — СПб.: Питер, 2019. (Главы 5: «Управление памятью», 3: «Механизмы управления»).
- Рихтер Дж. Windows для профессионалов: создание эффективных Win32-приложений с учетом специфики 64-разрядной версии Windows. — СПб.: Питер, 2008. (Главы 13–18: Архитектура виртуальной памяти, кучи, MMF и DLL).
- Таненбаум Э., Бос Х. Современные операционные системы. 4-е изд. — СПб.: Питер, 2015. (Глава 3: «Управление памятью»).
- Microsoft Learn. Memory Management & Dynamic-Link Libraries Documentation. URL: https://learn.microsoft.com/en-us/windows/win32/memory/memory-management
📌 Слайд 21: Вопросы для самоконтроля
- Как формируется физический адрес при 4-уровневой трансляции x86-64 и какова роль регистра CR3?
- Чем отличается страница в состоянии
Reservedот страницы в состоянииCommitted?- В чем различие между аппаратными сбоями Hard Page Fault и Soft Page Fault?
- Каковы правила выравнивания и гранулярности при вызовах
VirtualAlloc?- В чем разница между вызовами
VirtualFreeс флагамиMEM_DECOMMITиMEM_RELEASE?- Почему не рекомендуется использовать
VirtualAllocдля мелких объектов вместоHeapAlloc?- Как организовать межпроцессный обмен данными через
CreateFileMappingсINVALID_HANDLE_VALUE?- Что необходимо указать в коде DLL для создания разделяемой секции памяти (
#pragma data_seg)?- Какие операции запрещено выполнять внутри функции
DllMainиз-за блокировки Loader Lock?- Чем отличается неявное связывание через
.libот явной загрузки черезLoadLibrary/GetProcAddress?
📌 Слайд 22: Рекомендуемая литература
- Руссинович М., Соломон Д., Ионеску А. Внутреннее устройство Microsoft Windows. 7-е изд. — СПб.: Питер, 2019. (Главы 5: «Управление памятью», 3: «Механизмы управления»).
- Рихтер Дж. Windows для профессионалов: создание эффективных Win32-приложений с учетом специфики 64-разрядной версии Windows. — СПб.: Питер, 2008. (Главы 13–18: Архитектура виртуальной памяти, кучи, MMF и DLL).
- Таненбаум Э., Бос Х. Современные операционные системы. 4-е изд. — СПб.: Питер, 2015. (Глава 3: «Управление памятью»).
- Microsoft Learn. Memory Management & Dynamic-Link Libraries Documentation. URL: https://learn.microsoft.com/en-us/windows/win32/memory/memory-management