Мониторинг исполнения потоков в приложениях WinXP/2000
Роман Лут
В данной статье автор описывает библиотеку, позволяющую наглядно отобразить исполнение потоков (threads) на графике (timeline), а также измерять процессорное время, полученное каждым потоком, с точностью до микросекунд. Предполагается, что читатель знаком с архитектурой Windows, Windows API, языком ассемблера x86 и архитектурой IA-32. Конечно, хотелось бы объяснить как можно доступнее, но изложение всего указанного материала достойно целой книги. Я постараюсь добавить ссылки на материалы, в которых можно прочитать обо всем этом подробнее. Следует заметить, что тема несколько отклонилась от разработки игр, поэтому если вас не интересует внутреннее устройство библиотеки, можно сразу перейти к разделу 3 - "Использование". Введение Думаю, все согласятся, что средства мониторинга и отладки приложений существенно облегчают жизнь программистов и помогают сделать приложения лучше. Практически ни один серьезный IDE на сегодняшний день не обходится без мощного отладчика. Важную роль играют и утилиты мониторинга, встроенные в сами приложения - консоль, замеры скорости выполнения функций, лог. В статье "То, что вам никто не говорил о многозадачности в Windows"[1] я привел основные правила исполнения потоков. К ней прилагается специальная программка, которая наглядно показывает, как Windows распределяет процессорное время. Наличие такого же монитора исполнения в собственном приложении значительно облегчило бы поиск причин неправильного распределения ресурсов процессора. Даже зная все правила, порой довольно сложно однозначно сказать, что все работает так, как задумано, не имея возможности в этом убедиться. К сожалению, принцип работы примера основан на том, что алгоритмы, исполняемые в тестовых потоках, должны периодически записывать отсчеты времени в специальный буфер. Понятно, что применение такого подхода в реальном приложении невозможно. Необходимо найти другое решение. Поможет ли нам операционная система? Самым очевидным решением было бы получение обратных вызовов (callback) от операционной системы в момент переключения потоков. В Windows 98 есть калбек Call_When_Thread_Switched, но в системах Windows XP/2000 он больше не поддерживается. Единственную информацию, которую можно получить через WinAPI - это количество процессорного времени, выделенное потоку - функция GetThreadTimes(). Информацию от счетчиков производительности (загрузка процессора, количество переключений контекста), доступную через WMI, я не считаю полезной. Это не поможет нам отобразить график. К тому же, функция GetThreadTimes() подсчитывает время только тогда, когда поток полностью использовал свой Quantum, и произошло насильственное вытеснение. Quantum составляет 10-15мс. Если поток вышел из объекта ожидания, выполнил работу за 9мс и опять "заснул", что является довольно распространенным сценарием, функция GetThreadTimes() не засчитает потоку никакого времени[2]. Часть 1. Пассивный профайлер Для определения наиболее критичных участков кода применяют профайлеры. С некоторыми изменениями профайлер можно применить для мониторинга выполнения потоков. Пассивный профайлер работает по принципу "вы работайте, а я посмотрю". Ключевыми моментами для создания пассивного профайлера являются:
Мы уже знаем, что планировщик Windows не рассчитан на real-time задачи, и поэтому не существует способа получать периодические события таймера с точностью 1 мс. Единственные события, которые могут происходить регулярно - это аппаратные прерывания. Аппаратные прерывания Сегодняшний компьютер на платформе IA-32[3] содержит целых три аппаратных таймера, способных вызывать прерывания (IRQ):
Windows использует IRQ8 (RTC) для работы планировщика потоков (то самое аппаратное прерывание, о котором говорилось в прошлой статье). Нам остаются доступными таймер 8254 и Local APIC. Можно написать драйвер, который настроит аппаратный таймер на вызов прерываний с достаточно высокой точностью (например, 1КHz). Настройка таймера сводится к выводу 4-5 байт в порты ввода-вывода. Драйвер будет обрабатывать указанное прерывание. По идеологии драйверов Windows, драйвер должен предоставить процедуру обработки прерывания (InterruptService routine - ISR) [25]. Когда происходит аппаратное прерывание, управление получает Windows. Система начинает вызывать по очереди все ISR, которые обрабатывают указанное прерывание (на одном IRQ могут "висеть" несколько устройств). К сожалению, получив управление внутри ISR, мы никак не сможем узнать, в каком месте прервалось выполнение программы. Функции GetCurrentThreadId() и GetCurrentProcessId() в обработчике прерывания недоступны (как и почти все другие функции), поскольку прерывание не выполняется в контексте потока. Сохраненный указатель выборки команд процессора (EIP) находится где-то в стеке, но мы не знаем его структуру. Чтобы получать значение регистра EIP, нужно обрабатывать прерывание напрямую. Когда процессор входит в обработчик прерывания, в стеке сохраняются регистры SS,ESP,flags,CS,EIP. Регистры стека сохраняются только тогда, когда на момент прерывания процессор находится в user mode (ring 3). Ему необходимо перейти в kernel mode (ring 0) и установить новый стек. Структура стека, если произошел переход Ring 3 - Ring0: [ESP] eip [ESP+0x04] cs [ESP+0x08] flags [ESP+0x0c] esp [ESP+0x10] ss Структура стека, если процессор находился в Ring 0: [ESP] eip [ESP+0x04] cs [ESP+0x08] flags Зная структуру стека, в обработчике прерывания можно получить EIP,CS, флаги процессора и регистр FS, который в user mode адресует Thread Information Block (TIB)[6]. Для прямой обработки прерывания придется выполнить работу, которую обычно выполняет система. 1. Настроить контроллер прерываний. 2. Найти неиспользуемый номер прерывания путем анализа таблицы прерываний (IDT)[9]. 3. Настроить IDT на вызов своей процедуры[10]. Оценив все сложности (программирование таймера, контроллера прерываний, анализ IDT), я решил, что проще будет просто "повеситься" на IRQ8 и вызвать timeBeginPeriod(1) (это заставит систему запрограммировать таймер на 1KHz). После завершения анализа EIP/CS/FS, новый обработчик будет передавать управление оригинальному обработчику системы.
Страница сайта http://silicontaiga.ru
Оригинал находится по адресу http://silicontaiga.ru/home.asp?artId=5786 |