Точная настройка системы

Анализ и улучшение работы системы

Статья включает:

Получение информации о статусе системы
Улучшение производительности
Уменьшение времени начальной загрузки
Файловые системы и драйверы блочного ввода/вывода
Производительность и отказоустойчивость
Обновление метаданных
Пропускная способность
Конфигурация
Влияние значения параметра commit при блочных операциях ввода/вывода
Размер буфера ввода/вывода
Двойная буферизация
Сравнение функций, использующих файловый дескриптор и stdio
Предварительное задание размера файла
Точная настройка USB устройств хранения данных
Планирование периодической фоновой работы с помощью cron и crontab

Получение информации о статусе системы

В составе ЗОСРВ «Нейтрино» имеются следующие утилиты, которые могут быть использованы для получения сведений о процессах, потоках и степени загруженности системы:

hogs
Выводит список процессов, которые в текущий момент времени занимают ЦПУ;
pidin (Process ID INfo)
Отображает сведения о текущем состоянии процессов и их потоков;
ps
Выводит данные о статусе процессов;
top
Отображает степень использования системы (Unix).

Для получения более подробных данных необходимо использовать tracelogger, slogger и qconn. tracelogger и qconn позволяют получить трассу (журнал) событий ядра, изменения состояния системы при использовании инструментальной версии ядра операционной системы procnto-*-instr. Эта версия поддерживает ряд диагностических интерфейсов, которые расходуют несколько больше системных ресурсов и обычно не требуются в тиражируемых изделиях. Этим они отличаются от обычных версий ядра операционной системы.

Улучшение производительности

После запуска утилиты hogs вы сможете узнать, какие процессы занимают наибольшее процессорное время. Например:

$ hogs -n -% 5 PID NAME MSEC PIDS SYSTEM 1 1315 53% 43% 6 devb-eide 593 24% 19% 54358061 make 206 8% 6% 1 2026 83% 67% 6 devb-eide 294 12% 9% 1 2391 75% 79% 6 devb-eide 335 10% 11% 54624301 htmlindex 249 7% 8% 1 1004 24% 33% 54624301 htmlindex 2959 71% 98% 54624301 htmlindex 4156 96% 138% 54624301 htmlindex 4225 96% 140% 54624301 htmlindex 4162 96% 138% 1 71 35% 2% 6 devb-eide 75 37% 2% 1 3002 97% 100%

Давайте посмотрим на результаты вывода. Первая итерация показывает, что процесс с PID=1 занимает 53% процессорного времени. Процесс с номером 1 всегда является администратором процессов, procnto. В данном случае это поток режима простоя (ожидания), который использует большую часть процессорного времени. Строка для процесса с именем devb-eide соответствует дисковым операциям ввода/вывода. Процессорное время использует также утилита make.

На второй итерации наибольшее процессорное время занимают процессы procnto и devb-eide, но из последующих итераций видно, что в дело вступает процесс htmlindex (программа, которая создает индекс ключевых слов для онлайновой документации), который отбирает на себя 96% процессорного времени. После завершения процесса htmlindex процессор оказывается занятым процессами procnto и devb-eide, пока идет запись HTML-файлов. Фактически большая часть занятости процессора приходится (включая цикл простоя) на процесс procnto.

Не беспокойтесь о том, что процесс htmlindex потребляет до 96% процессорного ресурса. На самом деле это хорошо: если запущена всего лишь одна программа, то она и должна использовать большую часть процессорного времени.

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

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

Уменьшение времени начальной загрузки

Вот несколько рекомендаций, которые помогут ускорить процесс начальной загрузки:

Более подробно об этом см. раздел Управление запуском ЗОСРВ «Нейтрино».

Файловые системы и драйверы блочного ввода/вывода

Здесь приведены основные этапы для улучшения работоспособности файловой системы и драйверов блочного ввода/вывода (devb-*):

  1. Используйте ключи запуска для оптимизации работы дискового оборудования и драйверов. Это особенно важно для целевых устройств, реализованных в архитектурах, отличных от х86, и без жестких дисков (например, с Microdrive, Compact Flash). Отказ от использования самых быстрых из доступных режимов DMA (или даже переход на режим PIO) может до 10 раз замедлить скорость работы. Более подробно об этом см. раздел Подключение оборудования.

  2. Оптимизируйте работу файловой системы, используя соответствующие ключи:

  3. Оптимизируйте код приложения в соответствии с приведенными далее рекомендациями:

Производительность и отказоустойчивость

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

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

Обновление метаданных

