---

[[toc]]

---

# Многозадачность, Планирование на основе Приоритетов

Здесь приводятся заметки для Группы Автоматизации **DaqGroup**  
по **планированию задач** (потоков, процессов) на основе **приоритетов**,  
в первую очередь в рамках библиотеки **crwlib** и пакета **crwdaq**.  
Смотрите также "ленту новостей" **[daqgroupnews.md](daqgroupnews.md)**.

<b id="toc" class="big memo">Содержание</b>

---

## Планирование задач и система приоритетов - общие сведения

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

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

  Итак, современные **OS** (операционные системы) используют для (псевдо) параллельного выполнения задач, т.е.
многозадачности (_multitasking_) тот или иной алгоритм **разделения времени**, при котором каждой
задаче выделяется часть процессорного времени.
Этот алгоритм называют **диспетчеризацией** или **планированием**,
а соответствующий программный модуль - **диспетчером** (_dispatcher_)
или **планировщиком** (_scheduler_).

Большинство современных планировщиков имеют **систему приоритетов**, согласно которой планирование
выполнения задач (процессов, потоков) выполняется с учетом их приоритетов.
Процессорное время выделяется в первую очередь задачам, имеющим более высокий приоритет.
При этом высокоприоритетная задача может вытеснить (_preempt_) низкоприоритетную, если
ей необходимо обслуживание.
Такое поведение планировщика называется приоритетной вытесняющей многозадачностью
(_priority based preemptive multitasking_).

Несмотря на концептуальную простоту такой системы приоритетов, её реализации довольно сложны,
имеют много параметров, многие из которых в разных системах именуются по-разному, что вносит
дополнительную путаницу.


<a href="#toc" class="bold memo">Перейти к Содержанию</a>

---

## Логическая система приоритетов Free Pascal

Операционные системы - такие как **Linux**, **Windows** - используют разные
алгоритмы диспетчеризации и системы приоритетов, и даже разную терминологию.
Свести эти разные системы в одну формулу не получается.

Поэтому в библиотеке **crwlib** на языке **Free Pascal** принята **логическая**
схема приоритетов, которая проецируется (моделируется) на каждой платформе по-своему.

Приоритеты процессов (_process_) и потоков (_thread_) определяются:

1. Классом приоритета процесса (**ProcessPriority**) из набора 6 классов:

   - **Idle**   - фоновый,
   - **Lower**  - ниже нормального,
   - **Normal** - нормальный,
   - **Higher** - выше нормального,
   - **High**   - высокий,
   - **RealTime** - реального времени.

2. Относительным приоритетом потока (**ThreadPriority**) из набора 7 приоритетов:

   - **tpIdle** - фоновый поток,
   - **tpLowest** - низкий приоритет,
   - **tpLower** - приоритет ниже нормального,
   - **tpNormal** - нормальный приоритет,
   - **tpHigher** - приоритет выше нормального,
   - **tpHighest** - высокий приоритет,
   - **tpTimeCritical** - критичный ко времени поток.

Таким образом, есть набор из **`6 * 7 = 42`** логических приоритетов.  
С помощью **таблицы приоритетов** логические приоритеты на конкретной
платформе переводятся в системно - зависимые уровни физических приоритетов,
значение и смысл которых определяется для каждой системы по-своему.

Название **RealTime** класса приоритета не должно обманывать: речь идет не о (жестком)
реальном времени с гарантией обслуживания, а о пожелании или рекомендации операционной
системе выделять процессу больше времени в приоритетном порядке. Однако процессы класса
приоритета **RealTime** остаются обычными пользовательскими процессами, приоритет которых
ниже любой системной задачи (типа обработки прерываний или аппаратного ввода-вывода).

<a href="#toc" class="bold memo">Перейти к Содержанию</a>

---

## Система приоритетов Windows

С точки зрения ядра **Windows**, пользовательские задачи (процессы и потоки) имеют
абсолютный **уровень** (_level_) приоритета в диапазоне **`[0..31]`**.
Уровни не инвертированы, т.е. чем выше уровень, тем выше приоритет.
Уровень **0** соответствует пустому циклу, т.е. специальному (фиктивному)
системному потоку, утилизирующему не используемое другими потоками время.

Потоки процессов с классом приоритета **RealTime** имеют уровень приоритета **`[16..31]`**,  
все остальные пользовательские потоки имеют уровень приоритета в диапазоне **`[1..15]`**.  
Нормальный (по умолчанию) приоритет (**Normal**, **tpNormal**) соответствует
уровню приоритета **8**.

