Antichat снова доступен.
Форум Antichat (Античат) возвращается и снова открыт для пользователей.
Здесь обсуждаются безопасность, программирование, технологии и многое другое.
Сообщество снова собирается вместе.
Новый адрес: forum.antichat.xyz
Управление памятью в ядре Windows XP |

11.01.2008, 16:44
|
|
Флудер
Регистрация: 27.12.2005
Сообщений: 2,372
Провел на форуме: 5339610
Репутация:
4360
|
|
Управление памятью в ядре Windows XP
Управление памятью в ядре Windows XP
Для начала нечто вроде вступления. Статья рассчитана на тех, кто уже работал с памятью в режиме ядра и отличает MmProbeAndLockPages от MmMapLockedPagesSpecifyCache, а так же знаком с основами устройства управления памятью у процессора - каталоги страниц (PDE), таблицы страниц (PTE), исключение ошибки страницы (#PF). Для исправления первого упущения рекомендуется сначала прочитать соответствующие статьи Four-F (http://www.wasm.ru/series.php?sid=9, части 6 и 9), для исправления второго - статьи цикла BrokenSword "Процессор Intel в защищенном режиме" (http://www.wasm.ru/series.php?sid=20, части 6 и 7, кстати, в части 7 есть ошибка в картинке - вместо картинки для PDE 4M страниц представлена картинка для PDE 4K страниц).
I. Устройство PDE/PTE, невалидные PTE
Рассмотрим сначала как в Windows используются поля PTE, которые помечены Intel как доступные программному обеспечению операционной системы (Avail.)
Эти три бита операционная система Windows использует следующим образом (структуры при выключенном и включенном PAE соответственно):
Код:
typedef struct _MMPTE_HARDWARE {
ULONG Valid : 1;
ULONG Write : 1;
ULONG Owner : 1;
ULONG WriteThrough : 1;
ULONG CacheDisable : 1;
ULONG Accessed : 1;
ULONG Dirty : 1;
ULONG LargePage : 1;
ULONG Global : 1;
ULONG CopyOnWrite : 1; // software field
ULONG Prototype : 1; // software field
ULONG reserved : 1; // software field
ULONG PageFrameNumber : 20;
} MMPTE_HARDWARE, *PMMPTE_HARDWARE;
typedef struct _MMPTE_HARDWARE_PAE {
ULONGLONG Valid : 1;
ULONGLONG Write : 1;
ULONGLONG Owner : 1;
ULONGLONG WriteThrough : 1;
ULONGLONG CacheDisable : 1;
ULONGLONG Accessed : 1;
ULONGLONG Dirty : 1;
ULONGLONG LargePage : 1;
ULONGLONG Global : 1;
ULONGLONG CopyOnWrite : 1; // software field
ULONGLONG Prototype : 1; // software field
ULONGLONG reserved0 : 1; // software field
ULONGLONG PageFrameNumber : 24;
ULONGLONG reserved1 : 28; // software field
} MMPTE_HARDWARE_PAE, *PMMPTE_HARDWARE_PAE;
Комментариями помечены такие поля.
Поле CopyOnWrite означает, является ли страница копируемой при записи. Такие страницы с пользовательской стороны задаются атрибутом PAGE_WRITECOPY или PAGE_EXECUTE_WRITECOPY и означают, что процессу будет выделена личная копия страницы при попытке записи в неё. Остальные будут использовать публичную не модифицированную копию.
Поле Prototype для валидного PTE означает, что это т.н. прототипный PTE, используемый для разделения памяти между процессами с помощью механизма проецированных в память файлов (Memory Mapped Files, MMF, см. документацию на Win32 API CreateFileMapping, OpenFileMapping, MapViewOfFile(Ex))
Поле reserved для валидного PTE не используется, для невалидного PTE этот бит называется Transition и установлен, когда PTE считается переходным.
Не буду рассказывать про аппаратное управление памятью и остальные поля структур PDE/PTE: об этом неплохо писали уже не один десяток раз. Последующее же повествование пойдет про формат тех PTE, которые использует Windows при флаге Valid = 0, или про недействительные (невалидные) PTE.
- Paged out PTE (выгруженные PTE) - невалидный PTE, описывающий страницу, выгруженную в файл подкачки. По первому требованию она вновь будет считана и включена в рабочий набор. PTE описывается структурой
Код:
typedef struct _MMPTE_SOFTWARE {
ULONG Valid : 1;
ULONG PageFileLow : 4;
ULONG Protection : 5;
ULONG Prototype : 1;
ULONG Transition : 1;
ULONG PageFileHigh : 20;
} MMPTE_SOFTWARE;
А при включенном PAE такой:
Код:
typedef struct _MMPTE_SOFTWARE_PAE {
ULONGLONG Valid : 1;
ULONGLONG PageFileLow : 4;
ULONGLONG Protection : 5;
ULONGLONG Prototype : 1;
ULONGLONG Transition : 1;
ULONGLONG Unused : 20;
ULONGLONG PageFileHigh : 32;
} MMPTE_SOFTWARE_PAE;
При этом
Valid = 0
PageFileLow содержит номер страничного файла (которых, как нетрудно догадаться, максимально может быть 16 штук)
Protection, соответственно, атрибуты доступа на страницу, задается в виде констант MM_*:
Код:
#define MM_ZERO_ACCESS 0 // this value is not used.
#define MM_READONLY 1
#define MM_EXECUTE 2
#define MM_EXECUTE_READ 3
#define MM_READWRITE 4 // bit 2 is set if this is writable.
#define MM_WRITECOPY 5
#define MM_EXECUTE_READWRITE 6
#define MM_EXECUTE_WRITECOPY 7
#define MM_NOCACHE 8
#define MM_DECOMMIT 0x10
#define MM_NOACCESS MM_DECOMMIT|MM_NOCACHE
Prototype = 0
Transition = 0
PageFileHigh - номер страницы в страничном файле (о страничном файле и подкачке далее)
- Demand zero PTE (обнуляемые по требованию PTE) - невалидный PTE, описывающий страницу, которой еще нет в рабочем наборе, но по обращению она должна быть выделена либо из списка обнуленных страниц, либо из списка свободных страниц, обнулена и добавлена в рабочий набор. Описывается аналогично выгруженному PTE за тем исключением, что PageFileHigh = 0.
- Prototype PTE (прототипные PTE) - невалидные PTE, которые описывают страницы, разделяемые несколькими процессами, например, спроецированные в память файлы. Точнее, такие PTE создаются в одном экземпляре и не включаются в списки PDE, а в списках PDE процесса располагаются следующие невалидные PTE, ссылающиеся на прототипные PTE, соответственно их версии для систем без PAE и с PAE:
Код:
typedef struct _MMPTE_PROTOTYPE {
ULONG Valid : 1;
ULONG ProtoAddressLow : 7;
ULONG ReadOnly : 1;
ULONG WhichPool : 1;
ULONG Prototype : 1;
ULONG ProtoAddressHigh : 21;
} MMPTE_PROTOTYPE;
typedef struct _MMPTE_PROTOTYPE_PAE {
ULONGLONG Valid : 1;
ULONGLONG Unused0: 7;
ULONGLONG ReadOnly : 1;
ULONGLONG Unused1: 1;
ULONGLONG Prototype : 1;
ULONGLONG Protection : 5;
ULONGLONG Unused: 16;
ULONGLONG ProtoAddress: 32;
} MMPTE_PROTOTYPE_PAE;
При этом:
Valid = 0
ProtoAddress (ProtoAddressLow/ProtoAddressHigh) содержат ссылку на прототипный PTE, описывающий разделяемую страницу.
Prototype = 1
Protection содержит атрибуты защиты страницы (MM_*)
ReadOnly установлен, если страница должна только читаться. Игнорируется при загрузке образов в пространство сеанса - загрузчику позволяется писать в такие страницы в целях обработки импортов или расставления релоков.
WhichPool назначение этого поля мне неизвестно..
- Transition PTE (переходные PTE) - невалидные PTE, описывающие страницу, которая находится в списке Standby, Modified или ModifiedNoWrite страниц (об этих списках далее). При обращении страница возвращается в рабочий набор. Описываются следующими структурами:
Код:
typedef struct _MMPTE_TRANSITION {
ULONG Valid : 1;
ULONG Write : 1;
ULONG Owner : 1;
ULONG WriteThrough : 1;
ULONG CacheDisable : 1;
ULONG Protection : 5;
ULONG Prototype : 1;
ULONG Transition : 1;
ULONG PageFrameNumber : 20;
} MMPTE_TRANSITION;
typedef struct _MMPTE_TRANSITION_PAE {
ULONGLONG Valid : 1;
ULONGLONG Write : 1;
ULONGLONG Owner : 1;
ULONGLONG WriteThrough : 1;
ULONGLONG CacheDisable : 1;
ULONGLONG Protection : 5;
ULONGLONG Prototype : 1;
ULONGLONG Transition : 1;
ULONGLONG PageFrameNumber : 24;
ULONGLONG Unused : 28;
} MMPTE_TRANSITION_PAE;
При этом:
Valid = 0
Prototype = 0
Transition = 1
Назначение остальных полей аналогично валидному PTE
II. Обработка ошибок страниц
Когда процессор сталкивается с невалидным PTE, генерируется исключение ошибки страницы (#PF, Page Fault). В Windows обработчик _KiTrap0E вызывает MmAccessFault() для обработки исключения, которая после некоторого числа проверок вызывает MiDispatchFault, если страница должна быть разрешена успешно.
MiDispatchFault вызывает одну из следующих функций для разрешения ошибки страницы:
- MiResolveProtoPteFault вызывается при ошибке страницы на PTE c флагом Prototype=1
Она исследует прототипный PTE, на который указывает сбойный PTE и:
1. Если прототипный PTE тоже имеет флаг Prototype, значит это разделяемые страницы проецированного в память файла. Вызывается MiResolveMappedFileFault.
2. Если прототипный PTE имеет флаг Transition, то значит, что это переходный PTE, его страница находится в списке модифицированных или простаивающих страниц. Попала она туда в результате усечения рабочего набора. Вызывается MiResolveTransitionFault.
3. Если у прототипного PTE Transition==0 && Prototype==0 && PageFileHigh==0, то это demand-zero PTE. Вызывается MiResolveDemandZeroFault.
4. Если у прототипного PTE Transition==0 && Prototype==0 && PageFileHigh!=0, то страница выгружена в своп. Вызывается MiResolvePageFileFault.
- MiResolveTransitionFault вызывается, когда сбойный PTE имеет флаг Transition=1, либо если он указывает на прототипный PTE, имеющий флаг Transition.
Поскольку страницы в этом состоянии оказываются в результате усечения рабочего набора или других обстоятельств, когда понадобились физические страницы, то разрешение такой ошибки страницы должно заключаться в возвращении страницы в рабочий набор. Поскольку страница еще не выгружена на диск, то сделать это очень просто - нужно лишь записать валидный PTE на место недействительного.
Например, в состояние Transition как раз и переводит страницы функция MmTrimAllSystemPagableMemory(0), но о ней подробнее далее в части статьи, посвященную подкачке.
- MiResolveDemandZeroFault вызван при обработке ошибки страницы, обнуляемой по требованию. Если запрос был из пользовательского режима, тогда идет попытка выделения физической страницы из списка обнуленных страниц (о поддерживаемых списках физических страниц далее). Если это не удается, выделяется свободная страница и обнуляется. При запросе из режима ядра обнуление не форсируется при выделении страницы из списка свободных страниц. Для обнуления используются зарезервированные системные PTE или гиперпространство.
- MiResolvePageFileFault вызывается при обработке ошибки страницы, которая была выгружена в файл подкачки. Инициируется операция чтения файла подкачки за счет возвращения статуса STATUS_ISSUE_PAGING_IO, страницы читаются из файла подкачки кластерами для снижения числа ошибок страниц.
Когда MiDispatchFault получает статус STATUS_ISSUE_PAGING_IO, она выполняет операцию чтения страниц с помощью функции IoPageRead, которая производит создание обычного IRP для операции IRP_MJ_READ, но ставит в нем специальный флаг IRP_PAGING_IO. Страница выбирается из списка свободных или обнуленных страниц.
- MiResolveMappedFileFault вызывается из MiResolveProtoPteFault когда Prototype==1 у прототипного PTE. Тогда PTE трактуется следующим образом (варианты без PAE и с PAE):
Код:
typedef struct _MMPTE_SUBSECTION {
ULONG Valid : 1;
ULONG SubsectionAddressLow : 4;
ULONG Protection : 5;
ULONG Prototype : 1;
ULONG SubsectionAddressHigh : 20;
ULONG WhichPool : 1;
} MMPTE_SUBSECTION;
typedef struct _MMPTE_SUBSECTION {
ULONGLONG Valid : 1;
ULONGLONG Unused0 : 4;
ULONGLONG Protection : 5;
ULONGLONG Prototype : 1;
ULONGLONG Unused1 : 21;
ULONGLONG SubsectionAddress : 32;
} MMPTE_SUBSECTION;
Он содержит адрес объекта SUBSECTION, поддерживающего спроецированный файл. Например, в SUBSECTION::ControlArea->FilePointer хранится FILE_OBJECT файла.
III. Управление физической памятью
Физическая память в системе описывается определенными структурами режима ядра. Они необходимы для поддержания списка свободных и занятых страниц, для удовлетворения аллокаций и других операций с памятью. Для начала рассмотрим, какие же основные части ядра отвечают за описание и распределение физической памяти системы.
Первой структурой, на которую мы обратим внимание, будет MmPhysicalMemoryDescriptor, имеющую описание:
Код:
typedef struct _PHYSICAL_MEMORY_RUN {
PFN_NUMBER BasePage;
PFN_NUMBER PageCount;
} PHYSICAL_MEMORY_RUN, *PPHYSICAL_MEMORY_RUN;
typedef struct _PHYSICAL_MEMORY_DESCRIPTOR {
ULONG NumberOfRuns;
PFN_NUMBER NumberOfPages;
PHYSICAL_MEMORY_RUN Run[1];
} PHYSICAL_MEMORY_DESCRIPTOR, *PPHYSICAL_MEMORY_DESCRIPTOR;
PPHYSICAL_MEMORY_DESCRIPTOR MmPhysicalMemoryDescriptor;
Переменная ядра MmPhysicalMemoryDescriptor описывает всю доступную и пригодную для использования физическую память в системе и инициализируется при загрузке.
Ядро поддерживает шесть списков страниц (из восьми возможных состояний), в которых размещаются практически все физические страницы, разве что за исключением тех, что используются самим менеджером памяти. Списки страниц поддерживаются указателями u1.Flink и u2.Blink в структуре MMPFN (о ней далее).
Это списки: - ZeroedPageList - Список обнуленных страниц, которые можно выдавать по запросу из пользовательского кода. В фоновом режиме работает поток MmZeroPageThread (в него переходит первичный поток KiSystemStartup после всей инициализации) и занимается обнулением свободных страниц с перемещением их в этот список. При запросе страницы пользовательским кодом это наиболее приоритетный список, откуда может быть вынута страница.
- FreePageList - список свободных страниц. Они могут быть переданы пользоваелю после обнуления (поток либо сигналит событие MmZeroingPageEvent, тогда страницу обнуляет поток обнуления страниц MmZeroPageThread, либо в некоторых исключительных случаях обнуляет самостоятельно - например при обработке #PF на PTE типа demand-zero. В этом случае передача страницы потоку обнуления повлечет за собой дополнительные потери времени), в запросах от пользователя это второй по приоритету список после ZeroedPageList.
- StandbyPageList - список простаивающих страниц. Эти страницы раньше входили в рабочий набор (процесса или системы), но в последствии были удалены из него. Страница не была изменена с последней записи на диск, PTE, ссылающийся на такую страницу, находится в переходном (transition) состоянии и страница может быть использована для удовлетворения запроса на выделение памяти, но после просмотра списков обнуленных и свободных страниц. ( В Windows 2003 есть 8 подсписков, поддерживающих простаивающие страницы, по приоритетам, они описаны в массиве MmStandbyPageListByPriority[]. В Windows XP и ниже список один)
- ModifiedPageList - список модифицированных страниц, они тоже раньше входили в рабочий набор, но в последствии были удалены из него в результате сокращения рабочих наборов по какой-либо причине. Страницы были изменены с момента последней записи на диск и должны быть записаны в файл подкачки. PTE все еще ссылается на страницу, но недействителен и находится в состоянии transition.
- ModifiedNoWritePageList - список модифицированных, но не записываемых страниц. Аналогично предыдущему, но страница не должна быть записана на диск.
- BadPageList - список страниц, которые были помечены менеджером памяти плохими по каким-либо причинам. Они не должны быть использованы. Например, поток обнуления страниц временно помечает страницы плохими, когда ищет область из страниц, ждущих обнуления, чтобы они не были вдруг переданы какому-нибудь процессу по запросу выделения продолжительного региона памяти (MmAllocateContiguousMemory). PTE не должны ссылаться на такую страницу.
Состояние страниц, не являющиеся списками: - ActiveAndValid - страница активная и действительная, не входит ни в один список. Такие страницы являются частью рабочего набора или не входят ни в один рабочий набор и являются частью неподкачиваемой памяти системы. На них ссылаются действительные PTE.
- TransitionPage - временное состояние страницы на время ожидания операции ввода-вывода.
Указатели на списки хранит переменная ядра MmPageLocationList[], содержимое которой объявлено следующим образом:
Код:
PMMPFNLIST MmPageLocationList[8] =
{
&MmZeroedPageListHead,
&MmFreePageListHead,
&MmStandbyPageListHead,
&MmModifiedPageListHead,
&MmModifiedNoWritePageListHead,
&MmBadPageListHead,
NULL,
NULL };
Есть два важных потока, оперирующих списками страниц - поток обнуления страниц и поток записи модифицированных страниц. - Поток обнуления страниц. В него KiSystemStartup переходит после инициализации всех компонентов системы и запуска менеджера сессий smss. Он занимается тем, что в цикле ожидает события MmZeroingPageEvent. Когда оно наступает (а наступает оно при наличии в системе достаточного количества свободных страниц, чтобы поток обнуления мог их стереть), захватывается спин-блокировка базы данных фреймов (PFN Database), выделяется страница из списка свободных страниц, проецируется в гиперпространство и обнуляется, после чего включается в список обнуленных страниц и цикл повторяется.
- Поток записи модифицированных страниц. После старта подсистемы управления памятью MmInitSystem() создает через PsCreateSystemThread поток MiModifiedPageWriter, который стартует второй вспомогательный поток MiMappedPageWriter, а сам переходит в MiModifiedPageWriterWorker.
Основной функцией выгрузки страниц в своп-файл является MiGatherMappedPages, о выгрузке будет рассказано далее в следующей части статьи.
MmPfnDatabase.
MmPfnDatabase - это массив структур MMPFN, описывающих каждую физическую страницу в системе. Это, пожалуй, второй по важности объект, после массивов PDE/PTE, которые поддерживают низкоуровневые операции с памятью. В списках PFN хранится информация о конкретной физической странице.
Схематично MMPFN представляется в следующем виде (полное объявлеие прилагается к исходникам к статье, в том числе и для других версий ОС - Windows 2000, Windows 2003 Server):
Код:
typedef struct _MMPFN {
union {
PFN_NUMBER Flink; // Used if (u3.e1.PageLocation < ActiveAndValid)
WSLE_NUMBER WsIndex; // Used if (u3.e1.PageLocation == ActiveAndValid)
PKEVENT Event; // Used if (u3.e1.PageLocation == TransitionPage)
NTSTATUS ReadStatus; // Used if (u4.InPageError == 1)
} u1;
PMMPTE PteAddress;
union {
PFN_NUMBER Blink; // Used if (u3.e1.PageLocation < ActiveAndValid)
ULONG ShareCount; // Used if (u3.e1.PageLocation >= ActiveAndValid)
ULONG SecondaryColorFlink; // Used if (u3.e1.PageLocation == FreePageList or == ZeroedPageList)
} u2;
union {
struct _MMPFNENTRY {
ULONG Modified : 1;
ULONG ReadInProgress : 1;
ULONG WriteInProgress : 1;
ULONG PrototypePte: 1;
ULONG PageColor : 3;
ULONG ParityError : 1;
ULONG PageLocation : 3;
ULONG RemovalRequested : 1;
ULONG CacheAttribute : 2;
ULONG Rom : 1;
ULONG LockCharged : 1;
ULONG ReferenceCount : 16;
} e1;
struct {
USHORT ShortFlags;
USHORT ReferenceCount;
} e2;
} u3;
MMPTE OriginalPte;
union {
ULONG EntireFrame;
struct {
ULONG PteFrame : 26;
ULONG InPageError : 1;
ULONG VerifierAllocation : 1;
ULONG AweAllocation : 1;
ULONG LockCharged : 1;
ULONG KernelStack : 1;
ULONG Reserved : 1;
};
} u4;
} MMPFN, *PMMPFN;
Последний раз редактировалось _Great_; 11.01.2008 в 17:18..
|
|
|
|
|
Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
|
|
|
|