Linux Kernel (Ядро линукса) (часть 1)


Действия функции scheduleQ перед переключением процесса

Цель функции schedule о состоит в замене текущего процесса на какой-то другой. То есть основным результатом ее деятельности является обновление локальной переменной next так, чтобы она указывала на дескриптор процесса, выбранного для замены текущего. Если ни один выполняемый процесс в системе не имеет приоритета, более высокого, чем у текущего процесса, то процесс next, в конечном счете, совпадет с процессом current, и никакого переключения не произойдет.

Функция schedule о начинает работу с отключения вытеснения в ядре и инициализирует несколько локальных переменных:
need_resched: preempt_disable; prev = current; rq = this_rq;
Здесь указатель, находящийся в current, сохраняется в переменной prev, а адрес очереди на выполнение, принадлежащей локальному процессору, сохраняется в переменной rq.

Затем функция schedule убеждается, что процесс prev не держит глобальную блокировку ядра :
if (prev->lock_depth >= 0) up(&kernel_sem);
Обратите внимание, что функция schedule о не меняет значение поля lock depth. Когда процесс prev возобновляет свое выполнение, функция заново захватит мьютекс kernei sem, если значение этого поля будет неотрицательным. Таким образом, глобальная блокировка ядра автоматически освобождается и обновляется по ходу переключения процессов.

