Les instructions x86 nécessitent-elles leur propre encodage ainsi que tous leurs arguments pour être présents en mémoire en même temps?

64

J'essaie de comprendre s'il est possible d'exécuter une machine virtuelle Linux dont la RAM n'est sauvegardée que par une seule page physique.

Pour simuler cela, j'ai modifié le gestionnaire de défaut de page imbriqué dans KVM pour supprimer le bit actuel de toutes les entrées de table de page imbriquée (NPT), à l'exception de celui correspondant au défaut de page actuellement traité.

En essayant de démarrer un invité Linux, j'ai observé que les instructions d'assemblage qui utilisent des opérandes de mémoire, comme

add [rbp+0x820DDA], ebp

conduire à une boucle de défaut de page jusqu'à ce que je restaure le bit actuel pour la page contenant l'instruction ainsi que pour la page référencée dans l'opérande (dans cet exemple [rbp+0x820DDA]).

Je me demande pourquoi c'est le cas. Le CPU ne doit-il pas accéder aux pages mémoire de manière séquentielle, c'est-à-dire d'abord lire l'instruction puis accéder à l'opérande mémoire? Ou x86 nécessite-t-il que la page d'instructions ainsi que toutes les pages d'opérandes soient accessibles en même temps?

Je teste sur AMD Zen 1.

savvybug
la source
2
Pourquoi voudriez-vous faire ça?
SS Anne
11
Juste par intérêt technique :)
savvybug
14
Vote pour l'idée de projet hilarant.
pipe
10
C'est fou au niveau de "démarrer Linux sur un émulateur 486 fonctionnant en JavaScript dans le navigateur". J'aime cela.
chrylis -on strike-
3
Heh, apparemment j'ai amené cette question à la même conclusion logique à laquelle vous pensiez déjà, à propos de l'ensemble de travail minimum pour des progrès futurs garantis. J'avais déjà répondu à cela avant d'ajouter ce nouveau premier paragraphe à la question. : PI a ajouté quelques liens et plus de détails dans quelques endroits (par exemple, le marcheur de page est autorisé à mettre en cache en interne certaines entrées du répertoire de pages invité), car cette question reçoit beaucoup plus d'attention que je ne le pensais grâce à une manière ou à une autre de HNQ.
Peter Cordes

Réponses:

56

Oui, ils nécessitent le code machine et tous les opérandes de mémoire.

Le CPU ne doit-il pas accéder aux pages mémoire de manière séquentielle, c'est-à-dire d'abord lire l'instruction puis accéder à l'opérande mémoire?

Oui, c'est logiquement ce qui se passe, mais une exception de défaut de page interrompt ce processus en 2 étapes et annule toute progression. Le CPU n'a aucun moyen de se rappeler de quelle instruction il s'agissait au milieu d'un défaut de page.

Lorsqu'un gestionnaire de défauts de page revient après avoir traité un défaut de page valide, RIP = l'adresse de l'instruction défaillante, de sorte que le processeur réessaye de l'exécuter à partir de zéro .

Il serait légal pour le système d'exploitation de modifier le code machine de l'instruction défaillante et de s'attendre à ce qu'il exécute une instruction différente à iretpartir du gestionnaire de défaut de page (ou de toute autre exception ou gestionnaire d'interruption). Donc AFAIK, il est architecturalement nécessaire que le CPU refasse la récupération de code de CS: RIP dans le cas dont vous parlez. (En supposant qu'il retourne même au CS: RIP défaillant au lieu de planifier un autre processus en attendant le disque sur une erreur de page matérielle, ou en délivrant un SIGSEGV à un gestionnaire de signal sur une erreur de page non valide.)

Il est probablement également architecturalement requis pour l'entrée / sortie de l'hyperviseur. Et même si ce n'est pas explicitement interdit sur le papier, ce n'est pas ainsi que les CPU fonctionnent.

@torek commente que certains microprocesseurs (CISC) décodent partiellement les instructions et vident l'état du micro-enregistrement sur une erreur de page , mais x86 n'est pas comme ça.


Quelques instructions sont interruptibles et peuvent faire des progrès partiels, comme rep movs(memcpy dans une boîte) et d'autres instructions de chaîne, ou rassembler des magasins de charges / scatter. Mais le seul mécanisme est la mise à jour des registres architecturaux comme RCX / RSI / RDI pour les opérations de chaîne, ou les registres de destination et de masque pour les regroupements (par exemple, manuel pour AVX2vpgatherdd ). Ne pas conserver l'opcode / décoder entraîne un registre interne caché et le redémarrer après iret à partir d'un gestionnaire de défauts de page. Ce sont des instructions qui effectuent plusieurs accès aux données distincts.

Gardez également à l'esprit que x86 (comme la plupart des ISA) garantit que les instructions sont atomiques wrt. interruptions / exceptions: elles se produisent entièrement, ou ne se produisent pas du tout, avant une interruption. Interrompre une instruction d'assemblage pendant son fonctionnement . Ainsi, par exemple, il add [mem], regserait nécessaire de rejeter la charge si la partie de stockage était défaillante, même sans lockpréfixe.


