Kennen und unterstützen alle Treiberprogrammierer das Interface fürs Powermanagement, steht einem funktionierenden Suspend-to-Ram und Suspend-to-Disk nichts mehr im Wege.
Die Zukunft ist grün! Auch die von Linux. Mit jeder neuen Version wird sie noch grüner, schließlich tunen die Entwickler den Kernel mit immer ausgereifteren Stromspartechniken (siehe Kasten “Green Computing”). Ganz aktuell überarbeiten die Kernelhacker das Linux-Powermanagement (PM). Sie hoffen damit Treiberentwickler dafür zu gewinnen, noch mehr PM-Funktionen zu unterstützen. Das Powermanagement kümmert sich um Suspend-to-Ram (STR) und um Suspend-to-Disk (STD). An anderer Stelle nennen Applikationen das auch Schlafenlegen (Sleep) oder Einfrieren des Systems (Hibernate).
|
Green Computing |
|---|
|
Seit Energiepreise und Verkaufszahlen von Netbooks gleichermaßen in die Höhe schießen, nimmt die Öffentlichkeit den Stromverbrauch von IT-Hardware wahr. Statt Prozessorleistung vergleichen Anwender jetzt die Prozessorleistung pro Watt. Statt Tipps zum Übertakten fragen Anwender in den einschlägigen Foren zunehmend danach, wie sie ihren Rechner untertakten können, und erhoffen davon ein probates Mittel zum Stromsparen und damit längere Akkulaufzeit im laufenden Betrieb. Zwei Ansätze zum EnergiesparenGreen Computing verspricht – neben der umweltfreundlichen Produktion und Wiederverwertung der Geräte – den Stromverbrauch von Servern, Desktops, Notebooks und sonstigen prozessorgesteuerten Geräten auf ein Minimum zu senken. Seine Realisierungsvarianten fußen alle auf zwei Grundprinzipien: Erstens stellen die Geräte nur so viel Leistung zur Verfügung, wie tatsächlich benötigt wird. Andernfalls drehen sie den Strom ab. Zweitens verschieben Systeme Aufgaben möglichst auf einen späteren Zeitpunkt, wenn auch andere Komponenten eine Leistung anfordern. Das nennen die Experten Leistungsbündelung. Sanftes HerunterregelnNeben dem im Beitrag vorgestellten Powermanagement bietet Linux weitere Stromspartechniken. Es unterstützt etwa Prozessoreigenschaften wie Speedstep oder den konfigurierbaren Loadbalancer des Schedulers. Letzterer versucht alle Threads auf einer Mehrprozessormaschine auf eine möglichst kleine Anzahl von CPU-Kernen zu gruppieren. Ist das System nicht vollständig ausgelastet, lassen sich so einige Prozessorkerne in einen Schlafzustand versetzen. Der Anwender aktiviert diese Funktion durch »echo 1 > /sys/devices/system/cpu/sched_mc_power_savings«. Das seit gut einem Jahr im Kernel aufgenommene Tickless-Patch [4] leistet auch seinen Beitrag zum Energiesparen: Linux löst nur dann Interrupts aus, wenn es etwas abzuarbeiten gibt. Für den energiebewussten Kernelhacker bringt das Tickless-Patch die Funktionen »round_jiffies()« und »round_jiffies_relative()« mit. Sie verschieben zeitgesteuerte Aktionen auf die nächste volle Sekunde und verhindern häufiges Aufwecken für nur kurze Zeiträume. Der Erfolg dieser und anderer Maßnahmen ist durchaus messbar: Ein Vergleichstest bescheinigt einem Red-Hat-Linux-Server einen um bis zu zwölf Prozent günstigeren Stromverbrauch als einem Server unter Windows 2008 [5]. |
Suspend-to-Ram und Suspend-to-Disk sind nur zwei der drei möglichen Schlaf-zustände, die die Industrie eingeführt hat, um Strom zu sparen (siehe Abbildung 1 und Tabelle 1). Den dritten Zustand – Standby – kennt Linux auch. Aber weil er kaum Vorteile bietet, setzt Linux ihn typischerweise nicht ein: Das Betriebssystem versetzt dabei seine Komponenten nur in einen leichten Schlummer und benötigt daher etwas mehr Strom als STR. Das wiegt die nur unwesentlich kürzere Zeit, um wieder aufzuwachen, nicht auf, findet Kernelentwickler Pavel Machek und kommentiert dies mit “easy, but useless”.

Abbildung 1: Linux stellt einen von drei Schlafzuständen zur Auswahl, um verschieden tief zu schlafen und so Strom zu sparen.
Damit Suspend-to-Ram und Suspend-to-Disk funktionieren, müssen nicht nur die Hardware, sondern vor allem die Treiber die Methoden unterstützen. Daran hapert es aber vielfach. Oft deshalb, weil die Programmierer das zugehörige Interface einerseits nicht kennen und es sich darüber hinaus in der Vergangenheit gelegentlich änderte. Zum Glück erfordern die letzten Änderungen des Powermanagement-Code im Kernel nicht gleich eine Neu-Implementierung aller Treiber; vielmehr passen Entwickler ihre Treiber mit wenigen Zeilen Code an.
|
Tabelle 1: |
|
|---|---|
|
Zustand |
Beschreibung |
|
S0 |
Betriebsbereit (Working) |
|
S1 |
Schlafend, der CPU-Kontext bleibt erhalten |
|
S2 |
Schlafend, der CPU-Kontext geht verloren |
|
S3 |
Suspend-to-Ram (STR), diverse Hardware ist abgeschaltet |
|
S4 |
Suspend-to-Disk (STD), gesichert auf Hintergrundspeicher |
|
S5 |
Ausgeschaltet |
Altes und neues API
Um den Ruhezustand (Suspend) und den Tiefschlaf (Hibernate) zu unterstützen, implementieren Treiberprogrammierer bis einschließlich Kernel 2.6.26 ein einfaches API. Es besteht aus einer Funktion zum Einschlafen (Suspend) und einer zum Aufwachen (Resume). Es findet noch weithin Anwendung, auch wenn es die Linux-Entwickler in Version 2.6.27 aktualisiert haben.
Im alten Modell ruft Linux in jedem Treiber die zwei genannten Methoden auf. Beide erhalten neben einem Zeiger auf das schlafen zu legende oder aufzuweckende Gerät noch einen Parameter vom Typ »pm_message_t«. Er gibt über den anvisierten Zustand Auskunft, zur Auswahl stehen Suspend-to-Ram oder Suspend-to-Disk. Innerhalb der Suspend-Funktion implementieren Entwickler die notwendigen Schritte, um das entsprechende Gerät ordnungsgemäß schlafen zu legen. Typischerweise schalten sie dazu alle DMA-Transfers und I/O-Operationen ab, retten den Gerätezustand und deaktivieren dann das Gerät. Innerhalb der Resume-Funktion aktivieren sie das Gerät wieder, restaurieren den geretteten Zustand und setzen eventuell unterbrochene Zugriffe fort.
Starke Bindungen
Das Powermanagement folgt programmtechnisch dem Gerätemodell. Es bietet drei Möglichkeiten, eigene Funktionen einzubinden: Entweder bindet der Entwickler die Suspend- und Resume-Methoden an das Klassen-Geräteobjekt (»struct class_device«) oder an das Geräteobjekt (»struct device«). Das zugehörige Bussystem, zum Beispiel PCI oder USB, verwaltet es. Wer also ein neues Bussystem in den Kernel integrieren möchte – etwa für Controller Area Networks – bindet die beiden Funktionen direkt an das Busobjekt vom Typ »struct bus_type«.
An welches Objekt der Entwickler die Funktionen bindet, hängt vom Zeitpunkt ab, zu dem Linux die Funktionen aufrufen soll. Tatsächlich durchläuft der Kernel mehrere Zustände, bis er zum Stillstand kommt (siehe Abbildung 2). Sobald der Anwender das System schlafen legt, schreibt Linux zunächst alle im Page-Cache befindlichen, ungesicherten Daten auf den Hintergrundspeicher. Dazu verwendet es den Systemaufruf »sys_sync()«, den auch das Kommando »sync« verwendet. Registrierte Komponenten wie Threads informiert das Betriebssystem per Suspend-Notifier über die bevorstehende Zustandsänderung. Hat es seine Callback-Funktionen abgearbeitet, friert es die Rechenprozesse ein. Aus Diskussionen der Linux-Entwickler lässt sich ablesen, dass das Einfrieren der Rechenprozesse wohl in künftigen Kernelversionen unterbleibt.

