При падении производительности сервера на базе Win2k3 и возникновении проблем
со стабильностью его работы администратору нужно заглянуть во множество уголков
операционной системы. Для выявления узких мест могут быть использованы различные
решения, в том числе и хардкорные. Например, отладчик уровня ядра Soft-Ice.

 

Никому не скрыться

Операционные системы семейства NT поддерживают развитую систему мониторинга
счетчиков производительности, отображающих в реальном времени, сколько «тиков»
ушло на ту или иную операцию. Не отстают от них и процессоры, позволяющие
регистрировать практически любые события: от количества переключений контекста
до интенсивности кэш-промахов. Специальные программы-профилировщики обобщают эту
информацию, выявляя так называемые «горячие точки», в которых система проводит
наибольшую часть своего времени. Однако точность измерений невелика. Прежде чем
приступать к профилировке (или снятию показаний счетчиков производительности),
требуется устранить все побочные факторы, способные ввести профилировщик в
заблуждение. В противном случае придется исходить из предположения, что все
приложения и драйвера работают правильно, чего в жизни, увы, практически никогда
не бывает.

Soft-Ice, будучи отладчиком уровня ядра, позволяет остановить систему в любой
момент и посмотреть, какими интересными делами она сейчас занимается. С ним
поиск «бутылочного горлышка» занимает буквально считанные минуты, причем
результат абсолютно надежен. Не скрыться ни «кривым» драйверам, ни некорректно
работающему аппаратному обеспечению. Кстати, знания ассемблера не потребуется –
невероятно, но факт! Достаточно освоить несколько несложных команд, а все
остальное Soft-Ice сделает сам!

 

Сеанс термоядерной отладки

Как узнать, на что уходит львиная доля системного времени? Очень просто —
установить Soft-Ice, вызвать его комбинацией <CTRL-D>, посмотреть, чем
занимается сервер. Выйти из Soft-Ice по <CTRL-D> или команде «x;<ENTER>», затем
тут же вызывать его вновь (чем занимается система на этот раз?). Повторяя данную
операцию в цикле, можно быстро собрать богатый статистический материал.
Совершенно очевидно: истинное процессорное время, потребляемое каждым
компонентом, прямо пропорционально частоте его появления при вызове отладчика.

Когда сервер вообще ничем не нагружен, свыше 90% «всплытий» приходится на
псевдопроцесс «Idle». Он означает, что текущие операции выполняются столь
быстро, что мы их просто не засекаем (так как они занимают ничтожные доли
процессорного времени). При этом сервер может обслуживать достаточно большое
количество пользователей, дефрагментировать жесткий диск в фоновом режиме и
заниматься прочими делами. Вот так парадокс! Сервер работает, а процессор —
отдыхает. Это означает, что конфигурация сервера имеет определенный избыток по
мощности, и узких мест в ней нет.

При дальнейшем росте загрузки мы получаем более или менее «гладкое»
распределение, попадая то в интерпретатор PHP, то в процесс, обслуживающий SQL,
то в другой сервис. Вполне нормальное явление, однако доминирование одного
процесса над другим — плохой признак, указывающий на дефекты проектирования
программы. В нормальной ситуации свыше 90% машинного времени уходит на
ввод/вывод, в течение которого процессы, инициировавшие запрос ввода/вывода,
спят, как младенцы, независимо от того, справляется дисковая подсистема/сетевая
карта со своей работой или нет.

Повышение активности процессов свидетельствует о «тяжелых» операциях типа
криптографии, упаковке/распаковке потока данных и прочих «наукоемких» задачах,
большинство из которых, кстати говоря, сугубо опционально. В частности,
шифрование можно либо вообще отключить, либо выбрать более «легкие» алгоритмы.
Аналогично обстоит дело и с упаковкой. Естественно, ситуацию с вещанием
цифрового аудио/видео мы в расчет не берем. Тут, понятно, требуется мощный
процессор с емкой кэш-памятью. А как узнать, что кэш-памяти достаточно для
решения поставленной задачи? Очень просто! Если Soft-Ice останавливается на
произвольных машинных инструкциях — все ОК. А вот если доминируют инструкции
чтения/записи в память (MOV плюс что-то в квадратных скобках, а так же MOVSx,
CMPSx и STOSx), то установка процессора с более емкой кэш-памятью существенно
поднимет производительность!

