Open Source im professionellen Einsatz
Linux-Magazin 03/2012
© psdesign1, Fotolia

© psdesign1, Fotolia

Kernel- und Treiberprogrammierung mit dem Linux-Kernel – Folge 61

Kern-Technik

,

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.

1530

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).

Abbildung 2: Suspend to Disk ist der komplizierteste aller Schlafzustände.

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" );

Diesen Artikel als PDF kaufen

Express-Kauf als PDF

Umfang: 4 Heftseiten

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

Linux-Magazin kaufen

Einzelne Ausgabe
 
Abonnements
 
TABLET & SMARTPHONE APPS
Bald erhältlich
Get it on Google Play

Deutschland

Ähnliche Artikel

  • Kern-Technik

    Vorgefertigte Codestücke helfen dabei, die Komplexität von Treibercode zu beherrschen. Makros und Muster unterstützen Treiberentwickler bei kritischen Abschnitten und dem Powermanagement.

  • Kernel-Management

    Der Linux-Kernel versetzt einzelne Geräte in den Stromsparzustand, wenn er sie gerade nicht benötigt. Dank des Runtime-Powermanagement-Subsystems läuft das gut organisiert ab.

  • Kern-Technik

    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.

  • Einschlaf-Hilfen

    Mit dem neuen Kernel hält ACPI auf relativ breiter Front in Linux Einzug. Die vereinheitlichte Hardware- Konfiguration ist schon aus dem Kernel 2.4 bekannt, beim Powermanagement kommen aber gleich mehrere Schlafmodi hinzu.

  • Kern-Technik

    Einen Treiber in den Kernel einzubinden ist leider wenig intuitiv. Deshalb gibt es hier ein Grundgerüst als Ausgangspunkt. Es zeigt, wie der Kernel dem Treiber Gerätenummern zuweist und der Gerätedateiverwalter Udev eigenständig die zugehörigen Gerätedateien anlegt.

comments powered by Disqus

Stellenmarkt

Artikelserien und interessante Workshops aus dem Magazin können Sie hier als Bundle erwerben.