Pourquoi est-ce que je peux effectuer des opérations en virgule flottante dans un module du kernel Linux?

Je cours sur un système x86 CentOS 6.3 (kernel v2.6.32).

J’ai compilé la fonction suivante dans un module de pilote de caractère simple comme une expérience pour voir comment le kernel Linux réagit aux opérations en virgule flottante.

static unsigned floatstuff(void){ float x = 3.14; x *= 2.5; return x; } ... printk(KERN_INFO "x: %u", x); 

Le code compilé (qui n’attendait pas), j’ai donc inséré le module et vérifié le journal avec dmesg . Le journal indiquait: x: 7 .

Cela semble étrange; Je pensais que vous ne pouviez pas effectuer d’opérations en virgule flottante dans le kernel Linux – enregistrez certaines exceptions telles que kernel_fpu_begin() . Comment le module a-t-il effectué l’opération en virgule flottante?

Est-ce parce que je suis sur un processeur x86?

Je pensais que vous ne pouviez pas effectuer d’opérations en virgule flottante dans le kernel Linux

Vous ne pouvez pas en toute sécurité : ne pas utiliser kernel_fpu_begin() / kernel_fpu_end() ne signifie pas que les instructions du FPU seront erronées (pas au moins sur x86).

Au lieu de cela, il corrompra silencieusement l’état FPU de l’espace utilisateur. C’est mauvais; ne fais pas ça.

Le compilateur ne sait pas ce que kernel_fpu_begin() signifie, il ne peut donc pas vérifier / avertir du code qui comstack en instructions FPU en dehors des régions de début de FPU.

Il peut exister un mode de débogage dans lequel le kernel désactive les instructions SSE, x87 et MMX en dehors des régions kernel_fpu_begin / end , mais cela serait plus lent et ne se ferait pas par défaut.

Il est cependant possible que: la définition de CR0::TS = 1 entraîne une erreur dans les instructions x87, donc la commutation de contexte FPU paresseuse est possible, et il existe d’autres bits pour SSE et AVX.


Il existe de nombreuses façons pour le code de kernel bogue de causer de sérieux problèmes. C’est juste l’un des nombreux. Dans C, vous savez à peu près toujours quand vous utilisez une virgule flottante (à moins qu’une faute de frappe n’entraîne une constante ou quelque chose dans un contexte qui comstack réellement).


Pourquoi l’état architectural de FP est-il différent de l’entier?

Linux doit sauvegarder / restaurer l’état entier chaque fois qu’il entre / quitte le kernel. Tout le code doit utiliser des registres entiers (sauf pour un bloc linéaire de calcul FPU géant qui se termine par un jmp au lieu de ret ( ret modifie rsp ).)

Mais le code du kernel évite généralement la FPU, donc Linux laisse l’état de la FPU non enregistré à l’entrée d’un appel système, sauvegardant uniquement avant un changement de contexte réel sur un processus d’ espace utilisateur différent ou sur kernel_fpu_begin . Sinon, il est courant de retourner au même processus d’espace utilisateur sur le même cœur, de sorte que l’état du FPU n’a pas besoin d’être restauré car le kernel ne l’a pas touché. (Et c’est là que la corruption se produirait si une tâche du kernel modifiait réellement l’état de la FPU. Je pense que cela va dans les deux sens: l’espace utilisateur pourrait également corrompre votre état de FPU).

L’état entier est assez petit, seulement 16 registres 64 bits + RFLAGS et des registres de segment. L’état de la FPU est deux fois plus important même sans AVX: 8 registres x87 80 bits et 16 registres XMM ou YMM ou 32x ZMM (+ mots de contrôle d’état + MXCSR et x87). Les registres MPX bnd0-4 sont également regroupés avec “FPU”. A ce stade, “état FPU” signifie simplement tous les registres non entiers. Sur mon Skylake, dmesg dit x86/fpu: Enabled xstate features 0x1f, context size is 960 bytes, using 'compacted' format.

Voir Comprendre l’utilisation de FPU dans le kernel Linux ; Linux moderne ne fait pas les changements de contexte FPU paresseux par défaut pour les changements de contexte (uniquement pour les transitions kernel / utilisateur). (Mais cet article explique ce qu’est Lazy).

La plupart des processus utilisent SSE pour copier / mettre à zéro les petits blocs de mémoire dans le code généré par le compilateur, et la plupart des implémentations de chaînes / memcpy / memset utilisent SSE / SSE2. De même, la sauvegarde / restauration optimisée supscope par le matériel est désormais une chose ( xsaveopt / xrstor), de sorte que la sauvegarde / restauration de FPU “enthousiaste” peut en fait être moins efficace si certains / tous les registres FP n’ont pas été utilisés. Par exemple, ne sauvegardez que les 128b faibles des registres YMM s’ils ont été mis à zéro avec vzeroupper afin que le processeur sache qu’ils sont propres. (Et marquez ce fait avec un seul bit dans le format de sauvegarde.)

Avec un changement de contexte “avide”, les instructions FPU restnt activées en permanence, de sorte qu’un code de kernel incorrect peut les corrompre à tout moment.

Ne fais pas ça!

En mode kernel, le mode FPU est désactivé pour plusieurs raisons:

  • Il permet à Linux de fonctionner dans des architectures sans FPU
  • Il évite de sauvegarder et de restaurer l’ensemble des registres à chaque transition kernel / espace utilisateur (il peut doubler le temps de changement de contexte)
  • Fondamentalement, toutes les fonctions du kernel utilisent également des entiers pour représenter des nombres décimaux -> vous n’avez probablement pas besoin de virgule flottante
  • Sous Linux, la préemption est désactivée lorsque l’espace kernel est exécuté en mode FPU
  • Les nombres à virgule flottante sont mauvais et peuvent générer un très mauvais comportement inattendu

Si vous voulez vraiment utiliser des numéros de FP (et vous ne devriez pas), vous devez utiliser les primitives kernel_fpu_begin et kernel_fpu_end pour éviter de casser les registres d’espace utilisateur, et vous devez prendre en compte tous les problèmes possibles Nombres.

Pas sûr d’où vient cette perception. Mais le kernel s’exécute sur le même processeur que le code du mode utilisateur et a donc access au même jeu d’instructions. Si le processeur peut faire de la virgule flottante (directement ou par un coprocesseur), le kernel le peut aussi.

Vous pensez peut-être à des cas où l’arithmétique en virgule flottante est émulée dans un logiciel. Mais même ainsi, il serait disponible dans le kernel (enfin, à moins d’être désactivé d’une manière ou d’une autre).

Je suis curieux, d’où vient cette perception? Peut-être que je manque quelque chose.

Trouvé ceci . Semble être une bonne explication.

Le kernel du système d’exploitation peut simplement désactiver le FPU en mode kernel.

Pendant le fonctionnement de FPU, alors que le kernel d’opération en virgule flottante activera et désactivera le FPU, le FPU sera activé.

Mais vous ne pouvez pas l’imprimer.