Добавил:
Developerrnrn Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ОС Нижний.pdf
Скачиваний:
34
Добавлен:
25.03.2023
Размер:
2.75 Mб
Скачать

Лабораторный практикум по курсу "Операционные системы"

Архитектура планировщика в Linux (Ядро 2.4.18)

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

Ядро Linux поддерживает два независимых диапазона приоритетов. Первый используется для процессов разделения времени – nice value, число от -20 до 19 со значением по умолчанию равным 0. Большие значения nice соответствуют меньшему приоритету. Процессы с меньшим значением nice выполняются перед процессами с низким значением. Значение nice также используется при определении размера кванта времени, предоставляемого процессу – процессы с меньшим значением nice получают большие кванты.

Второй диапазон - приоритеты реального времени. Они имеют значения от 0 до 99. Все процессы реального времени являются более приоритетными, чем процессы разделения времени.

Linux поддерживает две дисциплины обслуживания процессов реального времени – FIFO и Round Robin (SCHED_FF и SCHED_RR соответственно). Для остальных работает специальная дисциплина SCHED_OTHER, которая рассмотрена ниже. Процессы SCHED_FF исполняются до тех пор, пока они не выполнят системный вызов освобождения процессора, и для них не определяется размер кванта. Для процессов SCHED_RR квант определяется, и в рамках одного значения приоритета происходит их поочередное выполнение.

Для обеих дисциплин реального времени используются статические приоритеты, то есть неизменяемые планировщиком по своему усмотрению. Диапазон приоритетов реального времени – от 0 до MAX_RT_PRIO – 1 (по умолчанию, 100 – 1 = 99). Приоритеты процессов разделения времени – от MAX_RT_PRIO до MAX_PRIO = MAX_RT_PRIO+40, что по умолчанию составляет диапазон от 100 до 139. Значение приоритета процесса разделения времени вычисляется как MAX_RT_PRIO + nice + 20.

Квант – числовое значение, определяющее, как долго процесс может выполняться до того, как он будет вытеснен. Планировщик должен динамически определять размеры квантов. Использование слишком больших квантов ухудшает время отклика процессов, слишком малых – приводит к потерям времени на переключение контекстов. Планировщик Linux вычисляет размер кванта на основании приоритета процесса.

Ниже приоритет или

Выше приоритет или

меньше интерактивность

больше интерактивность

Минимум – 10 мс

По умолчанию -150 мс Максимум – 300 мс

Рис. 53 Размер кванта, выделяемого процессу

102 Учебно-исследовательская лаборатория «Информационные технологии»

Лабораторный практикум по курсу "Операционные системы"

Отметим, что процесс нет должен использовать весь квант сразу. Он может выполнить одно действие на 100 мс или 5 действий по 20 мс, отказываясь от центрального процессора в конце каждого действия.

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

Планировщик оперирует понятием jiffie – это время между двумя последовательными прерываниями таймера. Квант содержит несколько jiffie.

Реализация планировщика содержится в файле kernel/sched.c

Очередь процессов

Основная структура данных планировщика – очередь процессов (runqueue) – список процессов, готовых к исполнению на конкретном процессоре. Каждый готовый к исполнению процесс находится ровно в одной очереди. Очередь процессов также содержит параметры планирования для данного процессора. Она имеет следующую структуру.

struct runqueue {

 

 

 

spinlock_t

lock;

/* признак блокировки очереди */

unsigned long

nr_running;

/* число процессов в очереди */

unsigned long

nr_switches;

 

 

 

 

/* число переключений контекста */

unsigned long

expired_timestamp;

 

 

 

/* время последней смены массивов */

struct task_struct *curr;

/* исполняющийся процесс */

struct task_struct *idle;

 

 

 

 

/* idle-процесс данного процессора */

struct prio_array

*active;

 

 

/* указатель на массив приоритетов активных процессов */

struct prio_array

*expired;

 

 

/* указатель на массив приоритетов истекших процессов */

struct prio_array

arrays[2];

/* массивы приоритетов */

int

prev_cpu_load[NR_CPUS];

}

 

 

/* загрузка каждого процессора */

 

 

 

 

Для работы с очередями процессов определена группа макросов, например: cpu_rq(cpu) – возвращает указатель на очередь указанного процессора; this_rq() – возвращает указатель на очередь текущего процессора;

task_rq(p) – возвращает указатель на очередь, которой принадлежит указанный процесс.

Учебно-исследовательская лаборатория «Информационные технологии» 103

Лабораторный практикум по курсу "Операционные системы"

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

static inline runqueue_t *lock_task_rq( task_t *p, ulong flags ); static inline void lock_task_rq(runqueue_t *rq, ulong flags );

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

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

struct prio_array {

 

 

int

nr_active;

/* число

активных процессов */

spinlock_t *lock;/* указатель на признак

блокировки очереди */

runqueue_t *rq;

/* очередь

– владелец массива */

unsigned long bitmap[BITMAP_SIZE];

/* битовый массив */

list_t

queue[MAX_PRIO];

/* списки по приоритетам */

};

 

 

 

MAX_PRIO – максимальное число приоритетов в системе (по умолчанию, 140). Массив приоритетов содержит битовый массив, в котором отведен бит для каждого уровня приоритета. Изначально, все биты равны 0. Когда процесс с некоторым уровнем приоритета переходит в состояние «готов к исполнению», соответствующий бит устанавливается в 1. Таким образом, поиск процесса с наивысшим приоритетом сводится к поиску первого бита в данном массиве (sched_find_first_bit()) и взятию первого элемента из соответствующего списка.

Кванты времени центрального процессора

Множество операционных систем, включая ранние версии Linux, производят вычисление размеров квантов следующим образом:

for (each task on the system) { recalculate priority recalculate timeslice

}

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

Описываемая версия планировщика не требует подобного цикла пересчета. Вместо этого, поддерживаются два массива приоритетов для каждого процессора: активный и истекший. Активный массив содержит процессы, еще не использовавшие полностью предоставленный им квант времени, истекший – исчерпавшие свой квант. Когда размер кванта процесса уменьшается до 0, производится перерасчет кванта, и процесс перемещается в истекший

104 Учебно-исследовательская лаборатория «Информационные технологии»

Лабораторный практикум по курсу "Операционные системы"

массив. Когда в активном массиве не останется элементов, он просто меняется местами с пассивным (см. void schedule(void)):

struct prio_array array = rq->active; if (!array->nr_active) {

rq->active = rq->expired; rq->expired = array;

}

Выбор процесса на исполнение

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

asmlinkage void schedule(void)

{

...

/* Для процесса запоминается момент завершения его кванта */ prev->sleep_timestamp = jiffies;

/* При работе планировщика прерывания разрешены */ spin_lock_irq(&rq->lock);

/* если задача находится в состоянии TASK_RUNNING, то она остается в этом состоянии; если задача находится в состоянии TASK_INTERRUPTIBLE и для нее поступили сигналы, то она переводится в состояние TASK_RUNNING. В любом другом случае задача удаляется из очереди runqueue */

switch (prev->state) { case TASK_INTERRUPTIBLE:

if (unlikely(signal_pending(prev))) { prev->state = TASK_RUNNING; break;

}

default:

deactivate_task(prev, rq); case TASK_RUNNING:

;

}

/* Если в очереди нет процессов, запускается idle-процесс*/ if (unlikely(!rq->nr_running)) {

next = rq->idle;

Учебно-исследовательская лаборатория «Информационные технологии» 105

Соседние файлы в предмете Современные операционные системы