Aus Linux-Magazin 10/2013

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

© psdesign1, Fotolia

Der Linux-Kernel abstrahiert und verwaltet die Zugriffe auf GPIOs, also jene Ein- und Ausgabekanäle, die beispielsweise auf dem Raspberry Pi zu finden sind. Wer sich mit einem eigenen Treiber dort einklinkt, kann mit erheblich schnelleren Peripheriezugriffen rechnen.

Es ist einfacher als gedacht und macht Spaß, den Raspberry Pi zum Ansteuern von Hardware zu verwenden. Die 26-polige Steckerleiste des preiswerten Kleincomputers stellt dazu insgesamt 17 von den 54 Ein- und Ausgabeleitungen des Prozessors zur Verfügung, die so genannten GPIOs (General Purpose Input Output).

Jeder GPIO-Pin ist flexibel konfigurierbar: Er kann als Ausgabeleitung dienen, an der programmgesteuert entweder 3,3  V (logisch 1) oder 0  V (logisch 0) anliegen. Als Eingabe konfiguriert, übernimmt der Prozessor nach Aufforderung eine 1, falls Spannung anliegt, ansonsten eine 0.

Wer eine gelbe, grüne oder rote Leuchtdiode (LED), einen Widerstand um die 300 Ohm und ein paar Kabel zur Hand hat, kann schon mit den ersten Versuchen loslegen.

Bastelzeit

Wie in Abbildung 1 zu sehen ist, sind Widerstand und LED in Serie geschaltet und mit der Steckerleiste auf dem Raspberry Pi verbunden. Die Seite der Reihenschaltung, an der die Kathode der LED (die Seite mit dem kürzeren Beinchen beziehungsweise mit der abgeflachten Unterkante) liegt, wird mit GND (Pin 6) verbunden; die andere Seite mit GPIO 4 (Pin 7). Hilfreich für den Aufbau ist ein so genanntes Breadboard, in das man die Drähte der Bauteile steckt.

Abbildung 1: Mit Breadboard, Kabeln und Widerstand: Schaltung zum Ansteuern einer LED über die GPIOs eines Raspberry Pi.

Abbildung 1: Mit Breadboard, Kabeln und Widerstand: Schaltung zum Ansteuern einer LED über die GPIOs eines Raspberry Pi.

Ein einfacher Test der Schaltung ist über das »/sys« -Dateisystem mit einem Shellskript möglich. Bevor dieses den Wert der Ausgabeleitung GPIO 4 auf 0 oder 1 setzen kann, muss es die Leitung reservieren und konfigurieren (Abbildung 2). Zur Reservierung schreibt das Skript die Nummer der GPIO-Leitung (aus Sicht der CPU, also nicht die Nummer des Pins an der Doppelstiftleiste) in die Datei »/sys/class/gpio/export« . Dazu sind allerdings Rootrechte notwendig. Daraufhin legt der Kernel ein neues Verzeichnis an, für GPIO 4 beispielsweise »gpio4« .

Abbildung 2: Aktionen und Zugriffsmöglichkeiten auf GPIOs unter Linux.

Abbildung 2: Aktionen und Zugriffsmöglichkeiten auf GPIOs unter Linux.

In diesem Verzeichnis gibt es eine Datei »direction« , in der man mit den Schlüsselworten »out« und »in« festlegt, ob es sich um eine Ausgabe- oder eine Eingabeleitung handelt. Das Kommando

echo "1" >/sys/class/gpio/gpio4/value

setzt die Spannung auf 3,3  V – die LED leuchtet. Eine 0 in diese Datei geschrieben stellt eine Spannung von 0  V ein und schaltet die LED wieder aus.

Listing 1 zeigt ein Skript, das aus der LED ein Blinklicht macht. Dazu schaltet es die LED ein, schläft eine Sekunde, schaltet sie aus und schläft nochmals eine Sekunde. Der Befehl »trap« dient übrigens dazu, beim Abbrechen des Skripts, beispielsweise per [Strg]+[C], den Pin wieder freizugeben.

Listing 1