После этого вызывается функция sched ciocko для чтения регистра TSC и преобразования его значения в наносекунды. Полученная отметка времени сохраняется в локальной переменной now. Затем функция schedule вычисляет продолжительность отрезка процессорного времени, используемого процессом prev:
now = sched_clock(); run_time = now — p rev-> time stamp ; if (run_time > 1000000000) run_time = 1000000000;
Как обычно, происходит усечение до одной секунды (выраженной в наносекундах). Переменная run time нужна, чтобы "выставить счет” процессу за использование процессора. Однако процессу с большим средним временем сна делается поблажка”:
run_time /= (CURRENT_BONUS(prev)
Вспомним, что макрос current bonus возвращает значение между 0 и 10, пропорциональное среднему времени сна процесса.
Прежде чем приступать к просмотру выполняемых процессов, функция schedule о должна отключить локальные прерывания и получить спин- блокировку, защищающую очередь на выполнение:
spin_lock_irq(&rq->lock) ;
может оказаться, что процесс prev в данный момент завершает свою работу. Чтобы распознать этот случай, функция
schedule () проверяет флаг pf_dead:
if (prev->flags & PF_DEAD) prev->state = EXIT_DEAD;
Затем функция schedule о изучает состояние процесса prev. Если процесс не является выполняемым и не был вытеснен в режиме ядра он должен быть удален из очереди на выполнение. Однако, если у него есть не заблокированные сигналы, ожидающие доставки, и он находится в состоянии task_interruptible, функция переводит процесс в состояние TASK_RUNNING и оставляет его в очереди. Такая операция — не то же самое, что предоставление процессора процессу prev. Она просто дает процессу prev шанс быть выбранным для выполнения:
if (prev->state != TASK_RUNNING &&
!(preempt_count() & PREEMPT_ACTIVE)) {
if (prev->state == TASK_INTERRUPTIBLE && signal_pending(prev)) prev->state = TASK_RUNNING; else {if (prev->state == TASK_UNINTERRUPTIBLE) rq->nr_uninterruptible++; deactivate_task(prev, rq) ;}}

Функция deactivate tasko удаляет процесс из очереди на выполнение:

rq->nr_running—; dequeue_task(р, p->array); p->array = NULL;
Теперь функция schedule о проверяет количество выполняемых процессов, оставшихся в очереди. Если таковые имеются, функция вызывает функцию dependent sieeper о. В большинстве случаев вызванная функция немедленно возвращает ноль. Однако, если ядро поддерживает технологию Hyper- Threading, функция проверяет приоритет процесса, который сейчас будет выбран для выполнения. Если этот приоритет значительно ниже, чем у соседнего процесса, уже выполняемого на логическом процессоре того же физического процессора, то в данном случае функция schedule о отказывается от выбора низкоприоритетного процесса и выполняет процесс swapper:
if (rq->nr_running) {
if (dependent_sleeper(smp_processor_id, rq)) { next = rq->idle; goto switch_tasks;}}
Если в очереди нет выполняемых процессов, происходит вызов функции idiejoaiance , чтобы перенести несколько выполняемых процессов в локальную очередь. Функция idiejoaiance о аналогична функции ioad_ balance if (!rq->nr_running) {
idiejoaiance (smp_processor_id () , rq) ;
if (!rq->nr_running) { next = rq->idle; rq->expired_timestamp = 0;
wake_sleeping_dependent(smp_processor_id(), rq); if (!rq->nr_running) goto switch_tasks;}}
Если функции idie baiance не удается перенести какой-нибудь процесс в локальную очередь на выполнение, функция schedule о вызывает функцию wake sieeping dependentо, чтобы перепланировать выполняемые процессы в простаивающих процессорах (то есть в процессорах, выполняющих процесс swapper). Как было сказано при обсуждении функции dependent_sleeper, этот нетипичный случай может произойти, если ядро поддерживает Hyper- Threading. В однопроцессорных системах, или если все попытки перенести хотя бы один процесс в локальную очередь на выполнение закончились неудачей, функция выбирает процесс swapper на роль процесса next и переходит к следующему этапу.

Предположим, функция schedule о определила, что очередь на выполнение содержит несколько выполняемых процессов. Теперь ей нужно убедиться, что хотя бы один из них активен. Если это не так, функция обменивает содержимое полей active И expired В структуре runqueue. Таким образом, все процессы с истекшими квантами времени становятся активными, а опустевший набор процессов готов принять процессы, кванты которых истекут в будущем.
array = rq->active; if (!array->nr_active) {
rq->active = rq->expired; rq->expired = array; array = rq->active; rq->expired_timestamp = 0; rq->best_expired_prio = 140;}
Сейчас самое время поискать выполняемый процесс в структуре prio array t с активными процессами. Во-первых, функция schedule о ищет первый ненулевой бит в битовой маске набора активных процессов. Вспомним, что бит в маске установлен, если соответствующий список приоритетов не пуст. Индекс первого ненулевого бита укажет на список процессов, наиболее подходящих для выполнения. Затем из этого списка извлекается первый дескриптор процесса:
idx = sched_find_first_bit(array->bitmap);
next = list_entry(array->queue[idx].next, task_t, run_list);
В основе кода функции sched find first bit о лежит ассемблерная инструкция bsfi, возвращающая индекс младшего из битов, установленных в единицу в 32-битовом слове.

Теперь локальная переменная next хранит указатель на дескриптор процесса, который заменит процесс prev. Функция schedule о читает значение из поля next->activated. Это поле кодирует состояние процесса на момент пробуждения.

Если next является обычным процессом, и его пробуждение происходит из состояния тask_interruртIble или task_stopped, планировщик добавляет к среднему времени сна процесса количество наносекунд, прошедших с момента постановки процесса в очередь на выполнение. Иными словами, время сна процесса увеличивается и включает в себя время, проведенное им в очереди на выполнение в ожидании процессора:
if (next->prio >= 100 && next->activated > 0) {
unsigned long long delta = now — next->timestamp; if (next->activated == 1)
delta = (delta 38) / 128; array = next->array; dequeue_task(next, array);
recalc_task_prio(next, next->timestamp + delta); enqueue_task(next, array);}next->activated = 0;
Обратите внимание, что планировщик проводит различие между процессом, разбуженным обработчиком прерывания или функцией отложенного выпол
нения, и процессом, разбуженным системным вызовом или потоком ядра. В первом случае планировщик прибавляет все то время, что процесс ждал в очереди на выполнение, а во втором — только некоторую часть этого времени. Дело в том, что интерактивные процессы с большей вероятностью пробуждаются от асинхронных событий (представим себе пользователя, нажимающего на клавиши), чем от синхронизированных.

Предыдущая страница | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | Следующая страница




Возможно, Вас также заинтересует:

ОС Knoppix - это Linux без про...

ВведениеЕсли вы цените свое время, умеете считать деньги и знаете стоимость информации, то эта книга...

Linux Kernel (Ядро линукса) (ч...

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

Linux Kernel (Ядро линукса) (ч...

Копирование при записи В системах Unix первых поколений создание процесса было реализовано довольно...

Linux Kernel (Ядро линукса) (ч...

Буферы блоков и головы буферовУ каждого буфера есть дескриптор голова буфера, имеющий тип buffer...