O Torvalds Linus

Сайт о *nix системах и всем что с ними связано

Начиная с Intel 80486DX, блок операций с плавающей точкой (Floating Point Unit, FPU) интегрирован в процессор. Тем не менее название математический сопроцессор используется по-прежнему и напоминает о тех днях, когда вычисления с плавающей точкой выполнялись дорогим специализированным чипом. Для поддержки совместимости со старыми моделями арифметические функции выполняются при помощи ESCAPE – инструкций, т. е. инструкций с префиксным байтом, имеющим значение от 0xd8 до Oxdf. Эти инструкции работают с набором процессорных регистров для операций с плавающей точкой. Очевидно, что если процесс использует ESCAPE – инструкции, то содержимое регистров с плавающей точкой является частью его аппаратного контекста и должно быть сохранено. Впоследствии компания Intel встроила в свои микропроцессоры Pentium новый набор ассемблерных инструкций. Они получили название MMX – инструкций и были задуманы для ускорения работы мультимедийных приложений. MMX – инструкции используют регистры блока операций с плавающей точкой (блока FPU).

Очевидным недостатком этого архитектурного решения является невозможность для программистов применять инструкции с плавающей точкой вместе с MMX – инструкциями. Достоинство же заключается в том, что разработчики операционной системы могут игнорировать новый набор инструкций, потому что фрагмент кода, переключающего процессы, отвечающий за сохранение состояния блока FPU, может быть использован и для сохранения состояния блока ММХ.
MMX – инструкции ускоряют работу мультимедийных приложений, потому что реализуют внутри процессора конвейер SIMD (Single – Instruction Multiple – Data, одна инструкция -множественные данные”). В модели Pentium III возможности SIMD – конвейера расширяются за счет введения расширений SSE (Streaming SIMD Extensions, потоковые расширения SIMD), которые позволяют обрабатывать значения с плавающей точкой, хранящиеся в 128 – битовых регистрах, называемых ХММ – регистроми. Такие регистры не перекрываются регистрами FPU и ММХ, так что инструкции SSE можно смело смешивать с инструкциями FPU/ММХ. В модели Pentium 4 появилась еще одна функциональная возможность -расширения SSE2. В принципе, это SSE – расширения с поддержкой значений с плавающей точкой с повышенной точностью. Расширения SSE2 используют тот же набор ХММ – регистров, что и расширения SSE.

Микропроцессоры 80×86 не сохраняют автоматически регистры FPU, ММХ и ХММ в сегменте состояния задачи. Однако они предоставляют некоторую аппаратную поддержку, позволяющую ядру сохранять эти регистры только тогда, когда это действительно необходимо. Поддержка сводится к флагу ts в регистре его, причем соблюдаются следующие правила:
? каждый раз, когда производится переключение аппаратного контекста, флаг ts устанавливается;
? каждый раз, когда инструкция ESCAPE, ММХ, SSE или SSE2 выполняется при установленном флаге ts, управляющий блок возбуждает исключение “Устройство недоступно”.

Флаг ts позволяет ядру сохранять и восстанавливать регистры FPU, ММХ и ХММ, только когда это действительно необходимо. В качестве иллюстрации предположим, что процесс А пользуется математическим сопроцессором. Когда происходит переключение контекста с А на В, ядро устанавливает флаг ts и сохраняет регистры с плавающей точкой в сегменте состояния задачи, которым пользуется процесс А. Если новый процесс В не работает с математическим сопроцессором, ядру не нужно будет восстанавливать содержимое регистров с плавающей точкой. Но как только процесс В попытается выполнить инструкцию ESCAPE или ММХ, процессор возбудит исключение “Устройство недоступно”, и соответствующий обработчик загрузит в регистры с плавающей точкой значения, сохраненные в сегменте состояния задачи процесса В.

Теперь мы опишем структуры данных, применяемые при избирательной загрузке регистров FPU, ММХ и ХММ. Они хранятся в дескрипторе процесса, в субполе thread. i387, формат которого описывается объединением
i387_union:
union i387_union (
struct i387_fsave_struct fsave; struct i387_fxsave_struct fxsave; struct i387_soft_struct soft;
);
Из описания видно, что поле может хранить структуры только одного из трех типов. Тип i387_soft_struct применяется моделями процессоров без математического сопроцессора, и ядро Linux до сих пор поддерживает эти старые чипы, эмулируя сопроцессор программным образом. Впрочем, мы больше не будем обсуждать этот морально устаревший случай. Тип i387_fsave_struct используется в процессорах, имеющих математический сопроцессор и, возможно, блок ММХ. Наконец, тип i387_fxsave_struct используется процессорами, поддерживающими расширениями SSE и SSE2.
Дескриптор процесса содержит два дополнительных флага:
? флаг TS USEDFPU, ВХОДЯЩИЙ В состав ПОЛЯ status дескриптора thread_info. Он показывает, использовал ли процесс регистры FPU, ММХ и ХММ в текущем выполнении;
? флаг PF USED MATH, ВХОДЯЩИЙ В состав ПОЛЯ flags дескриптора task_struct. Этот флаг показывает, имеет ли смысл содержимое субполя thread.i387. Флаг сбрасывается (содержимое смысла не имеет) в следующих двух случаях:
oкогда процесс запускает новую программу, делая системный вызов execve () (см. главу 20). Поскольку управление не будет возвращено прежней программе, данные в поле thread.i387 никогда не понадобятся;
oкогда процесс, выполнявший программу в пользовательском режиме, запускает процедуру обработчика сигнала (см. главу 11). Поскольку обработчики сигналов асинхронны по отношению к программе, содержимое регистров с плавающей точкой может быть бессмысленным для обработчика сигнала. Тем не менее ядро сохраняет регистры с плавающей точкой в поле thread.i387 до начала выполнения обработчика и восстанавливает их после его окончания. Поэтому обработчику сигнала разрешается использовать математический сопроцессор.
Сохранение регистров FPU
Как было сказано ранее, функция switch to () выполняет макрос uniazy
fpu, передавая ему в качестве аргумента дескриптор замещаемого процесса prev. Макрос проверяет значение флага ts usedfpu у процесса prev. Если флаг установлен, значит, процесс prev пользовался инструкциями FPU, ММХ, SSE или SSE2, и ядро должно сохранить соответствующий аппаратный контекст:
if (prev ->thread_info ->status & TS_USEDFPU) save_init_fpu(prev);
Функция save init fpu () выполняет следующие действия:
1. Сохраняет содержимое регистров FPU в дескрипторе процесса prev, после чего заново инициализирует блок FPU. Если процессор использует расширения SSE/SSE2, функция также сохраняет содержимое регистров ХММ и заново инициализирует блок SSE/SSE2. Для реализации всего этого достаточно пары строчек на мощном расширенном встроенном ассемблере; либо
asm volatile( “fxsave %0 ; fnclex”
: M=mM (prev ->thread.i387.fxsave) );,
если процессор использует расширения SSE/SSE2, либо
asm volatile( “fnsave %0 ; fwait”
: “=m” (prev ->thread.i387.fsave) );
в противном случае.
2. Сбрасывает флаг ts usedfpu процесса prev: prev ->thread_info ->status &= ~TS_USEDFPU;
3. Устанавливает бит ts в регистре его при помощи макроса stts (), который на практике возвращает следующие ассемблерные инструкции:
movl %сгО, %еах orl $8,%еах movl %еах, %сгО.

Add A Comment