Метаданными называются данные о данных или все служебные данные и атрибуты, включенные в блок сохраняемых данных пользователя. К ним относится, например, имя файла, используемые физические блоки, временные отметки об изменении или доступе к данным и т.д.

Самой дорогостоящей операцией для файловой системы является обновление метаданных. Это обусловлено двумя причинами:

Почти все операции в файловой системе (даже чтение файла, если не задан ключ noatime, см. описание функции io-blk.so) требуют, в той или иной степени, обновления метаданных.

Порядок обновления метаданных

Некоторые операции в файловой системе воздействуют сразу на несколько блоков диска. Например, рассмотрим ситуацию создания или удаления файла. В большинстве файловых систем имя файла (или ссылка на него, link) отделяется от фактических атрибутов файла (блок индексных дескрипторов, inode). Это согласуется с концепцией POSIX о жестких ссылках (hard links), множественных именах для одного и того же файла.

Обычно индексные дескрипторы (inodes) занимают на диске фиксированное место (файл .inodes для fs-qnx4.so или в заголовке группы каждого цилиндра для fs-ext2.so).

Создание файла с новым именем требует, таким образом, выделения свободной записи в блоке индексных дескрипторов, заполнение ее параметрами, касающимися нового файла, и помещения имени файла в соответствующий каталог. При удалении файла его имя удаляется из родительского каталога, а запись в блоке индексных дескрипторов помечается как свободная.

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

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

Пропускная способность

Другим ключевым моментом является производительность последовательного доступа к файлу, или поточная пропускная способность (raw throughput), когда в файл записывается большой блок данных (или читается весь файл целиком). В самой файловой системе может быть обнаружен такой тип последовательного доступа, и будет предпринята попытка оптимизировать использование диска путем выполнения следующих действий:

Наиболее эффективным способом высокопроизводительного доступа к диску является использование стандартных процедур POSIX, которые работают с дескрипторами файлов – open(), read() и write(), – потому что при этом осуществляется прямой доступ к файловой системе без вмешательства библиотеки libc.

Если вас волнует проблема производительности, то мы не рекомендуем использовать функции стандартного ввода/вывода (<stdio.h>), которые работают с переменными FILE, потому что в них вводится еще один слой программного кода и слой буферизации. Кроме того, по умолчанию размер буфера BUFSIZ установлен равным 1 Кбайт, поэтому весь доступ к диску ограничен размерами этого буфера, что приводит к большим издержкам в производительности за счет рассылки дополнительных сообщений и переключения контекстов.

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

Оптимизировать производительность можно, организовав доступ к диску порциями удобного размера. Этот размер должен быть достаточно большим для минимизации издержек на переключение контекстов и рассылку сообщений в ЗОСРВ «Нейтрино», но не настолько большим, чтобы превысить предельные возможности драйверов для блочных операций или повысить издержки на рассылку сообщений. Оптимальным является использование значения 32 Кбайт.

Иногда приходится выполнять доступ к файлу на границах блоков для всего множества секторов диска. Поскольку самой маленькой единицей доступа к дисковому/блочному устройству является один сектор, то операции частичной записи при этом потребуют выполнения полного цикла чтения/мо­дификации/записи. Получить значение оптимального размера для операций ввода/вывода можно, вызвав функцию statvfs(), хотя для большинства дисков принимается значение 512 байт/сектор.

И наконец, для ситуаций, требующих очень высокой производительности (например, потоковое видео), можно обойти все процедуры буферизации файловой системы и воспользоваться прямым доступом в память (DMA) между областью данных пользователя и диском. Но в этом случае надо принимать в расчет следующие факторы:

В настоящее время мы рекомендуем использовать режим DMA лишь в случае крайней необходимости. Не все дисковые драйверы корректно поддерживают этот режим, поэтому не существует средств отправки запроса дисковому драйверу для задания требований к его интерфейсу с целью обеспечения безопасной работы в режиме DMA. Неопытные пользователи могут иметь большие проблемы при переходе в такой режим!

В некоторых случаях, когда вы знаете полный размер конечного файла с данными, имеет смысл попробовать сразу установить его предварительный размер, а не передавать эту функцию файловой системе, которая автоматически наращивает размер файла по мере его записи. Это даст возможность файловой системе выдать один явный запрос на выделение памяти вместо нескольких "порционных" запросов. В некоторых файловых системах такая возможность существует, так что файл можно разместить более оптимально и непрерывно. Кроме того, уменьшится число обновлений метаданных, что происходит во время фазы записи, а значит, увеличится производительность системы при записи, т.к. последовательный поток не будет разбиваться на фрагменты.

