Ответ 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.