Приоритеты процессов и потоков **Windows** проецируются на логическую систему приоритетов
с помощью следующей таблицы.  
Эта таблица определена системой и не меняется. Можно менять только принадлежность потока
к той или иной ячейке в таблице, задавая класс приоритета и приоритет потока.

Таблица приоритетов **Windows**:

| Поток/<br>Процесс | tpIdle | tpLowest | tpLower | tpNormal | tpHigher | tpHighest | tpTimeCritical |
|-------------------|--------|----------|---------|----------|----------|-----------|----------------|
| **Idle**          |   1    |   2      |   3     |   4      |    5     |   6       |  15            |
| **Lower**         |   1    |   4      |   5     |   6      |    7     |   8       |  15            |
| **Normal**        |   1    |   6      |   7     |   8      |    9     |  10       |  15            |
| **Higher**        |   1    |   8      |   9     |  10      |   11     |  12       |  15            |
| **High**          |   1    |  11      |  12     |  13      |   14     |  15       |  15            |
| **RealTime**      |  16    |  22      |  23     |  24      |   25     |  26       |  31            |

Переключение потоков идет в циклическом режиме **Round Robin** по таймеру с квантом времени
**`10 мс`** (на одноядерных процессорах) или **`16 мс`** (на многоядерных процессорах).
С помощью специальных системных функций системный квант времени можно изменять
в диапазоне **`1..16 мс`**.

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

<a href="#toc" class="bold memo">Перейти к Содержанию</a>

---

## Система приоритетов Linux

Планировщик (**scheduler**) ядра **Linux** оперирует с потоками,
имеющими параметры описания приоритетов (**prio**, **nice**, **rlimit**) и
алгоритмы планирования, приведенные ниже.

**Алгоритмы планирования.**

| Алгоритм           | **prio** | **nice** | **rlimit** | Описание                                                        |
|--------------------|----------|----------|------------|-----------------------------------------------------------------|
| **Алгоритмы**      | **разделения** | **времени** |   | Используются для большинства обычных прикладных задач.          |
| **SCHED_OTHER**    | 100..139 | -20..19  | 1..40      | Планировщик по умолчанию **CFS** (_Completely Fair Scheduler_). |
| **SCHED_BATCH**    | 100..139 | -20..19  | 1..40      | Планировщик пакетной обработки данных (неинтерактивных задач).  |
| **SCHED_IDLE**     | 100..139 | нет      | нет        | Фоновый планировщик с очень низким приоритетом (_idle_).        |
| **Алгоритмы**      | **реального**  | **времени** |   | Используются для важных (критических) прикладных задач.         |
| **SCHED_FIFO**     | 1..99    | нет      | нет        | Планирование по порядку **FCFS** (_first comes, first serve_).  |
| **SCHED_RR**       | 1..99    | нет      | нет        | Планирование по циклу **RR** (_round robin_).                   |
| **SCHED_DEADLINE** | 1..99    | нет      | нет        | Планирование с предельным сроком обслуживания.                  |

Алгоритмы планирования делятся на две группы:

- Алгоритмы реального времени (**SCHED_FIFO**, **SCHED_RR**, **SCHED_DEADLINE**) с параметром **prio** в диапазоне **[1..99]**.  
  В этих алгоритмах параметры **nice**, **rlimit** не используются, а задается параметр **prio**.

- Алгоритмы разделения времени (**SCHED_OTHER**, **SCHED_BATCH**, **SCHED_IDLE**) с параметром **prio** в диапазоне **[100..139]**.  
  В этих алгоритмах параметр **prio** явно не используются, вместо этого он задается косвенно через задание параметра **nice**,  
  а вспомогательный параметр  **rlimit** используется в некоторых вызовах вместо **nice**.

Все алгоритмы реального времени имеют приоритет над алгоритмами разделения времени. В силу того, что высокий приоритет алгоритмов реального
времени при ошибке программирования может легко привести к блокировке (зависанию) системы, эти алгоритмы надо отнести скорее к системному
программированию. Здесь они не обсуждаются. При реализации библиотек **crwlib** было принято решение ограничиться алгоритмами разделения
времени - а фактически алгоритмом по умолчанию **SCHED_OTHER**, который используется для большинства прикладных задач.
При этом приоритет задается с помощью параметра **nice**.

**Параметры для описания приоритета.**

