суббота, 30 июля 2011 г.

Windows Logical Prefetcher

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 файл, а при следующем запуске префетчер заранее загрузит необходимые файлы для процесса.

На этом кратенький, и не претендующий на истину в последней инстанции, обзор префетчера закончен.

Комментариев нет:

Отправить комментарий