Zugriff per Shellskript

01 #!/bin/bash
02
03 trap "echo \"4\" >/sys/class/gpio/unexport" EXIT
04
05 echo "4" >/sys/class/gpio/export
06 echo "out" >/sys/class/gpio/gpio4/direction
07
08 while true
09 do
10     echo "1" >/sys/class/gpio/gpio4/value
11     sleep 1
12     echo "0" >/sys/class/gpio/gpio4/value
13     sleep 1
14 done

250-mal schneller

Eine Messung ergibt, dass sich eine solche Ein-Aus-Sequenz per Sys-Filesystem (ohne die Sleep-Befehle) abhängig vom Kernel und dessen Konfiguration 1000- bis 1500-mal pro Sekunde (mit 1 kHz bis 1,5 kHz) auf dem Raspberry Pi ausführen lässt. Wer die GPIOs jedoch über einen dedizierten Treiber im Kernel anspricht, kann Steigerungen dieser Frequenz um den Faktor 250 erreichen.

Ein normaler, zeichenorientierter Gerätetreiber implementiert typischerweise Schnittstellenfunktionen, die der Kernel beim Laden des Treibers (»mod_init()« ) aufruft, beim Entladen (»mod_exit()« ), beim Öffnen der zugeordneten Gerätedatei (»driver_open()« ), beim Schließen derselben (»driver_close()« ), beim Lesen von der Gerätedatei (»driver_read()« ) und beim Schreiben auf die Gerätedatei (»driver_write()« ).

Ein bisschen abgekupfert

Wer einen Treiber schreiben möchte, der die Ein- und Ausgabeleitungen nutzt, kann die Funktionen »mod_init()« und »mod_exit()« einfach von einem anderen Treiber abkupfern. Im Rahmen dieser Funktionen meldet sich der Treiber beim Kernel mit einem Namen an und übergibt dabei eine Tabelle mit den Funktionen (»driver_open()« , »driver_read()« , »driver_write()« und »driver_close()« ), die bei einem Zugriff durch eine Applikation aufgerufen werden.

Diese Funktionen sind auch relevant, wenn es um den Hardwarezugriff geht. Dabei muss der Treiber logischerweise die gleichen Schritte (Reservieren, Ein- oder Ausgabe festlegen, eigentlicher Zugriff und die Freigabe, Abbildung 2) durchführen wie beim Zugriff über das Sys-Filesystem.

Zum Reservieren (Export) eines GPIO im Kernel dient die Funktion »gpio_request()« (Tabelle 1), für die Freigabe (Unexport) »gpio_free()« . Soll die Leitung als Ausgabe dienen, wird »gpio_direction_output()« , als Eingabe »gpio_direction_input()« aufgerufen. Um die Spannung auf 3,3  V oder 0  V zu setzen, wird »gpio_set_value()« mit den Parametern GPIO-Nummer und auszugebender Wert (1 oder 0) abgesetzt [1].

Tabelle 1

Kernelinterne Funktionen für GPIO (Auswahl)

Managementfunktionen

int gpio_request(unsigned gpio, const char *label)

Reserviert GPIO mit der Nummer »gpio« unter dem Namen »label« .

void gpio_free(unsigned gpio)

Gibt den reservierten GPIO-Pin »gpio« frei.

int gpio_direction_input(unsigned gpio)

Konfiguriert den Pin »gpio« als Eingabe.

int gpio_direction_output(unsigned gpio, int value)

Konfiguriert den Pin »gpio« als Ausgabe.

int gpio_set_debounce(unsigned gpio, unsigned debounce)

Setzt die Entprell-Zeit (in Mikrosekunden), in der der Pin nach einem Zustandswechsel den gleichen Wert haben muss.

int gpio_cansleep(unsigned gpio)

Die Funktion gibt einen Wert ungleich null zurück, falls Kommits auf einen Bus warten (schlafen) können. In diesem Fall darf kein Zugriff aus einem Interrupt-Kontext heraus erfolgen.

Zugriffsfunktionen

inline bool gpio_is_valid(int number)

Überprüft, ob »number« existiert.