Для продления размера файла существует POSIX-функция ftruncate(). При стандартном использовании функции требуется, чтобы новое пространство данных было сначала сделано нулевым, подразумевая, что файл эффективно записывается дважды. Поэтому такой способ годится для случая, когда вы можете подготовить файл во время начальной фазы, когда вопрос с производительностью некритичен. Для продления размера файла без начального обнуления зоны данных существует и другая функция, devctl(), не входящая в состав интерфейса POSIX. Эта функция обеспечивает получение описанных выше преимуществ без затрат на стирание содержимого (см. параметр DCMD_FSYS_PREGROW_FILE в файле <sys/dcmd_blk.h>).

Конфигурация

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

В следующих разделах иллюстрируется влияние на производительность различных конфигураций.

Влияние значения параметра commit при блочных операциях ввода/вывода

В таблице ниже показано, как значение параметра commit влияет на время создания и удаления файла при работе на компьютере x86 PIII-450 с диском UDMA-2 EIDE, функционирующем под управлением файловой системы QNX4. Числа в таблице означают количество успешно созданных и удаленных за 1 сек файлов размером 0 Кбайт.

Уровень commit Создано файлов Удалено файлов
high 866 1221
medium 1030 2703
low 1211 2710
none 1407 2718

Обратите внимание, что при значении commit=high все операции записи на диск выполняются синхронно, поэтому требуются значительные затраты на обновление записей каталогов и POSIX-параметра mtime (временная метка последней модификации) в родительском каталоге. При значении commit=none все операции записи на диск оказываются отложенными (задержанными) в кэше. Поэтому несколько файлов могут быть созданы/удалены в блоке оперативной памяти без необходимости доступа к физическому диску (естественно, любой сбой электропитания приведет к потере этих файлов после перезапуска системы).

Размер буфера ввода/вывода

В таблице ниже показано, как размер буфера ввода/вывода влияет на последовательный доступ к файлу на компьютере x86 PIII-725 с диском UDMA-4 EIDE, работающем под управлением файловой системы QNX4. Числа в таблице соответствуют скорости передачи данных в мегабайтах в секунду при записи и чтении файла размером 256 Мбайт.

Размер буфера, Кбайт Запись Чтение
1 14 16
2 16 19
4 17 24
8 18 30
16 18 35
32 19 36
64 18 36
128 17 37

Обратите внимание, что скорость последовательного чтения данных при правильно выбранном размере буфера может удваиваться. Это происходит из-за того, что уменьшаются временные затраты на переключение контекстов и передачу сообщений. Заметьте, что чтение файла размером 256 Мбайт порциями по 1 Кбайт требует отправки 262 144 сообщений _IO_READ, тогда как при размере буфера (порции) в 16 Кбайт требуется только 16 384 таких сообщений, 1/16 от совсем не маленького перерасхода.

Производительность при записи не показывает такого впечатляющего результата, потому что по умолчанию данные помещаются в буферный кэш отложенной записи и записываются большими непрерывными блоками под управлением таймера. Разница почувствуется при использовании бита О_SYNC. Граничным фактором в данном случае является необходимость периодического синхронного обновления битовой карты и индексных дескрипторов при распределении памяти по мере роста размера файла (см. далее конкретный пример или случай с перезаписью уже распределенного файла).

Двойная буферизация

В таблице ниже показано влияние двойной буферизации при работе стандартной библиотеки ввода/вывода на компьютере x86 PIII-725 с диском UDMA-4 EIDE, работающем под управлением файловой системы QNX4. Числа в таблице соответствуют скорости передачи данных в мегабайтах в секунду при записи и чтении файла размером 256 Мбайт с размером буфера ввода/вывода 8 Кбайт.

Сценарий Запись Чтение
Дескриптор файла 18 31
Стандартный ввод/вывод 13 16
setvbuf() 17 30

Здесь можно увидеть влияние задаваемого по умолчанию размера буфера (BUFSIZ, или 1 Кбайт) при стандартном вводе/выводе. Если поступает запрос на передачу данных размером 8 Кбайт, то это происходит путем выполнения 8 операций над блоками размером 1 Кбайт. Обратите внимание, как работа стандартного ввода/вывода согласуется с приведенным ранее тестом (см. подраздел "Размер буфера ввода/вывода" ранее в этой статье) для значения размера 1 Кбайт, а случай с использованием дескриптора файла дает те же результаты, что и случай с размером буфера 8 Кбайт.

Когда используется функция setvbuf() для увеличения размера блока буфера для стандартного ввода/вывода до значения 8 Кбайт, то результаты сразу приближаются к оптимальному случаю использования дескриптора файла (маленькая разница возникает из-за усложнения программного кода и дополнительного использования функции memcpy() при передаче данных пользователя во внутренний стандартный файловый буфер ввода/вывода).

