- PVSM.RU - http://www.pvsm.ru -

Ускорение перечисления процессов и потоков в ОС Windows

Ускорение перечисления процессов и потоков в ОС Windows - 1Иногда бывает нужно перечислить все процессы или потоки, которые в данный момент работают в ОС Windows. Это может понадобиться по разным причинам. Возможно, мы пишем системную утилиту вроде Process Hacker [1], а может быть мы хотим как-то реагировать на запуск/остановку новых процессов или потоков (писать лог, проверять их, внедрять в них свой код). Самым правильным способом это реализовать является, конечно же, написание драйвера. Там всё просто — используем PsSetCreateProcessNotifyRoutine [2] и PsSetCreateThreadNotifyRoutine [3] для установки колбек-функций, которые будут вызываться при запуске/остановке процессов и потоков. Работает очень быстро и не ест ресурсы. Именно так и делают все серьёзные инструменты. Но разрабатывать драйвера — не всегда подходящий способ. Их нужно уметь правильно писать, их с недавних пор обязательно нужно подписывать сертификатами (что не бесплатно) и регистрировать в Microsoft (что не быстро). И ещё их не удобно распространять — например, программы с ними нельзя выкладывать в Microsoft Store.

Ну, давайте тогда пользоваться тем, что предлагает публичный WinAPI. А предлагает он функцию CreateToolhelp32Snapshot() [4], которую предлагается использовать как-то вот так [5]. Всё, кажется, хорошо — есть информация о процессах, потоках. Немного расстраивает тот факт, что вместо элегантных колбеков мы вынуждены делать бесконечный пулинг в цикле, но это ладно.

Самая большая проблема здесь — это производительность. Связка CreateToolhelp32Snapshot() + Process32First() + Process32Next() работает ну очень медленно. Возможно, проблема лежит где-то в той же области, что и описанная вот в этой статье [6] проблема с Heap32First() + Heap32Next(). Кратко — в силу исторических причин кое-где проход по линейному списку занимает квадратичное время.

Можно ли как-то всё это ускорить? Можно. Но придётся сойти со светлого пути использования одних лишь публичных функций WinAPI.

Долгое время я считал, что функции WinAPI делятся на публичные (описанные в MSDN, допустимые для использования) и недокументированные (найденные при реверс-инжиниринге системных библиотек, не описанные в MSDN, не поддерживаемые официально). Этот черно-белый мир казался мне простым и логичным: для публичных продуктов используем публичные функции, для личных учебных целей, утилит, внутренних инструментов — можно пытаться использовать и недокументированные.

Но оказалось, что между этими мирами есть и серая зона. Это функции, которые описаны в MSDN (это делает их похожими на публичные), но Microsoft сообщает, что может изменить или удалить их в любой момент (как недокументированные). Почему такие функции существуют? Всё просто — с одной стороны их польза слишком велика, чтобы её прятать, но с другой стороны у будущих версий ОС могут возникнуть внутренние причины их изменить. Такие функции в одной из встреченных мною терминологий называются «приватными». Пример — NtQuerySystemInformation() [7].

Оцените первую строчку в документации — «NtQuerySystemInformation may be altered or unavailable in future versions of Windows. Applications should use the alternate functions listed in this topic» — при этом для половины описанных применений этих самых «альтернативных функций» и не описано. Можно ли пользоваться такими функциями? Каждый решает это для себя сам. Лично мне кажется, что «волков бояться — в лес не ходить». Как-будто любая другая функция в MSDN гарантированно никогда не станет «altered or unavailable in future versions of Windows». Любой код нуждается в проверке на новых версиях ОС. Любой код нуждается в поддержке и адаптации. И если есть функция, которая вот сейчас работает и приносит пользу, то почему бы её не использовать?

А NtQuerySystemInformation приносит существенную пользу. Вот график роста производительности, который получила библиотека MHook [8] при переходе с CreateToolhelp32Snapshot() на NtQuerySystemInformation():

image

Как использовать NtQuerySystemInformation()? Очень просто:

  1. Ищем функцию «NtQuerySystemInformation» в библиотеке «ntdll.dll». Теоретически её там может и не найтись, но на практике на всех версиях ОС от Vista до Win10 она есть точно. Не уверен на счёт XP (не было возможности и необходимости проверить)
  2. Выделяем память для буфера с результатами
  3. Вызываем функцию с параметром SystemProcessInformation
  4. Обходим результаты, перемещаясь между записями для отдельных процессов с использованием значения «NextEntryOffset» из структуры описания текущего процесса.
  5. Не забываем освободить память, выделенную на шаге №2

Примеры кода можно найти здесь [9] или здесь [10].

Лично мне с помощью перехода с CreateToolhelp32Snapshot() на NtQuerySystemInformation() удалось в одном достаточно ресурсоёмком сценарии выиграть около 2% общей загрузки процессора, что достаточно много.

Мораль этой истории в том, что медленная работа WinAPI функции не всегда является окончательным приговором. Операционная система — большая и сложная штука, в ней вполне может оказаться другой способ получения нужной информации.

Автор: tangro

Источник [11]


Сайт-источник PVSM.RU: http://www.pvsm.ru

Путь до страницы источника: http://www.pvsm.ru/c-3/276199

Ссылки в тексте:

[1] Process Hacker: https://processhacker.sourceforge.io/

[2] PsSetCreateProcessNotifyRoutine: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntddk/nf-ntddk-pssetcreateprocessnotifyroutine

[3] PsSetCreateThreadNotifyRoutine: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntddk/nf-ntddk-pssetcreatethreadnotifyroutine

[4] CreateToolhelp32Snapshot(): https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms682489(v=vs.85).aspx

[5] как-то вот так: https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms686701(v=vs.85).aspx

[6] этой статье: https://habrahabr.ru/company/infopulse/blog/352056/

[7] NtQuerySystemInformation(): https://msdn.microsoft.com/en-us/library/windows/desktop/ms724509(v=vs.85).aspx

[8] MHook: https://www.apriorit.com/dev-blog/469-mhook-enhancements

[9] здесь: http://www.rohitab.com/discuss/topic/40504-using-ntquerysysteminformation-to-get-process-list/

[10] здесь: https://malwaretips.com/threads/ntquerysysteminformation-and-process-ids.76276/

[11] Источник: https://habrahabr.ru/post/352060/?utm_campaign=352060