Aus Linux-Magazin 09/2006

Hardware-Management mit der Libhal

Die Libhal verhindert, dass das Hardware-Management zu einer Odyssee im Weltall wird. Dieser Artikel verrät C-Kundigen, wie sie in eigenen Programmen die Libhal-API dafür nutzen, Geräte zu finden und sich über Veränderungen unterrichten zu lassen.

Prima, wenn das Betriebssystem beim Booten Hardware findet und sogar neu eingesteckte Geräte richtig einbindet. Dumm nur, wenn die gerade laufende Anwendung das nicht bemerkt. Ohne einen Neustart des Programms kommt man in solchen Fällen nicht weiter. Wer etwa eine Videokamera ansteckt, während das Schnittprogramm läuft, braucht kaum zu hoffen, dass es davon etwas mitbekommt. Das HAL-Framework (Hardware Abstraction Layer) bietet die passende Infrastruktur zur Lösung des Problems [1]. Dieser Artikel erklärt, wie man es in eigenen Programmen nutzt.

Im Zentrum der HAL-Architektur steht der Daemon »hald«, der Informationen über Hardware einerseits vom Kernel, andererseits von so genannten Device Information Files (Endung ».fdi«) bekommt (siehe Artikel [2]). Mit ihm kommunizieren Anwendungsentwickler über die Libhal, die alle wichtigen Funktionen dafür bereitstellt.

Zwar wollen sich die Entwickler bis zur Version 1.0 nicht endgültig auf das API festlegen, doch langsam scheint die Häufigkeit der Änderungen abzunehmen. Die Beispiele des Artikels lassen sich mit HAL 0.5.7 unter Fedora Core 5 kompilieren, die Entwicklungsbibliotheken »hal-devel« und »dbus-devel« vorausgesetzt. Die HAL-Version von Ubuntu Dapper Drake ist die gleiche, jedoch heißen die nötigen Entwicklungspakete »libdbus-1-dev«, »libdbus-glib-1-dev« sowie »libhal-dev«.

Gegenüber älteren HAL-Versionen haben sich zum Teil nur die Funktionsnamen geändert, zum Beispiel tragen viele Funktionen nun den Namen »libhal_Funktion()« statt »hal_Funktion()«.

Verbindung zum D-Bus

Im Hintergrund kommunziert jede Anwendung, die HAL verwendet, über D-Bus. Zwar kapselt die Libhal einen großen Teil dieser Kommunikation, ein paar Handgriffe bleiben dem Programmierer aber noch zu tun. An erster Stelle steht die Verbindung zum Systembus, den die Funktion »dbus_bus_get()« mit dem Argument »DBUS_BUS_SYSTEM« herstellt (Listing 1, Zeile 16).

Im Listing steht davor nur noch die Initialisierung der D-Bus-spezifischen Fehlervariablen »dbus_error«. Beispielhaft zeigt der nach der Verbindungsanfrage folgende Code die Fehlerverarbeitung (Zeilen 17 bis 21), bei weiteren Funktionsaufrufen fehlt sie. In richtigen Programmen ist selbstverständlich jeder möglicherweise auftretende Fehler zu behandeln.

In Zeile 23 fordert »libhal_ctx_new()« einen HAL-Kontext an, der im Hintergrund den Zustand und einige Variablen führt, die der Programmierer somit nicht für jede Anfrage angeben muss. Die eigentliche Verbindung zum D-Bus stellt »libhal_ctx_set_dbus_connection()« her.

Lshal selbst gemacht

Das Beispiel in Listing 1 ist eine stark gekürzte Variante des meist schon installierten Tools »lshal«. Sie gibt nur die Geräte auf der obersten Hierarchiestufe aus und geht nicht den ganzen Baum durch, in dem HAL die verfügbaren Geräte strukturiert. Prinzipiell unterscheidet HAL nicht zwischen logischen und physischen Geräten. Es kann also gut sein, dass ein bestimmtes Stück Hardware auf mehreren HAL-Geräte abgebildet ist.

Listing 1:
»lshal.c«

