Prinzipiell lassen sich C und Perl gut miteinander verheiraten. Die zum Zugriff benutzte XS-Schnittstelle hat es allerdings in sich, weil sie C- und Perl-Syntax in schwer verdaulicher Weise mischt. Einfacher ist der Abstieg in die Unterwelt der C-Systemprogrammierung mit dem Inline-Modul.
Unix-Systemprogrammierung in Perl ist leicht zu bewerkstelligen – jedenfalls solange man im Posix-Bereich bleibt. »socket()«, »shmget()«, »ioctl()« und Konsorten sind alles Funktionen, die der Perl-Kern dem Programmierer von Haus aus zur Verfügung stellt. Und das beiliegende Posix-Modul bietet auch Exoten wie »uname()« oder »sigprocmask()« an. Für manche Erweiterungen unter Linux gibt\’s allerdings nur eine C-Schnittstelle, aber die lässt sich ganz leicht aus Perl heraus ansprechen. Wie kann man zum Beispiel feststellen, wie viel RAM auf dem gerade genutzten Rechner zur Verfügung steht?
Unter Linux ist dafür »sysinfo()« zuständig, eine C-Funktion, die – wie ein Blick in die »man sysinfo« zeigt – einen Pointer auf eine »sysinfo«-Struktur entgegennimmt und diese dann mit allerlei Nützlichem füllt. Abbildung 1 zeigt die entsprechende Manualseite, die die Struktur »sysinfo« in ihre Teilfelder aufschlüsselt und die Aufruf- und Rückgabeparameter der Funktion »sysinfo()« anzeigt:
#include <sys/sysinfo.h> sysinfo(struct sysinfo *info);
Berühmt-berüchtigte XS-Schnittstelle
Gibt »sysinfo()« den Wert »0« zurück, was in C Fehlerfreiheit signalisiert, enthält die Struktur hinter dem Pointer »info« Informationen darüber, wie lange der Ofen schon läuft, drei Werte für die gegenwärtige Last (als Ganzzahlen von 0 bis 65535, gemittelt über 1, 5 und 15 Minuten), wie viel RAM installiert und verfügbar ist, die Anzahl der gegenwärtig laufenden Prozesse und Ähnliches mehr. Um von Perl auf Funktionen von C-Bibliotheken zuzugreifen, nutzt man traditionell die so genannte XS-Schnittstelle, ein berühmt-berüchtigtes Sprachengemisch, das dazu dient, Perl und C zusammenzuleimen. Das neue Perl-Modul »Sysinfo::RAM« mit XS-Anbindung ist schnell mittels
h2xs -An Sysinfo::RAM
hergestellt. Füllt man das so erzeugte Template »RAM.xs« mit dem Inhalt nach Listing 1 und tippt die übliche Installationsfolge
perl Makefile.PL make make install
ein, dann lässt sich anschließend in einem Perl-Programm ganz lässig Folgendes schreiben – und das Ganze funktioniert tatsächlich:
use Sysinfo::RAM; print Sysinfo::RAM::free_ram(), " Bytes freies RAMn";
Die Zeilen 1 bis 5 in Listing 1 ziehen Headerdateien für Perls C-Schnittstelle herein. Zeile 6 holt »sys/sysinfo.h«, das das später aufgerufene »sysinfo()« benötigt. Zeile 8 legt den Namen des später verwendeten Perl-Moduls und des darin definierten Package fest. Die beiden Zuweisungen müssen per Tabulator voneinander getrennt stehen.
Die Zeilen 11 und 12 geben Rückgabewert und Namen der definierten Funktion an. Es ist wichtig, auf die Zeilentrennung zu achten, da Perls XS-Parser kein richtiges C, sondern nur einfache Texterkennung beherrscht. Nach dem Schlüsselwort »CODE:« in Zeile 14 folgt der C-Code der später im Perl-Raum ansprechbaren Funktion »free_ram()«, die lediglich eine »sysinfo«-Struktur anlegt, einen Pointer darauf an die Systemfunktion »sysinfo()« weitergibt und überprüft, ob »0« oder ein Fehlerwert zurückkommt.
Geht der Aufruf gut, gibt die Funktion das Feld »freeram« der »sysinfo«-Struktur (ein »unsigned long«-Wert, wie die »sysinfo«-Manualseite verrät) zurück. Im Fehlerfall liefert »free_ram()« »0« zurück. Die Rückgabe erfolgt nicht etwa mittels »return()« wie allgemein in C üblich, sondern über eine Zuweisung an die XS-Pseudovariable »RETVAL«, die später, nach dem »OUTPUT:«-Schlüsselwort in Zeile 23, XS den Rückgabewert signalisiert.
|
Listing 1: |
|---|
01 #include "EXTERN.h"
02 #include "perl.h"
03 #include "XSUB.h"
04
05 #include "ppport.h"
06 #include <sys/sysinfo.h>
07
08 MODULE=Sysinfo::RAM PACKAGE=Sysinfo::RAM
09
10 ###########################################
11 unsigned long
12 free_ram()
13 ###########################################
14 CODE:
15 { struct sysinfo si;
16
17 if(sysinfo(&si) == 0) {
18 RETVAL = si.freeram;
19 } else {
20 RETVAL = (unsigned long) 0;
21 }
22 }
23 OUTPUT:
24 RETVAL
|
Einfacher geht es mit dem Inline-Modul
Dieses XS-Sprachengemisch ist nun nicht jedermanns Sache und deswegen hat der etwas exzentrische Brian Ingerson (auf jeder Perl-Konferenz leicht an der Anzahl seiner Tatoos zu erkennen) schon vor einiger Zeit »Inline.pm« auf den Weg gebracht, ein Perl-Modul, mit dem man einfach C-Programme in Perl-Skripte einbinden kann. Mit der CPAN-Shell ist es schnell installiert.
Listing 2 zeigt, wie ein scheinbar hinter dem »__END__« des Perl-Programms abgekoppeltes C-Codestück die »sysinfo()«-Funktion nutzt, um das »uptime«-Feld der »sysinfo«-Struktur zu extrahieren und einen »long«-Wert, also die Anzahl der seit dem System-Boot verstrichenen Sekunden, zurückliefert. Im Fall eines Fehlers kommt der »long«-Wert »-1L« zurück.
Zeile 9 in Listing Listing 2 zieht das Inline-Modul mit dem Parameter »”C”« herein, um ihm zu signalisieren, dass hinter dem »__END__« des Programms noch eine »__C__«-Sektion steht. Sie enthält ein C-Programm, dessen kompilierte Version vom Skript genutzt wird. Das Skript selbst muss dann nur in Zeile 11 die Funktion »uptime()« aufrufen, schon kommt eine Sekundenzahl zurück. »Normalize_DHMS()« aus dem altbekannten »Date::Calc«-Modul von Steffen Beyer wandelt sie in Tage, Stunden, Minuten und Sekunden um. Hexerei? Keineswegs.
|
Listing 2: |
|---|
01 #!/usr/bin/perl
02 ###################################################
03 # uptime - determine Linux' uptime
04 # Mike Schilli, 2002 (m@perlmeister.com)
05 ###################################################
06 use warnings;
07 use strict;
08 use Date::Calc qw(Normalize_DHMS);
09 use Inline "C";
10
11 my $secs = uptime();
12
13 my ($d,$h,$m,$s) = Normalize_DHMS(0, 0, 0, $secs);
14
15 printf "Uptime: $d days $h hours $m mins $s secsn";
16
17 __END__
18 __C__
19
20 #include <sys/sysinfo.h>
21
22 long uptime() {
23 struct sysinfo si;
24
25 if(sysinfo(&si) == 0) {
26 return si.uptime;
27 } else {
28 return -1L;
29 }
30 }
|
In Listing 4 ist die Implementierung zu sehen. Zeile 27 erzeugt mit »newHV()« einen neuen leeren Hash und macht ihn mit »sv_2mortal()« auch gleich sterblich. Falls der »sysinfo«-Aufruf in Zeile 29 einen Fehler zurückliefert, gibt die Zeile 30 über einen Pointer auf die vordefinierte Variable »PL_ sv_undef« den Wert »undef« in die Perl-Welt zurück. Falls dagegen alles wie gewünscht klappt, pumpen die »hv_store()«-Aufrufe ab Zeile 33 neue Paare aus Schlüssel und Wert in den Hash.
Auf jeden Schlüsselstring folgen dessen Länge, ein neuer Skalar und der Wert »0«. Damit kann Perl den internen Hashwert des Eintrags selbst ausrechnen. Hier muss man allerdings gut aufpassen: Die Werte des Hashs dürfen nicht mit »sv_2mortal()« behandelt werden, da sie auch in der Perl-Welt noch existieren müssen. Der Garbage Collector wird sie aber automatisch wegputzen, sobald der Hash seine Existenz beendet und nicht noch andere Referenzen auf die Einträge zeigen. Auch die an das C-Interface übergebenen Parameter erfasst Inline automatisch, sofern es sich um C-Standardtypen handelt. Deren Zuordnungen zu entsprechenden Perl-Typen sind in der Perl beiliegenden so genannten »typemap«-Datei aufgeführt.
In Listing 5 zapfen wir die Linux-Systemfunktion »setitimer« an, die dafür sorgt, dass der laufende Prozess nach einer bestimmten Zeit und dann in ebenfalls einstellbaren Abständen das Signal »SIGVTALRM« erhält. Es fängt das Skript danach in einem Perl-Signalhandler ab und ermöglicht es somit, den Prozess selbst dann noch zu kontrollieren, falls er sich in einer Endlosschleife festgefressen hat.
Wie die Manualseite verrät, hört »setitimer« auf die Signatur:
int setitimer(
int which,
const struct itimerval *value,
struct itimerval *ovalue);
Für »which« wählen wir den in »sys /time.h« festgelegten Wert »ITIMER_ VIRTUAL«, um nur die vom Prozess selbst verbratene Zeit zu zählen.
|
Listing 4: |
|---|
01 #!/usr/bin/perl
02 ###########################################
03 # sysinfo - Show Linux System Statistics
04 # Mike Schilli, 2002 (m@perlmeister.com)
05 ###########################################
06 use warnings;
07 use strict;
08 use Inline "C";
09
10 my $h = sysinfo_as_hashref();
11
12 print "Uptime: $h->{uptime} secsn";
13 print "Load: $h->{load1}, $h->{load5}, " .
14 "$h->{load15}n";
15 print "$h->{procs} processes runningn";
16
17 __END__
18 __C__
19
20 #include <sys/sysinfo.h>
21
22 HV *sysinfo_as_hashref() {
23 Inline_Stack_Vars;
24 struct sysinfo si;
25 HV *hash;
26
27 hash = (HV*) sv_2mortal((SV*)newHV());
28
29 if(sysinfo (&si)) {
30 return &PL_sv_undef;
31 }
32
33 hv_store(hash, "uptime", 6,
34 newSViv(si.uptime), 0);
35 hv_store(hash, "load1", 5,
36 newSViv(si.loads[0]), 0);
37 hv_store(hash, "load5", 5,
38 newSViv(si.loads[1]), 0);
39 hv_store(hash, "load15", 6,
40 newSViv(si.loads[2]), 0);
41 hv_store(hash, "procs", 5,
42 newSViv(si.procs), 0);
43
44 return hash;
45 }
|
|
Listing 5: |
|---|
01 #!/usr/bin/perl
02 ###########################################
03 # Interrupt a busy process in intervals
04 # Mike Schilli, 2002 (m@perlmeister.com)
05 ###########################################
06 use warnings;
07 use strict;
08
09 use Inline "C";
10
11 $SIG{VTALRM} = sub {
12 print "Just checking!n";
13 };
14
15 interval_set(10, 0, 0, 250000);
16
17 while(1) {
18 # Crunch, crunch!
19 }
20
21 __END__
22 __C__
23
24 #include <sys/time.h>
25
26 /* ##################################### */
27 void interval_set(
28 long secs, long usecs,
29 long isecs, long iusecs
30 ){
31 /* ##################################### */
32 struct itimerval timer;
33
34 timer.it_value.tv_sec = secs;
35 timer.it_value.tv_usec = usecs;
36 timer.it_interval.tv_sec = isecs;
37 timer.it_interval.tv_usec = iusecs;
38
39 setitimer (ITIMER_VIRTUAL,
40 &timer, NULL);
41 }
|
Komplexe Parameter
Der zweite Parameter »value« enthält zwei Einträge: »it_interval« und »it_value«. Beide sind Strukturen vom Typ »timeval«, die jeweils Sekunden- und Mikrosekundenwerte als »long«-Werte enthalten. »it_ interval« gibt an, nach welcher Zeit der Timer das Signal erstmals zu schicken hat, und »it_value«, in welchen Zeitabständen das regelmäßig nach dem ersten Mal erfolgen soll. Der dritte Parameter von »setitimer()« – »ovalue« – dient zum Auslesen des bisher festgesetzten Werts und interessiert hier nicht weiter. Er wird im Listing einfach auf »NULL« gesetzt.
Die über Inline von C nach Perl importierte Funktion »interval_set()« erwartet der Einfachheit halber keinen Strukturen-Wirrwarr, sondern schlicht vier »long«-Werte: Ein Wertepaar mit je einem Wert für Sekunden und Mikrosekunden, das für das erste Intervall zuständig ist, und ein weiteres Paar für die folgenden Intervalle. Der Aufruf
interval_set(10, 0, 0, 250000);
lässt den Prozess also zunächst zehn Sekunden und null Mikrosekunden lang laufen, bevor das erste Signal kommt, und sendet ab dann jede Viertelsekunde ein neues. Der ab Zeile 11 in Listing 5 definierte Signalhandler fängt die Signale ab, verhindert damit den sofortigen Programmabbruch und gibt nur eine kurze Meldung aus.
Die ab Zeile 17 startende Endlosschleife lässt die CPU mehr oder weniger heulend Kreise drehen, verhindert aber nicht, dass regelmäßig der Signalhandler angesprungen wird. »setitimer« und andere exotische System-Calls sind übrigens schön in[6] beschrieben.
Mit XS oder Inline lassen sich selbst die kompliziertesten Perl-C-Anbindungen bewerkstelligen. Strukturen, Arrays, Objekte, sogar C++-Bibliotheken – alles kein Problem. Hier nicht zur Sprache kam das Werkzeug Swig[2]. Es handelt sich dabei um ein Werkzeug zur automatischen Generierung von Wrapper-Code um C- und C++-Funktionen für alle möglichen Skriptsprachen wie Perl, Python, Ruby, ja sogar für Java.
Wer sich mit Perl-C-Verbindungen herumschlagen muss, wird etwas Literatur zu schätzen wissen. Das leider nicht ganz einfach zu lesende Standardwerk von Tim Jennes und Simon Cozen[3] zeigt auch Lösungen zu den verzwicktesten Problemen. Hingegen bietet das Perl-Modul-Buch von Sam Tregar[4] eine knappe, aber nützliche Einführung in die Thematik. Auch die Manualseiten[5] sind eine wichtige Informationsquelle. Sie geben Einblick in die C-Datenstrukturen, die man kennen muss, wenn man zwischen den Welten wandert. Nur keine Angst! (uwo)
|
Infos |
|---|
|
[1] Listings zu diesem Artikel: [ftp://www.linux-magazin.de/pub/listings/magazin/2003/02/Perl] [2] Universeller Skriptsprachen-Wrapper Swig: [http://www.swig.org/] [3] “Extending and Embedding Perl”, Tim Jenness, Simon Cozens, Manning, 2002 [4] “Writing Perl Modules for CPAN”, Sam Tregar, Apress, 2002 [5] Perl-Manualseiten: Inline::C-Cookbook, perlapi, perlguts, perlintern, perlxs, perlxstut, Dokumentation der Perl-C-Schnittstelle [6] “Advanced Linux Programming”, Mark Mitchell, Jeffrey Oldham, Alex Samuel, New Rider, 2002 |
|
Der |
|---|
|
Michael Schilli arbeitet als Web-Engineer für AOL/Netscape in Mountain View, Kalifornien. Er hat “Goto Perl 5” (deutsch) und “Perl Power” (englisch) für Addison-Wesley geschrieben und ist unter [mschilli@perlmeister.com] zu erreichen. Seine Homepage ist [http://perlmeister.com]. |








