Переключение центрального процессора между задачами (процессами и нитями) выполняет специальная компонента подсистемы управления процессами, называемая планировщиком (scheduler). Именно планировщик определенным образом выбирает из множества неспящих, готовых к выполнению (runable) задач одну, которую переводит в состояние выполнения (running).
Процедуры, определяющие способ выбора и моменты выполнения выбора, называются алгоритмами планирования. Выбор задачи, подлежащей выполнению, естественным образом происходит в моменты времени, когда текущая выполнявшаяся задача переходит в состояние сна (sleep) в результате выполнения операции ввода-вывода.
Вытесняющие алгоритмы планирования, кроме всего прочего, ограничивают непрерывное время выполнения задачи, принудительно прерывая ее выполнение по исчерпанию выданного ей кванта времени (timeslice) и вытесняя ее во множество готовых, после чего производят выбор новой задачи, подлежащей выполнению.
По умолчанию для пользовательских задач используется вытесняющий алгоритм CFS (completely fair scheduler), согласно которому процессорное время распределяется между неспящими задачами справедливым (fair) образом.
Для каждой задачи определяется выделяемая справедливая (в соответствии с ее относительным «приоритетом») доля процессорного времени, которую она должна получить при конкуренции за процессор. Для двух задач с любыми одинаковыми приоритетами должны быть выделены равные доли (в 50% процессорного времени), а при различии в приоритетах на одну ступень разница между выделяемыми долями должна составить ≈10% процессорного времени (т. е. 55 и 45% соответственно).
Для удовлетворения этого требования алгоритм планирования CFS назначает каждой ступени приоритета соответствующий «вес» задачи, а процессорное время делит между всеми неспящими задачами пропорционально их весам. Шкала весов учитывает только требование 10% разницы между двумя задачами с различием в относительных приоритетах на одну ступень.
Таким образом, две задачи с любыми одинаковыми приоритетами будут иметь равные веса wi =wj = w, а доли процессорного времени составят μi, = wi /(wi + wj) — 1/2 и μj = w/(wi + wj) = 1/2. Для двух задач с приоритетами, отличающимися на одну ступень wi ≠ wj, а μi — μj = 1/10, откуда несложно получить, что wi /wj = 11/9 — правило построения шкалы весов, а μi = 11/20 = 0,55 и μ2 = 9/20 = 0,45, что и требовалось получить.
Для дифференциации задач используют 40 относительных POSIX-приоритетов на шкале от —20 до +19, называемых «любезностью» задачи NICE.
Относительный приоритет буквально определяет, насколько «любезна» будет задача по отношению к остальным готовым к выполнению задачам при конкуренции за процессорное время освободившегося процессора. Наименее «любезным»с относительным приоритетом —20 (наивысшим) планировщик выделит большую долю процессорного времени, а наиболее «любезным», с приоритетом +19 (наинизшим) – меньшую.
При отсутствии конкуренции, когда количество готовых к выполнению задач равно количеству свободных процессоров, приоритет не будет играть никакой роли.
В примере из листинга ниже при помощи команды bzip запущены два процесса сжатия ISO-образа с наилучшим качеством одновременно друг другу на «заднем фоне». В выводе свойств pcpu (percent cpu, процент потребляемого процессорного времени), pri (priority), ni (nice) и psr (processor number) их процессов при помощи ps оказывается, что они потребляют практически одинаковые доли (проценты) процессорного времени, и это для одинаковых программ вполне соответствует интуитивным ожиданиям.
После повышения любезности (понижения относительного приоритета) одного из них в при помощи команды renice до значения +10 отношение потребляемых долей процессорного времени не изменилось, что означает отсутствие конкуренции за процессор, подтверждаемое как столбцом PSR, доказывающим номер процессора, выполняющего программу, так и командами nproc и lscpu.
Относительный приоритет NICE
fitz@ubuntu:~$ bzip2 —best -kf plan9.iso &
[1] 12944
fitz@ubuntu:~$ bzip2 —best -kf plan9.iso &
[2] 12945
fitz@ubuntu:~$ ps fo pid, pcpu, pri,ni, psr, cmd
PID %CPU PRI NI PSR CMD
12808 0.1 19 0 0 -bash
12944 94.5 19 0 2 \_ bzip2 —best -kf plan9.iso
12945 96.0 19 0 1 \_ bzip2 —best -kf plan9.iso
12946 0.0 19 0 3 \_ ps fo pid,pcpu,pri,ni,psr,cmd
fitz@ubuntu:~$ renice +10 12945
12945 (process ID) old priority 0, new priority 10
fitz@ubuntu:~$ ps fo pid,pcpu,pri,ni,psr,cmd
PID %CPU PRI NI PSR CMD
12808 0.1 19 0 0 -bash
12944 94.8 19 0 0 \_ bzip2 —best -kf plan9.iso
12945 97.0 9 10 0 \_ bzip2 —best -kf plan9.iso
12948 0.0 19 0 1 \_ ps fo pid,pcpu,pri,ni,psr,cmd
fitz@ubuntu:~$ nproc
4
fitz@ubuntu:~$ lscpu
Architecture: i686
CPU op-node(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 4
On-line CPU(s) list: 0-3
fitz@ubuntu:~$ taskset -p -c 3 12808
pid 12808’s current affinity list: 0-3
pid 12808’s new affinity list: 3
fltz@ubuntu:~$ nice -n 5 time bzip2 —best -kf plan9.iso &
[1] 29331
fitz@ubuntu:~$ nice -n 15 time bzip2 —best -kf plan9.iso &
[2] 29333
fttz@ubuntu:~$ ps fo pid,pcpu,pri,ni,psr,cmd
PID %CPU PRI NI PSR CMD
28573 0.0 19 0 3 -bash
29331 0.0 9 5 3 \_ time bzip2 —best -kf plan9,iso
29332 91,9 5 3 3 | \_ bzip2 —best -kf plan9.iso
29333 0.0 4 15 3 \_ time bzip2 —best -kf plan9.iso
29334 9;7 4 15 3 | \_ bzip2 —best -kf plan9.iso
29336 0.0 19 0 3 \_ ps fo pid,pcpu,pri,ni,psr,cmd
fitz@ubuntu:~$ wait
51.10user 0.22systen 0:56.86elapsed 90%CPU (0avgtext+0avgdata 31248maxresident)k
0inputs+180560outputs (0major+1004minor)pagefaults 0swaps
[1] — Готово nice -n 5 time bzip2 —best -kf plan9.iso
53.79user 0.20systen 1:43.08elapsed 52%CPU (Oavgtext+Gavgdata 31520naxresident)k
0inputs+180560outputs (0major+1515minor)pagefaults 0swaps
[2] + Готово nice -n 15 time bzip2 —best -kf plan9.iso
Проиллюстрировать действие относительного приоритета NICE на многопроцессорной системе можно, создав искусственную конкуренцию двух процессов за один процессор. Для этого при помощи команды taskset устанавливается привязка (affinity) командного интерпретатора (PID=12808) к процессору 3 (привязка, как и прочие свойства и атрибуты процесса, наследуется потомками).
Затем при помощи команды nice запускаются две программы упаковки с относительными приоритетами 5 и 15 в режиме измерения потребления времени при помощи команды time. В результате доли процессорного времени распределяются неравномерно, причем их разница зависит от разницы в относительных приоритетах (и от свойств конкурирующих процессов, но в примере они одинаковые). Дождавшись завершения процессов заднего фона, при помощи встроенной команды wait можно оценить разницу в реальном времени выполнения упаковки, вызванную неравным распределением процессора между процессами упаковщиков.
Кроме приоритетной очереди, планировщик Linux позволяет использовать еще два алгоритма планирования — FIFO и RR, предназначенные для задач реального времени. Вытесняющий алгоритм RR (round robin) организует простейшее циклическое обслуживание с фиксированными квантами времени, тогда как FIFO (first in first out) является его невытесняющей модификацией, позволяя задаче выполняться непрерывно долго, до момента ее засыпания. По сути, оба алгоритма организуют задачи в одну приоритетную очередь (PQ, priority queue) со статическими приоритетами на шкале от 1 до 99, выбирая для выполнения всегда самую высокоприоритетную из множества готовых.
Перевод задачи под управление тем или алгоритмом планирования производится при помощи назначения ей политики планирования (scheduling policy) посредством команды chrt. Различают пять политик планирования, три из которых — SCHED_OTHER, SCHED_BATCH и SCHED_IDLE — реализуются алгоритмом CFS, а две оставшиеся — SCHED_FIFO, SCHED_RR — одноименными алгоритмами FIFO и RR. Политика SCHED_OTHER, она же SCHED_NORMAL, применяется по умолчанию и обслуживает класс задач TS (time sharing), требующих «интерактивности», а политика SCHED_BATCH предназначается для выполнения «вычислительных» задач класса пакетной В (batch) обработки.
Разница между политиками состоит в том, что планировщик в случае необходимости всегда прерывает задачи класса В в пользу задач класса TS, но никогда наоборот. Политика SCHEDJDLE формирует класс задач, выполняющихся только при «простое» (idle) центрального процессора за счет выделения им планировщиком CFS очень небольшой доли процессорного времени. Для этого задачам IDL-класса назначают минимально возможный вес — меньший, чем вес самых «любезных» (NICE = +19) задач TS -класса.
В примере из листинга ниже проиллюстрировано распределение процессорного времени алгоритмом планирования CFS между (конкурирующими за один процессор) одинаковыми процессами TS-, В- и IDL-классов, назначенных им при запуске упаковщиков bzip2 посредством команды chrt.
Классы процессов
fitz@ubuntu:~$ ps f
PID TTY STAT TIME COMMAND
12658 pts/6 Ss 0:00 bash
12065 pts/6 R+ 0:00 \_ ps f
fitz@ubuntu:~$ taskset -p -c 3 12058
pid 12058’s current affinity list: 0-3
pid 12058’s new affinity list: 3
fitz0ubuntu:~$ chrt -b 0 time bzip2 —best -kf plan9.iso &
[1] 12410
fitz@ubuntu:~$ chrt -o 0 tine bzip2 —best -kf plan9.iso &
[2] 12412
fitzflubuntu:~$ chrt -i 0 time bzip2 —best -kf plan9.iso &
[3] 12414
fitz@ubuntu:~$ ps fo pid,pcpu,class,prl,ni,psr,cmd
PID %CPU CLS PRI NI PSR CMD
12058 0.1 TS 19 0 3 -bash
12410 0,0 В 19 — 3 \_ time bzip2 —best -kf plan9.iso
12411 50.0 В 19 — 3 | \_ bzip2 —best -kf plan9.iso
12412 0.0 TS 19 0 3 \_ time bzip2 —best -kf plan9.tso
12413 49.7 TS 19 0 3 | \_ bzip2 —best -kf plan9.iso
12414 0.0 IDL 19 — 3 \_ time bzip2 —best -kf plan9.iso
12415 0.1 IDL 19 — 3 | \_ bzip2 —best -kf plan9.iso
12471 0.0 TS 19 0 3 \_ ps fo pid,pcpu,class,pri,ni,psr,end
fitz@ubuntu:~$ wait
53.85user 0.26system 1:45.98elapsed 51%CPU (0avgtext+0avgdata 31248naxresident)k
0inputs+180560outputs (0major+1004minor)pagefaults 0swaps
53.96user 0.22system 1:46.04elapsed 51XCPU (0avgtext+0avgdata 31248maxresident)k
0inputs+18056Ooutputs (0major+1004minor)pagefaults 0swaps
52.74user 0.27system 2:41.54elapsed 32XCPU (0avgtext+0avgdata 31248maxresident)k
0inputs+180560outputs (0major+1004minor)pagefaults 0swaps
[1] Готово chrt -b 0 time bzip2 —best -kf plan9.iso
[2] — Готово chrt -o 0 time bzip2 —best -kf plan9.iso
[3] + Готово chrt -i 0 time bzip2 —best -kf plan9.iso
В листинге ниже показана конкуренция процессов под управлением RR-планировщика, использующего статические приоритеты. Так как операция назначения политик планирования «реального времени» FIFO и RR является привилегированной, то сначала командный интерпретатор переводится в RR-класс (-r) с наивысшим статическим приоритетом 99 при помощи команды chrt, выполняемой от липа суперпользователя root.
При последующих запусках упаковщиков класс будет унаследован и не потребует повышенных привилегий. Два процесса упаковщиков запускаются командой chrt с одинаковыми статическими приоритетами 1, при вязанные к одному процессору командой tdskset, в результате чего получают равные доли процессорного времени, что вполне соответствует интуитивным ожиданиям от вытесняющего циклического планировщика RR.
Нужно отметить, что шкала статических приоритетов 1—>99 классов RR и FIFO, как и шкала «любезности» NICE +19->-20 классов TS и В, отображаются на общую шкалу приоритетов PRI так, что верхняя часть шкалы PRI: 41—>139 соответствует статическим приоритетам, а нижняя часть шкалы PRI: 0—>39 соответствует «любезности».
Классы процессов реального времени
fitz@ubuntu:~$ ps f
PID TTY STAT TIME COMMAND
15313 pts/0 S 0:00 -bash
15520 pts/0 R+ 0:00 \_ ps f
fitz@ubuntu:~$ sudo chrt -pr 99 15313
fitz@ubuntu:~$ ps fo ptd,psr,cls,ni,prt,pcpu,comm
PID PSR CLS NI PRI %CPU COMMAND
15313 0 RR — 139 0.1 bash
15550 1 RR — 139 0.0 \_ ps
fitz@ubuntu:~$ chrt -r 1 taskset -c 2 bzip2 —best -kf plan9.iso &
[1] 15572
fitz@ubuntu:~$ chrt -r 1 taskset -c 2 bzip2 —best -kf plan9.iso &
[2] 15573
fitz@ubuntu:~$ ps fo pid,psr,cls,ni,pri,pcpu,comm
PID PSR CLS NI PRI %CPU COMMAND
15313 0 RR — 139 0.1 bash
15572 2 RR — 41 51.8 \_ bzip2
15573 2 RR — 41 48.8 \_ bzip2
15597 1 RR — 139 0.0 \_ ps
fitz@ubuntu:~$ chrt -r 2 taskset -c 2 bzip2 —best -kf plan9.iso &
[3] 15628
fitz@ubuntu:~$ ps fo pid,psr,cls,ni,pri,pcpu,comm
PID PSR CLS NI PRI %CPU COMMAND
15313 0 RR — 139 0.1 bash
15572 2 RR — 41 48.3 \_ bzip2
15573 2 RR — 41 47.5 \_ bzip
215573 2 RR — 41 47.5 \_ bzip2
15628 2 RR — 42 93.3 \_ bzip2
15630 1 RR — 139 0.0 \_ ps
fitz@ubuntu:~$ top -b -n1 -p 15572,15573,15628
top — 14:44:01 up 4:27, 2 users, load average: 5.07, 3.42, 2.50
Tasks: 3 total, 3 running, 0 sleeping, 0 stopped, 0 zombie
Cpu(s): 18.5%us, 3.49%sy, 0.6%ni, 76.0%id, 1.43%wa, 0.0%hi, 0.1%si, 0.0%st
Mem: 8192144k total, 3917972k used, 4274172k free, 151936k buffers
Swap: 4104188k total, 0k used, 4104188k free, 2713336k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
15628 fitz -3 0 9536 7880 468 R 100 0.1 1:14.96 bzip2
15572 fitz -2 0 9536 7880 460 R 0 0.1 1:30.95 bzip2
15573 fitz -2 0 9536 7884 464 R 0 0.1 1:30.01 bzip2
Добавление третьего процесса упаковщика со статическим приоритетом 2 приводит к резкому перекосу выделяемой доли процессорного времени в его пользу. Это объясняется тем, что алгоритм планирования RR (равно как и FIFO) всегда выбирает процесс с самым высоким статическим приоритетом из множества готовых, поэтому процессам с более низкими приоритетами процессорное время будет выделено только при засыпании всех процессов с большими приоритетами. А упаковщик, хоть и выполняет операции ввода-вывода, в реальности практически не засыпает № связи с использованием дискового кэша.
Странный результат, изображаемый командой ps, объясняется «несовершенством» ее способа расчета доли процессорного времени %CPU, выделяемой процессу. Расчет производится как отношение чистого потребленного процессорного времени (за все время существования процесса) к промежутку реального времени, прошедшему с момента порождения процесса, что соответствует среднему, но не мгновенному значению потребляемой доли.
Гораздо более ожидаемый результат получает команда top, выполняющая расчет мгновенной доли процессорного времени как отношение чистого потребленного процессорного времени (за небольшой промежуток наблюдения) к реальному времени наблюдения.