В Windows используется концепция demand paging, означающая, что физическая память выделяется только при обращении к виртуальному адресу, но никак не при выделении виртуальных адресов.
На уровне ОС это реализуется через дерево VAD, привязанное к процессу(EPROCESS->VadRoot) и описывающее диапазоны и другие атрибуты этой выделенной ранее через сервисные функции памяти. При обращении к памяти срабатывает обработчик страничных фолтов(KiTrap0E), который через VAD ищет соответствующий диапазон, и в случае успеха(для private memory это наличие атрибута закоммиченой памяти) из free/standby/zeroed списков выделяется физическая страница и заполняется PDE/PTE(проставляются атрибуты, valid, pfn и т.д.).
Стало интересно, какие странички подгружаются первыми при старте приложения.
Логично было бы предположить, что это страничка с apc диспатчером и другие страницы ntdll.dll, однако это не так.
Оказалось, что первая страничка это KUSER_SHARED_DATA, к которой идет обращение при создании секции для ехе процесса:
MiMapViewOfImageSection()
{
...
if ( ControlArea->Segment->u2.ImageInformation->ImageContainsCode &&
((ControlArea->Segment->u2.ImageInformation->Machine < USER_SHARED_DATA->ImageNumberLow) ||
(ControlArea->Segment->u2.ImageInformation->Machine > USER_SHARED_DATA->ImageNumberHigh) )
...
}
Затем при создании и инициализации PEB будет второй #PF:
MmCreatePeb()
{
...
Status = MiCreatePebOrTeb (TargetProcess, sizeof(PEB), (PVOID)&PebBase);
...
try
{
PebBase->InheritedAddressSpace = InitialPeb->InheritedAddressSpace;
...
}
Следом идет создание TEB для первичного потока, при её инициализации случится еще один #PF:
MmCreateTeb()
{
...
Status = MiCreatePebOrTeb (TargetProcess, TebSize, (PVOID) &TebBase);
...
try
{
TebBase->NtTib.ExceptionList = EXCEPTION_CHAIN_END; // только на x86
...
}
Перед тем, как запустить юзермодную APC стартующую поток, ядро копирует контекст в юзермодный стек, что приводит к #PF:
KiInitializeUserApc()
{
...
RtlCopyMemory ((PULONG)(UserStack + sizeof(KAPC_RECORD)), &ContextFrame, sizeof(CONTEXT));
...
TrapFrame->Eip = (ULONG)KeUserApcDispatcher;
...
}
И только потом, при переходе в user mode, при выполнении KiUserApcDispatcher подгрузится страничка ntdll.dll.
Хотя подгрузится это не совсем подходящее слово, т.к. подгрузилась с диска эта dll еще при старте ОС, а в последующих случаях, т.е. при старте новых процессов все её странички разрешались ввиде soft fault's, т.е. из памяти, без обращений к диску.
Также стоит заметить, что из рабочего набора эти же странички будут выкинуты в результате усечения рабочего набора процесса, как только в этом будет необходимость.
Необходимость может наступить, например, при достижении лимита(данные для х86):
ULONG PspMinimumWorkingSet = 20;
ULONG PspMaximumWorkingSet = 45;
Soft fault's практически бесплатны в плане временных затрат( мне думается самое затратное там - это переключение контекста при срабатывании обработчика страничного фолта ), и в дальнейшем при необходимости, ранее выкинутые странички будут вновь добавлены в рабочий набор процесса.
На уровне ОС это реализуется через дерево VAD, привязанное к процессу(EPROCESS->VadRoot) и описывающее диапазоны и другие атрибуты этой выделенной ранее через сервисные функции памяти. При обращении к памяти срабатывает обработчик страничных фолтов(KiTrap0E), который через VAD ищет соответствующий диапазон, и в случае успеха(для private memory это наличие атрибута закоммиченой памяти) из free/standby/zeroed списков выделяется физическая страница и заполняется PDE/PTE(проставляются атрибуты, valid, pfn и т.д.).
Стало интересно, какие странички подгружаются первыми при старте приложения.
Логично было бы предположить, что это страничка с apc диспатчером и другие страницы ntdll.dll, однако это не так.
Оказалось, что первая страничка это KUSER_SHARED_DATA, к которой идет обращение при создании секции для ехе процесса:
MiMapViewOfImageSection()
{
...
if ( ControlArea->Segment->u2.ImageInformation->ImageContainsCode &&
((ControlArea->Segment->u2.ImageInformation->Machine < USER_SHARED_DATA->ImageNumberLow) ||
(ControlArea->Segment->u2.ImageInformation->Machine > USER_SHARED_DATA->ImageNumberHigh) )
...
}
Затем при создании и инициализации PEB будет второй #PF:
MmCreatePeb()
{
...
Status = MiCreatePebOrTeb (TargetProcess, sizeof(PEB), (PVOID)&PebBase);
...
try
{
PebBase->InheritedAddressSpace = InitialPeb->InheritedAddressSpace;
...
}
Следом идет создание TEB для первичного потока, при её инициализации случится еще один #PF:
MmCreateTeb()
{
...
Status = MiCreatePebOrTeb (TargetProcess, TebSize, (PVOID) &TebBase);
...
try
{
TebBase->NtTib.ExceptionList = EXCEPTION_CHAIN_END; // только на x86
...
}
Перед тем, как запустить юзермодную APC стартующую поток, ядро копирует контекст в юзермодный стек, что приводит к #PF:
KiInitializeUserApc()
{
...
RtlCopyMemory ((PULONG)(UserStack + sizeof(KAPC_RECORD)), &ContextFrame, sizeof(CONTEXT));
...
TrapFrame->Eip = (ULONG)KeUserApcDispatcher;
...
}
И только потом, при переходе в user mode, при выполнении KiUserApcDispatcher подгрузится страничка ntdll.dll.
Хотя подгрузится это не совсем подходящее слово, т.к. подгрузилась с диска эта dll еще при старте ОС, а в последующих случаях, т.е. при старте новых процессов все её странички разрешались ввиде soft fault's, т.е. из памяти, без обращений к диску.
Также стоит заметить, что из рабочего набора эти же странички будут выкинуты в результате усечения рабочего набора процесса, как только в этом будет необходимость.
Необходимость может наступить, например, при достижении лимита(данные для х86):
ULONG PspMinimumWorkingSet = 20;
ULONG PspMaximumWorkingSet = 45;
Soft fault's практически бесплатны в плане временных затрат( мне думается самое затратное там - это переключение контекста при срабатывании обработчика страничного фолта ), и в дальнейшем при необходимости, ранее выкинутые странички будут вновь добавлены в рабочий набор процесса.
Комментариев нет:
Отправить комментарий