01 #include <stdio.h>
02 #include <hal/libhal.h>
03 #include <dbus/dbus.h>
04 #include <dbus/dbus-glib.h>
05 
06 int main(int argc, char **argv)
07 {
08   static LibHalContext *ctx;
09   DBusError dbus_error;
10   DBusConnection *dbus_conn;
11   char **devices;
12   int i_devices, i;
13   char *udi;
14 
15   dbus_error_init(&dbus_error);
16   dbus_conn = dbus_bus_get (DBUS_BUS_SYSTEM, &dbus_error);
17   if (dbus_error_is_set(&dbus_error)) {
18     g_warning("Could not connect to system bus %sn", dbus_error.message);
19     dbus_error_free (&dbus_error);
20     return 1;
21   }
22 
23   ctx = libhal_ctx_new();
24   if (ctx == NULL) {
25     g_warning("Could not create HAL contextn");
26   }
27 
28   libhal_ctx_set_dbus_connection(ctx, dbus_conn);
29 
30   if (!(devices = libhal_get_all_devices (ctx, &i_devices, &dbus_error))) {
31      warn ("HAL not running: %s", dbus_error.message);
32      dbus_error_free (&dbus_error);
33      libhal_ctx_shutdown (ctx, NULL);
34      libhal_ctx_free (ctx);
35      return 1;
36   }
37 
38   for (i = 0; i < i_devices; i++) {
39     printf("%sn", devices[i]);
40   }
41 
42   dbus_error_free (&dbus_error);
43   libhal_ctx_free(ctx);
44 }

Eindeutige Identifier

Die Funktion, die alle Geräte der obersten Ebene ausgibt, ist »libhal_get_all_devices()«. Als Argumente erwartet sie einen HAL-Kontext und die Adresse einer Integer-Variablen, in die sie die Anzahl der gefundenen Geräte einträgt. Der Rückgabewert ist ein String-Array, der den Unique Device Identifer (UDI) der HAL-Objekte enthält, also zum Beispiel »/org/freedesktop/Hal/devices/acpi_PWRF«. Mit Hilfe der Integer-Variablen lässt sich bequem über die Rückgabewerte iterieren (Zeilen 38 bis 40). Damit bleibt am Ende des Programms nur noch, die angeforderten Ressourcen wieder freizugeben.

Um den Quellcode zu übersetzen, ist eine ganze Reihe von Include-Pfaden und Bibliotheken anzugeben. Dabei hilft das auf Linux-Systemen etablierte Pkgconfig, das zu jeder Entwicklungsbibliothek die nötigen Infos führt.

Zum Beispiel gibt »pkg-config –cflags hal« die Include-Pfade für HAL aus, unter anderem das Define »DBUS_API_SUBJECT_TO_CHANGE«, mit dem Programmierer signalisieren sollen, dass sie den Entwicklungszustand des API zur Kenntnis genommen haben. Ein Makefile für die Übersetzung der Beispielprogramme findet sich zusammen mit den Listings unter [3].

Geräte suchen

Die meisten Anwendungen dürften sich wenig für den Gesamtbestand an vorhandener Hardware interessieren, sondern nur für eine bestimmte Klasse. Auch dafür bietet HAL eine Lösung: Jedes Gerät besitzt eine ganze Reihe von Eigenschaften (Properties), die der Programmierer über das Libhal-API auslesen kann. Einen Überblick geben das systemweite »/usr/bin/lshal« oder der »hal-device-manager« (Abbildung 1). Eigenschaften gliedern sich in die Klassen Metadata, Physical, Functional und Policy (Zugriffschutz). Manche der Properties beschreiben so genannte Capabilities: Fähigkeiten, die ein Gerät besitzt. Ein CD-ROM besitzt beispielsweise die Capability »storage.cdrom«.

Abbildung 1: Der »hal-device-manager« (Gerätemanager) zeigt die Eigenschaften jedes Geräts, hier ein logisches Alsa-Device für Aufnahmen (Capture).

Abbildung 1: Der »hal-device-manager« (Gerätemanager) zeigt die Eigenschaften jedes Geräts, hier ein logisches Alsa-Device für Aufnahmen (Capture).

Das Beispiel in Listing 2 sucht mit Hilfe von Capabilities alle vorhandenen Alsa-Geräte. Dazu verwendet es die Funktion »libhal_find_device_by_capability()«, die gegenüber »libhal_get_all_devices()« als zusätzliches Argument eben jene Capability erwartet, im Beispiel den String »alsa«. Das Listing zeigt außerdem, wie man mit der Funktion »libhal_device_get_property_string()« eine Eigenschaft (Property) des Geräts ausliest. Das Suffix »_string« zeigt an, dass die Eigenschaft vom Typ String ist.

Listing 2:
»find-alsa.c« (Ausschnitt)

01 if (!(devices = libhal_find_device_by_capability (ctx, "alsa", &i_devices, &dbus_error))) {
02      warn ("HAL not running: %s", dbus_error.message);
03      dbus_error_free (&dbus_error);
04      libhal_ctx_shutdown (ctx, NULL);
05      libhal_ctx_free (ctx);
06      return 1;
07  }
08 
09  for (i = 0; i < i_devices; i++) {
10     type = libhal_device_get_property_string(ctx, devices[i], "alsa.type", &dbus_error);
11     printf("%s (%s)n", devices[i], type);
12  }

