Open Source im professionellen Einsatz

Kernel- und Treiberprogrammierung mit dem Kernel 2.6 – Folge 55

Kern-Technik

,

Mit ausgefeiltem Ressourcen-Management und eigenen Namensräumen organisiert der Kernel eine effiziente Container-Virtualisierung. Die Unterstützung im Userland könnte jedoch besser sein.

Seit IT-Urzeiten ist eine der vornehmsten Aufgaben des Betriebssystems die Virtualisierung. Galt es damals vor allem, die CPU mit ihren Registern zu virtualisieren und damit das Multitasking beziehungsweise das Multithreading zu ermöglichen, ist heute die Virtualisierung des kompletten Rechners trendy. Festplatten, DVD-Laufwerke, Netzwerkschnittstellen, Grafikkarten und Hauptspeicher stehen dem Anwender scheinbar exklusiv zur Verfügung, real aber teilt er sich die Ressourcen mit den übrigen Usern.

Dank Virtualisierung lassen sich mehrere, auch unterschiedliche Betriebssysteme auf der Hardware gleichzeitig betreiben, ohne Umbooten zu müssen. Auf einer leistungsfähigen Hardware konsolidieren Unternehmen Server, was typischerweise den Wartungs- und Administrationsaufwand reduziert.

Das Hauptargument für die Virtualisierung ist aber meist eine erhöhte Sicherheit, die Systemverwalter zum einen durch die damit verbundene Isolierung der Systeme, zum anderen durch die reduzierte Komplexität erreichen. Sie handeln damit gemäß dem Motto: Eine Betriebssysteminstanz – ein Dienst.

Ins Gefängnis

Dabei gibt es verschiedene Ansätze: so genannte Container-Virtualisierung, Hypervisor-Technik und Hardware-Emulation (siehe Kasten "Virtualisierungstechniken").

Virtualisierungstechniken

Auch wenn es so aussieht: Festplatten, Grafikkarten oder auch Prozessoren sind nicht immer real. Immer häufiger bekommen Betriebssysteme oder Applikationen virtuelle Hardware vorgesetzt. Die Virtualisierung unterscheidet das Host- vom Gastsystemen. Der Host kontrolliert real die Hardware. Er erzeugt die virtuelle Sicht, auf die seine Gäste bauen. Sie setzen hierbei drei Basistechnologien ein:

* Container-Virtualisierung: Das Betriebssystem schafft durch eigene Namensräume eine virtuelle Umgebung (Container). Gastsysteme nutzen den Betriebssystemkern des Hostsystems. Beispiel: Linux Container LXC, Open VZ.

* Hypervisor-Technik: Eine als Hypervisor bezeichnete Systemkomponente kontrolliert die eigentliche Hardware. Die Prozessorhersteller Intel und AMD unterstützen diese Art der Virtualisierung in modernen Prozessoren durch besondere Technologien (VT-x und Pacifica). Oft ist der Hypervisor mit dem Hostsystem verbandelt. Die Technik ermöglicht es, eine Hardware gleichzeitig von unterschiedlichen Betriebssystemen verwenden zu lassen. Beispiele: VMware, Virtualbox.

* Hardware-Emulation: Hier wird eine Hardware, ein Rechner emuliert. Sehr vorteilhaft ist, dass die Architektur des emulierten Rechners (Gastsystem) sich komplett von der Architektur des Hostsystems unterscheiden kann. Dem stehen jedoch eine niedrige Arbeitsgeschwindigkeit des Gastsystems und eine hohe Auslastung des Hostsystems gegenüber. Beispiel: Qemu.

Die Variante, die dabei die geringsten Ressourcen benötigt, ist die Container-Virtualisierung (siehe Abbildung 1). Die Idee hinter dieser Technik ist, möglichst viele Ressourcen gemeinsam zu nutzen. So verwenden beispielsweise die virtualisierten Systeme denselben Betriebssystemkern und häufig auch dieselben Systemprogramme. Das spart Speicher und CPU-Zeit.

Abbildung 1: LXC legt innerhalb von Containern eigene Namensräume an, mit denen Applikationen eine Reihe von Systemressourcen unabhängig vom restlichen Kernel benennen dürfen.

Diese Art der Virtualisierung ist – da sie sehr wenig zusätzliche Ressourcen benötigt – schon lange in Gebrauch, beispielsweise unter dem Namen Chroot. Allerdings virtualisiert Chroot im Wesentlichen nur das Root-Filesystem. Linus Torvalds hat dagegen in den letzten Jahren seinen Betriebssystemkern mit einer fast vollständigen Container-Virtualisierung ausgestattet. Dabei hat er zwei wesentliche Technologien integriert: Kontrollgruppen und Namensräume.

