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 à iret
partir 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], reg
serait nécessaire de rejeter la charge si la partie de stockage était défaillante, même sans lock
pré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):
movsq
ou movsw
instruction 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 movsd
est é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 invlpg
ou 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.