Сравнение функций, использующих файловый дескриптор и stdio

Вот еще один пример (таблица ниже), где сравнивается доступ с использованием дескриптора файла и стандартный ввод/вывод на компьютере x86 PIII-725 с диском UDMA-4 EIDE, работающем под управлением файловой системы QNX4. Числа в таблице соответствуют скорости передачи данных в мегабайтах в секунду при записи и чтении файла размером 256 Мбайт и при использовании файлового дескриптора (fd) и стандартного ввода/вывода (stdio).

Размер буфера Запись (fd) Чтение (fd) Запись (stdio) Чтение (stdio)
32 1,5 1,7 10,9 12,7
64 2,8 3,1 11,7 14,3
128 5,0 5,6 12,0 15,1
256 8,0 9,0 12,4 15,2
512 10,8 12,9 13,2 16,0
1024 14,1 16,9 13,1 16,3
2048 16,1 20,6 13,2 16,5
4096 17,1 24,0 13,9 16,5
8192 18,3 31,4 14,0 16,4
16 384 18,1 37,3 14,3 16,4

Обратите внимание, насколько доступ через функцию read() чувствителен к размеру буфера. Это связано с тем, что при каждом вызове функции read() отправляется сообщение _IO_READ и при этом происходит переключение контекста и отправка сообщения файловой системе. Если каждый раз передается малый объем данных, то непроизводительные издержки операционной системы становятся ощутимыми.

Поскольку при стандартном вводе/выводе с помощью функции fread() используется внутренний буфер размером 1 Кбайт, то число отправляемых сообщений _IO_READ оказывается постоянным независимо от размера пользовательского буфера. Поэтому пропускная способность для всех случаев напоминает ситуацию с доступом через дескриптор файла с размером буфера 1 Кбайт (наблюдается небольшая деградация при меньших значениях размера буфера из-за увеличивающегося количества вызовов библиотеки libc). Таким образом, вы должны на основании этих примеров выбрать в вашем приложении предпочтительный вариант использования процедур ввода/вывода при доступе к файлам.

Предварительное задание размера файла

В этом примере иллюстрируется, какое влияние оказывает предварительное задание размера файла с данными на компьютере x86 PIII-725 с диском UDMA-4 EIDE, работающем под управлением файловой системы QNX4. Числа в таблице ниже соответствуют времени в миллисекундах, которое необходимо для создания и записи файла размером 256 Мбайт при размере буфера ввода/вывода 8 Кбайт.

Сценарий Создание Запись Всего
write() 0 15073 15 073 (15 сек)
ftruncate() 13908 8510 22 418 (22 сек)
devctl() 55 8479 8534 (8,5 сек)

Обратите внимание, насколько замедляет работу постепенное наращивание длины файла в результате вызовов функции write() по сравнению с установлением размера путем однократного вызова функции ftruncate(). В последнем случае файловая система должна только один раз выделить большой/непрерывный блок экстентов данных и обновить атрибуты метаданных индексных дескрипторов. Отметьте также, что время перезаписи уже выделенных блоков данных значительно меньше времени при динамическом выделении блоков (последовательные записи не прерываются необходимым периодическим синхронным обновлением битовой карты).

Несмотря на то, что полное время на предварительную установку размера файла и на перезапись оказывается больше, чем для случая с постепенным наращиванием размера, сам этап предварительной установки может быть осуществлен при выполнении фазы инициализации, где временные затраты не очень критичны. Зато впоследствии повышается производительность во время собственно записи файла.

Оптимальным вариантом будет использование предварительной установки размера файла без начального обнуления зоны данных (что делает функция devctl()), после чего в выделенную область производится перезапись реальных данных.

Точная настройка USB устройств хранения данных

При существовании в конфигурации хоста больших файлов (например, музыкальных) на USB устройстве хранения данных, необходимо убедиться, что конфигурация обеспечивает достаточное количество оперативной памяти для опережающего чтения данных больших файлов, таких как MP3. Изменение конфигурации можно произвести корректировкой значений параметров cache и vnode, которые devb-umass передает io-blk.so с помощью опции blk.

Обычная начальная конфигурация для опции blk: cache=512k,vnode=256.

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

Планирование периодической фоновой работы с помощью cron и crontab

Сервер cron планирует выполнение команд на определенное время без вмешательства пользователя. Этот сервер поддерживает пользовательские записи cron и функционирует непрерывно. Сервер должен запускаться в фоновом режиме.