int gpio_get_value(unsigned gpio)

Liest GPIO-Pin »gpio« ein.

void gpio_set_value(unsigned gpio, int value)

Falls »value« gleich 0 ist, »gpio« auf 0 V setzen, andernfalls auf 3,3 V.

int gpio_get_value_cansleep(unsigned gpio)

Liest den GPIO-Pin aus einem Nicht-Interrupt-Kontext heraus ein.

void gpio_set_value_cansleep(unsigned gpio, int value)

Setzt den GPIO-Pin aus einem Nicht-Interrupt-Kontext heraus auf »value« .

Integrationsfunktionen

int gpio_export(unsigned gpio, bool direction_may_change)

Gibt die Reservierung von »gpio« für den Zugriff über das Sys-Filesystem frei.

void gpio_unexport(unsigned gpio)

Hebt den Zugriff über das Sys-Filesystem auf.

extern int gpio_export_link(struct device *dev, const char *name, unsigned gpio)

Die Funktion dient dazu, einen symbolischen Link zwischen Treiber und ins Sys-Filesystem exportierten Links herzustellen.

extern int gpio_sysfs_set_active_low(unsigned gpio, int value)

Markiert »gpio« als Active-Low-Signal.

Listing 2 zeigt das Einbinden dieser Funktionen in einen minimalistischen Treiber, sodass dieser GPIO 4 auf 0  V setzt, wenn eine Applikation eine 0 auf die Gerätedatei »/dev/fastgpio4« schreibt, und auf 3,3  V bei jedem anderen Wert. In »driver_open()« findet dazu das Reservieren (Zeile 18) und Festlegen der Ein- und Ausgaberichtung (Zeile 23) statt. Die Freigabe erfolgt in »driver_close()« (Zeile 37), also wenn die Anwendung die Ressource nicht mehr benötigt und den Systemcall »close()« aufruft.

Listing 2

Kernel-Treiber fastgpio.c

001 #include <linux/module.h>
002 #include <linux/fs.h>
003 #include <linux/cdev.h>
004 #include <linux/device.h>
005 #include <linux/gpio.h>
006 #include <asm/uaccess.h>
007
008 static dev_t gpio_dev_nr;
009 static struct cdev *driver_object;
010 static struct class *gpio_class;
011 static struct device *gpio_dev;
012
013 static int driver_open( struct inode
014     *geraete_datei,struct file *instanz)
015 {
016   int err;
017
018   err = gpio_request( 4, "rpi-gpio-4" );
019   if (err) {
020     printk("gpio_request %d\n",err);
021     return -1;
022   }
023   err = gpio_direction_output( 4, 0 );
024   if (err) {
025     printk("gpio_dir_output %d\n", err);
026     gpio_free( 4 );
027     return -1;
028   }
029   printk("gpio 4 configured\n");
030   return 0;
031 }
032
033 static int driver_close( struct inode
034     *geraete_datei, struct file *instanz)
035 {
036   printk( "driver_close called\n" );
037   gpio_free( 4 );
038   return 0;
039 }
040
041 static ssize_t driver_write( struct file
042     *instanz, const char __user *user,
043     size_t count, loff_t *offset )
044 {
045   unsigned long not_copied, to_copy;
046   u32 value=0;
047
048   to_copy = min( count, sizeof(value) );
049   not_copied=copy_from_user(&value,user,
050       to_copy);
051   if (value==1)
052     gpio_set_value(4,1);
053   else
054     gpio_set_value(4,0);
055   //gpio_set_value( 4, value?0:1 );
056   return to_copy-not_copied;
057 }
058
059 static struct file_operations fops = {
060   .owner= THIS_MODULE,
061   .write= driver_write,
062   .open= driver_open,
063   .release= driver_close,
064 };
065
066 static int __init mod_init( void )
067 {
068   if( alloc_chrdev_region(&gpio_dev_nr,
069     0,1,"fastgpio4")<0 )
070     return -EIO;
071   driver_object = cdev_alloc();
072   if( driver_object==NULL )
073     goto free_device_number;
074   driver_object->owner = THIS_MODULE;
075   driver_object->ops = &fops;
076   if( cdev_add(driver_object,
077     gpio_dev_nr,1) )
078     goto free_cdev;
079   gpio_class = class_create( THIS_MODULE,
080     "fastgpio4" );
081   if( IS_ERR( gpio_class ) ) {
082     pr_err("fastgpio4:no udev support\n");
083     goto free_cdev;
084   }
085   gpio_dev=device_create(gpio_class,NULL,
086     gpio_dev_nr,NULL,"%s","fastgpio4");
087   dev_info(gpio_dev, "mod_init");
088   return 0;
089 free_cdev:
090   kobject_put( &driver_object->kobj );
091 free_device_number:
092   unregister_chrdev_region(gpio_dev_nr,1);
093   return -EIO;
094 }
095
096 static void __exit mod_exit( void )
097 {
098   dev_info(gpio_dev, "mod_exit");
099   device_destroy(gpio_class,gpio_dev_nr);
100   class_destroy( gpio_class );
101   cdev_del( driver_object );
102   unregister_chrdev_region(gpio_dev_nr,1);
103   return;
104 }
105
106 module_init( mod_init );
107 module_exit( mod_exit );
108 MODULE_LICENSE("GPL");

