Pull to refresh

ОСРВ QNX: Планирование потоков

*nix *
Продолжение цикла заметок об операционной системе реального времени QNX. В этот раз я хотел бы рассказать о планировании потоков в QNX6*. Как уже известно читателям (которые ознакомились с предыдущей заметкой цикла), микроядро QNX6 управляет потоками, а не процессами. И именно микроядро осуществляет загрузку контекста того потока, который должен получить управление в следующий момент. Выбор потока, который будет выполняться процессором (т.е. активно использовать процессорное время) и есть планирование потоков.

Когда происходит планирование потоков


Микроядро QNX Neutrino не работает постоянно, а получает управление только в случае системных вызовов, исключений и прерываний. Также микроядро во время своей работы выполняет планирование потоков. Отсюда можно сделать верный вывод, что операция планирования потоков происходит не сама по себе, а по какому-то событию. На самом деле таких событий немного:
  • Вытеснение. Если поток с более высоким приоритетом, чем выполняющийся в настоящее время, перешёл в состояние готовности (READY), то микроядро остановит поток, выполняющийся в данный момент, переключит контексты и запустит на выполнение поток с более высоким приоритетом. Поток, который выполнялся до этого останется первым в очереди на выполнение.
  • Блокирование. Поток во время своего выполнения (т.е. во время работы) может вызвать функцию, которая приведёт к его блокированию. Например, попытается захватить семафор или передаст сообщение при помощи системной функции MsgSend() (прямо или опосредованно). В этом случае ядро удалит такой процесс из очереди на выполнение и передаст управление другому потоку.
  • Уступка (передача управления, yield). Поток может добровольно передать управление, если вызовет функцию sched_yield(). В этом случае поток ставится последним в очереди на выполнение и микроядро передаст управление другому потоку (может быть и тому, который только что отдал управление).

Параметры потока, влияющие на планирование


Одна из основных функций микроядра QNX Neutrino (и, пожалуй, самая важная после обмена сообщениями) — это планирование потоков. Именно микроядро переключает контексты и выбирает, какой поток будет выполняться в следующий момент времени. Микроядро делает всё это не просто так и не по желанию своей левой пятки, а основываясь на следующих параметрах потоков:
  • Приоритет потока (уровень приоритета потока). Каждый поток в ОСРВ QNX6 выполняется на каком-то определённом приоритете. Чем выше приоритет, тем больше шансов у потока получить процессор в первую очередь. При наличии в системе двух и более потоков в состоянии READY (готовых к выполнению), микроядро передаст управление тому потоку, чей приоритет выше.
  • Дисциплина планирования. Каждый поток в системе выполняется с определённой дисциплиной планирования. Микроядро учитывает дисциплину планирования при наличии в системе двух и более потоков в состоянии READY, выполняющихся, на одном приоритете.
Приоритет потока это число в диапазоне 0-255 для потоков суперпользователя (выполняющихся от пользователя root) и 0-63 для потоков обычных пользователей. Диапазон доступных рядовому пользователю приоритетов может быть изменён при подготовке загрузочного образа. Для этого надо указать опцию -p программе procnto. Также следует учитывать, что на нулевом (самом низком) приоритете выполняется поток idle, который всегда получает управление, если в системе нет больше потоков с более высокими приоритетами в состоянии READY.

В операционной системе QNX6 поддерживается несколько дисциплин планирования потоков: FIFO, карусельная (циклическая, round-robin, RR) и спорадическая**. Этот атрибут потока будет учитываться только, если микроядру приходится выбирать между потоками с одинаковым уровнем приоритета. Дисциплины планирования будут описаны дальше.

Существует ещё один фактор, который влияет на порядок переключения потоков. Все потоки готовые выполняться и использовать процессор (т.е. потоки в состоянии READY) помещаются в очередь. Таких очередей в системе 256 (по количеству приоритетов). При прочих равных условиях, когда микроядру приходится выбирать среди двух потоков с одинаковым уровнем приоритета, выполняться начнёт тот поток, который находится первым в очереди. Ещё раз повторюсь, при вытеснении потока более приоритетным, он ставится первым в очереди, а при уступке (вызове sched_yield()), поток становится последним в очереди.

Дисциплина планирования FIFO


