Open Source im professionellen Einsatz
Linux-Magazin 02/2003

C-Funktionen in Perl mit dem Inline-Modul

Eingang zur Unterwelt

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.

1400

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


Abbildung 1: Die Sysinfo-Manualseite mit Sysinfo-Struktur.

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:
>>RAM.xs<<

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:
>>uptime<<

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:
>>sysinfo_hash<<

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:
>>timer<<

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 }

Linux-Magazin kaufen

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

Deutschland

Ähnliche Artikel

  • Tooltipps
  • Java entfällt in Linux Standard Base (LSB) 4.1

    Der Linux-Standard für Bibliotheken und Dateisystem-Layout verzichtet in Version 4.1 auf Java.

  • Perl und C

    Der Perl-Interpreter perl ist in C geschrieben und bietet recht komfortable Schnittstellen, um ihn mit erstaunlich schnellen Zusatzfunktionen auf Maschinenebene aufzupeppen. Allerdings profitieren nur ganz bestimmte Anwendungen tatsächlich vom Mixed Language Programming.

  • Tooltipps

    Im Kurztest: Checkraid 0.68, Cpufreppy 2014-07-02, Ddrutility 2.5, Hashrat 1.0, Linux-Dash 0.5, Multitail 6.2.1

  • Prozess-Spion

    Einem Prozess bei der Arbeit auf die Finger sehen - unter Linux erlaubt das Ptrace. Das eingebaute Tool nutzen hilfreiche Debugger wie feindselige Prozess-Kidnapper gleichermaßen. Ein CPAN-Modul führt die Technik in Perl ein, und wo das nicht reicht, helfen in C geschriebene Erweiterungen weiter.

comments powered by Disqus

Ausgabe 06/2017

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