Zadanie 4: Maszyna wirtualna¶
Data ogłoszenia: 08.06.2021
Termin oddania: 07.09.2021 (brak możliwości spóźnienia)
Materiały dodatkowe¶
przykładowe użycie KVM:
exvm.c
nagłówek przydatny do napisania rozwiązania: https://github.com/mwkmwkmwk/alienvm-tests/blob/main/alienvm.h
testy do zadania: https://github.com/mwkmwkmwk/alienvm-tests/
Wprowadzenie¶
W ostatnim czasie w pobliżu wydziału rozbił się statek kosmitów. Udało się nam wydobyć z niego nośniki danych zawierające programy na bardzo zaawansowane technicznie komputery kosmitów, lecz niestety nie przetrwał żaden działający komputer – nie możemy więc uruchomić ich programów. Jednak po wielu miesiącach inżynierii wstecznej w końcu udało nam się ustalić, jak działają interfejsy sprzętowe komputerów kosmitów.
O komputerach kosmitów¶
Wygląda na to, że kosmici są bardzo zaawansowani — używają 64-bitowych procesorów x86 i części standardowych peryferiów.
Komputer kosmitów składa się z:
1 (jednego) procesora x86
procesor ma wbudowany lokalny APIC (o identyfikatorze 0, pod adresem
0xfee00000
)
1 (jednego) standardowego IO APIC (pod adresem
0xfec00000
), wraz ze starym PIC dla kompatybilności wstecznejstandardowego układu 8254 PIT (podłączonego w standardowy sposób do PIC + IO APIC)
16MB RAMu, zajmującego ciągły obszar zaczynający się od adresu 0 pamięci fizycznej
64kiB ROMu zawierającego BIOS, pod adresem
0xffff0000
(wszystkie zapisy do tego obszaru są ignorowane)1 (jednego) dwukierunkowego portu szeregowego o wysokiej wydajności
1 (jednego) urządzenia blokowego o wysokiej wydajności
1 (jednego) jednokierunkowego portu debugowania
1 (jednego) układu zarządzania maszyną
Port szeregowy — wysył¶
Część wysyłająca portu szeregowego działa na zasadzie DMA i używa bufora pierścieniowego. Wysył portu szeregowego używa też kilku rejestrów sterujących zmapowanych do pamięci oraz linii przerwania numer 3.
- MMIO
0xe0000000
:SERIAL_OUT_DESC_PTR
Rejestr 32-bitowy. Wskaźnik (fizyczny) na stronę pamięci zawierający dane kontrolne wysyłu portu szeregowego. Musi być wielokrotnością rozmiaru strony (4kiB).
- MMIO
0xe0000004
:SERIAL_OUT_SETUP
Rejestr 32-bitowy. Zapis do tego rejestru resetuje urządzenie i konfiguruje jego pracę.
bit 0:
ENABLE
— jeśli ustawione, część wysyłająca powinna po resecie rozpocząć pracę; jeśli wyzerowane, urządzenie nic nie robi.bity 8-15:
NPAGES_M1
— rozmiar bufora urządzenia w stronach, pomniejszony o 1 (czyli wartość 3 w tym polu oznacza 4 strony)
- MMIO
0xe0000008
:SERIAL_OUT_NOTIFY
Rejestr 32-bitowy. Zapis do tego rejestru powiadamia urządzenie o tym, że system operacyjny wysłał nowe dane w buforze. Sama wartość zapisana tutaj jest ignorowana.
Strona pamięci wskazywana przez SERIAL_OUT_DESC_PTR
ma następujący format
(wszystko to 32-bitowe słowa little-endian):
offset
0x000 + i * 4, i <= NPAGES_M1
:BUFFER_PTR
, tablica wskaźników (fizycznych, 32-bitowych) na kolejne strony bufora danych urządzenia. Każdy wpis powinien być wielokrotnością 4kiB. Tablica ta powinna zostać wypełniona przez system operacyjny przed startem urządzenia, a potem nie powinna być zmieniana dopóki urządzenie jest aktywne.offset
0x800
:PUT
— indeks bajtu w buforze, pod którym system operacyjny zapisałby kolejny bajt danych do wysyłu (czyli indeks pierwszego bajtu, który jeszcze nie został wypełniony i wysłany przez system). Powinien być zainicjowany przez system operacyjny (prawdopodobnie na 0) przed startem urządzenia, a potem może zostać zmieniony przez system operacyjny w dowolnym momencie.offset
0xc00
:GET
— indeks bajtu w buforze, od którego urządzenie powinno wysyłać kolejne bajty. Powinien być zainicjowany przez system operacyjny (prawdopodobnie na 0) przed startem urządzenia, a potem może być modyfikowany tylko przez urządzenie.
Urządzenie powinno działać następująco:
while (1) {
if (desc.GET >= (NPAGES_M1 + 1) * 0x1000)
error();
if (desc.PUT >= (NPAGES_M1 + 1) * 0x1000)
error();
if (desc.GET == desc.PUT)
czekaj na zapis do SERIAL_OUT_NOTIFY;
else {
// znajdź adres fizyczny bajtu do wysłania
ptr = desc.BUFFER_PTR[desc.GET >> 12] + (desc.GET & 0xfff);
byte = *(uint8_t*)ptr;
send(byte);
// zwiększ desc.GET o 1, zawijając modulo rozmiar bufora
if (desc.GET == (NPAGES_M1 + 1) * 0x1000 - 1)
desc.GET = 0;
else
desc.GET++;
// wyzwól linię przerwania #3 (linia #3 w PIC oraz IO-APIC)
trigger_edge_irq(3);
}
}
Urządzenie może jednak zastosować pewne optymalizacje:
urządzenie może zakładać, że system operacyjny nie pisze do
desc.GET
, gdy ono jest aktywne (czyli urządzeniu wolno mieć własną kopię tego indeksu w lokalnym rejestrze, pobraną na samym początku działania, a potem tylko kopiować wartość tego rejestru do pamięci)urządzenie może wysłać wiele bajtów naraz, wykonując tylko raz zapis do
desc.GET
w pamięci i wyzwolenie przerwaniaurządzenie może również zakładać, że system operacyjny zapisze rejestr MMIO
SERIAL_OUT_NOTIFY
zawsze po aktualizacjidesc.PUT
— może więc przechowywać lokalną kopię tego słowa w lokalnym rejestrze i aktualizować ją tylko w momencie zapisu tego rejestu MMIO.urządzenie może zakładać, że
BUFFER_PTR
nie zostaną zmienione w trakcie pracy (może więc przechowywać lokalne kopie)
Urządzenie musi jednak przestrzegać zasad:
urządzenie może przeczytać dane z bufora dopiero po tym, jak przeczyta wartość z
desc.PUT
, która na to pozwala (nie wolno wczytywać danych na zapas)urządzenie może zapisać
desc.GET
dopiero po wczytaniu odpowiednich danych z buforaurządzenie może wyzwolić przerwanie dopiero po zapisie
desc.GET
Port szeregowy — odbiór¶
Część odbierająca portu szeregowego działa na zasadzie DMA i używa bufora pierścieniowego. Odbiór portu szeregowego używa też kilku rejestrów sterujących zmapowanych do pamięci oraz linii przerwania numer 4.
- MMIO
0xe0001000
:SERIAL_IN_DESC_PTR
Rejestr 32-bitowy. Wskaźnik (fizyczny) na stronę pamięci zawierający dane kontrolne odbioru portu szeregowego. Musi być wielokrotnością rozmiaru strony (4kiB).
- MMIO
0xe0001004
:SERIAL_IN_SETUP
Rejestr 32-bitowy. Zapis do tego rejestru resetuje urządzenie i konfiguruje jego pracę.
bit 0:
ENABLE
— jeśli ustawione, część odbierająca powinna po resecie rozpocząć pracę; jeśli wyzerowane, urządzenie nic nie robi.bity 8-15:
NPAGES_M1
— rozmiar bufora urządzenia w stronach, pomniejszony o 1 (czyli wartość 3 w tym polu oznacza 4 strony)
- MMIO
0xe0001008
:SERIAL_IN_NOTIFY
Rejestr 32-bitowy. Zapis do tego rejestru powiadamia urządzenie o tym, że system operacyjny odebrał dane z bufora. Sama wartość zapisana tutaj jest ignorowana.
Strona pamięci wskazywana przez SERIAL_IN_DESC_PTR
ma następujący format
(wszystko to 32-bitowe słowa little-endian):
offset
0x000 + i * 4, i <= NPAGES_M1
:BUFFER_PTR
, tablica wskaźników (fizycznych, 32-bitowych) na kolejne strony bufora danych urządzenia. Każdy wpis powinien być wielokrotnością 4kiB. Tablica ta powinna zostać wypełniona przez system operacyjny przed startem urządzenia, a potem nie powinna być zmieniana dopóki urządzenie jest aktywne.offset
0x800
:GET
— indeks bajtu w buforze, od którego system operacyjny będzie czytał kolejny bajt. Urządzenie nie powinno pisać kolejnego bajtu do bufora (powinno zawiesić odbiór), gdyby zapis spowodowałGET == PUT
(przepełnienie bufora). Powinien być zainicjowany przez system operacyjny (prawdopodobnie na 0) przed startem urządzenia, a potem może zostać zmieniony przez system operacyjny w dowolnym momencie.offset
0xc00
:PUT
— indeks bajtu w buforze, pod którym urządzenie ma zapisać kolejny odebrany bajt danych. Powinien być zainicjowany przez system operacyjny (prawdopodobnie na 0) przed startem urządzenia, a potem może być modyfikowany tylko przez urządzenie.
Urządzenie powinno działać następująco:
while (1) {
if (desc.GET >= (NPAGES_M1 + 1) * 0x1000)
error();
if (desc.PUT >= (NPAGES_M1 + 1) * 0x1000)
error();
next_put = desc.PUT + 1;
if (next_put == (NPAGES_M1 + 1) * 0x1000)
next_put = 0;
if (desc.GET == next_put)
czekaj na zapis do SERIAL_IN_NOTIFY;
else {
// znajdź adres fizyczny miejsca do którego zapisujemy odebrany bajt
ptr = desc.BUFFER_PTR[desc.GET >> 12] + (desc.GET & 0xfff);
byte = recv();
*(uint8_t*)ptr = byte;
// zwiększ desc.GET o 1, zawijając modulo rozmiar bufora
desc.PUT = next_put;
// wyzwól linię przerwania #4 (linia #4 w PIC oraz IO-APIC)
trigger_edge_irq(4);
}
}
Urządzenie może jednak zastosować pewne optymalizacje:
urządzenie może zakładać, że system operacyjny nie pisze do
desc.PUT
, gdy ono jest aktywne (czyli urządzeniu wolno mieć własną kopię tego indeksu w lokalnym rejestrze, pobraną na samym początku działania, a potem tylko kopiować wartość tego rejestru do pamięci)urządzenie może odebrać wiele bajtów naraz, wykonując tylko raz zapis do
desc.PUT
w pamięci i wyzwolenie przerwaniaurządzenie może również zakładać, że system operacyjny zapisze rejestr MMIO
SERIAL_IN_NOTIFY
zawsze po aktualizacjidesc.GET
— może więc przechowywać lokalną kopię tego słowa w lokalnym rejestrze i aktualizować ją tylko w momencie zapisu tego rejestu MMIO.urządzenie może zakładać, że
BUFFER_PTR
nie zostaną zmienione w trakcie pracy (może więc przechowywać lokalne kopie)
Urządzenie musi jednak przestrzegać zasad:
urządzenie może zapisać dane do bufora dopiero po tym, jak przeczyta wartość z
desc.GET
, która na to pozwalaurządzenie może zapisać
desc.PUT
dopiero po zapisaniu odpowiednich danych do buforaurządzenie może wyzwolić przerwanie dopiero po zapisie
desc.PUT
Urządzenie blokowe¶
Urządzenie blokowe działa na zasadzie DMA i używa bufora pierścieniowego. Używa też kilku rejestrów sterujący zmapowanych do pamięci oraz linii przerwania 5.
- MMIO
0xe0002000
:BLOCK_DESC_PTR
Rejestr 32-bitowy. Wskaźnik (fizyczny) na stronę pamięci zawierający dane kontrolne urządzenia blokowego. Musi być wielokrotnością rozmiaru strony (4kiB).
- MMIO
0xe0002004
:BLOCK_SETUP
Rejestr 32-bitowy. Zapis do tego rejestru resetuje urządzenie i konfiguruje jego pracę.
bit 0:
ENABLE
— jeśli ustawione, urządzenie blokowe powinno po resecie rozpocząć pracę; jeśli wyzerowane, urządzenie nic nie robi.bity 8-14:
NREQUESTS_M1
— rozmiar kolejki żądań urządzenia pomniejszony o 1 (czyli wartość 3 w tym polu oznacza 4 żądania)
- MMIO
0xe0002008
:BLOCK_NOTIFY
Rejestr 32-bitowy. Zapis do tego rejestru powiadamia urządzenie o tym, że system operacyjny wysłał nowe żądania. Sama wartość zapisana tutaj jest ignorowana.
- MMIO
0xe000200c
:BLOCK_CAPACITY
Rejestr 32-bitowy, tylko do odczytu. Podaje rozmiar urządzenia blokowego w 4096-bajtowych blokach.
Obsługa urządzenia blokowego odbywa się przez wysyłanie mu żądań w kolejce. Urządzenie odpowiada na każde z żądań przez odesłanie statusu żądania.
Strona pamięci wskazywana przez BLOCK_DESC_PTR
ma następujący format
(wszystko to 32-bitowe słowa little-endian):
pierwsza połowa strony zawiera kolejkę żądań:
offset
0x000 + i * 0x10, i <= NREQUESTS_M1
:REQ[i].BUFFER_PTR
, wskaźnik (fizyczny) na bufor danych dla danego żądania. Powinien być wielokrotnością 4kiB. Bufor ma zawsze dokładnie 4kiB danych.offset
0x004 + i * 0x10, i <= NREQUESTS_M1
:REQ[i].BLOCK_IDX
, indeks bloku dla danego żądania (liczony od 0).offset
0x008 + i * 0x10, i <= NREQUESTS_M1
:REQ[i].TYPE
, typ danego żądania:0
:READ
— żądanie odczytu bloku o indeksieBLOCK_IDX
z urządzenia i zapisanie danych do bufora pod adresemBUFFER_PTR
1
:WRITE
— żądanie zapisu bloku o indeksieBLOCK_IDX
w urządzeniu danymi przeczytanymi z bufora pod adresemBUFFER_PTR
offset
0x00c + i * 0x10, i <= NREQUESTS_M1
:REQ[i].STATUS
, wynik danego żądania (wypełniany przez urządzenie po zakończeniu obsługi żądania):0
:SUCCESS
— blok został zapisany bądź odczytany bez napotkania problemu1
:INVALID_IDX
—BLOCK_IDX
jest większy bądź równyBLOCK_CAPACITY
2
:IO_ERROR
— urządzenie blokowe napotkało błąd wejścia/wyjścia
offset
0x800
:PUT
— indeks kolejnego żądania w kolejce, które zostanie dopiero wysłane przez system operacyjny (czyli indeks pierwszego żądania, które jeszcze nie zostało wypełnione i wysłane przez system). Powinien być zainicjowany przez system operacyjny (prawdopodobnie na 0) przed startem urządzenia, a potem może zostać zmieniony przez system operacyjny w dowolnym momencie.offset
0xc00
:GET
— indeks pierwszego żądania w kolejce, które nie zostało jeszcze przetworzone przez urządzenie. Powinien być zainicjowany przez system operacyjny (prawdopodobnie na 0) przed startem urządzenia, a potem może być modyfikowany tylko przez urządzenie.
Urządzenie powinno działać następująco:
while (1) {
if (desc.GET > NREQUESTS_M1)
error();
if (desc.PUT > NREQUESTS_M1)
error();
if (desc.GET == desc.PUT)
czekaj na zapis do BLOCK_NOTIFY;
else {
int i = desc.GET;
if (desc.REQ[i].TYPE == 0) {
// odczyt
REQ[i].STATUS = block_device_read(.offset = REQ[i].BLOCK_IDX * 4096, .size = 4096, .data_ptr = REQ[i].BUFFER_PTR);
} else if (desc.REQ[i].TYPE == 1) {
REQ[i].STATUS = block_device_write(.offset = REQ[i].BLOCK_IDX * 4096, .size = 4096, .data_ptr = REQ[i].BUFFER_PTR);
}
// zwiększ desc.GET o 1, zawijając modulo rozmiar bufora
if (desc.GET == NREQUESTS_M1)
desc.GET = 0;
else
desc.GET++;
// wyzwól linię przerwania #5 (linia #5 w PIC oraz IO-APIC)
trigger_edge_irq(5);
}
}
Urządzenie może jednak zastosować pewne optymalizacje:
urządzenie może zakładać, że system operacyjny nie pisze do
desc.GET
, gdy ono jest aktywne (czyli urządzeniu wolno mieć własną kopię tego indeksu w lokalnym rejestrze, pobraną na samym początku działania, a potem tylko kopiować wartość tego rejestru do pamięci)urządzenie może przetworzyć wiele żądań naraz, wykonując tylko raz zapis do
desc.GET
w pamięci i wyzwolenie przerwaniaurządzenie może również zakładać, że system operacyjny zapisze rejestr MMIO
SERIAL_OUT_NOTIFY
zawsze po aktualizacjidesc.PUT
— może więc przechowywać lokalną kopię tego słowa w lokalnym rejestrze i aktualizować ją tylko w momencie zapisu tego rejestu MMIO.
Urządzenie musi jednak przestrzegać zasad:
urządzenie może przeczytać parametry żądania z bufora (i dane do zapisu) dopiero po tym, jak przeczyta wartość z
desc.PUT
, która na to pozwala (nie wolno wczytywać żądań na zapas)urządzenie może zapisać
desc.GET
dopiero po zapisaniu polaREQ[i].STATUS
i (w przypadku odczytu) zapisaniu danych do buforaurządzenie może wyzwolić przerwanie dopiero po zapisie
desc.GET
Port debugowania¶
Port debugowania składa się z jednego portu wejścia/wyjścia:
- Port
0x800
:DEBUG_OUT
Port obsługuje tylko 8-bitowe zapisy. Zapis dowolnego bajtu powoduje wysłanie go przez port debugowania maszyny. W naszym zadaniu, każdy zapisany tutaj bajt ma zostać natychmiast przekazany na
stderr
hypervisora.
Układ zarządzania maszyną¶
Układ zarządzania maszyną składa się z jednego portu wejścia/wyjścia:
- Port
0x900
:SHUTDOWN
Port obsługuje tylko 8-bitowe zapisy. Zapis dowolnego bajtu natychmiast wyłącza maszynę. Wartość zapisanego bajtu jest kodem diagnostycznym wyłączenia maszyny (w naszym zadaniu ma zostać przekazana dalej jako kod wyjścia z procesu hypervisora).
Zadanie¶
Napisać hypervisor, który będzie wirtualizował komputery kosmitów. Hypervisor powinien używać interfejsu KVM do wirtualizacji procesora, kontrolerów przerwań i timerów, a resztę urządzeń peryferyjnych symulować w przestrzeni użytkownika.
Wszystkie urządzenia obsługują DMA tylko z/do pamięci RAM (w przypadku próby użycia adresu z poza zakresu przeznaczonego na RAM, hypervisor powinien zakończyć wykonanie z błędem).
Zasady oceniania¶
Za zadanie można uzyskać do 10 punktów. Na ocenę zadania składają się dwie części:
wynik testów (od 0 do 10 punktów)
ocena kodu rozwiązania (od 0 do -10 punktów)
Punktacja¶
Punkty ujemne za kod można było dostać za:
Brak poprawnej obsługi błędów (-0.2)
Brak weryfikacji poprawnego zakresu wskaźników (-0.5)
Brak weryfikacji wyrównania adresu w obsłudze urządzenia blokowego (-0.2)
Niepoprawna obsługa wielokrotnego setup (brak możliwości wyłączenia urządzenia, wielokrotne uruchamianie wątków) (-0.3)
Brak użycia volatile przy dostępie do danych modyfikowanych w innym wątku (-0.5)
Forma rozwiązania¶
Jako rozwiązanie należy wysłać paczkę zawierającą:
dowolną liczbę plików źródłowych z kodem rozwiązania
jeśli rozwiązanie jest napisane w języku kompilowanym, plik Makefile kompilujący rozwiązanie, lub odpowiadający plik z innego sensownego systemu budowania (np. cmake)
plik readme z krótkim opisem rozwiązania i instrukcjami kompilacji
Rozwiązanie (po ew. kompilacji) powinno znajdować się w pliku wykonywalnym
o nazwie avm
. Program powinien mieć następujący interfejs:
./avm <bios.bin> [<drive.img>]
Wywołanie programu powinno uruchomić hypervisor. Program powinien zakończyć
pracę, gdy maszyna wirtualna użyje portu SHUTDOWN
, bądź gdy wirtualizacja
napotka błąd (np. brak dostępu do /dev/kvm
, błąd wewnętrzny KVM, nieznany
adres MMIO, …).
W przypadku napotkania błędu, program powinien wypisać opis problemu na stderr
(nie precyzujemy dokładnego formatu, ale powinien wskazywać na ogólne źródło
błędu) i zakończyć się z kodem wykonania 127. Poza tym przypadkiem, program
nie powinien wypisywać nic na stderr poza emulacją portu DEBUG_OUT
.
Pierwszym (obowiązkowym) argumentem programu jest nazwa pliku zawierającego obraz BIOSu. Ten plik musi mieć długość dokładnie 65536 bajtów (program powinien się zakończyć błędem w przeciwnym wypadku).
Drugim (opcjonalnym) argumentem programu jest nazwa pliku zawierającego obraz urządzenia blokowego. Ten plik musi mieć długość będącą wielokrotnością 4096 bajtóœ (program powinien zakończyć się błędem w przeciwnym wypadku). Program nie powinien pozwalać na zapis poza początkową długość obrazu. W przypadku braku tego argumentu, należy emulować urządzenie blokowe o długości 0.
Port szeregowy maszyny wirtualnej powinien zostać podłączony do stdin
i stdout
programu.
Rozwiązania prosimy nadsyłać na adres p.zuk@mimuw.edu.pl
z kopią
do mwk@mimuw.edu.pl
.
Wskazówki¶
Rozwiązanie prawdopodobnie będzie wymagało utworzenia kilku wątków (1 na VCPU + co najmniej 1 na urządzenia wejścia/wyjścia).
Szkic rozwiązania:
Otworzyć
/dev/kvm
.Sprawdzić zgodność numeru wersji (
KVM_GET_API_VERSION
).Poznać rozmiar mapowania do vcpu (
KVM_GET_VCPU_MMAP_SIZE
).Stworzyć maszynę wirtualną (
KVM_CREATE_VM
).Wykonać
KVM_SET_TSS_ADDR
(proponuję parametr0xfffe8000
) iKVM_SET_IDENTITY_MAP_ADDR
(proponuję parametr0xfffec000
), na wypadek uruchomienia hypervisora na starych procesorach Intela.Stworzyć PIC+APIC (
KVM_CREATE_IRQCHIP
) oraz PIT (KVM_CREATE_PIT2
).Stworzyć anonimowe mapowanie na RAM i wpiąć je w VM (
KVM_SET_USER_MEMORY_REGION
).Stworzyć mapowanie BIOSu i wpiąć je w VM (j/w).
Zainicjować wątki obsługjące urządzenia, ustanowić kanały komunikacji z nimi.
Stworzyć procesor (
KVM_CREATE_VCPU
).Ustawić identyfikację procesora (najprościej skopiować dane z
KVM_GET_SUPPORTED_CPUID
prosto doKVM_GET_SUPPORTED_CPUID
) — bez tego APIC nie będzie w pełni funkcjonalny.Zmapować blok kontrolny vcpu.
Rozpocząć wykonywanie vcpu przez uruchomienie
KVM_RUN
w pętli (nie jest wymagana inicjalizacja stanu procesora — KVM domyślnie resetuje nowo utworzony procesor).Obsłużyć
KVM_EXIT_IO
przez emulację odpowiednich urządzeń (w razie nieznanego adresu należy zakończyć wykonanie hypervisora błędem)Obsłużyć
KVM_EXIT_MMIO
przez emulację odpowiednich urządzeń (w razie nieznanego adresu należy zakończyć wykonanie hypervisora błędem)Obsługa pozostałych wyjść z KVM nie jest wymagana (o ile używamy in-kernel irqchip) — w razie wystąpienia nieznanego wyjścia, należy zakończyć wykonanie hypervisora błędem.
Zgłaszanie przerwania z wątku (lub wątków) urządzenia można robić albo za pomocą KVM_IRQ_LINE
, albo za pomocą eventfd w połączeniu z KVM_IRQFD
.
Obsługę rejestrów NOTIFY
można opcjonalnie usprawnić przez użycie KVM_IOEVENTFD
.
W wątku (lub wątkach) urządzeń może się przydać poll
lub podobny syscall, gdyż
działające urządzenie musi jednocześnie oczekiwać na gotowość wejścia/wyjścia
(choćby na stdin) oraz żądania zmiany stanu od vcpu (przez rejestr SETUP
).
Mogą też przydać się pipe lub eventfd do komunikacji setupu między wątkiem vcpu
a wątkiem urządzenia.
Literatura¶
Documentation/kvm/api.rst
w źródłach kernela — dokumentacja API KVMhttps://software.intel.com/content/www/us/en/develop/download/intel-64-and-ia-32-architectures-sdm-combined-volumes-1-2a-2b-2c-2d-3a-3b-3c-3d-and-4.html tom 3, rozdziały 23-33: jak działa wirtualizacja sprzętowa w procesorach Intela (dla ciekawych)
https://developer.amd.com/resources/developer-guides-manuals/ AMD64 Architecture Programmer’s Manual Volume 2, rozdział 15: jak działa wirtualizacja sprzętowa w procesorach AMD (dla ciekawych)