Le pire des cas, le nombre de pages d'espace utilisateur invité présentes pour faire avancer le dossier peut être de 6 (plus des sous-arborescences de table de pages du noyau invité distinctes pour chacune):

  • movsqou movswinstruction de 2 octets couvrant une limite de page, donc les deux pages sont nécessaires pour qu'elle décode.
  • opérande source qword [rsi]également un partage de page
  • opérande de destination qword [rdi]également un fractionnement de page

Si l'une de ces 6 pages fait défaut, nous revenons à la case départ.

rep movsdest également une instruction de 2 octets, et faire des progrès sur une étape aurait la même exigence. Des cas similaires comme push [mem]ou pop [mem]pourraient être construits avec une pile mal alignée.

Une des raisons (ou avantages secondaires) pour / de rendre les charges de regroupement / magasins de dispersion "interruptibles" (en mettant à jour le vecteur de masque avec leur progression) est d'éviter d'augmenter cette empreinte minimale pour exécuter une seule instruction. Également pour améliorer l'efficacité de la gestion de plusieurs défauts lors d'une collecte ou d'une diffusion.


@Brandon souligne dans les commentaires qu'un invité aura besoin de ses tables de pages en mémoire , et les fractionnements de page de l'espace utilisateur peuvent également être des fractionnements de 1 Go, de sorte que les deux côtés se trouvent dans des sous-arborescences différentes du PML4 de niveau supérieur. La marche de page HW devra toucher toutes ces pages de table de pages d'invité pour progresser. Une situation aussi pathologique ne se produira probablement pas par hasard.

Le TLB (et les internes du marcheur de page) sont autorisés à mettre en cache certaines des données de la table de pages, et ne sont pas tenus de redémarrer le cheminement de page à partir de zéro, sauf si le système d'exploitation l'a fait invlpgou a défini un nouveau répertoire de page de niveau supérieur CR3. Aucun de ces éléments n'est nécessaire lors du changement d'une page de non présent à présent; x86 sur papier garantit qu'il n'est pas nécessaire (donc la "mise en cache négative" des PTE non présents n'est pas autorisée, du moins invisible pour le logiciel). Ainsi, le processeur peut ne pas VMexit même si certaines des pages de table de pages physiques invitées ne sont pas réellement présentes.

Les compteurs de performance PMU peuvent être activés et configurés de telle sorte que l'instruction nécessite également un événement perf pour une écriture dans un tampon PEBS pour cette instruction. Avec un masque de compteur configuré pour ne compter que les instructions de l'espace utilisateur, pas le noyau, il se pourrait bien qu'il continue d'essayer de déborder le compteur et de stocker un échantillon dans le tampon chaque fois que vous revenez dans l'espace utilisateur, produisant un défaut de page.

Peter Cordes
la source
15
Le pire des cas pour une seule instruction pourrait être quelque chose comme " push dword [foo" (ou même juste call [foo]) avec tout ce qui est mal aligné sur "la limite de la table du pointeur du répertoire de pages" (en ajoutant jusqu'à 6 pages, 6 tables de pages, 6 répertoires de pages, 6 PDPT et un PML4); avec la fonction «échantillonnage basé sur des événements précis avec tampon PEBS» du processeur activée et configurée de telle sorte que les pushdonnées de surveillance des performances soient ajoutées au tampon PEBS. Pour un conservateur "minimum de pages fournies par l'hôte afin que l'invité puisse progresser dans les cas pathologiques", je voudrais au moins 16 pages.
Brendan
4
Notez que ce genre de chose a toujours été courant dans les architectures CISC-y. Certains microprocesseurs décodent partiellement les instructions et vident l'état du micro-enregistrement sur une erreur de page, mais d'autres ne le font pas et / ou n'exigent pas que les opérandes d'adresse pour les instructions "loop-y" (DBRA sur m68k, MOVC3 / MOVC5 sur Vax, etc.) soient dans des registres similaires à votre exemple REP MOVS.
torek
1
@Brendan: quelqu'un a compté le pire des cas sur une instruction VAX comme environ 50 pages. J'oublie les détails, mais vous mettriez évidemment l'instruction elle-même sur une limite de page, utilisez quelque chose comme la recherche de table de traduction avec la table couvrant une limite de page, utilisez (rX) [rY] avec les indirects aux limites de page, et bientôt. Les instructions les plus velues prenaient jusqu'à 6 opérandes (les chargeant dans r0-r5) et les six pourraient être des doubles indirects, je pense.
torek
3
L'OS pourrait changer l'instruction, mais elle peut aussi changer EIP. Il y a donc une question logique de suivi. Quel est le nombre minimum de pages nécessaires, en supposant un schéma de correctif d'instructions intelligent? Par exemple, copiez la valeur non alignée dans un tampon de travail aligné, émulez l'instruction et IRET dans l'instruction suivante.
MSalters
1
La page contenant les iretinstructions du système d'exploitation doit également être en mémoire. Il s'agit d'une instruction d'un octet, donc d'une page supplémentaire. L'adresse d'interruption du gestionnaire de défauts de page doit également être en mémoire, mais il peut s'agir de la même page que ci-dessus.
Stig Hemmer