Die eigentlichen Hardwarezugriffe sind in »driver_write()« kodiert (Zeile 41). Diese Funktion kommt zum Einsatz, wenn eine Applikation »write()« verwendet. Die Schreibfunktion im Kernel kopiert dank der Minimum-Funktion die richtige Menge Daten zwischen Anwendung und Kernel. So darf der Treiber nicht mehr Bytes kopieren, als die Applikation anfordert, und auch nicht mehr Daten, als im Kernel überhaupt zur Verfügung stehen. Die nachfolgende Zeile kopiert schließlich per »copy_from_user()« die Daten von der Applikation in den Kernel und schreibt sie per »gpio_set_value()« (Zeilen 52 und 54) auf die Hardware.

Leider ist das Übersetzen des Treibers für den Raspberry Pi nicht ganz so einfach wie für einen herkömmliche PC. Hierfür sind nämlich vorkompilierte Kernelquellen notwendig. Die vorige “Kern-Technik” [2] hat die erforderlichen Vorbereitungen beschrieben, sowohl für eine Cross-Entwicklung als auch für die Entwicklung direkt auf dem Mini-Computer selbst. Allerdings ist dort ein Fehler zu berichtigen: Zum Generieren des Kernels ist »make bcmrpi_defconfig« (mit Unterstrich) aufzurufen.

Das Makefile (Listing 3) übersetzt den Treiber beim Aufruf von »make« , das Kommando »insmod fastgpio.ko« lädt ihn auf dem Raspberry Pi in den Kernel. Geht alles gut, existiert danach die Gerätedatei »/dev/fastgpio4« . Für einen effizienten Zugriff bietet sich eine eigene Applikation an, wie in Listing 4 vorgestellt. Zum Übersetzen der im Heimatverzeichnis des Users Pi liegenden Quelltextdatei »appl.c« eignet sich das folgende Kommando:

Listing 4

Effizienter Zugriff mit C-Programm appl.c

01 #include <stdio.h>
02 #include <unistd.h>
03 #include <fcntl.h>
04 #include <time.h>
05
06 int main( int argc, char **argv )
07 {
08   int fd_gpio;
09   int value;
10   struct timespec sleeptime;
11
12   fd_gpio=open("/dev/fastgpio4",O_WRONLY);
13   if (fd_gpio<0) {
14       printf("kann GPIO nicht oeffnen\n");
15       return -1;
16   }
17
18   sleeptime.tv_sec = 0;
19   sleeptime.tv_nsec= 500000000;
20   while (1) {
21       value = 1;
22       write(fd_gpio,&value,sizeof(value));
23       clock_nanosleep( CLOCK_MONOTONIC, 0,
24             &sleeptime, NULL );
25       value = 0;
26       write(fd_gpio,&value,sizeof(value));
27       clock_nanosleep( CLOCK_MONOTONIC, 0,
28             &sleeptime, NULL );
29   }
30   return 0;
31 }

