сохранение регистров общего назначения в switch_to () в linux 2.6 - PullRequest
2 голосов
/ 31 мая 2019

Я видел код switch_to в статье «Эволюция переключателя контекста x86 в Linux» в ссылке https://www.maizure.org/projects/evolution_x86_context_switch_linux/

В большинстве версий switch_to сохраняются / восстанавливаются только ESP / RSP и / или EBP / RBP, но не другие регистры, сохраняемые при вызове во встроенном ассемблере. Но версия 2.2.0 для Linux 2.2.0 сохраняет их в этой функции, потому что она использует программную коммутацию контекста, а не полагается на аппаратные средства TSS. Более поздние версии Linux все еще выполняют переключение контекста программного обеспечения, но не имеют этих инструкций push / pop.

Сохраняются ли регистры в другой функции (возможно, в функции schedule())? Или нет необходимости сохранять эти регистры в контексте ядра?

(я знаю, что эти регистры пользовательского контекста сохраняются в стеке ядра, когда система переходит в режим ядра).

1 Ответ

3 голосов
/ 04 июня 2019

В версиях 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 перед / после всего остального.

...