Note: Для работы сервера cron необходимо выделить каталог /var/spool/cron целиком. Поэтому на одну файловую систему, содержащую этот каталог, может приходиться только один сервер cron. Как правило, сервер cron функционирует на сетевом сервере. Команды определяются в соответствии с инструкцией, доступной в файлах crontab, перейти к которым можно с помощью утилиты crontab. Для минимизации затрат cron проверяет содержимое файлов /var/spool/cron/crontabs при первом запуске, а затем повторяет проверку только для файлов, измененных утилитой crontab.

Утилита cron использует данные, считанные из следующих источников:

/var/spool/cron
Для каждой команды cron предполагаются исключительные права на использование этого каталога.
/var/spool/cron/cron.allow
Если этот файл существует, он перечисляет пользователей, обладающих полномочиями на выполнение утилиты crontab. По умолчанию такими полномочиями обладают все пользователи. Список cron.deny (см. ниже) переопределяет значения в списке cron.allow.
/var/spool/cron/cron.deny
Если этот файл существует, он перечисляет пользователей, не обладающих полномочиями на выполнение утилиты crontab. Этот список переопределяет значения в списке пользователей, обладающих необходимыми полномочиями (файл cron.allow).
/var/spool/cron/crontabs/*
Из файлов этого каталога считываются периодически выполняемые команды.

Если файлы /var/spool/cron/cron.allow и /var/spool/cron/cron.deny не существуют, то изменение записей crontab разрешено только суперпользователю. Если файл cron.allow не существует, а файл cron.deny существует и является пустым, необходимыми полномочиями обладают все пользователи. В каждой строке файла полномочий содержится только одно имя пользователя.

После определения списка пользователей, которым нужно разрешить (или запретить) выполнение утилиты crontab, нужно создать записи по планированию периодической фоновой работы.

Утилита crontab используется для создания или замены пользовательской записи crontab. Новую запись crontab можно указать путем определения файла, содержащего настройки для записей crontab. Если этот файл не определен, используется стандартный поток ввода.

Запись crontab состоит из строк, в каждой из которых содержится 6 полей. Эти поля отделяются друг от друга пробелами. Первые пять из них – целочисленные шаблоны, определяющие следующие значения:

Каждый из этих шаблонов может быть обозначен звездочкой (все допустимые значения), элементом, списком элементов, разделенных запятой, или диапазоном через дефис (-).

Элемент – это число или два числа, указанные через дефис (т.е. интервал, включающий предельные значения). День может быть определен в двух полях (день месяца и день недели). Если указаны два значения, они оба применяются.

В качестве примера определения двух видов дней можно привести следующую строку:

0 0 1,15 * 1

Команда запускается в 00:00 на 1-й и 15-й день каждого месяца, а также каждый понедельник в 00:00. Для определения дней только в одном поле в другом поле необходимо ввести значение *:

0 0 * * 1

Команда выполняется только по понедельникам.

Шестое поле строки в записи crontab – это строка, выполняемая интерпретатором команд в определенное время. Символ % в этом поле (если он не замаскирован обратной косой чертой) преобразуется в символ newline (символ новой строки).

Интерпретатор команд выполняет только первую строку (до символа % или до конца строки) поля команды.

Примеры записей crontab:

Вызов программы calendar ежедневно через 1 минуту после полуночи:

1 0 * * * calendar -

Просмотр фактического времени на системной консоли каждые 20 минут:

1,21,41 * * * * (echo -n " "; date; echo) > /dev/con1

Очистка рабочих каталогов UUCP каждый день недели в 5:30:

30 5 * * 1-5 /usr/lib/uucp/uuclean

Удаление всех файлов в каталоге /tmp, не измененных за последние 7 дней:

0 4 * * * find /tmp -mtime +7 -exec rm -f {} \;

Пример настройки с нуля

Допустим, что существует несколько пользователей. Один из них new_user. Требуется, чтобы каждый час с понедельника по пятницу в файл /tmp/ls.log выводилось содержимое каталога /home/, при этом пользователю new_user периодическое выполнение такого задания было недоступно.

  1. Создать файл /var/spool/cron/cron.deny с содержимым

    new_user

  2. Создать файл ls_task, например в каталоге /home/ с содержимым

    0 * * * 1-5 ls -la /home/ > /tmp/ls.log

  3. Создать файл /tmp/ls.log:

    cd /tmp touch ls.log

  4. Запустить cron:

    cron &

  5. Активировать запись crontab:

    crontab /home/ls_task

  6. Проверить, активировалась ли запись:

    crontab -l




Предыдущий раздел: перейти