Listing 3

Makefile zum Fastgpio-Treiber

01 ARCH  := arm
02 UNAME := $(shell uname -m)
03 ifeq ($(UNAME),armv6l)
04         KDIR    := /usr/src/linux/
05 else
06         CROSS_COMPILE:=arm-linux-gnueabi-
07         KDIR    := /usr/src/linux/arm
08 endif
09
10 ifneq ($(KERNELRELEASE),)
11 obj-m   := fastgpio.o
12
13 else
14
15 default:
16         $(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KDIR) M=$(PWD) modules
17 endif
18
19 clean:
20         rm -rf *.ko *.o .*.cmd .tmp_versions Module.symvers
21         rm -rf modules.order *.mod.c
$ cc -g -Wall appl.c -lrt -o appl

Da das Programm auf die Funktion »clock_nanosleep()« – die moderne Art des Schlafens [3] – zurückgreift, benötigt sie die Realtime-Bibliothek. Dazu dient der Parameter »-lrt« (für “lib realtime”). Falls der Treiber geladen ist, kann Root die Applikation »appl« starten:

# chrt 50 ./appl

Ist die Schaltung korrekt aufgebaut, blinkt nun die LED. Das Kommando »chrt 50« sorgt übrigens dafür, dass das Programm die Realzeit-Priorität 50 erhält und sich auf diese Weise besser für zeitgesteuerte Aufgaben eignet.

In Kilohertz ausgedrückt

Wer die Funktion »clock_nanosleep()« weglässt, kann grob die mit einem Treiber maximal erreichbare Frequenz messen. Auf einem Raspbian der Version 2013-07-25 [4] waren das bei einem Versuch der Autoren immerhin 280 kHz, auf einem Raspbian 3.6.11 waren per Digital-Oszilloskop sogar knapp 380 kHz zu messen. Anders ausgedrückt: Jede Ein-Aus-Sequenz war nur 2,6 Mikrosekunden lang (Abbildung 3).

Abbildung 3: Das Oszilloskop zeigt, wie schnell der Treiberzugriff ist.

Abbildung 3: Das Oszilloskop zeigt, wie schnell der Treiberzugriff ist.

Allerdings muss dabei die Treiberfunktion »driver_write()« auf das hier abgedruckte Minimum reduziert bleiben. Bereits eine Debugmeldung per »printk« verschlechtert das Zeitverhalten deutlich. Und noch ein Hinweis: Mit Realzeitpriorität, also mit Hilfe des Kommandos »chrt 50« gestartet, kann das Programm nur abbrechen, wer entweder die Funktion »clock_nanosleep()« eingebaut lässt oder der Shell eine noch höhere Priorität zuweist.

Hinter den Kulissen

Am Beispiel des Zugriffs auf die GPIOs des Raspberry Pi lässt sich erläutern, welche komplexen Aufgaben der Linux-Kernel dem Programmierer abnimmt. Hardware-affine Entwickler wissen bereits, dass sich die Funktionen der Hardware durch Lesen und Schreiben auf spezifische Adressen ansprechen lassen. So adressierte Speicherzellen heißen Register.

Beim Raspberry Pi kann der stolze Besitzer dem Handbuch [5] entnehmen, dass die Register für den GPIO-Zugriff im Adressraum ab 0xF2200000 angesiedelt sind (Tabelle 2). Abbildung 4 zeigt zusätzlich den Aufbau der Konfigurationsregister. Leicht lässt sich da erkennen, dass einzelnen Bits spezifische Funktionen zugeordnet sind. Im Fall des GPIO-Konfigurationsregisters sind für jede GPIO-Leitung drei Bits zusammengefasst. Tabelle 3 zeigt hierfür eine Auswahl relevanter Codes.

Tabelle 2

Die GPIO-Register des Raspberry Pi (Auswahl, [5], Seite 90)

Register

Adresse

Beschreibung

GPFSEL0

