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

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

В составе ОСРВ QNX Neutrino имеются следующие утилиты, которые могут быть использованы для точной настройки системы:
Более подробно об этом см. в руководстве по утилитам "Описание программы. Часть 1. Справочник по утилитам" КПДА.10964-01 13 01.

Для отображения более подробных данных необходимо использовать tracelogger и Analysis Toolkit (см. подраздел «SAT»). SAT записывает в журнал события ядра, изменения состояния системы, с использованием специально-оборудованной версией ядра (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 оказывается весьма полезной. Выданная ей информация показывает, какие из процессов отбирают на себя большую часть процессорного времени. На основании этого можно перестроить систему приоритетов, отдавая преимущество наиболее важным потокам. (Не забывайте, что в ОСРВ QNX Neutrino приоритеты являются свойством потоков, а не процессов.) Более подробно об этом см. в подразд. "Приоритеты" раздела 4.

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

touch /etc/system/config/nophoton


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

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

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

Файловые системы и драйверы блочного ввода/вывода (devb-*)

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

При создании или конфигурировании файловой системы приходится находить баланс между характеристиками производительности и отказоустойчивости.
Например, создание нового файла — с помощью функции create() — может потребовать выполнения всех необходимых циклов физической записи на диск, включая добавление нового имени файла в каталог файловой системы на диске, и только после этого клиент получает сигнал об успешном завершении операции.
Например, при записи данных в файл — с помощью функции write() — может потребоваться немедленная выдача ответа клиенту, тогда как данные будут сохраняться в кэш-памяти с целью их объединения с другими записываемыми на диск данными, чтобы сформировать большой непрерывный блок для однократного последовательного доступа к диску. При этом существует опасность потери данных в случае отказа электропитания.

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

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

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

Самой дорогостоящей операцией для файловой системы является обновление метаданных. Это обусловлено двумя причинами:
Почти все операции в файловой системе (даже чтение файла, если не задан ключ noatime, см. описание функции io-blk.so в руководстве "Описание программы. Часть 1. Справочник по утилитам" КПДА.10964-01 13 01) требуют, в той или иной степени, обновления метаданных.

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

Некоторые операции в файловой системе воздействуют сразу на несколько блоков диска. Например, рассмотрим ситуацию создания или удаления файла. В большинстве файловых систем имя файла (или ссылка на него, 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 Кбайт, предоставляемый средствами стандартного ввода/вывода, существенно сокращает число посылаемых в файловую систему сообщений. Для улучшения производительности можно использовать функции:
Оптимизировать производительность можно, организовав доступ к диску порциями удобного размера. Этот размер должен быть достаточно большим для минимизации издержек на переключение контекстов и рассылку сообщений в ОСРВ QNX Neutrino, но не настолько большим, чтобы превысить предельные возможности драйверов для блочных операций или повысить издержки на рассылку сообщений. Оптимальным является использование значения 32 Кбайт.

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

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

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

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

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

Управлять балансом между производительностью и отказоустойчивостью можно либо глобально, либо через файлы конфигурации. Далее приведены два варианта такого управления.
Функции fsync() и sync() дают возможность при отложенной записи сбрасывать на диск содержимое кэша файловой системы по требованию. В противном случае любые подготовленные для записи данные передаются из кэша на диск под управлением глобального параметра blk delwri= (значение по умолчанию равно 2 сек, см. описание файла io-blk.so в руководстве "Описание программы. Часть 1. Справочник по утилитам" КПДА.10964-01 13 01).
Примечание. При любом уровне, который менее отказоустойчив в сравнении с уровнем, задаваемым по умолчанию (medium), файловая система не будет гарантировать того же уровня целостности данных после сбоя из-за потери электропитания, поскольку в этом случае многоблочные обновления данных могут быть выполнены некорректно.

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

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

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

Таблица 20.1

Уровень commit

Создано файлов

Удалено файлов

high

866

1221

medium

1030

2703

low

1211

2710

none

1407

2718


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

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

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

Таблица 20.2

Размер буфера, Кбайт

Запись

Чтение

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. Граничным фактором в данном случае является необходимость периодического синхронного обновления битовой карты и индексных дескрипторов при распределении памяти по мере роста размера файла (см. далее конкретный пример или случай с перезаписью уже распределенного файла).

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

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

Таблица 20.3

Сценарий

Запись

Чтение

Дескриптор файла

18

31

Стандартный ввод/вывод

13

16

setvbuf()

17

30


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

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

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

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

Таблица 20.4

Размер буфера

Запись (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, работающем под управлением файловой системы QNX 4. Числа в табл. 20.5 соответствуют времени в миллисекундах, которое необходимо для создания и записи файла размером 256 Мбайт при размере буфера ввода/вывода 8 Кбайт.

Таблица 20.5

Сценарий

Создание

Запись

Всего

write()

0

15073

15 073 (15 сек)

ftruncate()

13908

8510

22 418 (22 сек)

devctl()

55

8479

8534 (8,5 сек)



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

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

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

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

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

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

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

Насколько маленькой может быть ваша система?

Наилучшим способом уменьшения размера системы является использование нашей среды разработки IDE для создания образа ОС. Рабочая среда "системный компоновщик" (System Builder) включает в себя инструмент Dietician (Диетолог), который помогает "похудеть" библиотекам, включаемым в образ системы. Более подробнее об этом см. в руководстве "IDE Users Guide", а также в руководстве "Building Embedded Systems".