Но это теория, переходим к практике и вещам, далеко не очевидным даже для
тех, кто давно использует Soft-Ice. Имя процесса, исполняемого в данный момент,
отображается в правом нижнем углу, а текущий исполняемый код — в окне CODE,
расположенном посередине экрана. В правильно сконструированной программе процесс
останавливается каждый раз в случайном месте. То есть колонка адресов каждый раз
будет отличаться. Это означает, что в исследуемом процессе нет горячих точек
(или они выражены крайне слабо). Напротив, если какие-то диапазоны адресов
встречаются чаще остальных — мы поймали горячую точку, которую разработчики
программы должны были устранить еще на стадии проектирования, ну или, в крайнем
случае, в процессе оптимизации продукта перед выбросом его на рынок. Впрочем,
если они ее не устранили, это не повод для паники, ибо, как мы уже говорили,
ресурсоемкие операции типа криптографии занимают намного больше времени, чем,
например, отдача пользователю файла в том виде, в котором он записан на диске.

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

В качестве наглядного примера рассмотрим простую программу, мотающую холостой
цикл, исходный код которой укладывается в одну строку на Си: «main(){while(1);}».

Откомпилируем ее без опций оптимизации (в случае MS VC это: «cl.exe hCPUl.c»),
чтобы оптимизатор не выбросил ненужный с его точки зрения цикл while, и запустим
ее на выполнение. На однопроцессорных машинах мы получим 100% загрузку по
индикатору. На двухпроцессорных (или однопроцессорных с двумя ядрами) — 50%.
Соответственно, четыре процессора/ядра дадут всего лишь 25%, хотя Soft-Ice при
каждом вызове будет всплывать в одном и том же процессе — hCPUl, исполняющемся в
одном и том же месте — в цикле while. То есть, согласно Soft-Ice, загрузка
процессора близка к 100%. Это одновременно верно и нет. С одной стороны поток, в
котором исполняется цикл while, нагружает всего лишь один процессор/ядро из всех
имеющихся, совсем не препятствуя исполнению потоков других процессов на
оставшихся процессорах (ядрах). Но реальное торможение намного больше, чем это
следует из простых арифметических расчетов. Системный планировщик распределяет
очереди потоков равномерно между всеми процессорами (ядрами), и если один из
процессоров (ядер) захватывается «неправильным» потоком, планировщик начинает
оптимизировать очередь потоков. Эффективность такой оптимизации невелика, в чем
нетрудно убедиться, измерив реакционноспособность сервера с запущенной
программой hCPUl и без нее.

Теперь перейдем к более сложным вопросам, зарывшись в недра драйверов.
Soft-Ice всегда отображает имя процесса в правом нижнем углу, вне зависимости от
того, находится ли система на прикладном (user-land) или ядерном (kernel-space)
уровне. А что у нас в ядре? Например, драйвера, которые могут вообще не иметь
отношения к отображаемому процессу. Просто драйвер получил управление (скажем,
по прерыванию, поступившему извне) именно в тот момент, когда система
переключила контекст на данный процесс. А может быть, совсем другой процесс
прямо или косвенно инициировал вызов драйвера и тут же заснул, система
переключила контекст на следующий процесс, а драйвер… как бы выразиться
деликатнее… если не завис, то конкретно застрял. Соответственно, при вызове
отладчика мы будет оказываться в нем чаще, чем в остальных, но имя процесса,
отображаемое в нижнем углу, тут не причем.