Если потоку задана дисциплина планирования FIFO (First In First Out, первый на входе, первый на выходе), то он может выполняться сколь угодно долго. Управление будет передано другому потоку только если поток будет вытеснен более приоритетным потоком, поток заблокируется или добровольно отдаст управление. При использовании этой дисциплины планирования, поток, который выполняет длительные математические вычисления, может полностью захватить процессор (т.е. не позволит выполняться потокам с тем же и более низким приоритетом).

Карусельная дисциплина планирования


Эта дисциплина планирования полностью аналогичная FIFO, за исключением того, что поток не выполняется «бесконечно», а работает только на протяжении определённого кванта времени (timeslice). По истечении кванта времени микроядро ставит процесс в конец очереди потоков, готовых к выполнению, и управление передаётся следующему потоку (на том же уровне приоритета). Если же на этом уровне приоритета нет других потоков в состоянии READY, то потоку выделяется ещё один квант времени.

Квант времени, который выделяется потокам с карусельной дисциплиной диспетчеризации для работы, может быть определён при помощи функции sched_rr_get_interval(). На самом деле квант времени (timeslice) ровно в четыре раза больше тактового интервала (ticksize). В свою очередь, тактовый интервал равен 1мс в системах с процессором 40МГц и выше и 10мс в системах с более медленным процессором***. Получается, что в привычных нам x86 компьютерах и ноутбуках квант времени равен 4мс.

Спорадическая дисциплина планирования


Как и в FIFO­ планировании, поток, для которого применяется спорадическое планирование, выполняется до тех пор, пока он не блокируется или не будет вытеснен потоком с более высоким приоритетом. Кроме того, так же как и в адаптивном планировании, поток, для которого применяется спорадическое планирование, получает пониженный приоритет. Однако спорадическое планирование даёт значительно более точное управление потоком.

При спорадическом планировании, приоритет потока может динамически изменяться между приоритетом переднего плана (foreground, нормальным приоритетом) и фоновым (background, пониженным) приоритетом. Для управления этим спорадическим переходом используются следующие параметры:
  • Начальный бюджет потока (initial budget) (С) — количество времени, за которое поток может выполняться с нормальным приоритетом (N), перед тем как получить пониженный приоритет (L).
  • Пониженный приоритет (low priority) (L) — приоритетный уровень, до которого приоритет потока будет снижен. При пониженном приоритете (L) поток выполняется в фоновом режиме. Если же поток имеет нормальный приоритет (N), он выполняется с приоритетом переднего плана.
  • Период пополнения (replenishment period) (T) — период времени, в течение которого поток может расходовать свой бюджет выполнения (execution budget).
  • Максимальное число текущих пополнений (max number of pending replenishments) — это значение устанавливает ограничение на количество выполняемых операций пополнения, тем самым ограничивая объём системных ресурсов, выделяемых на дисциплину спорадического планирования.
Как видно из рис. 1, алгоритм спорадического планирования устанавливает начальный бюджет выполнения потока C, который расходуется потоком в процессе его выполнения и пополняется с периодичностью, определенной параметром T. Когда поток блокируется, израсходованная часть бюджета выполнения потока R пополняется через какое-то установленное время (например, через 4мс), отсчитываемое от момента, когда поток перешёл в состояние готовности.


Рис. 1. Пополнение периода выполнения потока происходит периодически

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

Представим, например, систему, в которой поток никогда не блокируется и не прерывается — рис. 2.


Рис. 2. Приоритет потока снижается до того момента, пока его бюджет выполнения не пополнится

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

Как только происходит пополнение, приоритет потока повышается до начального уровня. Таким образом, в правильно настроенной системе поток выполняется каждый период времени Т в течение максимального времени С. Это обеспечивает такой порядок, при котором каждый поток, выполняемый с приоритетом N, будет использовать только C/T процентов системных ресурсов.

Когда поток блокируется несколько раз, несколько операций пополнения могут происходить в разные моменты времени. Это может означать, что бюджет выполнения потока в пределах периода времени T дойдёт до значения C; однако, на протяжении этого периода бюджет может не быть непрерывным.


Рис. 3. Приоритет потока изменяется между повышенным и пониженным

