Friedrich-Alexander-Universität Erlangen-Nürnberg  /   Technische Fakultät  /   Department Informatik

Übungen zu BS - FAQ

Tastatur-Interrupts kommen in KVM nur auf CPU 1 an

Ursache/Erläuterung

Seit dem Linux-Kernel v4.6 verwendet das KVM-Kernel-Modul für den I/O-APIC-Modus, den wir verwenden (Logical Destination Mode mit Lowest Priority Delivery Mode) standardmäßig eine Technik, die sich "Vector Hashing" nennt. Dabei wird die Interrupt-Vektor-Nummer durch eine Hash-Funktion (Interrupt-Vektor-Nummer modulo Anzahl virtueller Kerne) gejagt, und das Ergebnis als Ziel-Kern für diese Interrupt-Vektor-Nummer genommen. In unserem Fall ist das Ergebnis eben 33 % 4 == 1, so dass der Tastatur-Interrupt immer an die CPU 1 zugestellt wird. Für die Virtualisierung “echter” Betriebssysteme kann sich das durchaus lohnen, da so Caching-Effekte besser ausgenutzt werden können - für unser Lehrbeispiel ist das aber natürlich eher hinderlich.

Lösung

Per Modul-Parameter kann dieses Verhalten ausgeschaltet werden, so dass die Interrupts wieder an alle virtuellen CPUs verteilt werden. Um das auf dem aktuell gebooteten Kernel zu erreichen, muss das Kernel-Modul entladen und mit dem richtigen Parameter wieder geladen werden. Dies geht mit folgenden Befehlen auf der Kommandozeile (am Beispiel von Ubuntu 17.10 und einer Intel-CPU im Host):

sudo modprobe -r kvm_intel kvm
sudo modprobe kvm vector_hashing=N
sudo modprobe kvm_intel

Nach einem Neustart ist diese Einstellung jedoch wieder verloren. Um den Parameter automatisch beim Booten zu setzen, ist ein Eintrag in der Datei /etc/modprobe.d/kvm_options.conf nötig. Dazu führt ihr am einfachsten den folgenden Befehl aus:

echo "options kvm vector_hashing=N" | sudo tee /etc/modprobe.d/kvm_options.conf 

Im CIP ist KVM (inzwischen) mit der richtigen Einstellung konfiguriert, so dass dort die Interrupts auf alle Kerne verteilt werden sollten. Prüfen könnt ihr die aktuelle Einstellung, indem ihr die Datei /sys/module/kvm/parameters/vector_hashing auslest, bspw. mittels cat /sys/module/kvm/parameters/vector_hashing. Steht dort N, ist Vector Hashing deaktiviert.

Zu große Variablen (Stacks!)

Symptome

Euer Programm stürzt irgendwann ab, liefert einen "unexpected interrupt", bootet den Rechner oder erzeugt andere unerwünschte Ergebnisse. Möglicherweise tritt der Fehler nicht auf, wenn ihr irgendwo scheinbar belanglose Anweisungen einfügt, einen Anwendungsprozess mehr oder weniger erzeugt usw.

Ursache

Ihr legt zu große lokale Variablen in main () oder in einem eurer Anwendungsprozesse an.

Erläuterung

Das Hauptprogramm main () von OOStuBS bekommt von uns bei der Systeminitialisierung einen Stack der Größe 4 KB zugewiesen (siehe Startup-Code in startup.asm). Zum Anlegen kleiner lokaler Variablen ist das vollkommen ausreichend, nicht aber, um die Anwendungsprozesse mit eigenen Stacks zu versorgen.

Das folgende Stück Code ist also falsch!

int main () {
    char stack [4096];
    Application appl (stack+4096);
    ...
}

Hier werden auf dem initialen Stack ein 4 KB großes Feld und ein Application-Objekt angelegt, was zusammengenommen bereits größer als die verfügbaren 4 KB ist. Da aber OOStuBS keine Überprüfun der Stackgrenzen vornimmt und auch sonst keine Schutzkonzepte implementiert1, wird mit den lokalen Variablen Speicher überschrieben, der main() überhaupt nicht zur Verfügung steht. Das Ergebnis ist, dass globale Variablen wie z. B. die Interrupt-Vektortabelle überschrieben werden. Das kann unbemerkt bleiben, z. B. wenn nur Interruptvektoren zerstört werden, die nie benötigt werden, es kann aber auch zu Abstürzen oder anderen Fehlern führen. Das passiert insbesondere dann, wenn ihr entweder euren eigenen Code überschreibt oder wenn durch den Fehler unsinnige Werte als Adressen von Funktionen interpretiert werden.

Lösung

Legt große Variablen wie die Stacks der Anwendungsprozesse immer global an. Dann sorgt nämlich der Compiler dafür, dass der Speicherplatz für sie zur Verfügung steht. Wer will, kann das Schlüsselwort static verwenden, um anzuzeigen, dass die entsprechende Variable nur in der Datei referenziert werden soll, in der sie deklariert wurde.

static char stack [4096];

int main () {
    Application appl (stack+4096);
    ...
}

Außerdem müsst ihr aufpassen, dass ihr den Zeiger auf das obere Ende des Stacks (“top of stack”) übergebt, ansonsten wird es wieder zu ungewünschtem Verhalten kommen.


  1. In der Veranstaltung Betriebssystemtechnik werden dann räumliche Schutzkonzepte und Privilegienumschaltung erläutert und implementiert.