Как на самом деле работают функции schedule () + switch_to () из ядра Linux? - PullRequest
28 голосов
/ 29 июня 2011

Я пытаюсь понять, как на самом деле работает процесс расписания в ядре Linux. Мой вопрос не об алгоритме планирования. О том, как работают функции schedule() и switch_to().

Я постараюсь объяснить. Я видел это:

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

Изучая исходный код ядра (linux-2.6.10 - версия, на которой основана книга "Разработка ядра Linux, второе издание"), я также увидел, что некоторые коды могут вызывать функцию schedule() добровольно, давая другой процесс право на бег. Я видел, что функция switch_to() действительно переключает контекст. Я изучил некоторые архитектурно-зависимые коды, пытаясь понять, что на самом деле делал switch_to().

Это поведение вызвало некоторые вопросы, на которые я не смог найти ответы:

  1. Когда switch_to() заканчивается, каков текущий рабочий процесс? Процесс, который называется schedule()? Или следующий процесс, который был выбран для запуска?

  2. Когда schedule() вызывается по прерыванию, выбранный процесс, который нужно запустить, начинает выполняться после завершения обработки прерывания (после некоторого RTE)? Или до этого?

  3. Если функция schedule() не может быть вызвана из прерывания, когда установлен флаг- need_resched?

  4. Когда работает обработчик прерывания по таймеру, какой стек используется?

Я не знаю, смогу ли я прояснить ситуацию. Если я не смог, я надеюсь, что смогу сделать это после некоторых ответов (или вопросов). Я уже посмотрел на несколько источников, пытаясь понять этот процесс. У меня есть книга "Linux Kernel Development, sec ed", и я тоже ее использую. Я немного знаю о MIP и архитектуре H8300, если это поможет объяснить.

1 Ответ

31 голосов
/ 30 июня 2011
  1. После вызова switch_to() стек ядра переключается на стек задачи, указанной в next. Изменение адресного пространства и т. Д. Обрабатывается, например, context_switch().
  2. schedule() не может быть вызван в атомарном контексте, в том числе из прерывания (см. Проверку в schedule_debug()). Если требуется перепланирование, устанавливается флаг задачи TIF_NEED_RESCHED, который проверяется в пути возврата прерывания .
  3. См. 2.
  4. Я полагаю, что при использовании стеков по умолчанию 8K прерывания обрабатываются любым выполняемым в данный момент стеком ядра. Если используются стеки 4K, я думаю, что есть отдельный стек прерываний (автоматически загружаемый благодаря некоторой магии x86), но я не совсем уверен в этом.

Чтобы быть более подробным, вот практический пример:

  1. Произошло прерывание. Процессор переключается на процедуру батута прерывания, которая помещает номер прерывания в стек, а затем переводит в common_interrupt
  2. вызовы common_interrupt do_IRQ , который отключает приоритетное прерывание , затем обрабатывает IRQ
  3. В какой-то момент принимается решение о переключении задач. Это может быть от прерывания по таймеру или от вызова пробуждения. В любом случае set_task_need_resched вызывается, устанавливая флаг задачи TIF_NEED_RESCHED.
  4. В конце концов, CPU возвращается из do_IRQ в исходном прерывании и переходит к пути выхода IRQ. Если этот IRQ был вызван из ядра, он проверяет, установлено ли значение TIF_NEED_RESCHED и, если это так, вызывает preempt_schedule_irq , что кратко разрешает прерывания при выполнении schedule().
  5. Если IRQ был вызван из пространства пользователя, мы сначала проверяем, нужно ли что-нибудь сделать до возвращения. Если это так, мы переходим к retint_careful , который проверяет как ожидающий повторный график (и напрямую вызывает schedule() при необходимости), так и проверку ожидающих сигналов, затем возвращается к следующему раунду на retint_check до тех пор, пока не будут установлены более важные флаги.
  6. Наконец, мы восстанавливаем GS и возвращаемся из обработчика прерываний .

Что касается switch_to(); switch_to() (на x86-32) делает:

  1. Сохраните текущие значения EIP (указатель инструкции) и ESP (указатель стека), когда мы вернемся к этой задаче в какой-то момент позже.
  2. Переключить значение current_task. На этом этапе current теперь указывает на новое задание.
  3. Переключитесь на новый стек, затем вставьте EIP, сохраненный задачей, на которую мы переключаемся, в стек. Позже будет выполнен возврат с использованием этого EIP в качестве адреса возврата; вот как он возвращается к старому коду, который ранее назывался switch_to()
  4. Вызов __ switch_to () . На этом этапе current указывает на новую задачу, и мы находимся в стеке новой задачи, но различные другие состояния процессора не были обновлены. __switch_to() обрабатывает переключение состояний, таких как FPU, дескрипторы сегментов, регистры отладки и т. Д.
  5. По возвращении из __switch_to() возвращается адрес возврата, в который switch_to() вручную помещается в стек, возвращая выполнение, где оно было до switch_to(), в новой задаче. Выполнение теперь полностью возобновлено при переключении на задачу.

x86-64 очень похож, но должен сделать немного больше сохранения / восстановления состояния из-за другого ABI.

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