На рис. 3 видно, что в течение каждого 40 мс периода пополнения T бюджет выполнения потока C составляет 10 мс.
  1. Поток блокируется через 3 мс, поэтому 3 мс операция пополнения будет запланирована к выполнению через 40 мс, т.е. на тот момент завершения первого периода пополнения.
  2. Выполнение потока возобновляется на 6-й миллисекунде, и этот момент становится началом следующего периода пополнения T. В бюджете выполнения потока еще остается запас в 7 мс.
  3. Поток выполняется без блокировки в течение 7 мс, в результате чего бюджет выполнения потока исчерпывается, и приоритет потока снижается до уровня L, на котором он сможет или не сможет получить управление. Пополнение в объёме 7 мс запланировано на 46-ю миллисекунду (40 + 6), т.е. по истечении периода T.
  4. На 40-й миллисекунде бюджет потока пополняется на 3 мс (см. шаг 1 на схеме), в результате чего приоритет потока поднимается до нормального.
  5. Поток расходует 3 мс своего бюджета и затем снова переходит на пониженный приоритет.
  6. На 46-й миллисекунде бюджет потока пополняется на 7 мс (см. шаг 3), и поток снова получает нормальный приоритет.
И так далее. Таким образом, перемещаясь между двумя уровнями приоритета, поток обслуживает апериодические события в системе предсказуемо и управляемо.

Как установить приоритет и дисциплину планирования из программы


Каждый поток при запуске наследует свои приоритет и дисциплину планирования от потока-родителя. Во время работы поток может изменить эти атрибуты. Для этой цели в QNX6 присутствуют следующие функции:
POSIX-вызов Описание
sched_getparam() Получить приоритет.
sched_setparam() Установить приоритет.
sched_getscheduler() Получить дисциплину планирования.
sched_setscheduler() Установить дисциплину планирования.
Для того, чтобы получить или установить приоритет и дисциплину планирования не всего процесса, а отдельного потока, можно воспользоваться функциями SchedGet() и SchedSet().

Немного администрирования


В предыдущей заметке я уже ссылался на команду pidin. В этот раз мы познакомимся ещё с двумя командами, которые специфичны для QNX. Знать и уметь пользоваться этими командами должен каждый администратор QNX6. И, пожалуй, из самых важных команд это use.

Утилита use в каком-то роде аналог команды man. Утилита позволяет получить справку по исполняемому модулю (бинарному исполняемому файлу, скрипту или разделяемой библиотеке). Принцип работы use несколько отличается от man, т.к. вся справочная информация хранится в самом исполняемом модуле, а не отдельно. Вызывается команда, довольно просто, например:

# use sleep
sleep - suspend execution for an interval (POSIX)

sleep time
Where:
 time  is the number of seconds to sleep and can be a non-negative
       floating point number (0 <= time <= 4294967295).

Утилита use выводит небольшую справку по работе с командой, полное описание доступно в справочной системе.

Другая полезная команда, которая обязательно должна быть в арсенале каждого уважающего себя администратора QNX6 это pidin. Эта утилита предоставляет различного рода информацию о системе, в том числе и по исполняющимся потокам и процессам (в этом случае утилита похожа на утилиту ps). Например, чтобы посмотреть общую информацию по системе, следует выполнить следующую команду:

# pidin in
CPU:X86 Release:6.5.0  FreeMem:166Mb/255Mb BootTime:Jul 05 15:53:27 MSKS 2011
Processes: 43, Threads: 107
Processor1: 131758 Pentium II Stepping 5 2593MHz FPU

Вызов утилиты без параметров выведет на экран информацию по всем процессам и потокам. Чтобы получить информацию по интересующему процессу достаточно указать ключ -P, например:

# pidin -P io-audio
     pid tid name               prio STATE       Blocked
   90127   1 sbin/io-audio       10o SIGWAITINFO
   90127   2 sbin/io-audio       10o RECEIVE     1
   90127   3 sbin/io-audio       10o RECEIVE     1
   90127   4 sbin/io-audio       10o RECEIVE     1
   90127   5 sbin/io-audio       50r INTR
   90127   6 sbin/io-audio       50r RECEIVE     7

Можно посмотреть, как процесс использует память, для этого надо выполнить следующую команду:

# pidin -P io-audio mem
     pid tid name               prio STATE            code  data        stack
   90127   1 sbin/io-audio       10o SIGWAITINFO      128K  112K  8192(516K)*
   90127   2 sbin/io-audio       10o RECEIVE          128K  112K  4096(132K)
   90127   3 sbin/io-audio       10o RECEIVE          128K  112K  8192(132K)
   90127   4 sbin/io-audio       10o RECEIVE          128K  112K  4096(132K)
   90127   5 sbin/io-audio       50r INTR             128K  112K  4096(132K)
   90127   6 sbin/io-audio       50r RECEIVE          128K  112K  4096(132K)
            libc.so.3          @b0300000             472K   12K
            a-ctrl-audiopci.so @b8200000              12K  4096
            deva-mixer-ac97.so @b8204000              24K  8192

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

И последняя на сегодня, но не по значению, утилита slay. Как не сложно догадаться, эта команда используется для отправки сигналов процессам. По умолчанию отправляется сигнал SIGTERM, что, обычно, приводит к завершению процесса. Можно указать другой сигнал, который необходимо послать процессу. При таком использовании slay аналогична команде kill, но что самое интересное, команда slay принимает не только идентификатор процесса (PID), но и имя процесса. Это тоже очень удобно при администрировании. Помимо отправки сигналов, утилита может использоваться для смены приоритета или дисциплины планирования процесса. Если требуется изменить характеристики какого-то одного потока, то можно указать ключ -T. Следующие несколько команд изменяют приоритет и дисциплину планирования 3 потока процесса io-audio:

[22:47:33 root]# pidin -P io-audio
     pid tid name               prio STATE       Blocked
   90127   1 sbin/io-audio       10o SIGWAITINFO
   90127   2 sbin/io-audio       10o RECEIVE     1
   90127   3 sbin/io-audio       10o RECEIVE     1
   90127   4 sbin/io-audio       10o RECEIVE     1
   90127   5 sbin/io-audio       50r INTR
   90127   6 sbin/io-audio       50r RECEIVE     7
[22:47:36 root]# slay -T 3 -P 11r io-audio
[22:47:38 root]# pidin -P io-audio
     pid tid name               prio STATE       Blocked
   90127   1 sbin/io-audio       10o SIGWAITINFO
   90127   2 sbin/io-audio       10o RECEIVE     1
   90127   3 sbin/io-audio       11r RECEIVE     1
   90127   4 sbin/io-audio       10o RECEIVE     1
   90127   5 sbin/io-audio       50r INTR
   90127   6 sbin/io-audio       50r RECEIVE     7

Полное описание утилиты slay доступно в справочной системе QNX.

На всякий случай, хотел отметить, что в QNX6 присутствуют и знакомые UNIX утилиты ps и kill. Однако, пользоваться в QNX6 гораздо удобнее именно pidin и slay, т.к. они учитывают специфику системы.

Заключение


После прочтения этой заметки вы получили представление о планировании потоков в QNX6, достаточные знания о приоритетах и дисциплинах планирования, системных функциях и утилитах для управления процессами и потоками. В QNX также существует интересная технология Adaptive Partitioning (адаптивная декомпозиция), которая позволяет формировать группы процессов и назначать им процент использования процессорного времени. Чтобы не валить всё в одну кучу, я постараюсь описать эту технологию в одной из следующих заметок.

Список литературы

  1. Операционная система реального времени QNX Neutrino 6.3. Системная архитектура. ISBN 5-94157-827-X
  2. Операционная система реального времени QNX Neutrino 6.3. Руководство пользователя. ISBN 978-5-9775-0370-9
  3. Роб Кртен, «Введение в QNX Neutrino 2. Руководство для разработчиков приложений реального времени», 2-е издание. ISBN 978-5-9775-0681-6

* Под QNX6 в данной заметке понимается QNX 6.5.0. Поскольку ядро QNX Neutrino может быть доработано в одной из следующих версий, то механизмы планирования потоков, изложенные тут, могут измениться.

** В QNX6 есть также другая дисциплина планирования (OTHER, o), которая тождественна карусельной (round-robin, RR, r).

*** Тактовый интервал (ticksize) можно изменить, например, при помощи функции ClockPeriod().
Tags:
Hubs:
Total votes 52: ↑48 and ↓4 +44
Views 21K
Comments Comments 13