Выбор следующей задачи

После того как процесс инициализирован и размещен в очереди выполнения, в какой то момент он должен получить доступ к процессору для выполнения. За передачу управления процессором другому процессу отвечают две функции: schedule () и scheduler_tick (); scheduler_tick () - это системный таймер, который периодически вызывается ядром и помечает процессы, выполнение которых нужно распланировать. При наступлении события таймера текущий процесс замирает и ядро Linux берет управление процессором на себя. Когда сообщение таймера завершается, ядро обычно передает управление процессором процессу, который замер. При этом, если приостановленный процесс был помечен как планируемый, ядро вызывает schedule () и выбирает процесс, которому передается управления вместо того, который выполнялся перед тем, как ядро заняло процессор. Процесс, выполнявшийся перед тем, как ядро перехватило управление, называется текущим процессом. В некоторых особо сложных2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225

struct list_head *queue; unsigned long long now; unsigned long run_time; int idx;
/*
* Тест на атомарность.Так как do_exit() необходимо вызвать
* scheduled атомарно, мы игнорируем этот путь.
* В противном случае жалуемся, что мы вызываем планировщик,
* хотя это и не нужно. */
if (likely(!(current->state & (TASK_DEAD | TASK_ZOMBIE)))) { if (unlikely(in_atomic())) {
printk(KERN_ERR "bad: scheduling while atomic!\n ");
dump_stack();
}
}
need_resched: preempt_disable(); prev = current; rq = this_rq();
release_kernel_lock(prev); now = sched_clock();
if (likelytnow - prev->timestamp < NS_MAX_SLEEP_AVG))
run_time = now - prev->timestamp;
else
run_time = NS_MAX_SLEEP_AVG; /*
* Задача с интерактивным поведением получает меньше времени
* благодаря высокому sleep_avg для отсрочки потери ими их
* интерактивного статуса */
1) ;
i f (HIGH_CREDIT(prev)) run_time /= (CURRENT_BONUS(prev) ?
Строки 2213-2218
Мы подсчитываем длительность времени, в течение которого процесс будет активен в планировщике. Если процесс будет активен дольше чем среднее максимальное время сна (NS_MAX_SLEEP_AVG), мы устанавливаем его время выполнения равным среднему максимальному времени сна.
Таким образом, код ядра Linux вызывает другой блок кода на время временного среза. Временной срез (timeslice) связан как со временем между прерываниями планировщика, так и с длительностью времени, в течение которого процесс использует процессор. Если процесс израсходовал свой временной срез, процесс становится исчерпанным и неактивным. Временная отметка (timerstamp) - это абсолютное значение, определяющее, как долго процесс использует процессор. Процессор использует временную отметку для определения временного среза процесса, использующего процессор.
Например, представим, что Process А имеет временной срез длительностью 50 циклов таймера. Он использует процессор в течение 5 циклов и затем передает управление процессором другому процессу. Ядро использует временную отметку для определения того, что у Process А осталось 45 циклов временного среза.
Строки 2224-2225
Интерактивные процессы - это процессы, тратящие большинство отведенного им времени на ожидание ввода. Хорошим примером интерактивного процесса является контроллер клавиатуры - большинство времени он ожидает ввода, но, когда он случается, пользователь ожидает, что он получит высокий приоритет.
Интерактивные процессы, для которых кредит интерактивности больше 100 (значение по умолчанию), получают свое эффективное run_time, деленное на [sleep_avg/max_sleep_avg*MAX_BONUS (10) J .
kernel/sched.с 2226
2227 spin_lock_irq(&rq->lock) ; 2228
2229 /*
2230 * как только мы входим в приоритетное прерывание обслуживания,
2231 * сразу переходим к выбору новой задачи.
2232 */
2233 switch_count = &prev->nivcsw;
2234 if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {
2235 switch_count = &prev->nvcsw;
2236 if (unlikely!(prev->state & TASK_INTERRUPTIBLE) &&
2237 unlikely(signal_pending(prev))))
2238 prev->state = TASK_RUNNING;
2239 else
2240 deactivate_task(prev, rq) ;
2241 }
Фщрока2227
Функция выполняет блокировку очереди выполнения, так как мы хотим ее изменить.
Строки 2233-2241
Если мы вошли в schedule () с предыдущим процессом, являющимся приоритетным прерыванием обслуживания, то мы покидаем предыдущий запущенный процесс, если ожидается сигнал. Это значит, что ядро приоритетно прерывает обслуживание обычного процесса быстрым завершением; соответственно код содержится в двух операторах unlikely ()'. Если приоритетных прерываний обслуживания больше нет, мы убираем прервавшие процессы из очереди выполнения и продолжаем выбирать следующий процесс для выполнения.
kernel/sched.с
2143 cpu = smp_processor_id();
2244 if (unlikely! !rq->nr_running) ) {
2245 idle_balance(cpu, rq) ;
2246 if (!rq->nr_running) {
2247 next = rq->idle;
2248 rq->expired_timestamp = 0;
2249 wake_sleeping_dependent(cpu, rq) ;
2250 goto switch_tasks;
2251 }
2252 } 2253

2254 array = rq->active;
2255 if (unlikely(!array->nr_active)) {
2256 /*
2257 * Переключение между активным и истекшим массивами.
2258 */
2259 rq->active = rq->expired;
2260 rq->expired = array;
2261 array = rq->active;
2262 rq->expired_timestamp = 0;
2263 rq->best_expired_prio= MAX_PRIO;
2264 }

