понедельник, 17 октября 2011 г.

Может ли стек ядерного потока быть выгружаемым?

Знакомый увидил фактом того, что ядерный стек может быть выгружен.

Для меня это было откровением, ибо мне было известно, что для ядерного стека выделяется изначально три странички невыгружаемой памяти, также он может быть  расширен(динамически), если поток превратился в gui-поток (вызвав сервисную ф-цию из shadow ssdt).

Поэтому руководствуясь поговоркой "доверяй, но проверяй" я решил этот факт проверить.

Итак, на этапе инициализации системы, среди прочих, создаются системные потоки KeBalanceSetManager и KeSwapProcessOrStack.

BOOLEAN MmInitSystem( IN ULONG Phase, IN PLOADER_PARAMETER_BLOCK LoaderBlock )
{
...
    PsCreateSystemThread( &ThreadHandle, THREAD_ALL_ACCESS, &ObjectAttributes, 0L, NULL, KeBalanceSetManager, NULL );
    PsCreateSystemThread( &ThreadHandle, THREAD_ALL_ACCESS, &ObjectAttributes, 0L, NULL, KeSwapProcessOrStack, NULL );
...
}

Оба этих системных потока находятся в вечном ожидании и активизируются либо по таймеру, либо по эвенту.
Причем поток KeBalanceSetManager при срабатывании от таймера может инициализировать событие для второго потока(KeSwapProcessOrStack).
KeSwapProcessOrStack также может быть вызван из других мест(аттач/детач и т.д.), а также в случае нехватки памяти.

KeSwapProcessOrStack висит в вечном ожидании евента, и как только эвент получен, ф-ция проверяет есть ли в очередях запросы
на выгрузку страничек ядерного стека или наоборот, превращение их из нерезидентных в резидентных. Если таковые запросы в очередях обнаружены - они выполняются. Чтобы ядерный стек потока был выгружен, нужно чтобы он провисел в ожидании больше, чем stack protection time(15сек).

Проверить резидентен ли стек у потока можно, проверив переменную KernelStackResident:

typedef struct _KTHREAD
{
...
    BOOLEAN KernelStackResident;
...
} KTHREAD, *PKTHREAD, *PRKTHREAD;

Обратную операцию, то есть создание резидентного ядерного стека делает планировщик, при помещении потока в ready list (по прежнему, через сигнализацию эвента ).

Таким образом, якобы невыгружаемый ядерный стек потока при определенных условиях(поток в состоянии ожидания более 15ти секунд), может быть превращен из резидентного в нерезидентный.

Далее, менеджер памяти в ситуации нехватки памяти вполне может провести операцию усечения рабочего множества, и скинуть на диск эти самые более не резидентные странички.

3 комментария:

  1. Никто не любит читать RTFM. А между тем в DDK с лохматых времен было написано:

    If the WaitMode parameter is UserMode, the kernel stack can be swapped out during the wait. Consequently, a caller must never attempt to pass parameters on the stack when calling KeWaitForSingleObject using the UserMode argument. If you allocate the event on the stack, you must set the WaitMode parameter to KernelMode.

    Опять же стоит упомянуть про параметр GFlags
    FLG_DISABLE_PAGE_KERNEL_STACKS

    ОтветитьУдалить
  2. Мсдн это одно, но изучить как на самом деле это реализовано - совсем другое. Мне больше интересен второй вариант :) Спасибо за комментарий.

    ОтветитьУдалить
  3. Забыл еще про ф. KeSetKernelStackSwapEnable
    которая хотя и не документирована, но в DDK хедерах есть. Можно настоять таки на невыгружаемости.

    ОтветитьУдалить