0xF2200000

Konfiguration GPIO 0-9

GPFSEL1

0xF2200004

Konfiguration GPIO 10-19

GPFSEL2

0xF2200008

Konfiguration GPIO 20-29

GPFSEL3

0xF220000C

Konfiguration GPIO 30-39

GPFSEL4

0xF2200010

Konfiguration GPIO 40-49

GPFSEL5

0xF2200014

Konfiguration GPIO 50-53

GPSET0

0xF220001c

Ausgang 0 bis 31 auf 1 setzen

GPSET1

0xF2200020

Ausgang 32 bis 53 auf 1 setzen

GPCLR0

0xF2200028

Ausgang 0 bis 31 auf 0 setzen

GPCLR1

0xF220002C

Ausgang 32 bis 53 auf 0 setzen

GPLEV0

0xF2200034

Eingang 0 bis 31 einlesen

GPLEV1

0xF2200038

Eingang 32 bis 53 einlesen

Tabelle 3

Bitcodierung für die GPIO-Konfiguration

Bitcodierung

Bedeutung

»000«

GPIO-Pin als Eingabe

»001«

GPIO-Pin als Ausgabe

»010« bis »111«

sonstige Funktion

Abbildung 4: Das Konfigurationsregister des Raspberry Pi fasst für jede GPIO-Leitung drei Bits zusammen.

Abbildung 4: Das Konfigurationsregister des Raspberry Pi fasst für jede GPIO-Leitung drei Bits zusammen.

Der Dokumentation kann der Leser weiterhin entnehmen, dass GPIO 4 als Ausgang fungiert, falls im Register »GPFSEL0« , also an der Adresse 0xF2200000, die Bits 12, 13 und 14 den Bitcode »001« aufweisen. Da das Register aber immer nur als Ganzes geschrieben werden kann, würde das plumpe Schreiben des Hexwerts dazu führen, dass GPIO 4 zwar als Ausgang, die GPIOs 0 bis 3 und 5 bis 9 aber als Eingang konfiguriert sind. Das ist natürlich nicht gewollt.

Um den Zustand der übrigen GPIOs beim Schreiben nicht zu ändern, liest der Bastler daher zunächst das komplette Register aus. Danach setzt er per bitweiser Und-Verknüpfung die relevanten Bits auf Null, anschließend mit der Konfigurations-Bitfolge »001« per bitweiser Oder-Verknüpfung neu. Die in Kasten A in Abbildung 5 dargestellte Befehlsfolge realisiert dieses Vorgehen.

Abbildung 5: Im Vergleich zu früher (A) sind Hardwarezugriffe unter Linux heute komplexer (C).

Abbildung 5: Im Vergleich zu früher (A) sind Hardwarezugriffe unter Linux heute komplexer (C).

Der Programmierer kann im Fall des Raspberry Pi zwar per Zeiger direkt auf die Hardware-Adressen zugreifen, erhält dann aber keinen portierbaren Code. Die in der Software verwendeten Adressen entsprechen nämlich nicht auf jeder Plattform auch den physischen Adressen. Dazwischen liegt noch die Memory-Management-Unit, die je nach Plattform auch für I/O-Bereiche eine Adressenumsetzung durchführt.

Linus sei Dank

Um den Programmierer davon abzuschirmen und diesen Vorgang transparent zu gestalten, hat Linus Torvalds Zugriffsfunktionen eingeführt: »read[bwl]()« und »write[bwl]()« . »b« steht dabei für Byte, »w« für Word und »l« für long. »readb()« führt einen Byte-Zugriff, »readw()« einen Zwei-Byte-Zugriff und »readl()« einen Vier-Byte-Zugriff durch. Die damit zum obigen Code funktional identische, Linux-konforme Implementierung zeigt der Kasten B in Abbildung  5.

Bei der Kodierung gibt es noch einen weiteren Fallstrick. So sortieren moderne CPUs schon mal gerne aus Optimierungsgründen die Reihenfolge der programmierten Befehle um. Das machen sie, wenn die neue Reihenfolge aus Sicht der CPU funktional identisch zur programmierten Reihenfolge ist. Da der Prozessor aber nicht zwischen normalen Speicherzellen und Registern unterscheidet, kann ein Reordering von Registeradressen zu unvorhersehbaren Ergebnissen (Race Conditions) führen.

