diff --git a/course/02-KVM.md b/course/02-KVM.md index ca9d8d6866b60b038e463238c0ddc7c4daffaa85..855fd01563ab88aba7e89364b71845e873d80936 100644 --- a/course/02-KVM.md +++ b/course/02-KVM.md @@ -389,16 +389,23 @@ while (!done) { [//]: # ---------------------------------------------------------------- ## Memory-Mapped I/O devices (MMIO) +\small + - Device registers are **mapped** into the CPU **physical address space** - **RAM and devices registers share the same address space!** - Called **MMIO**: Memory-Mapped Input/Output - Read/write from/to these devices happen exactly like memory (RAM) - All CPU instructions dealing with memory operands can interact with these devices - - e.g. `mov` instruction (x86) + - \footnotesize e.g. `mov` instruction (x86) ```{.verysmall .assembler} - mov al,[42] ; reads 8-bits at MMIO address 42 + mov al,[42] ; reads 32-bits at MMIO address 42 ; and stores it into al register ``` + ```{.verysmall .assembler} + mov ebx,42 ; store 42 into ebx register + mov byte[ebx],17 ; writes 8-bits value 17 + ; to MMIO address 42 + ``` [//]: # ---------------------------------------------------------------- ## Port-Mapped I/O devices (PMIO) @@ -583,22 +590,24 @@ void read_sector(int n, uint8_t *data) { \definecolor{palechestnut}{rgb}{0.87, 0.68, 0.69} \setlength{\fboxsep}{6pt} -\fcolorbox{black}{palechestnut!50}{\parbox{10cm}{How to use these VMexits, to either \textbf{paravirtualize} or \textbf{emulate} a device?}} +\fcolorbox{black}{palechestnut!50}{\parbox{10cm}{How to use these VMexits, to either \textbf{paravirtualize} or \textbf{emulate} a device?}} [//]: # ---------------------------------------------------------------- -# Device paravirtualisation +# Device paravirtualization [//]: # ---------------------------------------------------------------- -## From device emulation to device paravirtualization +## Paravirtualization -::: incremental ::: +::: incremental -- Device emulation can be complex and difficult to implement -- Device drivers that trigger many `VMexits` have poor performance - - each `VMexit` triggers a context switch - - many context switches lead to poor performance -- Paravirtualized devices designed to be simple and efficient with few `VMexits` -- How? +- The idea behind paravirtualization is simple: the guest OS sends a service request to the VMM +- Conceptually very similar to a user app requesting a service from the OS + - this mechanism is called a **syscall** +- \textcolor{myblue}{In paravirtualization, guest requests a service from the VMM} + - \textcolor{myblue}{this mechanism is called an \textbf{hypercall}} +- Device paravirtualization is simpler and much easier to implement than emulation, hence we describe it first +\vspace{.3cm} +- **\textcolor{mygreen}{How to implement paravirtualization?}** ::: @@ -607,11 +616,11 @@ void read_sector(int n, uint8_t *data) { - Mechanism for the guest OS to request the help of the VMM - The VMM exposes an "API" of what functions are available to the guest OS - - typically used to access devices -- Examples of hypercalls: - - guest OS wants to read a disk sector - - guest OS wants to display something - - guest OS wants to send a network packet + - typically used to access and control devices +- Hypercall examples: + - guest OS requests to read a disk sector + - guest OS requests to display something + - guest OS requests to send a network packet [//]: # ---------------------------------------------------------------- ## Hypercalls vs system calls @@ -680,22 +689,45 @@ void read_sector(int n, uint8_t *data) { [//]: # ---------------------------------------------------------------- ## Hypercalls: parameters -```{.verysmall} -typedef struct { - uint8_t x; - int32_t val; - uint64_t msg; // a pointer -} __attribute__((packed)) params_t; -``` +\small + +- **\textcolor{mygreen}{Idea}**: hypercall parameters are serialized/deserialized into a structure, e.g. + ```{.verysmall .c} + typedef struct { + uint8_t val; + uint64_t ptr; // a pointer + } __attribute__((packed)) params_t; + ``` +- Fields can be used to store both inputs and/or outputs +- Structure must be **`packed`** as it might be padded differently between VMM and guest (it depends on how code was compiled!) +- Pointers must be declared as the **largest storage size** among VMM and guest + - \footnotesize otherwise risk of overflow if architectures are of different size (e.g. 32-bits guest and 64-bits VMM) -IMPORTANT: if VMM and guest architecture are of different size, e.g. guest is 32-bits and VMM is 64, the above structure must use the **largest** data storage type! +[//]: # ---------------------------------------------------------------- +## Hypercalls parameters: guest + +How does guest OS prepare an hypercall's parameters? + +- Initialize structure with required parameters +- Copy (shallow) structure's content to shared hypercall buffer +- Issue hypercall by writing the desired number to the dedicated hypercall address + +[//]: # ---------------------------------------------------------------- +## Hypercalls parameters: VMM + +How does VMM retrieve an hypercall's parameters? + +- Detect `VMExit` dedicated to an hypercall, then retrieve its number +- Execute function corresponding to the retrieved hypercall number [//]: # ---------------------------------------------------------------- -## Writing/read to/from shared buffer +## Hypercalls: rebasing pointers -- Compiler optimization: - - use memcpy if VMM must write something that must be read back by the guest OS! - - otherwise, compiler won't read the value again (will use the existing value as it doesn't know the value was modified in another thread/process) +- Remember that a pointer is just... an address +- This address is relative to the process' address space +- \textcolor{myred}{Guest OS address space $\neq$ VMM address space!} +- VMM must convert every guest address into the address relative to the VMM's address space +- This process is called **pointer rebasing** [//]: # ---------------------------------------------------------------- # VMExits: retrieving values @@ -969,6 +1001,18 @@ outb(0x3C5, 0x0F); ::: :::::: +<!-- +[//]: # ---------------------------------------------------------------- +## Performance consideration: emulation vs paravirtualization + +- Emulation typically triggers lots of `VMexits` +- Device drivers that trigger many `VMexits` have poor performance + - each `VMexit` triggers a context switch + - many context switches lead to poor performance +- Paravirtualized devices designed to be simple and efficient with only few `VMexits` + - leads to **much better performance** than emulation +--> + [//]: # ---------------------------------------------------------------- # VMM software architecture @@ -990,7 +1034,7 @@ outb(0x3C5, 0x0F); - Dedicate a thread for every vCPU - If using the SDL library to manage the display, **make sure** that only the main thread calls SDL functions[^8] - Dedicate a thread to interact with the user - - if using SDL, handle it in the same thread (main) as the one calling SDL functions + - if using SDL, handle it in the same thread (`main` function) as the one calling SDL functions - If VMM emulates a timer, have it run in a dedicated thread [^8]: \scriptsize [\textcolor{myblue}{https://documentation.help/SDL/thread.html}](https://documentation.help/SDL/thread.html) diff --git a/labs/lab-virtual_game_machine/lab-virtual_game_machine.md b/labs/lab-virtual_game_machine/lab-virtual_game_machine.md index 4f7b42e6f8facf6ae2c6ab3fd2f2596a7e75af63..6cc92654f7ea2cd02e1803adfdb2d17673846b0c 100644 --- a/labs/lab-virtual_game_machine/lab-virtual_game_machine.md +++ b/labs/lab-virtual_game_machine/lab-virtual_game_machine.md @@ -336,7 +336,7 @@ Le petit programme **`bin2array`**, dont le code source [se trouve ici](https:// ## Consignes à propos de l'implémentation - Gérez toujours les erreurs potentielles\ : testez toujours le retour de fonctions qui peuvent échouer, en particulier celles qui dépendent de l'utilisateur (ex. `open`). -- Assurez-vous que votre guest n'a aucun moyen de faire crasher[^1] votre VMM\ ; si c'est le cas, c'est que votre VMM n'est pas robuste (p.ex. exécuter un guest bidon ou volontairement buggé). +- Assurez-vous que votre guest n'a aucun moyen de faire crasher[^1] votre VMM\ ; si c'est le cas, c'est que votre VMM n'est pas robuste (p.ex. exécuter un guest bidon ou volontairement buggé, n° d'hypercalls invalides, etc.). - Modularisez votre code afin de ne pas avoir tout le code dans un même fichier source. - Utilisez des noms de variables qui ont du sens et qui aident à la compréhension du code. - Ecrivez des fonctions courtes, lisibles et réutilisables. diff --git a/labs/lab-virtual_game_machine/skeleton/guest/entrypoint_asm.s b/labs/lab-virtual_game_machine/skeleton/guest/entrypoint_asm.s index f1e90a750305e094691e0da0b0642a83ad42d3b9..1c2ea58fadf03feb88df23a501cf56defbfcf8a0 100644 --- a/labs/lab-virtual_game_machine/skeleton/guest/entrypoint_asm.s +++ b/labs/lab-virtual_game_machine/skeleton/guest/entrypoint_asm.s @@ -25,7 +25,10 @@ mov ss,ax mov es,ax call guest_main ; call guest C code entrypoint + +@forever: hlt ; halt the CPU +jmp @forever ; Setup a GDT table to identity map the full 4GB address space (32 bits) gdt_start: ; the GDT table starts here