Строка 2243
Мы получаем идентификатор текущего процессора с помощью smp_processor_ id О.
Строки 2244-2252
Если очередь выполнения не имеет в себе процессов, мы устанавливаем следующим процессом процесс простоя и сбрасываем временную отметку очереди выполнения в 0. На многопроцессорных системах мы сначала проверяем, выполняются ли на других процессорах процессы, которые можно выполнить на этом процессоре. В результате простаивающие процессы равномерно распределяются по всем процессорам системы. Только если не остается процессов, которые можно переместить на другие процессоры, следующим процессом в очереди выполнения мы устанавливаем процесс простоя и сбрасываем временную отметку истекших процессов.
Строки 2255—2264
Если очередь выполнения активных процессов пуста, мы переключаемся между активным и истекшим массивами указателей перед выбором нового процесса для выполнения.

kernel/sched.с
2266 idx = sched_find_first_bit(array->bitmap);
2267 queue = array->queue + idx;
2268 next = list_entry(queue->next, task_t, run_list); 2269
2270 if (dependent_sleeper(cpu, rq, next)) {
2271 next = rq->idle;
2272 goto switch_tasks;
2273 }
2274
2275 if (!rt_task(next) && next->activated > 0) {
2276 unsigned long long delta = now - next->timestarap;
2277
2278 if (next->activated == 1)
2279 delta = delta * (ON_RUNQUEUE_WEIGHT * 128 / 100) / 128; 2280

2281 array = next->array;
2282 dequeue_task(next, array);
2283 recalc_task_prio(next, next->timestamp + delta);
2284 enqueue_task(next, array);

2285 }
2286 next->activated = 0;

Строки 2266-2268
Планировщик ищет процесс с наивысшим приоритетом для запуска с помощью sched_f ind_f irst_bit () и затем устанавливает queue в указатель на список,
хранящийся в массиве приоритетов в специальном месте; next инициализируется первым процессом из queue.
Строки 2270-2273
Если активируемые процессы зависят от спящих сестринских процессов, мы выбираем новый процесс для активации и переходим в switch_task для продолжения функции планировщика.
Предположим, что у нас есть Process А, порожденный Process В для чтения с устройства, и Process А ожидает завершения Process В, чтобы продолжить свое выполнение. Если планировщик выберет для активации Process А, этот блок кода dependent_ sleeper () определит, что Process А ожидает Process В, и выберет более новый процесс для активации.
Строки 2275-2285
Если атрибут активации процесса больше 0 и следующий процесс не является задачей реального времени, мы удаляем его из queue, пересчитываем его приоритет и снова помешаем его в очередь.
Строка 2286
Мы устанавливаем атрибут активации процесса в 0 и затем выполняем его.kernel/sched.с 2287 switch_tasks:
prefetch(next); clear_tsk_need_resched(prev) RCU_qsctr(task_cpu(prev))++;
0) {
prev->sleep_avg -= run_time; if ((long)prev->sleep_avg <=
prev->sleep_avg = 0;
if (!(HIGH_CREDIT(prev) ||
LOW_CREDIT(prev)))
prev->interactive_credit—;
}
prev->timestamp = now;
if (likelytprev != next)) { next->timestamp = now; rq->nr_switches++; rq->curr = next; ++*switch_count;
next)
prepare_arch_switch(rq, next); prev = context_switch(rq, prev.
23 08 barrier О;
2309
2310 finish_task_switch(prev);
2311 } else
2312 spin_unlock_irq(&rq->lock); 2313

2314 reacquire_kernel_lock(current);
2315 preempt_enable_no_resched() ;
2316 if (test_thread_flag(TIF_NEED_RESCHED))
2317 goto need_resched;
2318 }

Строка 2288
Мы пытаемся поместить память структуры задачи нового процесса в кеш процессора первого уровня (L1). (См. более подробную информацию в include/linus/ prefetch, h.)
Строка 2290
Так как мы выполняем переключение контекста, нам нужно проинформировать об этом текущий процессор. Это позволяет многопроцессорному устройству получить доступ к разделяемому между несколькими процессорами ресурсу в эксклюзивном режиме. Этот процесс называется обновлением копирующего чтения. (Более подробную информацию см. в http://lse.sourceforge.net/locking/ rcupdate. html.)
Строки 2292-2298
Мы уменьшаем атрибут sleep_avg предыдущего процесса на количество времени, в течение которого он выполнялся, корректируя отрицательные значения. Если процесс не является ни интерактивным, ни неинтерактивным, а его значение интерактивности находится между наименьшим и наибольшим значениями, мы увеличиваем его значение интерактивности, так как он обладает низким значением среднего сна. Мы обновляем его временную отметку в значение текущего времени. Эта операция помогает планировщику следить за тем, сколько процессорного времени тратит текущий процесс, и оценить, сколько процессорного времени он потребует в будущем.
Строки 2300-2304
Если вы не выбрали тот же самый процесс, мы устанавливаем временную отметку процесса, увеличиваем счетчики очереди выполнения и устанавливаем в качестве текущего процесса новый процесс.
Строки 2306-2308
Эти строки описывают context_switch() на языке ассемблера. Задержимся на несколько абзацев до тех пор, пока мы погрузимся в объяснение переключения контекста в следующем разделе.
Строки 2314-2318
Мы перепоручаем блокировку ядра, включаем приоритетное прерывание обслуживания и смотрим, нужно ли нам производить немедленную перепланировку; если да, мы возвращаемся в начало schedule ().
Может случиться, что после выполнения context_switch () нам потребуется вы-ошнить перепланировку. Возможно, что scheduler_tick() пометит новый процесс вк нуждающийся в перепланировке или, когда включено приоритетное прерывание об-оуживания, он будет помечен. Мы продолжаем процесс перепланировки (и затем переключаем контекст) до тех пор, пока не найдем первый процесс, не требующий перепланировки. Процесс, завершающий schedule (), становится новым процессом, выполняемым на данном процессоре.