Почему я могу выполнять операции с плавающей запятой внутри модуля ядра Linux?

Я бегу на компьютер x86 с CentOS 6.3 (ядро версии 2.6.32 системы).

я скомпилировал следующую функцию в модуль драйвера символов голых костей в качестве эксперимента, чтобы увидеть, как ядро Linux реагирует на операции с плавающей запятой.

static unsigned floatstuff(void){
    float x = 3.14;
    x *= 2.5;
    return x;
}

...

printk(KERN_INFO "x: %u", x);

код компилируется (чего не ожидал), поэтому я вставил модуль и проверил журнал с dmesg. Журнал показывал:x: 7.

это кажется странным; я думал, что вы не можете выполнять операции с плавающей запятой в ядро Linux-сохранить некоторые исключения, такие как kernel_fpu_begin(). Как модуль выполнял операцию с плавающей точкой?

это потому что я на процессоре x86?

4 ответов


я думал, что вы не могли выполнять операции с плавающей запятой в ядре Linux

вы можете безопасное: отказ от использования kernel_fpu_begin() / kernel_fpu_end() не означает, что инструкции FPU будут неисправны (по крайней мере, на x86).

вместо этого он будет молча повреждать состояние FPU пользовательского пространства. Это плохо, не делай этого.

компилятор не знает, что kernel_fpu_begin() означает, что он не может проверять / предупреждать о коде, который компилируется в FPU инструкции за пределами регионов ФПУ-begin.

может быть режим отладки, в котором ядро отключает инструкции SSE, x87 и MMX вне kernel_fpu_begin / end регионы, но это будет медленнее и не делается по умолчанию.

это можно: настройки CR0::TS = 1 делает ошибку инструкций x87, поэтому ленивое переключение контекста FPU возможно, и есть другие биты для SSE и AVX.


здесь много способы для багги код ядра вызывает серьезные проблемы. Это лишь один из многих. В C вы почти всегда знаете, когда используете плавающую точку (если опечатка не приводит к 1. константа или что-то в контексте, который фактически компилируется).


почему архитектурное состояние FP отличается от integer?

Linux должен сохранять / восстанавливать целочисленное состояние каждый раз, когда он входит/выходит из ядра. Весь код должен использовать целочисленные регистры (за исключением гиганта линейный блок вычисления FPU, который заканчивается на jmp вместо ret (ret изменение rsp).)

но код ядра избегает FPU в целом, поэтому Linux оставляет состояние FPU несохраненным при входе из системного вызова, сохраняя только перед фактическим переключением контекста на другой пользовательского пространства процесс или on kernel_fpu_begin. В противном случае обычно возвращается к тому же процессу пользовательского пространства на том же ядре, поэтому состояние FPU не нужно восстанавливать, потому что ядро не трогал. (И именно здесь произойдет повреждение, если задача ядра действительно изменит состояние FPU. Я думаю, что это происходит в обоих направлениях: user-space также может повредить код состояние FPU).

целочисленное состояние довольно мало, только 16X 64-разрядные регистры + RFLAGS и сегмент регов. Состояние FPU более чем в два раза больше даже без AVX: 8X 80-разрядные x87 регистры, и 16X XMM или YMM, или 32X ZMM регистры (+ MXCSR, и x87 статус + управляющие слова). Также MPX bnd0-4 регистры объединены в "FPU". На данный момент" состояние FPU " означает только все нецелые регистры. На мой разъема, dmesg говорит x86/fpu: Enabled xstate features 0x1f, context size is 960 bytes, using 'compacted' format.

посмотреть понимание использования FPU в ядре linux; современный Linux не делает ленивые переключатели контекста FPU по умолчанию для переключателей контекста (только для переходов ядра/пользователя). (Но эта статья объясняет, что такое лень.)

большинство процессов используют SSE для копирования / обнуления небольших блоков памяти в сгенерированный компилятором код и большинство реализаций библиотеки string/memcpy/memset используют SSE/SSE2. Кроме того, поддерживаемое оборудование оптимизировано для сохранения / восстановления (xsaveopt/xrstor), поэтому "нетерпеливый" FPU save/restore может фактически сделать меньше работы, если некоторые / все регистры FP фактически не использовались. например, сохраните только низкие 128b регистров YMM, если они были обнулены с vzeroupper таким образом, процессор знает, что они чисты. (И отметьте этот факт только одним битом в save формат.)

С" нетерпеливым " переключением контекста инструкции FPU остаются включенными все время, поэтому плохой код ядра может повредить их в любое время.


не делай этого!

в пространстве ядра режим FPU отключен по нескольким причинам:

  • это позволяет Linux работать в архитектурах, которые не имеют FPU
  • это позволяет избежать сохранения и восстановления всего набора регистров каждого перехода ядра / пользовательского пространства (это может удвоить время переключения контекста)
  • в основном все функции ядра используют целые числа также для представления десятичных чисел -> вам, вероятно, не нужно плавающая точка
  • в Linux preemption отключается, когда пространство ядра работает в режиме FPU
  • числа с плавающей запятой-зло и может генерировать очень плохое неожиданное поведение

Если вы действительно хотите использовать номера FP (и вы не должны), вы должны использовать kernel_fpu_begin и kernel_fpu_end примитивы, чтобы избежать разрыва регистров пользовательского пространства, и вы должны учитывать все возможные проблемы (включая безопасность) при работе с FP числа.


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

возможно, вы думаете о случаях, когда арифметика с плавающей запятой эмулируется в программном обеспечении. Но даже в этом случае он будет доступен в ядре (ну, если он каким-то образом не отключен).

Мне интересно, где это восприятие исходит от? Может быть, я что-то упускаю.

нашел это. Кажется, это хорошее объяснение.


ядро ОС может просто отключить FPU в режиме ядра.

во время работы FPU, в то время как ядро операции с плавающей запятой включит FPU и после этого выключит FPU.

но вы не можете распечатать его.