| Параметр   | Диапазон | Формулы                          | Описание                                                                |
|------------|----------|----------------------------------|-------------------------------------------------------------------------|
| **prio**   | 1..139   | **`120+nice`**, **`140-rlimit`** | Инверсный приоритет с точки зрения ядра.                                |
| **nice**   | -20..19  | **`prio-120`**, **`20-rlimit`**  | Инверсный приоритет (уступчивость) для алгоритмов разделения времени.   |
| **rlimit** | 1..40    | **`140-prio`**, **`20-nice`**    | Приоритет (в формате команды rlimit) для алгоритмов разделения времени. |

- Параметр **prio** - инверсный приоритет (чем ниже **prio**, тем выше приоритет) с точки зрения ядра.  
  Для алгоритмов разделения времени работают формулы:  
  **`prio=120+nice`**, **`prio=140-rlimit`**

- Параметр **nice** (приятность, уступчивость) - инверсный приоритет для алгоритмов разделения времени.  
  Для алгоритмов разделения времени работают формулы:  
  **`nice=prio-120`**, **`nice=20-rlimit`**

- Параметр **rlimit** - приоритет для алгоритмов разделения времени в формате команды **rlimit**.  
  Для алгоритмов разделения времени работают формулы:  
  **`rlimit=140-prio`**, **`rlimit=20-nice`**

Параметр **rlimit** используется в системных вызовах (и в команде _rlimit_) по той причине,
что параметр **nice** лежит в диапазоне **[-20..19]** и включает значение **-1**,
которое используется как признак ошибки в системных вызовах.
Это создает неоднозначность интерпретации результатов системных вызовов.
Для устранения неоднозначности вместо **nice** используется легко вычисляемое значение **rlimit**,
которое неотрицательно и заведомо отличается от кода ошибки.

Для задания приоритетов задач (процессов, потоков) в системе **Unix** в библиотеке **crwlib** используется
параметр **nice**, задаваемый в соответствии с таблицей:


**Таблица приоритетов** в системе **Unix** в терминах **Nice**:

| Поток/<br>Процесс | tpIdle | tpLowest | tpLower | tpNormal | tpHigher | tpHighest | tpTimeCritical |
|-------------------|--------|----------|---------|----------|----------|-----------|----------------|
| **Idle**          |   19   |    17    |   16    |   15     |   14     |     13    |   -5           |
| **Lower**         |   19   |    12    |   11    |   10     |    9     |      8    |  -10           |
| **Normal**        |   19   |     2    |    1    |    0     |   -1     |     -2    |  -20           |
| **Higher**        |   14   |    -3    |   -4    |   -5     |   -6     |     -7    |  -20           |
| **High**          |    9   |    -8    |   -9    |  -10     |  -11     |    -12    |  -20           |
| **RealTime**      |    4   |   -13    |  -14    |  -15     |  -16     |    -17    |  -20           |


**Таблица приоритетов** в системе **Linux** в терминах **rlimit**:

| Поток/<br>Процесс | tpIdle | tpLowest | tpLower | tpNormal | tpHigher | tpHighest | tpTimeCritical |
|-------------------|--------|----------|---------|----------|----------|-----------|----------------|
| **Idle**          |    1   |    3     |    4    |   5      |  6       |      7    |     25         |
| **Lower**         |    1   |    8     |    9    |  10      | 11       |     12    |     30         |
| **Normal**        |    1   |   18     |   19    |  20      | 21       |     22    |     40         |
| **Higher**        |    6   |   23     |   24    |  25      | 26       |     27    |     40         |
| **High**          |   11   |   28     |   29    |  30      | 31       |     32    |     40         |
| **RealTime**      |   16   |   33     |   34    |  35      | 36       |     37    |     40         |

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

При задании приоритетов надо иметь в виду, что в системе **Unix** процессы пользователя могут
только уменьшать свой приоритет, т.е. повышать значение **nice**. Повышать приоритет можно только
с правами **root**, поэтому предполагается, что для пользователя доступен вызов **sudo**,
причем без пароля. Это поведение **sudo** можно настроить стандартными средствами, через
панель управления.

Необходимо также понимать разницу в работе системы приоритетов **Windows** и **Linux**.

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

Под **Linix** параметр **nice** каждого потока задается независимо. Значение **nice** для потоков
вычисляется в момент его задания по таблице с текущим значением приоритета процесса, т.е. основного
потока процесса. Если приоритет процесса изменяется, это означает лишь изменение параметра **nice**
для основного потока процесса. Все остальные потоки свой приоритет не меняют. Это различие в поведении
приоритетов потоков надо иметь в виду при решении задач прикладного программирования.




<a href="#toc" class="bold memo">Перейти к Содержанию</a>

---

> Успешной работы, группа автоматизации **DaqGroup**!
>
> *Copyright(c) 2023 Alexey Kuryakin daqgroup@mail.ru*

---