Daneben gibt es »strlist«, »int«, »uint64«, »bool« und »double« mit den entsprechenden Funktionen. Das Beispiel demonstriert, wie leicht man wichtige Informationen über das neu gefundene Gerät erhält. Mit dem Argument »alsa.type« aufgerufen verrät »libhal_device_get_property_string()« den Typ des (logischen) Alsa-Device: »sequencer« (Midi), »capture« (Aufnahme), »control«, »playback« und »timer«.

Abbildung 2 zeigt, wie viele Alsa-Geräte das selbst gestrickte Programm findet. Die echte Hardware sind ein On-Board-Soundchip und ein simples USB-Audio-Gadget. Mehr über HAL-Capabilities und Properties verrät die von [1] verlinkte Spezifikation im CVS.

Abbildung 2: Das nur wenige Zeilen umfassende »find-alsa« gibt zu jedem gefundenen Alsa-Device in Klammern den Typ an. Den hier gefundenen logischen Geräten entsprechen zwei echte Soundkarten.

Abbildung 2: Das nur wenige Zeilen umfassende »find-alsa« gibt zu jedem gefundenen Alsa-Device in Klammern den Typ an. Den hier gefundenen logischen Geräten entsprechen zwei echte Soundkarten.

Die bisherigen Beispiele haben nur eine Verbindung zum HAL-Daemon hergestellt, eine Anfrage gestellt sowie die Ergebnisse ausgegeben und sich dann wieder beendet. Das ist die übliche Vorgehensweise kleiner Tools, für länger laufende Programme sind andere Techniken gefragt. Solche Anwendungen können sich beim HAL-Daemon für Events registrieren und passend reagieren, wenn diese eintreten. Damit die Kommunikation klappt, müssen die Anwendungen im »GMainLoop« der Glib-Bibliotheken laufen. Andere Implementationen von D-Bus und HAL verwenden teilweise andere Main Loops, so die Qt-Variante.

In der Schleife

Listing 3 demonstriert, wie sich der Main Loop der Anwendung mit D-Bus und HAL verbinden lässt. Besonders wichtig ist die Funktion »dbus_connection_setup_with_g_main()«. Ohne sie funktioniert kein kontinuierlich laufendes HAL-Programm. Erst an ihrem Ende erzeugt die Main-Funktion den Main Loop und startet ihn.

Listing 3:
»alsa-event.c« (Ausschnitt)

01 ...
02 static void device_added (LibHalContext *ctx, const char *udi)
03 {
04   char **cap;
05   char *parent_udi;
06   size_t n;
07 
08   if (!(cap = libhal_device_get_property_strlist(ctx, udi, "info.capabilities", NULL))) {
09      return;
10   }
11 
12   for (n = 0; cap[n]; n++) {
13      if (strcmp(cap[n], "alsa") == 0) {
14         printf ("Alsa %s device added: ", libhal_device_get_property_string(ctx, udi, "alsa.type", NULL));
15         printf ("%s ", libhal_device_get_property_string(ctx, udi, "alsa.device_file", NULL));
16 
17         parent_udi = libhal_device_get_property_string(ctx, udi, "info.parent", NULL);
18         parent_udi = libhal_device_get_property_string(ctx, parent_udi, "info.parent", NULL);
19 
20         printf ("(%s)n", libhal_device_get_property_string(ctx, parent_udi, "info.vendor", NULL));
21      } else {
22         // printf ("other device added: %sn", udi);
23      }
24   }
25 }
26 
27 
28 int main(int argc, char **argv)
29 {
30    ...
31    GMainLoop *loop;
32 
33    ctx = libhal_ctx_new();
34    if (ctx == NULL) {
35       g_warning("Could not create HAL contextn");
36    }
37 
38    dbus_error_init(&dbus_error);
39    dbus_conn = dbus_bus_get (DBUS_BUS_SYSTEM, &dbus_error);
40 
41    if (dbus_error_is_set(&dbus_error)) {
42       g_warning("Could not connect to system bus %sn", dbus_error.message);
43    }
44    dbus_connection_setup_with_g_main (dbus_conn, NULL);
45    dbus_connection_set_exit_on_disconnect (dbus_conn, FALSE);
46 
47    libhal_ctx_set_dbus_connection(ctx, dbus_conn);
48    libhal_ctx_set_device_added(ctx, device_added);
49 
50    if (!libhal_ctx_init (ctx, &dbus_error)) {
51       warn ("libhal_ctx_init failed: %s", dbus_error.message);
52       dbus_error_free (&dbus_error);
53       libhal_ctx_free (ctx);
54       return 1;
55    }
56 
57    if (!(devices = libhal_get_all_devices (ctx, &nr, &dbus_error))) {
58       warn ("hald not running: %s", dbus_error.message);
59       dbus_error_free (&dbus_error);
60 
61       libhal_ctx_shutdown (ctx, NULL);
62       libhal_ctx_free (ctx);
63       return 1;
64    }
65 
66    loop = g_main_new(FALSE);
67    g_main_run(loop);
68 
69    libhal_ctx_free(ctx);
70 }

