Wer schläft, spart. Daher organisiert der Linux-Kernel das kollektive Schlafen der Rechnerkomponenten, bis das komplette System ruht. Er strukturiert Abhängigkeiten baumartig und verwendet ein Phasenmodell. Damit sind bei Bedarf alle Komponenten schnell wieder hellwach.
Es liegt nahe, das Runtime-Powermanagement (siehe Artikel “Einer schläft” im Schwerpunkt dieses Linux-Magazins) zum gleichzeitigen Schlafenlegen aller Komponenten zu nutzen. Dadurch ließe sich recht einfach das bekannte Suspend to RAM oder das Suspend to Disk (Hibernation) realisieren.
Dem widersprechen jedoch die Kernelentwickler [1]: Für viele Komponenten mag es zwar kein Unterschied sein, ob sie alleine oder im Kollektiv Strom sparen, für andere aber sehr wohl: Je nach Zustand soll sich die Hardware beim Aufwachen und beim Aufwecken anders verhalten. Wake on LAN beispielsweise ließe sich in dem einen Fall ignorieren, im anderen soll es zum Aufwachen schlafender Komponenten führen.
Legt sich das ganze System schlafen, muss Linux zudem ein Abbild des Arbeitsspeichers ziehen und sichern. Diese Vorgänge benötigen mehr Zeit als das bloße Schlafenlegen einer einzelnen Komponente. Daher behandelt Linux den System Sleep (Suspend) getrennt vom Runtime-Powermanagement. Der Kernel ermöglicht und ermuntert den Entwickler jedoch dazu, die Suspend- und Resume-Funktionen der Runtime-Energieverwaltung auch für den Systemschlaf zu verwenden.
Basis des System-Sleep-Framework ist die Information, die im Gerätemodell – repräsentiert durch das Sys-Filesystem – abgelegt ist. Schwierig gerät das Schlafenlegen und Aufwecken nämlich dadurch, dass es nicht in beliebiger Reihenfolge erfolgen darf. Beispielsweise kann ein USB-Hub nicht vor den angeschlossenen Geräten in den Stromsparmodus gehen. Daher greift der Kernel auf den im Gerätemodell abgelegten, baumartig strukturierten Aufbau der gesamten Hardware zurück.
Beim System Sleep ist das Schlafenlegen von den Blättern hin zur Wurzel organisiert: erst das am USB-Hub angeschlossene Gerät, dann der USB-Hub selbst. Beim Aufwachen verläuft alles anders herum. Hier arbeitet Linux den Baum von der Wurzel zu den Blättern hin ab, der USB-Hub ist vor dem USB-Gerät aktiv.
Callback-Funktionen
Der Treiber implementiert im einfachen Fall das Schlafenlegen in einer »suspend()« -Funktion, die die Zustände der Hardware sichert und das Gerät in den Stromsparmodus schaltet. Das Aufwecken in der »resume()« -Funktion aktiviert das Gerät und restauriert den ursprünglichen Zustand.
Allerdings sind während der Abarbeitung dieser Funktionen Interrupts freigegeben, sodass es zu Inkonsistenzen kommen kann, wenn die schlafen zu legende Hardware zeitgleich einen Interrupt auslöst. Um auch mit solchen Fällen koordiniert umgehen zu können, lässt der Kernel das Einschlafen in mehreren Phasen ablaufen und bietet den Kernelobjekten an, je nach Phase unterschiedliche Callback-Funktionen zu aktivieren [2].
Beim Suspend to RAM (Abbildung 1) informiert der Notifier »PM_SUSPEND _PREPARE« zunächst interessierte Treiber und Systemkomponenten vom bevorstehenden Einschlafen (siehe Kasten “Kommunikation per Notifier”). Anschließend friert das System alle Rechenprozesse ein und ruft in hierarchischer Reihenfolge die »prepare()« -Callbacks auf. Treiber implementieren diese Funktionen, um das Hinzufügen neuer Geräte (Hotplugging) zu verhindern.

Abbildung 1: Beim Suspend to RAM durchläuft der Kernel vier Phasen, in die sich Treiberprogrammierer per Callbacks einklinken können.
Kommunikation per Notifier
Das Notifier-Subsystem dient als zentrales Informations- und Ereignismanagement im Kernel. Ähnlich einem RSS-Feed versorgt der Kernel hierüber seine Abonnenten – also Subsysteme des Kernels und Gerätetreiber – mit Neuigkeiten, etwa über das Ändern des Prozessortakts oder auch über das Aktivieren von Powermanagement-Funktionen.
Für Letzteres sind insgesamt sechs Nachrichten definiert: »PM_HIBERNATION_PREPARE« , »PM_POST_HIBERNATION« , »PM_RESTORE_PREPARE« , »PM_POST_RESTORE« , »PM_SUSPEND_PREPARE« und »PM_POST_SUSPEND« . Treiber nutzen diese Nachrichten, um beispielsweise Firmware in den Hauptspeicher zu laden. Beim Aufwecken schreiben sie diese zurück in die Hardware.
Was die einzelnen Notifier bedeuten und wann sie verschickt werden, ist in der Dokumentation des Kernels genau nachzulesen [3]. Allgemeine Informationen und auch Codebeispiele zum Umgang mit Notifiern bietet [4].
Nach den »prepare()« -Funktionen sind die beschriebenen »suspend()« -Callbacks an der Reihe. Der Kernel sperrt Geräte-Interrupts und ruft »suspend_noirq()« -Callbacks auf. Daraufhin schaltet Linux mit Ausnahme des ersten alle Rechnerkerne ab, sperrt die noch freigegebenen Interrupts und legt in Betrieb befindliche Teile schlafen.
Alles rückwärts
Das Aufwecken vollzieht sich in umgekehrter Reihenfolge. Erst gibt Linux die Systeminterrupts frei, dann aktiviert es die schlafenden Rechnerkerne. Der Kernel ruft die »resume_noirq()« -Callbacks auf, aktiviert die Geräte-Interrupts, ruft die »resume()« -Funktionen auf und taut die Rechenprozesse wieder auf. Zuletzt informiert der »PM_POST_SUSPEND« -Notifier die Subsysteme und Treiber über das Ende des Aufweckens.
Richtig komplex wird es beim Suspend to Disk. Schließlich sind nach dem Anfertigen des Hauptspeicher-Image noch die Treiber erforderlich, die das Image auf den Massenspeicher sichern. Dazu ruft der Kernel nach dem Informieren von Kernel-Subsystemen und Treibern per Notifier »PM_HIBERNATION_PREPARE« erst die »prepare()« -, »freeze()« – und »freeze_noirq()« -Callbacks auf (siehe Abbildung 2).
Damit sind Tasks und Geräte inaktiv, das Image kann erzeugt werden. Ist dieser Vorgang abgeschlossen, aktiviert Linux die Geräte zum Abspeichern des Image wieder. Hierzu ruft der Kernel die »thaw_noirq()« -, die »thaw()« – und schließlich die »complete()« -Callbacks auf. Ist das Image auf Massenspeicher geschrieben, versetzt er die aktiven Geräte tatsächlich in den Schlafzustand. Entsprechenden Code bringen Programmierer in den »prepare()« -, »poweroff()« – und »poweroff_noirq()« -Callbacks unter.
Um das System aus dem Suspend to Disk wieder in den Normalbetrieb zu überführen, ist sogar ein zweiter Kernel erforderlich – schließlich ist das System komplett stromlos gewesen. Theoretisch könnte der Bootloader das Image von Kernel und Hauptspeicher in den Speicher zurückbefördern, in der Praxis aber erweisen sich die Bootloader als zu unflexibel.
Daher startet ein zweiter Kernel als flexibler Bootloader, der den richtigen Kernel plus Image in den Hauptspeicher wuchtet. Bei diesem Vorgang sind einige der Hardwarekomponenten durch die Treiber im Bootloaderkernel bereits initialisiert, andere nicht. Um diesem Umstand gerecht zu werden, definiert der PM-Core für diesen Teil des Aufweckens eigene Callbacks: »restore_noirq()« , »restore()« und »complete()« . Innerhalb der Callbacks werden die Geräte in den ursprünglichen Zustand zurückversetzt.
Alle Funktionen – sowohl die des Runtime-Powermanagements als auch die des System Sleep – sind in der Datenstruktur »struct dev_pm_ops« zusammengefasst. Die lässt sich über die Power-Domain an das Geräteobjekt binden, aber auch an ein Klassenobjekt oder in selteneren Fällen auch an ein Busobjekt. Sind die Callbacks an das Klassenobjekt gebunden, werden diese für jedes zur Klasse gehörende Gerät einmal mit der Adresse des jeweiligen Geräteobjekts als Parameter aufgerufen. Gibt es jedoch ein Callback sowohl im Klassenobjekt als auch im Geräteobjekt, favorisiert der Kernel die Geräteobjekt-Funktion.
Wer die Datenstruktur der Geräteklassenobjekte studiert, stellt fest, dass sich hier unabhängig von der Struktur »struct dev_pm_ops« Funktionen für Suspend und Resume einhängen lassen. Diese Elemente sind jedoch Relikte früherer Kernelversionen, die nur noch aus Kompatibilitätsgründen existieren. Kernelentwickler sind gehalten, das in diesem Artikel vorgestellte Interface zu verwenden.
Listing 1 zeigt die Unterstützung von System Sleep in einem Treiber für den Kernel 3.0, hier in Ubuntu 11.10, durch die beiden Funktionen »template_suspend()« (Zeile 12) und »template_resume()« (Zeile 18). Sobald der Treiber übersetzt und das erzeugte Modul per »insmod« in den Kernel geladen sowie System Sleep aktiviert ist, versetzt das Betriebssystem sämtliche Geräte der Geräteklasse »template« – im Beispiel nur eines – in den inaktiven Zustand.
Listing 1
System Sleep im Treiber
01 #include <linux/fs.h>
02 #include <linux/cdev.h>
03 #include <linux/device.h>
04
05 #define TEMPLATE "template"
06 static dev_t template_dev_number;
07 static struct cdev *driver_object;
08 struct class *template_class;
09 static struct device *template_dev;
10
11 #ifdef CONFIG_PM /* Powermanagement */
12 static int template_suspend(struct device *dev )
13 {
14 dev_info(dev,"template_runtime_suspend( %p )\n", dev );
15 return 0;
16 }
17
18 static int template_resume(struct device *dev)
19 {
20 dev_info(dev,"template_runtime_resume( %p )\n",dev);
21 return 0;
22 }
23 #endif
24
25 static const struct dev_pm_ops template_class_pm = {
26 .suspend = template_suspend,
27 .resume = template_resume,
28 };
29
30 static int __init template_init( void )
31 {
32 if( alloc_chrdev_region(&template_dev_number,0,1,TEMPLATE)<0 )
33 return -EIO;
34 driver_object = cdev_alloc();
35 if( driver_object==NULL )
36 goto free_device_number;
37 driver_object->owner = THIS_MODULE;
38 if( cdev_add(driver_object,template_dev_number,1) )
39 goto free_cdev;
40 template_class = class_create( THIS_MODULE, TEMPLATE );
41 if( IS_ERR( template_class ) ) {
42 pr_err("template: no udev support\n");
43 goto free_cdev;
44 }
45 template_dev = device_create( template_class, NULL,
46 template_dev_number, NULL, "%s", TEMPLATE );
47 #ifdef CONFIG_PM /* Powermanagement */
48 template_class->pm = &template_class_pm;
49 #endif
50 return 0;
51 free_cdev:
52 kobject_put( &driver_object->kobj );
53 free_device_number:
54 unregister_chrdev_region( template_dev_number, 1 );
55 return -EIO;
56 }
57
58 static void __exit template_exit( void )
59 {
60 device_destroy( template_class, template_dev_number );
61 class_destroy( template_class );
62 cdev_del( driver_object );
63 unregister_chrdev_region( template_dev_number, 1 );
64 return;
65 }
66
67 module_init( template_init );
68 module_exit( template_exit );
69 MODULE_LICENSE( "GPL" );
Konfigurationssache
Der Kernelentwickler rahmt Powermanagement-Code übrigens mit »CONFIG_PM« ein, wie in den Zeilen 11 bis 23 und 47 bis 49 zu sehen ist. Dadurch werden die Programmfragmente nur dann übersetzt, wenn Powermanagement im Kernel konfiguriert ist. Zum Aktivieren von Suspend to RAM reicht es bei vielen Linux-Distributionen aus, den Deckel eines Net- oder Notebooks zu schließen. Alternativ kann Root Suspend to RAM aber auch über das Sys-Dateisystem durch das Kommando
echo "mem" >/sys/power/state
auslösen (siehe Kasten “Sys-Filesystem-Interface fürs Powermanagement”). Die Aktivitäten des Treibers und von System Sleep lassen sich anhand der Meldungen in »/var/log/kern.log« gut verfolgen (Abbildung 3). Da er sich zeitlich betrachtet als Letzter beim Gerätemodell angemeldet hat, wird der Treiber »template« als Erster suspendiert. Die Zeitstempel im Logfile werden erst beim Aufwecken genommen; Tasks und auch der Syslog-Daemon sind zu Beginn des Einschlafvorgangs eingefroren.
Sys-Filesystem-Interface fürs Powermanagement
Zur Konfiguration von System Sleep befinden sich im Sys-Filesystem im Verzeichnis »/sys/power/« Dateien, die teilweise dokumentiert, teilweise auch nur für die Kernelentwickler selbst vorgesehen sind. Die Datei »/sys/power/state« steuert den Powermanagement-Zustand. Schreibt man »mem« in diese Datei, führt das System ein Suspend to RAM aus, »disk« aktiviert Suspend to Disk.
Über die Datei »/sys/power/disk« wird Suspend to Disk konfiguriert, wobei sich hier auch ein Testmodus einstellen lässt. »/sys/power/image_size« legt eine Obergrenze für das zu erzeugende Image fest, wobei das Limit jedoch weich ist.
Auf der PC-Plattform kommt bei System Sleep auch noch ACPI ins Spiel, das der Kasten “Advanced Configuration and Power Interface” genauer beschreibt. Die Linux-Entwickler sind ziemlich unglücklich über ACPI, denn es handelt sich um eine Spezifikation von mehr als 600 Seiten. Viele ACPI-Implementierungen sind fehlerhaft, nicht gut dokumentiert und nicht Open Source. Zudem ist ACPI betriebssystemabhängig implementiert. Aus diesem Grund meldet sich auch Linux beim ACPI als Windows-Betriebssystem an. Nur so erreichen die Entwickler ein konsistentes Verhalten. Darüber hinaus gab es Bestrebungen, Linux durch Patente von der Verwendung von ACPI auszuschließen.
Advanced Configuration and Power Interface (ACPI)
Typischerweise ist auf den PC-Plattformen ACPI für das Schlafenlegen von Hardware zuständig. Der von HP, Intel, Microsoft, Phoenix und Toshiba im Jahr 1996 definierte Standard [5] legt verschiedene Zustände fest, unter anderem die Sleep-Zustände (S-States). Die S-States wiederum korrespondieren mit den Device-Power-States (D-States). Sie definieren nur Stromspar-, aber keine Leistungszustände. Ein Rechner ist also nicht betriebsbereit, wenn er sich in einem der Zustände S1 bis S5 befindet (siehe Tabelle 1).
ACPI ist im Bios in Form von Tabellen, aber auch in Form von Methoden abgelegt. Die Tabellen beschreiben die Hardware, während die Methoden Code darstellen, der zu bestimmten Zeitpunkten durch das Betriebssystem aufzurufen ist. Damit läuft dieser für die Betriebssystementwickler unbekannte Code im Kontext des Linux-Kernels.
Powermanagement ist ein komplexer Vorgang, den alle Kernelkomponenten unterstützen müssen. Sowohl System Sleep als auch Runtime-Powermanagement zu implementieren hat sich unter den Treiberprogrammierern leider noch nicht zur Gänze herumgesprochen. Positiv formuliert: Der Linux-Kernel bietet noch reichlich Stromsparpotenzial. (mhu)
Infos
- Rafael Wysocki, “System Sleep vs. Runtime Power Management”: Vortrag auf der Linuxcon Japan 2011, https://events.linuxfoundation.org/slides/2011/linuxcon-japan/lcj2011_wysocki.pdf
- Rafael Wysocki, Alan Stern, “Device Power Management”: Dokumentation zum Kernel-Quellcode, https://www.kernel.org/doc/Documentation/power/devices.txt
- Rafael Wysocki, Alan Stern, “Suspend Notifiers”: Dokumentation zum Kernel-Quellcode, https://www.kernel.org/doc/Documentation/power/notifiers.txt
- Jürgen Quade, Eva-Katharina Kunst,”Kern-Technik”, Folge 46: Linux-Magazin 07/09, S. 90
- Hewlett-Packard et al., “Advanced Configuration and Power Interface Specification”: http://www.acpi.info