Barrieren

Hiergegen helfen so genannte Memory Barriers [6], die vor Lese- beziehungsweise nach Schreibzugriffen einzusetzen sind. Je nach Plattform aktiviert der Entwickler die Memory Barriers unterschiedlich. Linux abstrahiert das mit vier Funktionen: »rmb()« ist für Lesezugriffe, »wmb()« für Schreibzugriffe, »mb()« für Lese- und Schreibzugriffe zuständig. Die Funktion »barrier()« schaltet das Umsortieren durch den Compiler ab. Um das Reordering programmtechnisch zu berücksichtigen, muss man also noch, wie in Abbildung 5 (Kasten C) dargestellt, die Barrier-Funktionen einfügen.

Atomar

Noch ein weiteres Problem erfordert dringend die Hilfestellung durch den Kernel. Angenommen zwei unabhängige Programme konfigurieren jeweils einen anderen GPIO, Programm A etwa GPIO 4 als Input und Programm B GPIO 7 als Output. Dazu lesen beide Tasks den gegenwärtigen Konfigurationswert ein, jede führt die Änderungen aus und speichert die Werte zurück.

Da aber beide Programme ihre Änderungen ausgehend vom gemeinsamen Grundwert unabhängig voneinander ausgeführt haben, überschreibt das Programm, das seinen Wert zuletzt abspeichert, die Änderungen des anderen. Eine klassische Race Condition. Daher muss die Befehlssequenz (Lesen, Löschen, Setzen, Zurückschreiben) atomar, also ohne Unterbrechung beziehungsweise ohne Parallelität, abgearbeitet werden. Ohne Verankerung im Betriebssystemkern ist dieses Problem nicht zu lösen.

Es lohnt sich

Es ist also nicht nur praktisch, sondern auch technisch notwendig, dass der Linux-Kernel ein GPIO-Interface anbietet. Es empfiehlt sich daher, keinen Code zu verwenden, der das GPIO-Interface umgeht und direkt auf die Hardware zugreift, auch wenn derlei Software gelegentlich im Raspberry-Pi-Forum zu finden ist [7].

Das GPIO-Subsystem ist übrigens vergleichweise jung. Daher sollte es niemanden überraschen, wenn es in den kommenden Linux-Versionen noch grundsätzliche Änderungen an dessen Kernel-Interface gibt. Unabhängig davon lohnt es sich, den GPIO-Zugriff über einen Gerätetreiber zu realisieren. Gerade der eher leistungsschwache Raspberry Pi trumpft dann richtig auf. (mhu)

Infos

  1. Dokumentation zu GPIO im Kernelquellcode: https://www.kernel.org/doc/Documentation/gpio.txt
  2. Quade, Kunst, “Kern-Technik”: Linux-Magazin 08/13, S. 86
  3. Quade, Mächtel, “Moderne Realzeitsysteme kompakt”: Dpunkt-Verlag, 2012
  4. Raspbian: http://www.raspbian.org
  5. Broadcom Corporation, “BCM2835 ARM Peripherals”: http://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf
  6. Quade, Kunst, “Linux-Treiber entwickeln”, 3. Auflage: Dpunkt-Verlag, 2011, S. 220
  7. Raspberry-Pi-Forum, “RaspiUtils GPIO using a device driver”: http://www.raspberrypi.org/phpBB3/viewtopic.php?f=33&t=32152
  8. Listings zum Artikel: https://www.linux-magazin.de/static/listings/magazin/2013/10/kern-technik/

Der Autor

Eva-Katharina Kunst, Journalistin, und Jürgen Quade, Professor an der Hochschule Niederrhein, sind seit den Anfängen von Linux Fans von Open Source. In der Zwischenzeit ist die dritte Auflage ihres Buches “Linux Treiber entwickeln” erschienen.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 6 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