Определить, что система находится в kernel-space, очень просто. В
конфигурации по умолчанию нижняя половина адресного пространства принадлежит
прикладным программам, а верхняя — ядру. Соответственно, если мы наблюдаем
колонку адресов < 80000000h, отладчик находится внутри прикладных программ, в
противном случае — это ядро или один из его драйверов. Селектор в прикладном
режиме равен 1Bh и 08h в режиме ядра.

Примечание: при указании ключа 3GB в boot.ini ядро с драйверами «ужимается»
до 1 Гб. Получается, что граница ядерных земель отодвигается на 40000000h. Во
всяком случае, в 32-битном режиме все происходит именно так, а в 64-битном –
совсем не так (но поскольку Soft-Ice работает только в 32-битном режиме, оставим
проблемы 64-битных серверов за рамками статьи).

Допустим, Soft-Ice остановится в некотором процессе (к примеру, VMwareService),
но столбец адресов выходит за пределы 80000000h, и текущая исполняемая команда,
выделенная инверсной строкой, расположена по BFF0ACE2h. Как узнать, какому из
драйверов она принадлежит? Даем команду «DRIVER» или «MOD» и смотрим на базовые
адреса загрузки драйверов. К сожалению, Soft-Ice не сортирует их по списку
возрастания, но этот недостаток легко исправить. Достаточно выйти из Soft-Ice,
запустить поставляемый вместе с ним Symbol Loader и сохранить историю команд в
текстовой файл. Затем посредством MS Word или MS Excel преобразовать ее в
таблицу.

Драйвер, в чьих границах находится обозначенный адрес, и есть «виновник». В
нашем случае – это NDIS, низкоуровневый сетевой драйвер. При интенсивной работе
сетевой карты его появление в Soft-Ice вполне объяснимо, приемлемо и понятно.
Однако это должна быть действительно очень высокая нагрузка, в противном случае
– имеем дело с кривым драйвером или неправильной сетевой карта, смена которой
(вместе с драйвером) ощутимо повысит общую производительность сервера. Причем
(внимание!) факт подобной «кривизны» не определяет ни системный монитор, ни
легион программ, предназначенных для тюнинга сервера, поскольку все они работают
на более высоком уровне.

Другая распространенная ситуация — Soft-Ice начинает часто всплывать на
команде чтения (реже — записи) в/из порт ввода-вывода. Это плохой признак!
Нормальное железо, управляемое правильными драйверами, работает по прерыванию,
обмениваясь данными через DMA. Обращения к портам лишь инициируют ту или иную
операцию, а потому занимают ничтожное время.

Интенсивная работа с портами ввода/вывода нагружает системную шину, снижая
производительность всех подсистем компьютера в целом, а потому такой ситуации
необходимо избегать любой ценой. Легко сказать — избегать! Для этого, как
минимум, необходимо определить, к какому именно оборудованию обращается система.
No problem!

Смотрим на значение, содержащееся в регистре DX (младшие 16-бит регистра EDX
или, выражаясь человеческим языком, крайние четыре цифры справа). Выходим из
отладчика, запускаем «Диспетчер устройств». В меню «Вид» выбираем сортировку
устройств по ресурсам и в портах ввода/вывода тут же находим наш порт, равный в
данном случае 379h и принадлежащий LPT1 – порту принтера, который вообще не
установлен на обозначенной системе. Хм, странно, принтера нет, а обращение к
порту происходят с высокой степенью интенсивности, причем не по прерыванию, а по
опросу (готовности устройства).

Лезем в очередь печати и видим, что в ней каким-то чудом оказался некий
документ, и какой-то левый принтер (в смысле его драйвер) «мистически» повешен
на LPT1, хотя физического принтера нет. Очищаем очередь печати — не очищается!
Сносим драйвер — обращение к порту исчезает, а машина летит вперед с такой
производительностью, что буквально отрывается от асфальта. Другим путем
обнаружить «бутылочное горлышко» не удавалось — системный монитор упорно
твердил, что все нормально, никаких косяков.