Abbildung 2: Bei Suspend oder Hibernate versucht Linux zunächst ganze Geräteklassen, danach die an den Bussen befindlichen Geräte zum Nachtschlaf zu bewegen.
Anschließend versetzt Linux die Geräte in genau umgekehrter Reihenfolge ihrer Registrierung in den Schlafzustand. Dazu ruft es zuerst die Suspend-Methoden auf, die der Treiberentwickler über die Geräteklassen-Objekte dem Kernel bekannt gegeben hat (siehe Listing 1).
|
Listing 1: |
|---|
01 #include <linux/module.h>
02 #include <linux/fs.h>
03 #include <linux/device.h>
04 #include <linux/pm.h>
05 #include <asm/uaccess.h>
06
07 static int major = 0;
08 static int suspend_count = 0;
09
10 int my_suspend(struct device *dev,
11 pm_message_t state) {
12 switch (state.event) {
13 case PM_EVENT_ON:
14 printk("on ...n"); break;
15 case PM_EVENT_FREEZE:
16 printk("freeze ...n"); break;
17 case PM_EVENT_SUSPEND:
18 printk("suspend ...n"); break;
19 case PM_EVENT_HIBERNATE:
20 printk("hibernate ...n"); break;
21 default:
22 printk("pm_event: 0x%xn",
23 state.event); break;
24 }
25 printk("my_suspend(%p, %d)n",
26 dev, ++suspend_count);
27 return 0;
28 }
29
30 int my_resume(struct device *dev) {
31 printk("my_resume(%p, %d)n", dev,
32 ++suspend_count );
33 return 0;
34 }
35
36 static struct file_operations fops = {
37 .owner = THIS_MODULE,
38 };
39
40 static struct class *test_class;
41
42 static int __init my_module_init(void) {
43 printk("my_module_initn");
44 if ((major = register_chrdev(0,
45 "testdev", &fops))) {
46 test_class = class_create(THIS_MODULE,
46 "testclass");
47 if (IS_ERR(test_class)) {
48 printk("no udev supportn");
49 unregister_chrdev(major, "testdev");
50 return -EIO;
51 }
52 test_class->suspend = my_suspend;
53 test_class->resume = my_resume;
54 device_create(test_class, NULL,
55 MKDEV(major, 0), "lulu");
56 return 0;
57 }
58
59 printk("loading failedn");
60 return -EIO;
61 }
62
63 static void __exit my_module_exit(void) {
64 unregister_chrdev(major, "testdev");
65 if (!IS_ERR(test_class)) {
66 device_destroy(test_class,
67 MKDEV(major, 0));
68 class_destroy(test_class);
69 }
70 printk("my_module_exit; "
71 "%d times suspendedn",
72 suspend_count);
73 }
74
75 module_init(my_module_init);
76 module_exit(my_module_exit);
77 MODULE_LICENSE("GPL");
|
Im zweiten Schritt bearbeitet Linux die Funktionen der Geräteobjekte (siehe Listing 2) und drittens die des Bussystems. Das Powermanagement schaltet nun alle Prozessoren einer Mehrkernmaschine außer dem ersten ab, sperrt Interrupts und ruft die an die Geräteobjekte gekoppelten »suspend_late()«-Methoden auf. Nachdem die Systemgeräte abgeschaltet sind, schläft Linux. Erwartungsgemäß weckt Linux das System in umgekehrter Reihenfolge wieder auf.
|
Listing 2: |
|---|
01 ...
02 static int suspend_count = 0;
03 static int major = 0;
04 ...
05 static struct pci_device_id pci_drv_tbl[] = {
06 {MY_VENDOR_ID, MY_DEVICE_ID,
07 PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
08 {0, }
09 };
10
11 static struct pci_driver pci_drv = {
12 name: "pci_drv",
13 id_table: pci_drv_tbl,
14 probe: pci_init_one,
15 remove: pci_remove_one,
16 suspend: my_suspend,
17 resume: my_resume,
18 };
19
20 static int my_suspend(struct pci_dev *dev,
21 pm_message_t state) {
22 printk("my_suspend(%p, %d)n", dev,
23 ++suspend_count);
24 return 0;
25 }
26
27 static int my_resume(struct pci_dev *dev) {
28 printk("my_resume(%p, %d)n", dev,
29 ++suspend_count);
30 return 0;
31 }
32 ...
33 static int __init pci_drv_init(void) {
34 int ret;
35
36 if ((major = register_chrdev(0,
37 "driver-name", &driver_fops)) != 0)
38 {
39 if ((ret =
40 pci_register_driver(&pci_drv)) < 0 ) {
41 printk(KERN_ERR "can't register "
42 "PCI driver (%d)n", ret);
43 unregister_chrdev(major,"driver-name");
44 return -EIO;
45 }
46 return 0;
47 }
48 return -EIO;
49 }
50
51 static void __exit pci_drv_exit(void) {
52 pci_unregister_driver(&pci_drv);
53 unregister_chrdev(240, "driver-name");
54 printk("pci_drv_exit(%d)n",
55 suspend_count);
56 }
57 ...
|
Listing 1 zeigt ein vollständiges Beispiel, das Powermanagement-Funktionen über das Klassenobjekt im Kernel 2.6.26 einbindet. Bevor Experimentierfreudige den Code allerdings übersetzen und den Treiber per »insmod suspendtest.ko« laden, sollten sie sicherstellen, dass ihr System den Ruhezustand unterstützt. Bei den meisten Systemen drücken sie dafür kurz den Ein-Aus-Schalter und schicken es so in den Ruhezustand STR. Danach wecken sie es wieder auf.
Müde bin ich, geh’ zur Ruh
Das Powermanagement hat in der Zwischenzeit die Funktionen »my_suspend()« und »my_resume()« aufgerufen. Die Funktion »my_suspend()« hat die Nachricht vom Typ »pm_message_t« in den Zeilen 12 bis 24 dekodiert und die globale Variable »suspend_count« hochgezählt. Der Sparringspartner »my_resume()« inkrementiert die Variable »suspend_count« ebenfalls (Zeile 32). Wer das Modul mittels »rmmod suspendtest« entlädt, sorgt dafür, dass es den Wert von »suspend_count« über die Kernelmeldungen ausgibt. Typischerweise findet sich der Wert also in der Datei »/var/log/messages«. Alternativ lässt sich der Ringbuffer mittels »dmesg« ausgeben.
Schlafen in Bussen
Die Funktionen »my_suspend()« und »my_resume« lassen sich nicht nur über Geräteklassen einbinden, denn alternativ erledigt das auch das jeweilige Bus-Subsystem. Die Prototypen der Funktionen unterscheiden sich dazu nur leicht. Listing 2 zeigt ein zugehöriges Codefragment für den PCI-Bus. Die Zeilen 5 bis 18 fassen die Methoden in einem »pci_drv«-Objekt zusammen, Zeile 40 registriert sie. Testen lässt sich der Code aber nur, wenn auch wirklich ein PCI-Gerät angeschlossen ist. Nur in diesem Fall ruft das PCI-Subsystem die Funktionen auf. Eine Alternative zum Testen des Powermanagements zeigt Rafael J. Wysocki mit einem Patch für den Kernel, der im Sys-FS ein Gerät anlegt, das den Übergang in die jeweiligen Powerstates simuliert [2].
Die gezeigten Suspend- und Resume-Funktionen reizen die Möglichkeiten, Powermanagement im Treiber zu realisieren, bei Weitem noch nicht aus. Die Methoden »suspend_late(struct device *, struct pm_message_t *)« und »resume_early(struct device *)« ruft Linux – wie die Namen andeuten – beim Schlafenlegen zu einem späteren und beim Aufwecken zu einem früheren Zeitpunkt auf. Sie unterscheiden sich von den vorgenannten Funktionen darin, dass die Interrupts bereits beziehungsweise noch gesperrt sind (siehe Abbildung 2).
Einfrieren und auftauen
Wie bereits angedeutet, ändert Kernel 2.6.27 die Schnittstellen der Energieverwaltung. Leonard Brown und Rafael Wysocki haben sie auf dem diesjährigen Linux-Symposium in Ottawa vorgestellt [3], Torvalds hat die zugehörigen Patches im Juli in seinen Quellcode übernommen. Eines der Hauptprobleme beim bisherigen Ansatz bestand darin, dass der Funktion »resume()« der Parameter vom Typ »pm_message_t« fehlt. Daher ist nicht sichtbar, ob Linux den Treiber aus dem Ruhezustand oder aus dem Tiefschlaf aufweckt.
Einige Treiber wollen jedoch feiner steuern, wie sie ihr betreutes Gerät in den Schlaf wiegen oder behutsam wieder aufwecken. Das trifft insbesondere auf die Low-Level-Treiber von Bussystemen zu. Dem Wunsch kommen die Kernelentwickler nach: Für beide Übergänge in den Ruhezustand STR und den Schlafzustand STD bieten sie Callbackfunktionen an. Geht alles glatt, liefern sie »NULL«, sonst einen Fehlercode zurück.
Für den Ruhezustand stehen die vorgestellten Methoden »suspend()« und »resume()« bereit. Suspend-to-Disk erwartet jetzt die vier neuen Methoden »freeze()«, »thaw()«, »poweroff()« und »restore()« (siehe Abbildung 3): Das Subsystem ruft »freeze()« auf, bevor es das Image erzeugt und dieses später auf den Hintergrundspeicher schreibt (siehe Listing 3). Die »thaw()«-Methoden arbeitet es ab, bevor es das erzeugte Image speichert. Diese ruft das System auch auf, wenn es kein Image erzeugen konnte. Dann stellt es den ursprünglichen Betriebszustand wieder her.

Abbildung 3: Für die Implementierung von STD erwartet Kernel 2.6.27 neuerdings vier Methoden vom Treiber statt nur zwei. Gerade beim Suspend-to-Disk haben Entwickler feinere Möglichkeiten, etwa ein Image zu erzeugen und zu reagieren, falls dies fehlschlägt.
Nach dem Speichern des Image arbeitet der Kernel sämtliche »poweroff()«-Callbacks der Geräte ab. Erst damit zieht er sinnbildlich den Stecker der einzelnen Geräte, die dann stromlos sind. Den in Listing 3 ebenfalls aufgeführten Callback »prepare()« ruft das Powermanagement beim Suspend oder beim Hibernate auf. Anders als bei dem später folgenden Aufruf der Methode »suspend()« darf der Programmierer davon ausgehen, dass zum Zeitpunkt des Aufrufs noch alle Geräte aktiviert und nutzbar sind. Innerhalb dieser Funktion lässt sich auf normalem Wege Speicher reservieren.
|
Listing 3: Datenstrukturen zum |
|---|
01 ...
02 static struct pm_ops my_pm = {
03 prepare: my_prepare,
04 complete: my_complete,
05 suspend: my_suspend,
06 resume: my_resume,
07 freeze: my_freeze,
08 thaw: my_thaw,
09 poweroff: my_poweroff,
10 restore: my_restore,
11 };
12 ...
13 static int __init my_module_init(void) {
14 printk("my_module_initn");
15 if ((major = register_chrdev(0, "testdev",
16 &fops))) {
17 test_class = class_create(THIS_MODULE,
18 "testclass");
19 if (IS_ERR(test_class)) {
20 printk("no udev supportn");
21 unregister_chrdev(major, "testdev");
22 return -EIO;
23 }
24 test_class->pm = &my_pm;
25 device_create(test_class, NULL,
26 MKDEV(major, 0),
27 NULL, "lulu");
28 return 0;
29 }
30 printk("loading failedn");
31 return -EIO;
32 }
|
Gnadenfrist für alten Code
Das Gegenstück »complete()« ruft der PM-Core auf, nachdem er bei allen Geräten die Callbacks »resume()«, »thaw()« oder »restore()« abgearbeitet hat. Eine genaue Beschreibung all dieser Methoden enthalten die Kernelquellen in der Headerdatei »include/linux/pm.h«.
Treiberprogrammierer, die alte und neue Kernel unterstützen möchten, sind zurzeit noch fein raus: Das bisherige Interface steht bis auf Weiteres zusätzlich in den aktuellen Kerneln zur Verfügung. Bleibt abzuwarten, wann dieser Code auf einen biologisch abbaubaren grünen Code-Komposthaufen wandert. (mg)
|
Advanced Configuration and Power |
|---|
|
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 legt verschiedene Zustände fest, unter anderem die Sleep-Zustände (S-States, [6]). Die S-States wiederum korrespondieren mit den Device-Power-States (D-States). Sie definieren unabhängige Stromsparzustände, jedoch keine vollständigen Leistungszustände wie die S-States. Ein Gerät kann sich etwa in einem Sparmodus befinden, obwohl sich das Gesamtsystem im Betriebsmodus S0 befindet. Ein Rechner hingegen ist 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 und Methoden abgelegt. Die Tabellen beschreiben die Hardware, während die Methoden Code darstellen, den der ACPI-Code des Betriebssystems zu bestimmten Zeitpunkten im Kernelkontext aufruft. Den Code geben die Hardwarehersteller vor, die Kernelentwickler haben kaum eine Möglichkeit, um zu prüfen, was er genau tut. Viele Linux-Entwickler sind nicht besonders glücklich über ACPI. Die Spezifikation gilt als komplex und mit 600 Seiten als umfänglich. Daher sind viele Umsetzungen im Bios entweder fehlerhaft, nicht offengelegt oder schlecht dokumentiert. Linux führt den ACPI-Code mit privilegierten Rechten aus, was Experten für sicherheitskritisch halten. Der Code unterscheidet das aufrufende Betriebssystem. Aus diesem Grund meldet sich Linux neuerdings beim ACPI als Windows an. Nur so erreichen die Entwickler ein konsistentes Verhalten. Und nicht zuletzt hat Microsoft in der Vergangenheit versucht, über Patente Linux von der Verwendung von ACPI auszuschließen [7]. |
|
Infos |
|---|
|
[1] Timo Hönig, “ACPI-Implementierung in Linux 2.6”: Linux-Magazin 02/04, S. 32 [2] “Kernelpatches testen Ruhezustände im laufenden Betrieb”: Linux-Magazin Online [https://www.linux-magazin.de/news/kernelpatches_testen_ruhezustaende_im_laufenden_betrieb] [3] A. Leonard Brown und Rafael J. Wysocki, “Suspend-to-RAM in Linux”: [http://ols.fedoraproject.org/OLS/Reprints-2008/brown-reprint.pdf] [4] Eva-Katharina Kunst und Jürgen Quade, “Gerade echtzeitig”: Linux-Magazin 06/08, S. 106 [5] Tom Henderson und Rand Dvorak, “Linux captures the \’green\’ flag, beats Windows 2008 power-saving measures”: [http://www.networkworld.com/research/2008/060908-green-windows-linux.html] [6] Hewlett-Packard et al., “Advanced Configuration and Power Interface Specification”, Revision 3.0b, 2006: [http://www.acpi.info/DOWNLOADS/ACPIspec30b.pdf] [7] Julius Stiebert, “Microsoft: ACPI sollte nur unter Windows funktionieren”:[http://golem.de/0704/51686.html] |
|
Die Autoren |
|---|
|
Eva-Katharina Kunst, Journalistin, und Jürgen Quade, Professor an der Hochschule Niederrhein, sind seit den Anfängen von Linux Fans von Open Source. Unter dem Titel “Linux Treiber entwickeln” haben sie zusammen ein Buch zum Kernel 2.6 veröffentlicht. |





