В версиях Linux до 2.2.0 используется аппаратное переключение задач, когда TSS сохраняет / восстанавливает регистры для вас. Вот что делает "ljmp %0\n\t"
. (ljmp
- это синтаксис AT & T для дальней JMP, предположительно для шлюза задач). Я не очень знаком с аппаратными средствами TSS, потому что это не очень актуально; он по-прежнему используется в современных ядрах для получения RSP, указывающего на стек ядра для обработчиков прерываний, но не для переключения контекста между задачами.
Переключение аппаратных задач происходит медленно, поэтому более поздние ядра избегают этого. Linux 2.2 делает сохранение / восстановление сохраненных вызовов регистров вручную, с push
/ pop
до / после замены стеков. EAX, EDX и ECX объявлены как фиктивные выходные данные ("=a" (eax), "=d" (edx), "=c" (ecx)
), поэтому компилятор знает, что старые значения этих регистров больше недоступны.
Это разумный выбор, потому что switch_to
, вероятно, используется внутри не встроенной функции. Вызывающая сторона выполнит вызов функции, который в конечном итоге вернет (после выполнения некоторой задачи некоторое время) с восстановленными сохраненными вызовом регистрами, а закрытые вызовом регистры - как в обычном вызове функции. (Таким образом, код компилятора для функции, которая использует макрос switch_to
, не должен выдавать код сохранения / восстановления вне встроенного asm). Если вы подумаете о написании целой функции переключения контекста в asm (не inline asm), вы получите бесплатное дублирование энергозависимых регистров, потому что звонящие ожидают этого.
Так как же последующим ядрам избежать сохранения / восстановления этих регистров во встроенном asm?
Linux 2.4 использует "=b" (last)
в качестве выходного операнда, поэтому компилятор должен сохранять / восстанавливать EBX в функции, которая использует этот asm. Asm по-прежнему сохраняет / восстанавливает ESI, EDI и EBP (а также ESP). Текст статьи отмечает это:
Переключатель контекста ядра 2.4 вносит несколько незначительных изменений: EBX больше не выдвигается и не выталкивается, а теперь включается в выходные данные встроенной сборки. У нас есть новый входной аргумент.
Я не вижу, где они сообщают компилятору о том, что EAX, ECX и EDX не сохранились, так что это странно. Это может быть ошибкой, с которой им удается справиться, сделав функцию noinline
или что-то еще?
Linux 2.6 на i386 использует больше выходных операндов, которые заставляют компилятор обрабатывать сохранение / восстановление.
Но в Linux 2.6 для x86-64 есть хитрость, которая позволяет легко выполнить сохранение / восстановление для компилятора: #define __EXTRA_CLOBBER ,"rcx","rbx","rdx","r8","r9","r10", "r11","r12","r13","r14","r15"
Обратите внимание на объявление clobbers: : "memory", "cc" __EXTRA_CLOBBER
Это сообщает компилятору, что встроенный asm уничтожает все эти регистры, поэтому компилятор будет выдавать инструкции для сохранения / восстановления этих регистров в начале / конце любой функции, switch_to
в конечном итоге встроенной в.
Сообщение компилятору о том, что все регистры уничтожены после переключения контекста, решает ту же проблему, что и ручное сохранение / восстановление их с помощью встроенного asm. Компилятор все равно создаст функцию, которая подчиняется соглашению о вызовах.
Переключатель контекста переключается на стек новой задачи, поэтому сгенерированный компилятором код сохранения / восстановления всегда выполняется с соответствующим указателем стека. Обратите внимание, что явные инструкции push / pop внутри встроенного asm int Linux 2.2 и 2.4 перед / после всего остального.