Dazwischen steht noch die Registrierung des Event-Callbacks. Die Funktion »libhal_ctx_set_device_added()« registriert den Handler für den Fall, dass HAL ein neues Gerät entdeckt. Schließlich initialisiert »libhal_ctx_init()« noch den HAL-Kontext. Auch ohne dies funktioniert keine HAL-Anwendung. Das anschließende Auflisten aller Geräte dient nur dazu, die Verbindung zum HAL-Daemon zu überprüfen. Einige Beispiele bezeichnen es als eine Art Ping, was nebenbei auch zeigt, dass das HAL-API noch nicht unbedingt ausgereift ist.

Eigenschaften als String-Listen

Bemerkt der HAL-Daemon ein neues Gerät, ruft die Beispielanwendung die Funktion »device_added()« auf. Ähnlich wie im letzen Beispiel liest »libhal_device_get_property_…()« eine Eigenschaft des Geräts, diesmal aber keinen String, sondern eine ganze String-Liste (Zeile 2), die alle Capabilities enthält. Um sie zu durchsuchen, muss man in C schon eine Schleife bemühen, die als Schleifenbedingung überprüft, ob das jeweilige Element existiert. Besitzt es die Capability »alsa«, finden weitere Untersuchungen statt. So liefert Zeile 15 die Gerätedatei des (logischen) Alsa-Geräts. Die meisten Anwendungen dürften von dieser Property Gebrauch machen, denn die Kommunikation mit der Hardware selbst läuft auch mit HAL über die Gerätedateien.

Die dann folgenden Zeilen demonstrieren eine kleine Schwierigkeit beim Umgang mit dem HAL-API. Um an den Hersteller des logischen Geräts zu gelangen, muss der Anwender sich durch die HAL-Baumhierarchie hangeln. Das logische Device besitzt aus HAL-Sicht keinen Hersteller. Es liegt nahe, sich das Elternobjekt im HAL-Baum zu suchen. Doch auch dies besitzt keinen Hersteller, denn es handelt sich nur um die logische Alsa-Soundkarte.

Der Vendor lässt sich in diesem Fall nur über die Großeltern ermitteln. Der logische Aufbau dieser Hierarchie ist aber nirgends dokumentiert, denn er richtet sich natürlich auch nach der jeweiligen Hardware. Ohne Experimentieren kommt hier kaum jemand weiter. Beim Erforschen der Struktur hilft ebenfalls der erwähnte Hal-Device-Manager.

Beispiele aus der Praxis

Das Experimentieren mit dem Libhal-API geht zu Ende, die HAL-Infrastruktur ist seit zwei Distributions-Generationen im Einsatz. Es gibt aber nicht viele Anwendungen aus der Open-Source-Community, die HAL benutzen. Im Wesentlichen sind es einige Systemtools, die Programmierer von Red Hat und Novell geschrieben haben, etwa der Gnome Volume Manager, der Network Manager und die Automount-Programme.

Ein weiteres Beispiel ist Ivman (siehe Artikel im Heft-Schwerpunkt). Wie mit Python ein HAL-fähiges KDE-Programm entsteht, demonstriert der KDE HAL Device Manager [4]. C++-Freunde finden im Audioplayer BMPx [5] ein Beispiel, C#-Programmierer sollten sich das Audioprogramm Banshee [6] ansehen.

Infos

[1] HAL: [http://www.freedesktop.org/wiki/Software/hal]

[2] D-Bus und HAL: Linux-Magazin 05/06, S. 52

[3] Listings und Makefile online: [https://www.linux-magazin.de/Service/Listings/2006/09/HAL]

[4] KDE HAL Device Manager: [http://kubuntu.org/~jriddell/kde-hal-device-manager]

[5] BMPx: [http://beep-media-player.org]

[6] Banshee: [http://www.banshee-project.org]

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 4 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben