KnigaRead.com/

Роберт Лав - Разработка ядра Linux

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Роберт Лав, "Разработка ядра Linux" бесплатно, без регистрации.
Перейти на страницу:

• Функция load_balance() принимает решение о том, из какого массива приоритетов самой загруженной очереди будут проталкиваться процессы. Истекший массив является более предпочтительным, так как содержащиеся в нем задачи не выполнялись достаточно долгое время и, скорее всего, не находятся в кэше процессора (т.е. не активны в кэше, not "cache hot"). Если истекший массив приоритетов пуст, то ничего не остается, как использовать активный массив.

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

• Каждое задание с данным приоритетом анализируется для определения задания, которое не выполняется, не запрещено для миграции из-за процессорной привязки и не активно в кэше. Если найдена задача, которая удовлетворяет этому критерию, то вызывается функция pull_task() для проталкивания этой задачи из наиболее загруженной очереди в данную очередь.

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

Далее показана функция load_balance(), немного упрощенная, но содержащая все важные детали.

static int load_balance(int this_cpu, runqueue_t *this_rq,

 struct sched_domain *sd, enum idle_type idle) {

 struct sched_group *group;

 runqueue_t *busiest;

 unsigned long imbalance;

 int nr_moved;


 spin_lock(&this_rq->lock);


 group = find_busiest_group(sd, this_cpu, &imbalance, idle);

 if (!group)

  goto out_balanced;

 busiest = find_busiest_queue(group);

 if (!busiest)

  goto out_balanced;


 nr_moved = 0;

 if (busiest->nr_running > 1) {

  double_lock_balance(this_rq, busiest);

  nr_moved = move_tasks(this_rq, this_cpu, busiest,

   imbalance, sd, idle);

  spin_unlock(&busiest->lock);

 }

 spin_unlock(&this_rq->lock);


 if (!nr_moved) {

  sd->nr_balance_failed++;


  if (unlikely(sd->nr_balance_failed > sd->cache_nice_tries+2)) {

   int wake = 0;


   spin_lock(&busiest->lock);

   if (!busiest->active_balance) {

    busiest->active_balance = 1;

    busiest->push_cpu = this_cpu;

    wake = 1;

   }

   spin_unlock(&busiest->lock);

   if (wake)

    wake_up_process(busiest->migration_thread);

   sd->nr_balance_failed = sd->cache_nice_tries;

  }

 } else

  sd->nr_balance_failed = 0;

 sd->balance_interval = sd->min_interval;

 return nr_moved;

out_balanced:

 spin_unlock(&this_rq->lock);


 if (sd->balance_interval < sd->max_interval)

  sd->balance_interval *= 2;


 return 0;

}

Вытеснение и переключение контекста

Переключение контекста — это переключение от одной, готовой к выполнению задачи к другой. Это переключение производится с помощью функции context_switch(), определенной в файле kernel/sched.c. Данная функция вызывается функцией schedule(), когда новый процесс выбирается для выполнения. При этом выполняются следующие шаги.

• Вызывается функция switch_mm(), которая определена в файле include/asm/mmu_context.h и предназначена для переключения от виртуальной памяти старого процесса к виртуальной памяти нового процесса.

• Вызывается функция switch_to(), определенная в файле include/asm/system.h, для переключения от состояния процессора предыдущего процесса к состоянию процессора нового процесса. Эта процедура включает восстановление информации стека ядра и регистров процессора.

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


Таблица 4.2. Функции для управления флагом need_resched

Функция Назначение set_tsk_need_resched(task) Установить флаг need_resched для данного процесса clear_tsk_need_resched(task) Очистить флаг need_resched для данного процесса need_resched() Проверить значение флага need_resched для данного процесса. Возвращается значение true, если этот флаг установлен, и false, если не установлен

Во время переключения в пространство пользователи или при возврате из прерывания, значение флага need_resched проверяется. Если он установлен, то ядро активизирует планировщик перед тем, как продолжить работу.

Этот флаг не является глобальной переменной, так как обращение к дескриптору процесса получается более быстрым, чем обращение к глобальным данным (из-за скорости обращения к переменной current и потому, что соответствующие данные могут находиться в кэше). Исторически, этот флаг был глобальным в ядрах до серии 2.2. В ядрах серий 2.2 и 2.4 этот флаг принадлежал структуре task_struct и имел тип int. В серии ядер 2.6 этот флаг перемещен в один определенный бит специальной переменной флагов структуры thread_info. Легко видеть, что разработчики ядра никогда не могут быть всем довольны.

Вытеснение пространства пользователя

Вытеснение пространства пользователя (user preemption) происходит в тот момент, когда ядро собирается возвратить управление режиму пользователя, при этом устанавливается флаг need_resched и, соответственно, активизируется планировщик. Когда ядро возвращает управление в пространство пользователя, то оно находится в безопасном и "спокойном" состоянии. Другими словами, если продолжение выполнения текущего задания является безопасным, то безопасным будет также и выбор нового задания для выполнения. Поэтому когда ядро готовится возвратить управление в режим пользователя или при возврате из прерывания или после системного вызова, происходит проверка флага need_resched. Если этот флаг установлен, то активизируется планировщик и выбирает новый, более подходящий процесс для исполнения. Как процедура возврата из прерывания, так и процедура возврата из системного вызова являются зависимыми от аппаратной платформы и обычно реализуются на языке ассемблера в файле entry.S (этот файл, кроме кода входа в режим ядра, также содержит и код выхода из режима ядра). Если коротко, то вытеснение пространства пользователя может произойти в следующих случаях.

• При возврате в пространство пользователя из системного вызова.

• При возврате в пространство пользователя из обработчика прерывания.

Вытеснение пространства ядра

Ядро операционной системы Linux, в отличие от ядер большинства вариантов ОС Unix, является полностью преемптивным (вытесняемым, preemptible). В непреемптивных ядрах код ядра выполняется до завершения. Иными словами, планировщик не может осуществить планирование для выполнения другого задания, пока какое-либо задание выполняется в пространстве ядра — код ядра планируется на выполнение кооперативно, а не посредством вытеснения. Код ядра выполняется до тех пор, пока он не завершится (возвратит управление в пространство пользователя) или пока явно не заблокируется. С появлением серии ядер 2.6, ядро Linux стало преемптивным: теперь есть возможность вытеснить задание в любой момент, конечно, пока ядро находится в состоянии, когда безопасно производить перепланирование выполнения.

В таком случае когда же безопасно производить перепланирование? Ядро способно вытеснить задание, работающее в пространстве ядра, когда это задание не удерживает блокировку. Иными словами, блокировки используются в качестве маркеров тех областей, в которые задание не может быть вытеснено. Ядро рассчитано на многопроцессорность (SMP-safe), поэтому если блокировка не удерживается, то код ядра является реентерабельным и его вытеснять безопасно.

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*