Подтвердить что ты не робот

Как работает функция sched() + switch_to() из ядра linux?

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

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

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

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

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

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

  • Когда schedule() вызывается прерыванием, выбранный процесс запуска запускается, когда обработка прерываний заканчивается (после некоторого RTE)? Или до этого?

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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