Разгон и торможение Windows NT



         

Переключение контекста - часть 3


Функция HalRequestSoftwareInterrupt, реализованная в HAL, через короткий патрубок соединяется с функциями _HalpDispatchInterrupt/_HalpDispatchInterrupt, cохраняющими/восстанавливающими регистры в своих локальных переменных (не в контексте потока!) и на определенном этапе передающих управление на KiDispatchInterrupt, вновь возвращающую нас в ntoskrnl.exe и рекурсивно вызывающую SwapContext. Кто же тогда сохраняет/восстанавливает контексты? Оказывается – аппаратные обработчики. Список указателей на предустановленные обработчики находится в ntoskrnl.exe и содержится в переменной IDT (не путать с IDT-таблицей процессора!), которая, как и следовало ожидать, не экспортируется ядром, но присутствует в символьных файлах. При их отсутствии найти переменную IDT можно так: просматривая таблицу прерываний любых из ядерных отладчиков (Soft-Ice, Microsoft Kernel Debugger), определите адреса нескольких не переназначенных обработчиков прерываний (т. е. таких, которые указывают на ntoskrnl.exe, а не к драйверу) и, загрузив ntoskrnl.exe в дизассемблер, восстановите перекрестные ссылки, ведущие к ним. Это и будет структурой IDT.

Другие функции так же могут сохранять/восстанавливать текущий контекст (это, в частности, делает Kei386EoiHelper, расположенная в ntoskrnl.exe), поэтому накладные расходы на переключение между потоками оказываются достаточно велики и выливаются в тысячи и тысячи команд машинного кода, причем, каждое ядро имеет свои особенности реализации. Как оценить насколько одно из них производительнее другого?

Логично, если мы уговорим ядро переключать контексты так быстро, как только это возможно, то количество переключений в единицу времени и определит вклад накладных расходов в общее быстродействие ядра. Сказано – сделано. Создаем большое количество потоков (по меньшей мере сто или даже триста) и каждый из них заставляем циклически вызывать функцию Sleep(0), приводящую к отдаче квантов времени и как следствие – немедленному переключению на другой поток. Количество переключений контекста можно определить по содержимому специального счетчика производительности, отображаемого Системным Монитором, утилитой CPUMon Марка Руссиновича, отладчиком Microsoft Kernel Debugger и многими другими программами.

thread()

{     

       // отдаем процессорное время в бесконечном цикле

       while(1) Sleep(0);

}

#define defNthr      300

#define argNthr ((argc > 1)?atol(argv[1]):defNthr)

main(int argc, char **argv)

{

       int a, zzz;

      

       printf("creating %d threads...", argNthr);

      

       // создаем argNthr потоков

       for (a = 0; a < argNthr; a++) CreateThread(0, 0, (void*)thread, 0,0, &zzz);

       printf("OK\n"); thread();

       return 0;

}




Содержание  Назад  Вперед