Mit Hilfe der Control Groups lassen sich Rechenprozesse einer virtuellen Umgebung, dem Container beziehungsweise auch nur einem Namensraum zuordnen. Sie binden aber auch die Jobs fest an einen bestimmten Prozessorkern einer Multicore-Maschine [1] oder limitieren vorhandene Speicher-Ressourcen. Aus dem Userland wird dies alles über ein virtuelles Filesystem gesteuert.

Täuschungsmanöver

Normalerweise sind die Datenstrukturen für den kompletten Kernel eindeutig, etwa sein Name oder die von ihm vergebenen Prozess-IDs. Die Namespaces isolieren die Betriebsmittel, indem sie es Anwendern erlauben, Bereiche einzurichten, in denen die Bezeichner eine neue Gültigkeit besitzen. Inzwischen sind sieben Namensräume realisiert, die Tabelle 1 auflistet.

Tabelle 1: Namensräume

Innerhalb eines »UTS« -Namespace vergibt der Linux-Anwender individuelle Domainnamen, Nodenamen, eine Release, eine Version und schließlich den Maschinennamen – kurzum die Informationen, die er sonst per Systemcall »uname()« ausliest. »USER« steht für eigene User-IDs, »NET« für virtuelle Netzwerkgeräte, Routen und Sockets. »PROC« erzeugt einen Container mit eigenem Proc-Filesystem. Da der Kernel Objekte der Interprozess-Kommunikation ebenfalls über Namen referenziert, stellt »IPC« einen zugehörigen Namensraum für Shared-Memory, Messages und Semaphore bereit. »MNT« schließlich instanziert unabhängige Mountpoints.

Dreh- und Angelpunkt, um Namensräume zu nutzen, ist der Systemcall »clone()« . Linux erzeugt damit neue Rechenprozesse. Den gewünschten Namensraum steuert der Code beim Erzeugen über Flags, die die Container-Virtualisierung in »linux/sched.h« bereitstellt.

Mit Root-Rechten ausgestattet ruft der Applikationsprogrammierer »clone()« auf und übergibt beispielsweise das Flag »CLONE_NEWPID« . Der Kernel erstellt daraufhin einen neuen Namensraum für den Kindprozess. Das Programm in Listing 1 verdeutlicht dies zusammen mit Abbildung 2. Der Elternprozess gibt die PID des erzeugten Kindes (7055) und die eigene (7054) aus. Der Kindprozess zeigt dagegen nur die eigene PID an.

Abbildung 2: Die Prozesse, die virtualisierte Jobs abarbeiten, haben zwei PIDs: eine im globalen Namensraum sowie die PID in der virtualisierten Umgebung. Der Kindprozess im Beispielprogramm hat aus Elternsicht die PID 7055. Das Kind selbst meint, es besitze die PID 1.

Abbildung 2: Die Prozesse, die virtualisierte Jobs abarbeiten, haben zwei PIDs: eine im globalen Namensraum sowie die PID in der virtualisierten Umgebung. Der Kindprozess im Beispielprogramm hat aus Elternsicht die PID 7055. Das Kind selbst meint, es besitze die PID 1.

Listing 1

Namensräume anlegen

01 #include <asm/unistd.h>
02 #include <stdio.h>
03 #include <sched.h>
04 #include <unistd.h>
05 #include <wait.h>
06
07 int main(int argc, char **argv, char **envp)
08 {
09     pid_t pid;
10     int status;
11
12     pid = syscall(__NR_clone, CLONE_NEWPID | SIGCHLD,
13                   0, 0, 0);
14     if (pid == 0) {
15         printf("Kind-Thread: ");
16     } else {
17         printf("Eltern-Thread (Kind-PID %d): ", pid);
18     }
19     printf("eigene PID: %d\n", getpid());
20     wait(&status);
21     return 0;
22 }

Diesen Artikel als PDF kaufen

Express-Kauf als PDF

Umfang: 3 Heftseiten

Preis € 0,99
(inkl. 19% MwSt.)

Als digitales Abo

Als PDF im Abo bestellen

comments powered by Disqus

Ausgabe 07/2013

Preis € 6,40

Insecurity Bulletin

Insecurity Bulletin

Im Insecurity Bulletin widmet sich Mark Vogelsberger aktuellen Sicherheitslücken sowie Hintergründen und Security-Grundlagen. mehr...

Linux-Magazin auf Facebook