Занятие процессора

Процессы могут занимать процессор простым вызовом функции schedule (). Она обычно используется в коде ядра и драйвером устройства, которое хочет заснуть или дождаться поступления сигнала1. Другие задачи тоже постоянно хотят использовать процессор, и системный таймер должен сообщать им, когда они смогут выполниться. Ядро Linux периодически захватывает процессор, при этом активные процессы останавливаются, и затем выполняет несколько зависящих от времени задач. Одна из этих задач, scheduler_tick (), позволяет ядру заставить процесс приостановиться. Если процесс выполняется слишком долго, ядро не возвращает этому процессу управление и вместо него выбирает другой процесс. Теперь мы изучим, как scheduler_tick () определяет текущий процесс, который должен занять процессор.

kernel/sched.с
1981 void scheduler_tick(int user_ticks, int sys_ticks)
1982 {

1983 int cpu = smp_processor_id();
1984 struct cpu_usage_stat *cpustat = &kstat_this_cpu.cpustat;
1985 runqueue_t *rq = this_rq();
1986 task_t *p = current; 1987
1988 rq->timestamp_last_tick = sched_clock(); 1989
1990 if (rcu_pending(cpu))
1991 rcu_check_callbacks(cpu, user_ticks);

Строки 1981-1986
Этот блок кода инициализирует структуры данных, необходимые функции scheduler_tick(); cpu, cpu_usage_stat и rq получают значения иденти-
Соглашение Linux утверждает, что вы никогда не должны вызывать schedule во время циклической блокировки, так как это может завести систему в тупик. Это действительно хороший совет!
фикатора процессора, статус процессора и очередь выполнения для текущего процессора; р - это указатель на текущий выполняемый на сри процесс.
Строка 1988
Последний тик очереди выполнения устанавливается в текущее время в наносекундах. Строки 1990-1991
нужно проверить наличие требующих выполнения просроченных обновлений чтения-записи (RCU). Если это так, мы выполняем их с помощью rcu_check_callback ().
kernel 1993
1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016

/sched.с
/* обратите внимание: контекст irq этого таймера также
должен учитываться циклом for */ if (hardirg_count() - HARDIRQ_OFFSET) {
cpustat->irq += sys_ticks;
sys_ticks = 0,-} else if (softirq_count()) {
cpustat->softirq += sys_ticks;
sys_ticks = 0;
}
if (p == rq->idle) {
> 0)
if (atomic_read(&rq->nr_iowait) cpustat->iowait += sys_ticks; else
cpustat->idle += sys_ticks; if (wake_priority_sleeper(rq))
goto out; rebalance_tick(cpu, rq, IDLE); return;
}
if (TASK_NICE(p) > 0)
cpustat->nice += user_ticks; else
cpustat->user += user_ticks; cpustat->system += sys_ticks;

Строки 1994-2000
cpustat следит за статистикой ядра, и мы обновляем статистику об аппаратных и программных прерываниях по количеству наступивших системных тиков.
Строки 2002-2011
Если текущего выполняемого процесса нет, мы автоматически проверяем наличие процессов, ожидающих ввода-вывода. Если это так, статистика ввода-вывода

процессора увеличивается; в противном случае увеличивается статистика ожидающего процессора. В однопроцессорных системах rebalance_tick () не делает ничего, а на многопроцессорных системах rebalance_tick() старается сбалансированно загрузить текущий процессор, так как он простаивает.
Строки 2012-2016
В этом блоке кода собирается дополнительная статистика о процессоре. Если текущий процесс был niced, мы увеличиваем счетчик nice для процессора; в противном случае увеличивается пользовательский счетчик тиков. И наконец, мы увеличиваем системный счетчик тиков процессора.

kernel/sched.с
2019 if (p->array != rq->active) {
2020 set_tsk_need_resched(р);
2021 goto out;

2022 }
2023 spin_lock(&rq->lock); Строка 2019-2022
Здесь мы видим, почему мы сохраняем указатель на массив приоритетов в task_struct процесса. Планировщик проверяет текущий процесс и смотрит, не является ли он больше активным. Если процесс завершился, планировщик устанавливает флаг перепланировки процесса и переходит в конец функции scheduler_ tick(). В этой точке (строки 2092-2093) планировщик пытается сбалансированно загрузить процессор, так как активных задач нет. Этот случай наступает, когда планировщик перехватывает управление процессором перед тем, как текущий процесс сможет себя перепланировать или очиститься после удачного выполнения.
Строка 2023
В этой точке мы знаем, что текущий процесс был запущен и, не завершился и существует. Теперь планировщик хочет передать управление процессором другому процессу; первое,
2024
2025 * Задача, выполняющаяся во время этого тика, обновляет счетчик
2026 * временного среза. Обратите внимание: мы не обновляем приоритета
2027 * потока до тех пор, пока он не засыпает или не расходует свой
2028 * временной срез. Это позволяет интерактивным задачам использовать
2029 * свои временные срезы на наивысшем уровне приоритета levels.
2030 */
2031 if (unlikely(rt_task(p))) {
2032 2033
2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046

}

Строки 2031-2046
Простейший случай для планировщика наступает, когда текущий процесс является задачей реального времени. Задачи реального времени всегда имеют наивысший приоритет по сравнению с другими задачами. Если задача является FIFO и запущена, она продолжает свои операции, а мы переходим в конец функции и снимаем блокировку очереди выполнения. Если текущий процесс является циклической задачей реального времени, мы уменьшаем его временной срез. Если у задачи не осталось временного среза, наступает время перепланировать следующую циклическую задачу реального времени. Текущая задача получает новый временной срез, рассчитываемый task_timeslice (). Далее задача сбрасывает свой первый временной срез. Затем задача помечается как требующая перепланировки и, наконец, помещается в конец списка циклических задач реального времени удалением ее из активного массива очереди выполнения и помещением в его конец. После этого планировщик переходит в конец функции и снимает блокировку с очереди выполнения.
kernel/sched.с
2047 if (!--p->time_slice) {
2048 dequeue_task(p, rq->active);
2049 set_tsk_need_resched(p);
2050 p->prio = effective_prio(p);
2051 p->time_slice = task_timeslice(p);
2052 p->first_time_slice = 0; 2053
2054 if (!rq->expired_timestamp)
2055 rq->expired_timestamp = jiffies;

2056 if (!TASK_INTERACTIVE(p) || EXPIRED_STARVTNG(rq)) {
2 057 enqueue_task(p, rq->expired);
2058 if (p->static_prio < rq->best_expired_prio)
2059 rq->best_expired__prio = p->static_prio;
2060 } else
2061 enqueue_task(p, rq->active);
2062 } else {

Строки 2047-2061
В этой точке планировщик знает, что текущий процесс не является процессом реального времени. Он увеличивает временной срез процесса а также, в этом же блоке, временной срез, который истек и достиг 0. Планировщик удаляет задачу из активного массива и устанавливает флаг перепланировки процесса. Приоритет задачи пересчитывается, а временной срез сбрасывается. Обе эти операции производятся с учетом предыдущей активности процесса1. Если временная отметка истекшей очереди ожидания достигла 0, что обычно происходит, когда в массиве активной очереди выполнения не остается процессов, мы присваеваем ему значение текущего момента.
Моменты
Моменты (jiffies) - это 32-битовые переменные, отсчитывающие количество тиков с момента загрузки системы. На процессоре с частотой 100 Гц эти переменные переполнятся и будут обнулены примерно через 497 дней. Макрос в строке 20 представляет собой метод для доступа к этому значению в качестве и64. Кроме этого, в include/jif fies.h существует макрос для определения переполнения моментов.
include/linux/jiffies.h
017 extern unsigned long volatile jiffies; 020 u64 get_jiffies_64(void);

Обычно мы поощряем интерактивные задачи, заменяя их в активном массиве приоритетов очереди выполнения; случай else в строке 2060. Тем не менее мы не хотим тормозить истекшие задачи. Для определения того, не ожидает ли истекшая задача передачи ей процессора слишком долго, мы используем EXPIRED_STARVING () (см. EXPIRED_STARVING в строке 1968).
Функция возвращает true, если первая истекшая задача ожидает «неоправданно» долгое время или если массив истекших задач содержит задачу, имеющую более высокий приоритет, чем у текущего процесса. Неоправданность ожидания зависит от загрузки
temel/sched. с
} else { /*
* Предотвращение слишком долгих временных срезов, позволяющих * монополизировать процессор. Мы делаем это, разбивая временные
* срезы на меньшие порции.
* ОБРАТИТЕ ВНИМАНИЕ: это не значит, что временные срезы задачи
* истекли или были удалены другим путем, их обслуживание просто

* приоритетно прерываются другой задачей с эквивалентным
* приоритетом.(Задача с большим приоритетом может приоритетно
* прервать выполнение данной задачи.) Мы переносим эту задачу в
* конец списка уровней приоритетов, представляющий собой
* карусельную структуру задач с одинаковым приоритетом. *
* Применяется только к интерактивным задачам
* с диапазоном не менее TIMESLICE_GRANULARITY. */
if (TASK_INTERACTIVE(p) && !((task_timeslice(p) -p->time_slice) % TIMESLICE_GRANULARITY(p)) && (p->time_slice >= TIMESLICE_GRANULARITY(p)) && (p->array == rq->active)) {
dequeue_task(p, rq->active); set_tsk_need_resched(p); p->prio = effective_prio(p); enqueue_task(p, rq->active);
}
}
2090 out_unlock:
2091 spin_unlock(&rq->lock) ;
2092 out:
2093 rebalance_tick(cpu, rq, NOT_IDLE)
2094 }
Строки 2079-2089
Последний случай перед вызовом планировщика - это когда текущий процесс является запущенным и у него еще остался временной срез. Планировщику нужно удостовериться, что процесс с большим временным срезом не заблокирует процессор. Если задача интерактивна, имеет временной срез большей длины, чем TIMESLICE_GRANUARITY, и активна, планировщик убирает ее из активной очереди. После этого устанавливается флаг перепланировки задачи, пересчитывает-ся ее приоритет и она помещается обратно в активный массив очереди выполнения. Это позволяет быть уверенным, что процесс с определенным приоритетом и с большим временным срезом не застопорит другой процесс с аналогичным приоритетом.
Строки 2090-2094
Планировщик завершает перераспределение очереди выполнения и разблокирует ее; если выполнение происходит на SMP-системе, производится попытка сбалансированной нагрузки.
Связь того, как процесс помечается для перепланировки с помощью scheduler_ tick () и как процесс планируется с помощью schedule (), иллюстрирует работу планировщика в ядре Linux версии 2.6. Теперь мы углубимся в детали того, что называется в планировщике «приоритетом».