Logical Prefetcher появился в Windows XP, он представляет собой часть ядра и нужен для уменьшения числа head seek-ов жесткого диска, что соответственно увеличивает скорость запуска приложений или системы.
При старте процесса он отслеживает первые 10 секунд его работы(таймаут задается через реестр), затем запоминает имена файлов, смещения, а также метаданные фс к которым был произведен доступ, после чего данная информация записывается в файл в директории %SystemRoot%\Prefetch.
При повторном старте приложения, Logical Prefetcher проверяет, есть ли префетч файл в соответствующей директории, и если есть - парсит его и подгрузит все метаданные для каждой директории указанной в префетч файле.Затем идет отображение каждого файла, перечисленного в префетч файле, загрузка его и установка тех смещений, которые были запомнены ранее( и сохранены в префетч файле ).
Таким образом, вместо беспорядочного метания головок диска для доступа к небольшим порциям данных файла, получаем упорядоченный доступ к большим последовательным потокам данных, что может ускорить запуск процессов в большинстве случаев.
Prefetcher может ускорять не только запуск приложений, но и уменьшать boot-time системы.
Настройки префетчера храняться в реестре:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters
Для его включения нужно установить EnablePrefetcher = 3 (Disabled = 0, Application = 1, BootUp = 2, Application AND BootUp = 3), хотя по умолчанию он и так включен.
Там же прописан таймаут работы префетчера при первом запуске - AppLaunchTimerPeriod.
Пример префетч файла: C:\WINDOWS\Prefetch\VMMAP.EXE-3B5AFAED.pf (вывод утилитой от Руссиновича Strings):
SCCA
VMMAP.EXE
C-p
Xa%
0pL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\NTDLL.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\KERNEL32.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\UNICODE.NLS
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\LOCALE.NLS
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\SORTTBLS.NLS
\DEVICE\HARDDISKVOLUME2\INSTALLEDTOOLS\VMMAP\VMMAP.EXE
...
Структура .pf файла проста - заголовок + смещения + юникодные имена файлов.
Также для префетч файла есть несколько требований - это наличие сигнатуры в заголовке(SCCA), сам файл должен быть меньше 16мб, смещения не должны вылезать за пределы файла ( это проверяется ф-цией PfWithinBounds ).
Что касается имени префетч файла, например VMMAP.EXE-3B5AFAED.pf, то 3B5AFAED это хеш от пути, хеш подсчитывается следующей ф-цией:
#define RNDM_CONSTANT 314159269
#define RNDM_PRIME 1000000007
ULONG CcPfHashValue( PVOID Key, ULONG Len )
/*
Routine Description:
Generic hash routine.
Arguments:
Key - Pointer to data to calculate a hash value for.
Len - Number of bytes pointed to by key.
Return Value:
Hash value.
*/
{
char *cp = Key;
ULONG i, ConvKey=0;
for ( i = 0; i < Len; i++ )
{
ConvKey = 37 * ConvKey + (unsigned int)*cp;
cp++;
}
return ( abs( RNDM_CONSTANT * ConvKey ) % RNDM_PRIME );
}
Однако кроме обычных приложений есть еще и приложения которые имеют командную строку, для таких файлов хеш считается по другому, а сами файлы перечислены в реестре:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters\HostingAppList: DLLHOST.EXE,MMC.EXE,RUNDLL32.EXE
Ф-ция ответственная за проверку принадлежит или нет процесс данному списку называется CcPfIsHostingApplication.
Что касается реализации, то префетчер запускается при запуске первичного потока, цепочка вызовов выглядит так:
NtCreateThread => PspCreateThread => KeInitThread( ..., PspUserThreadStartup, ...):
VOID PspUserThreadStartup( IN PKSTART_ROUTINE StartRoutine, IN PVOID StartContext )
{
...
if (CCPF_IS_PREFETCHER_ENABLED())
{
//
// If this is the first thread we are starting up in this process, prefetch the pages likely to be used when initializing the application into the system cache.
//
if ((Process->Flags & PS_PROCESS_FLAGS_LAUNCH_PREFETCHED) == 0)
{
OldFlags = PS_TEST_SET_BITS(&Process->Flags, PS_PROCESS_FLAGS_LAUNCH_PREFETCHED);
if ((OldFlags & PS_PROCESS_FLAGS_LAUNCH_PREFETCHED) == 0)
{
if (Process->SectionObject)
{
//
// Notify cache manager of this application launch.
//
CcPfBeginAppLaunch( Process, Process->SectionObject );
}
}
}
}
//
// Queue the initial APC to the thread
//
KeRaiseIrql (APC_LEVEL, &OldIrql);
KiInitializeUserApc( PspGetBaseExceptionFrame (Thread),
PspGetBaseTrapFrame (Thread),
PspSystemDll.LoaderInitRoutine,
NULL,
PspSystemDll.DllBase,
NULL);
KeLowerIrql (PASSIVE_LEVEL);
...
}
Сам префетчинг происходит через перечисление содержимого директории ZwQueryDirectoryFile с классом FileNamesInformation, и загрузкой секций(CcPfPrefetchSections) через IoCreateFile + ZwCreateSection и метаданных (CcPfPrefetchMetadata), но тут уже идет обращение к ФС через ZwFsControlFile(..., FSCTL_FILE_PREFETCH,...).
Работа префетчера по сбору данных идет асинхронно через рабочие потоки, совершенно прозрачно для процесса.
Остался один неосвещенный вопрос - как именно префетчер узнает к какому файлу(секции) было обращение при первом запуске приложения?
Ответ - через отслеживание ошибок страниц.
Цепочка ф-ций, при страничном фолте:
_KiTrap0E => MmAccessFault => MiDispatchFault => MiCompleteProtoPteFault => CcPfLogPageFault.
VOID CcPfLogPageFault(IN PFILE_OBJECT FileObject, IN ULONGLONG FileOffset, IN ULONG Flags )
{
...
SECTION_OBJECT_POINTERS *sectionObjectPointer = FileObject->SectionObjectPointer;
...
}
С каждым процессом связана трасса - структура данных, позволяющая вести связи между собираемыми данными. Полученный PSECTION_OBJECT_POINTERS сохраняется в этой структуре, также извлекаются и сохраняются DataSectionObject и ImageSectionObject.
Таким образом, префетчер запоминает страницы относящиеся к запускаемым файлам, соотносит их с файлами( секциями ) и после того, как таймаут трассы процесса закончится, накопленные данные будут сброшены в .pf файл, а при следующем запуске префетчер заранее загрузит необходимые файлы для процесса.
На этом кратенький, и не претендующий на истину в последней инстанции, обзор префетчера закончен.
При старте процесса он отслеживает первые 10 секунд его работы(таймаут задается через реестр), затем запоминает имена файлов, смещения, а также метаданные фс к которым был произведен доступ, после чего данная информация записывается в файл в директории %SystemRoot%\Prefetch.
При повторном старте приложения, Logical Prefetcher проверяет, есть ли префетч файл в соответствующей директории, и если есть - парсит его и подгрузит все метаданные для каждой директории указанной в префетч файле.Затем идет отображение каждого файла, перечисленного в префетч файле, загрузка его и установка тех смещений, которые были запомнены ранее( и сохранены в префетч файле ).
Таким образом, вместо беспорядочного метания головок диска для доступа к небольшим порциям данных файла, получаем упорядоченный доступ к большим последовательным потокам данных, что может ускорить запуск процессов в большинстве случаев.
Prefetcher может ускорять не только запуск приложений, но и уменьшать boot-time системы.
Настройки префетчера храняться в реестре:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters
Для его включения нужно установить EnablePrefetcher = 3 (Disabled = 0, Application = 1, BootUp = 2, Application AND BootUp = 3), хотя по умолчанию он и так включен.
Там же прописан таймаут работы префетчера при первом запуске - AppLaunchTimerPeriod.
Пример префетч файла: C:\WINDOWS\Prefetch\VMMAP.EXE-3B5AFAED.pf (вывод утилитой от Руссиновича Strings):
SCCA
VMMAP.EXE
C-p
Xa%
0pL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\NTDLL.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\KERNEL32.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\UNICODE.NLS
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\LOCALE.NLS
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\SORTTBLS.NLS
\DEVICE\HARDDISKVOLUME2\INSTALLEDTOOLS\VMMAP\VMMAP.EXE
...
Структура .pf файла проста - заголовок + смещения + юникодные имена файлов.
Также для префетч файла есть несколько требований - это наличие сигнатуры в заголовке(SCCA), сам файл должен быть меньше 16мб, смещения не должны вылезать за пределы файла ( это проверяется ф-цией PfWithinBounds ).
Что касается имени префетч файла, например VMMAP.EXE-3B5AFAED.pf, то 3B5AFAED это хеш от пути, хеш подсчитывается следующей ф-цией:
#define RNDM_CONSTANT 314159269
#define RNDM_PRIME 1000000007
ULONG CcPfHashValue( PVOID Key, ULONG Len )
/*
Routine Description:
Generic hash routine.
Arguments:
Key - Pointer to data to calculate a hash value for.
Len - Number of bytes pointed to by key.
Return Value:
Hash value.
*/
{
char *cp = Key;
ULONG i, ConvKey=0;
for ( i = 0; i < Len; i++ )
{
ConvKey = 37 * ConvKey + (unsigned int)*cp;
cp++;
}
return ( abs( RNDM_CONSTANT * ConvKey ) % RNDM_PRIME );
}
Однако кроме обычных приложений есть еще и приложения которые имеют командную строку, для таких файлов хеш считается по другому, а сами файлы перечислены в реестре:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters\HostingAppList: DLLHOST.EXE,MMC.EXE,RUNDLL32.EXE
Ф-ция ответственная за проверку принадлежит или нет процесс данному списку называется CcPfIsHostingApplication.
Что касается реализации, то префетчер запускается при запуске первичного потока, цепочка вызовов выглядит так:
NtCreateThread => PspCreateThread => KeInitThread( ..., PspUserThreadStartup, ...):
VOID PspUserThreadStartup( IN PKSTART_ROUTINE StartRoutine, IN PVOID StartContext )
{
...
if (CCPF_IS_PREFETCHER_ENABLED())
{
//
// If this is the first thread we are starting up in this process, prefetch the pages likely to be used when initializing the application into the system cache.
//
if ((Process->Flags & PS_PROCESS_FLAGS_LAUNCH_PREFETCHED) == 0)
{
OldFlags = PS_TEST_SET_BITS(&Process->Flags, PS_PROCESS_FLAGS_LAUNCH_PREFETCHED);
if ((OldFlags & PS_PROCESS_FLAGS_LAUNCH_PREFETCHED) == 0)
{
if (Process->SectionObject)
{
//
// Notify cache manager of this application launch.
//
CcPfBeginAppLaunch( Process, Process->SectionObject );
}
}
}
}
//
// Queue the initial APC to the thread
//
KeRaiseIrql (APC_LEVEL, &OldIrql);
KiInitializeUserApc( PspGetBaseExceptionFrame (Thread),
PspGetBaseTrapFrame (Thread),
PspSystemDll.LoaderInitRoutine,
NULL,
PspSystemDll.DllBase,
NULL);
KeLowerIrql (PASSIVE_LEVEL);
...
}
Сам префетчинг происходит через перечисление содержимого директории ZwQueryDirectoryFile с классом FileNamesInformation, и загрузкой секций(CcPfPrefetchSections) через IoCreateFile + ZwCreateSection и метаданных (CcPfPrefetchMetadata), но тут уже идет обращение к ФС через ZwFsControlFile(..., FSCTL_FILE_PREFETCH,...).
Работа префетчера по сбору данных идет асинхронно через рабочие потоки, совершенно прозрачно для процесса.
Остался один неосвещенный вопрос - как именно префетчер узнает к какому файлу(секции) было обращение при первом запуске приложения?
Ответ - через отслеживание ошибок страниц.
Цепочка ф-ций, при страничном фолте:
_KiTrap0E => MmAccessFault => MiDispatchFault => MiCompleteProtoPteFault => CcPfLogPageFault.
VOID CcPfLogPageFault(IN PFILE_OBJECT FileObject, IN ULONGLONG FileOffset, IN ULONG Flags )
{
...
SECTION_OBJECT_POINTERS *sectionObjectPointer = FileObject->SectionObjectPointer;
...
}
С каждым процессом связана трасса - структура данных, позволяющая вести связи между собираемыми данными. Полученный PSECTION_OBJECT_POINTERS сохраняется в этой структуре, также извлекаются и сохраняются DataSectionObject и ImageSectionObject.
Таким образом, префетчер запоминает страницы относящиеся к запускаемым файлам, соотносит их с файлами( секциями ) и после того, как таймаут трассы процесса закончится, накопленные данные будут сброшены в .pf файл, а при следующем запуске префетчер заранее загрузит необходимые файлы для процесса.
На этом кратенький, и не претендующий на истину в последней инстанции, обзор префетчера закончен.
Комментариев нет:
Отправить комментарий