Наконец, Soft-Ice может останавливаться не только внутри драйверов, но и
ядерных функций, лежащих в области адресов 8xxxxxxxh, где находится ntoskrnl.exe.
Само ядро тут, понятное дело, не причем. Это просто какой-то противный драйвер
напрягает его вызовом тех или иных функций. Что же это за драйвер такой?

Даем команду «STACK» и смотрим на левую колонку адресов, по которой и
определяем драйвер описанным выше способом. В данном случае им оказался драйвер
DVD-привода, автоматически установленный системой. Тщательное расследование
выяснило, что к драйверу никаких претензий нет, а дефект проектирования
DVD-привода привел к тому, что драйвер начинал обмениваться с ним какими-то
командами (они так и не были найдены в стандарте на ATAPI-команды), даже если в
лотке отсутствовал диск. Смена DVD-привода «утихомирила» драйвер, «волшебный»
обмен прекратился, а общая производительность системы опять-таки возросла.

 

Заключение

Мы убедились, что поиск «узких» мест сервера при помощи Soft-Ice – это
простой, но надежный способ, охватывающий абсолютно все уровни: аппаратный,
ядерный и программный. Конечно, не стоит ждать от Soft-Ice точных
«телеметрических» показаний, выраженных в численном виде. В общем-то, этого и не
нужно. Если в системе имеется «бутылочное горлышко», то Soft-Ice обнаружит его
практически сразу после пяти-шести вызовов.
К недостаткам способа следует отнести, что на Win2k3 в 32-разрядном режиме он
еще работает (и то, не со всеми видеокартами и чипсетами), но более новые
системы ему не по зубам. К тому же Soft-Ice способен обрушивать систему, вызывая
голубые экраны смерти, зависания (от которых порой не спасает и RESET, а только
«передерживание» питания) и перезагрузки.

Последние два случая очень опасны — сброса дисковых буферов не происходит, и
раздел, с которым ведется активная работа на чтение/запись, рискует отправиться
к праотцам. Но даже если «полет нормальный», в момент вызова Soft-Ice сервер
останавливает системные часы (которые потом нам придется корректировать) и
прекращает весь сетевой обмен, что при продолжительном пребывании в отладчике
ведет к обрыву TCP/IP соединений, вызывая естественное недовольство
пользователей.

 

Почему счетчики производительности ненадежны

Почему же ошибаются счетчики производительности и, в частности, индикатор
загрузки процессора? Дело в том, что под «загрузкой процессора» создатели NT
понимают отнюдь не загрузку процессора, как таковую, а готовность потока
поделиться остатками кванта, отпущенного ему процессорного времени. На хакерских
конференциях было продемонстрировано большое количество exploit'ов, отдающих
~3%-5% от полного кванта времени и заставляющих счетчик производительности
отображать загрузку близкую к нулевой, чем с успехом пользуются многие
зловредные программы (например, использующие распределенные сети для взлома
паролей).

Другой источник ошибок — асинхронные операции ввода/вывода, поддерживаемые
ядром. При чтении данных с диска или файла подкачки поток «замораживается»
системой, передающей управление потокам этого же или другого процесса, в
результате чего поток, инициирующий процесс чтения, показывает намного более
низкое потребление процессорного времени, чем происходит в действительности.
Строго говоря, индикатор загрузки не лжет. Поток действительно «спит» во время
чтения с диска, но ведь нас интересует не формальная сторона проблемы, а полное
время, включающее в себя длительность всех операций ввода/вывода, поскольку
интенсивный ввод/вывод — отличное средство торможения!

Наконец, счетчик загрузки ЦП не учитывает время, потребляемое драйверами.
«Загрузка ядра» более или менее корректно вычисляется только для некоторых
системных вызовов, да и то с кучей оговорок и ограничений.



Полную версию статьи
читай в июльском номере
Хакера!

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии