Алгоритм планирования, который по умолчанию применяется в ядре, использует политику циклического разделения времени. Согласно ей все процессы имеют одинаковые шансы получить доступ к ресурсам ЦПУ; однако мы можем присвоить значению nice число в диапазоне от –20 (высокий приоритет) до +19 (низкий приоритет), чтобы сделать процесс более или менее предпочтительным при планировании. Но даже процессы с наименьшим приоритетом не могут быть полностью лишены процессорного времени.
Linux также реализует политики планирования реального времени, входящие в состав стандарта POSIX. Они позволяют приложению строго контролировать процедуру выделения ресурсов ЦПУ. Процессы, работающие в рамках этих двух политик (SCHED_RR — циклическая и SCHED_FIFO — «первым пришел — первым ушел») всегда имеют повышенный приоритет по сравнению со всеми другими процессами. Приоритет процессов реального времени варьируется от 1 (низкий) до 99 (высокий). Выполняясь, высокоприоритетный процесс полностью блокирует выдачу ресурсов ЦПУ процессам с низким приоритетом. Процесс, работающий в рамках политики SCHED_FIFO, сохраняет эксклюзивный доступ к процессору до тех пор, пока не завершится или добровольно не освободит ресурсы или пока его не вытеснит более приоритетный процесс. Похожие правила применимы и к политике SCHED_RR, с той лишь разницей, что выполнение процессов с одинаковым приоритетом регулируется циклическим алгоритмом.
Маска родственного процессора позволяет привязать выполнение процесса к определенному набору доступных ЦПУ (если речь идет о многопроцессорной системе). Это может улучшить производительность некоторых видов приложений.
Книга [Love, 2010] описывает подробности о приоритетах и планировании процессов в Linux в исторической перспективе. Дополнительную информацию о программном интерфейсе планирования в реальном времени, входящем в состав стандарта POSIX, можно найти в книге [Gallmeister, 1995]. Книга [Butenhof, 1996] посвящена POSIX-потокам, однако существенная ее часть покрывает программный интерфейс планирования в реальном времени и может значительно помочь при чтении этой главы.
Дополнительные сведения о привязке к процессорам и управлении выделением ресурсов ЦПУ и памяти для потоков в многопроцессорных системах ищите в исходном файле ядра Documentation/cpusets.txt, а также на справочных страницах mbind(2), set_mempolicy(2) и cpuset(7).
35.1. Реализуйте команду nice(1).
35.2. Напишите программу, которая устанавливает идентификатор администратора и является аналогом команды nice(1), работающим в режиме реального времени. Ее интерфейс командной строки должен быть следующим:
# ./rtsched policy priority command arg…
Аргумент policy может принимать два значения: r для SCHED_RR или f для SCHED_FIFO. Перед выходом данная программа должна сбрасывать свой привилегированный идентификатор (причины описаны в подразделе 9.7.1 и разделе 38.3).
35.3. Напишите программу, которая устанавливает для себя политику планирования SCHED_FIFO, после чего создает дочерний процесс. Оба процесса должны выполнить функцию, которая приводит к максимальному потреблению ресурсов процессора на протяжении 3 секунд (это можно сделать с помощью цикла, внутри которого делается системный вызов times(), определяющий количество процессорного времени, потребленного на текущий момент). После каждой четверти секунды потраченного процессорного времени функция должна выводить сообщение с идентификатором процесса и количеством потребленных им ресурсов ЦПУ. После первой секунды потраченного процессорного времени функция должна вызывать sched_yield(), чтобы освободить ЦПУ для других процессов (как вариант, процессы могут повышать приоритеты друг друга с помощью sched_setparam()). Вывод программы должен показать, что оба процесса поочередно выполнялись на протяжении одной секунды (отнеситесь серьезно к совету о недопустимости чрезмерного потребления ресурсов ЦПУ процессом реального времени, который был дан в подразделе 35.3.2).
35.4. Если в многопроцессорной системе два процесса обмениваются большими объемами данных через конвейер, их взаимодействие должно ускориться, если выполнять их на одном и том же ЦПУ. Дело в том, что при использовании одного процессора скорость доступа к данным конвейера возрастает, поскольку эти данные могут оставаться в кэше. Если применяются разные процессоры, преимущества хранения данных в кэше утрачиваются. Если у вас есть возможность работать на многопроцессорном компьютере, напишите программу, которая демонстрирует этот эффект с помощью вызова sched_setaffinity(), привязывая процессы сначала к одному, а потом к разным ЦПУ (применение конвейеров описано в главе 44).