ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/docs/pws2004/goofed.pod
Revision: 1.8
Committed: Fri May 28 15:28:27 2004 UTC (22 years, 4 months ago) by root
Branch: MAIN
Changes since 1.7: +138 -140 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 =head1 GOOFED - Generic Object Oriented Flexible Extensible Daemon
2    
3     =head2 Abstract
4    
5 root 1.2 Perl ist eine tolle Sprache. Leider hat sie auch viele
6 root 1.8 Abhängigkeiten. Besonders bei Embedding muss man im Allgemeinen eine
7 root 1.2 ganze Perl-Installation "mitschleppen". Bei einem bestimmten Projekt
8 root 1.8 musste ich ohne externe Dateien oder andere Abhängigkeiten auskommen. Um
9     trotzdem Perl einsetzen zu können, musste alles in das Binary
10     wandern. Die Probleme und Lösungen, die ich fand, sind im Folgenden
11 root 1.2 beschrieben.
12 root 1.1
13 root 1.2 =head1 Wozu, weshalb und warum?
14    
15 root 1.8 Das Projekt, für das Goofed entstand, bestand darin, den Radius-Server
16     eines großen Internet-Providers in einigen Funktionen zu ersetzen. Da die
17 root 1.2 Anforderungen durch viele Jahre hinweg sehr speziell wurden (ein typisches
18 root 1.8 "I<Wir> setzen den Standard"-Phänomen) und die alte Codebasis neuen
19 root 1.2 Anforderungen nicht mehr gut gewachsen war, musste ein neuer Daemon her.
20    
21 root 1.8 Recht früh kam die Idee, im Kern für die komplizierten Regeln eine
22     High-Level-Skriptsprache zu verwenden und nur für das eigentliche
23 root 1.2 Radius-Paket-Management C zu verwenden.
24    
25 root 1.8 Perl schien zuerst an vielen Gründen zu scheitern: kompliziertes
26     Build-System, viele Abhängigkeiten und Dateien, keine gute Kontrolle
27     über den Speicherbedarf (Leaks wären tödlich für einen Daemon, der
28     meherre Monate durchhalten muss) und Last-not-Least stand zu befürchten,
29     daß die Server-Abteilung die Lösung sofort ablehnen würde, sobald
30     bekannt würde, daß sie Perl benutzt (typisches "Real-World - macht
31     keinen Sinn"-Phänomen).
32 root 1.2
33 root 1.3 =head2 Eckpunkte der Implementierung
34 root 1.1
35 root 1.3 Trotz dieser Gegenanzeigen versprach Perl auch wichtige Vorteile, der
36     wichtigste war das Vorhandensein einer grossen Codebasis und viele
37 root 1.8 Module. Das und Perl selbst führen meist sehr schnell zu verwertbaren
38 root 1.3 Ergebnissen.
39    
40 root 1.8 Um die Anforderungen zu Erfüllen, habe ich folgende Entscheidungen
41     gefällt:
42 root 1.3
43     =over 4
44    
45     =item einzelnes Binary
46    
47     Bis auf die Konfigurationsdateien sollte alles - Perl-Interpreter, Module,
48     Bibliotheksdateien - in einer einzigen Datei sein und keine externen
49 root 1.8 Dateien benötigen oder anlegen.
50 root 1.3
51 root 1.8 Dies schließt leider das (ansonsten hervorragende) PAR-Modul aus, da
52 root 1.3 dieses alle Bibliotheksdateien zwar in eine Datei packt, zur Laufzeit
53     diese aber lediglich entpackt und entsprechend viele Dateien schreibt.
54    
55     =item festgelegte Perl-Version
56    
57     Die Perl-Version sollte hartverdrahtet werden. Der Grund liegt in vielen,
58 root 1.8 kleinen Änderungen auch innerhalb stabiler Release-Zyklen, die zu
59     Build-Problemen oder Fehler zur Laufzeit führen können. I<Never change a
60 root 1.3 running system> war hier ausschlaggebend.
61    
62 root 1.8 Ausserdem kann man, sobald man die Kontrolle über dei Perl-Quellen
63 root 1.3 hat, bedenkenlos Anpassungen vornehmen (ich nenne das die "Lizenz zum
64     Rumsauen"-Regel).
65    
66 root 1.8 Es sollte möglich sein, den Perl-Kern upzugraden, wobei aber klar sein
67     sollte, das dies Veränderungen am Quellcode nach sich zieht.
68 root 1.3
69     =item festgelegte Module und festgelegte Versionen
70    
71 root 1.8 Ähnliches gilt für Module: es sollte möglich sein, fast beliebige
72 root 1.3 Module einzubauen, aber auch diese sollten in einer festen Version kommen.
73    
74     Perl-Module auf CPAN haben - seien wir ehrlich - fast immer kleine und
75 root 1.8 große Bugs. Meistens kann man mit ihnen leben oder Workarounds finden
76     - häufig muss man dazu auf Interna zugreifen, die sich von Version zu
77     Version ändern.
78 root 1.3
79     =item "normales" Build-System, kein Configure
80    
81 root 1.8 Configure nichtinteraktiv auszuführen ist kompliziert, und sehr viel kann
82     schiefgehen. Ein einfaches Makefile fühlt sich wesentlich stabiler und
83 root 1.3 einfacher wartbar an.
84    
85     =back
86    
87 root 1.8 Da der Daemon sehr viele Anfragen erhält und in anderer Form
88     weiterleitet, wäre es schön, wenn man die Bearbeitung der Anfragen
89     verzahnen könnte. Dazu braucht man ein vernünftiges Event-System (der
90 root 1.3 Name sagts schon - das Event-Modul) und etwas, mit dem man effizient
91 root 1.8 und vor allem einfach Pseudoparallelität erreichen kann - Coroutinen,
92 root 1.3 implementiert mit dem Coro-Modul.
93    
94     Coro greift auf viele Perl-Interna zu und ist eigentlich recht
95     experimentell, ich hatte aber Erfahrungen mit langlaufenden Servern und
96 root 1.8 Coro und wußte daher, das es sehr stabil läuft, sobald es läuft.
97 root 1.3
98 root 1.8 Daß das Umfeld stabil ist, gab den Ausschlag.
99 root 1.3
100     =head1 Implementierung
101    
102     Nachdem ein paar grundlegende Gedanken gedacht waren, konnte ich an die
103 root 1.8 Implementierung gehen. Flexibilität ist alles, wenn etwas schiefgeht.
104 root 1.1
105     =head2 Perl? .... Microperl!
106 root 1.3
107 root 1.8 Wenig bekannt ist, daß es auch eine Microperl-"Distribution" gibt,
108 root 1.3 die ein sehr kleines und portables Perl verspricht - ohne Module, ohne
109 root 1.8 Systemabhängigkeiten und ohne interaktives Configure.
110 root 1.3
111     Die "Distribution" ist sehr klein: sie besteht nur aus den drei Dateien
112     C<README.micro>, C<Makefile.micro> und C<uconfig.sh>. Daher ist bei Perl
113     gleich mit dabei.
114    
115 root 1.8 Ich habe das Makefile und die nötigen Perl-Sourcen in ein
116     Unterverzeichnis kopiert und dann die Konfiguration, die standardmäßig
117     nur ISO-C89 + einige wenige Erweiterungen wie C<rename()> benötigt,
118 root 1.3 solange getuned, bis ich Zugriff auf die meisten POSIX-Funktionen hatte.
119    
120     Dazu habe ich die C<CFLAGS> von:
121    
122     -DPERL_CORE -DPERL_MICRO -DSTANDARD_C -DPERL_USE_SAFE_PUTENV
123    
124     auf
125    
126     -DPERL_CORE=1 -DPERL_MICRO=1
127    
128 root 1.8 geändert und die Konfiguration in C<uconfig.sh> getuned, compiliert und
129 root 1.3 solange iteriert, bis ich zum Link-Stadium kam.
130    
131     Danach hatte ich folgende Dateien in meinem C<uperl>-Verzeichnis:
132    
133     embed.h mg.h perlio.c proto.h thrdvar.h
134     EXTERN.h embedvar.h miniperlmain.c perlio.h reentr.c thread.h
135     INTERN.h ext.libs nostdio.h perliol.h reentr.h toke.c
136     Makefile form.h numeric.c perlsdio.h reentr.inc uconfig.sh
137     XSUB.h globals.c op.c perlvars.h regcomp.c universal.c
138     av.c gv.c op.h perly.c regcomp.h unixish.h
139     av.h gv.h opcode.h perly.h regexec.c utf8.c
140     config_h.SH handy.h opnames.h pp.c regexp.h utf8.h
141     configpm hv.c pad.c pp.h regnodes.h util.c
142     cop.h hv.h pad.h pp_ctl.c run.c util.h
143     cv.h intrpvar.h patchlevel.h pp_hot.c scope.c warnings.h
144     deb.c iperlsys.h perl.c pp_pack.c scope.h xsutils.c
145     doio.c keywords.h perl.h pp_proto.h sv.c
146     doop.c locale.c perlapi.c pp_sort.c sv.h
147     dump.c mg.c perlapi.h pp_sys.c taint.c
148    
149 root 1.8 Beim Bauen bekommt man die C<libuperl.a>, das Microperl-Äquivalent
150     zur C<libperl> und C<microperl>, das zum Bauen benötigt wird und auch
151 root 1.6 beim weiteren Build-Prozess hilfreich sein kann. C<microperl> wird z.B.
152     benutzt, um aus der C<uconfig.sh> das C<Config.pm>-Modul zu basteln.
153 root 1.3
154 root 1.6 Mit C<microperl>, C<libuperl.a>, C<Config.pm> und den Header-Dateien hat
155 root 1.8 man alles, was man zum Embedden benötigt.
156 root 1.3
157 root 1.8 Dieser Schritt war relativ leicht. Er wäre noch leichter gewesen, wenn
158 root 1.6 ich auf die meisten esoterischen Funktionen wie C<getpwnam>, C<mkdir>
159 root 1.8 oder Signal-Handling verzichtet hätte, da ich fast alles über die
160 root 1.6 C<POSIX>- und C<Event>-Module erreichen kann, und im Notfall auch auf C
161 root 1.8 zurückgreifen könnte. Im absoluten Notfall.
162 root 1.1
163 root 1.7 =head2 Bibliothekdateien
164 root 1.4
165 root 1.8 Im nächsten Schritt geht es um die Bilbiotheksdateien, genaugenommen das
166 root 1.6 I<lib>-Verzeichnis, in dem sich die C<.pm>-Dateien befinden. Theoretisch
167 root 1.8 könnte man Microperl ganz ohne dieses betreiben: das Ergebnis
168     wäre jedoch kaum noch "Perl" zu nennen, da auch Pragmas als Module
169 root 1.6 implementiert sind. Und wie man an anderen, einfacher embeddbaren Sprachen
170     sieht, ist Perl ohne Standardbibliothek ist eine nutzlosere embeddete
171     Sprache, als man denkt.
172    
173 root 1.8 Üblicherweise sind die C<.pm>-Dateien in den jeweiligen
174 root 1.6 Modul-Distributionen, aber bei Perl sind sie schon fertig abgepackt im
175     C<lib>-Verzeichnis.
176    
177 root 1.8 Das größte Problem war es, alle Dateien und Unterverzeichnisse in CVS
178 root 1.6 einzuchecken, nachdem ich sie aus der Perl-Distribution kopiert hatte
179     (C<cp -rp ~/../perl-5.8.x/lib lib-perl>).
180 root 1.4
181 root 1.8 Fragt sich noch, wie die Dateien in das Executable kommen. Dafür sorgt
182 root 1.7 folgende Regel im C<Makefile>:
183    
184     UPERL = uperl/microperl -Ilib-perl
185    
186     src/libfiles.c: s/gen_libfiles extensions uperl/microperl
187     $(UPERL) s/gen_libfiles >$@
188    
189 root 1.8 Die sorgt dafür, daß das Perl-Skript C<s/gen_libfiles> aus allen Dateien
190     die Datei C<src/libfiles.c> generiert (C<s> heißen bei mir Verzeichnisse
191 root 1.7 mit Skripten, eine alte Amiga-Gewohnheit).
192    
193     Die erzeugte Datei sieht so aus:
194    
195     const char *embed_files[] = {
196     /* 0 */
197     "\025Attribute/Handlers.pm\000\000\030Apackage Attribute::Handlers;\01...
198     " "data eq 'ARRAY';\012\011\011\011\\$data = [ \\$data ] unless \\$wa...
199     ...
200     /* 1 */
201     "\007Carp.pm\000\000\016\017package Carp;\012\012our $VERSION = '1.01'...
202     ...
203     ...
204     ,
205     };
206    
207     const int embed_file_num = 208;
208    
209 root 1.8 Die Datei ist 1139622 Bytes groß und mußte hier leider zweidimensional
210     gekürzt werden. Wie man sofort sieht, folgt sie C99 und nicht C89, aber
211 root 1.7 das habe ich in Kauf genommen.
212    
213 root 1.8 Was man nicht sofort sieht: Alle Dateien werden in einem großen
214     String-Feld gespeichert, pro Datei ein String. Am Anfang steht die Länge
215     des Dateinamens, der Dateiname, danach die Länge der Datei und danach
216     der Inhalt. Dieses Format habe ich gewählt, damit man die Datei schnell
217     mittels binärer Suche finden kann und nicht einmal C<strlen> aufrufen
218     muß, bzw. auch binäre Dateien ablegen kann.
219 root 1.7
220     Die Funktion zum Suchen eines Eintrages sieht so aus (etwas umformatiert,
221 root 1.8 damit sie besser paßt):
222 root 1.7
223     SV *
224     libfile(char *path)
225     PREINIT:
226     const char *s = 0, *f;
227     int c, l = 0, m, r = embed_file_num;
228     CODE:
229     do
230     {
231     m = (l + r) >> 1;
232     f = embed_files[m];
233     c = strncmp (f + 1, path, *f);
234    
235     if (c > 0) r = m - 1;
236     else if (c < 0) l = m + 1;
237     else
238     {
239     s = f + 1 + f[0];
240     l = ((unsigned char)s[0] << 24) | ((unsigned char)s[1] << 16)
241     | ((unsigned char)s[2] << 8) | ((unsigned char)s[3] );
242     s += 4;
243     break;
244     }
245     }
246     while (l <= r);
247    
248     RETVAL = s ? newSVpvn (s, l) : &PL_sv_undef;
249     OUTPUT:
250     RETVAL
251    
252 root 1.8 Sie implementiert eine ganz normale binäre Suche und liefert den Inhalt
253     der Datei als String, oder bei nichtfinden C<undef>. Ursprünglich
254 root 1.7 habe ich eine lineare Suche benutzt (bei Rapid Prototyping bleibt
255     manchmal etwas auf der Strecke, wenn man interessantere Stadien der
256 root 1.8 Entwicklung erreichen will und eine binäre Suche auch nach Jahrzehnten
257 root 1.7 Programmiererfahrung nicht auf Anhieb hinkriegt) und erwartet, das es
258 root 1.8 keinen Unterschied macht. Die binäre Suche macht das Ergebnis dennoch
259 root 1.7 einige Prozent schneller beim Starten, also scheint Perl beim Parsen recht
260     flott zu sein.
261    
262 root 1.8 Das Erzeugen der C-Strings aus den Dateien hat sich übrigens als weit
263     komplizierter herausgestellt, als ich dachte. Zum einen muß man solche
264     nervigen Sachen wie die maximale Zeilenlänge (4096) beachten, zum anderen
265 root 1.7 ist das Quoting garnicht so einfach. Folgende Funktion quoted den String:
266    
267     # print a c-escaped string
268     sub str {
269     my $str = $_[0];
270     while (length $str) {
271     local $_ = substr $str, 0, 2000, ""; # 4096 is ISO-C max.
272     s/\\/\\\\/g;
273     s/([^\x20-\x7e])/sprintf "\\%03o", ord $1/ge;
274     s/"/\\"/g;
275     s/\\x0a/\\n/g;
276     print " \"$_\"\n";
277     }
278     print " ,\n";
279     }
280    
281 root 1.8 Das andere Problem war, daß ich die Unmengen an Dokumentation nicht
282     unbedingt im fertigen Programm brauche. Die Lösung ist C<Pod::Stripper>,
283 root 1.7 das sich der Benutzung allerdings widersetzt, da es unbedingt einen
284 root 1.8 FileHandle möchte und String-Streams in Microperl nicht zur Verfügung
285 root 1.7 stehen:
286    
287     open my $ifh, "<", "$base/$path$_"
288     or die "$base/$path$_: $!";
289    
290     pipe my ($r, $w);
291     if (fork == 0) {
292     close $r;
293     Pod::Stripper->new->parse_from_filehandle ($ifh, $w);
294     exit;
295     } else {
296     close $w;
297     }
298    
299     local $/;
300     $file{"$path$_"} = <$r>;
301    
302     Ein C<fork()> pro Datei und Parsen ist zwar nicht kostenlos, aber ich baue
303     ja nicht unter Cygwin. Auf meinem Rechner braucht es 3,5s, was leider
304     etwas lang ist, da ich schlecht eine Makefile-Regel bauen kann, die von
305 root 1.8 allen Dateien in C<lib-perl> abhängt und daher jedesmal das Skript
306 root 1.7 aufrufe, wenn ich linke.
307    
308 root 1.1 =head2 Module Embedden
309    
310 root 1.8 Für C<goofed> brauche ich im Moment folgende Module:
311 root 1.7
312     Compress-LZF Coro-Event Coro-State Crypt-Rijndael Crypt-Twofish
313     Cwd Data Digest Event Fcntl File Filter IO List MIME POSIX PerlIO
314     Pod-Stripper Socket Spread Sys Time attrs daemon re
315    
316     Header und Library sind zwar genug, um diese zu bauen, aber
317 root 1.8 Perl-Module wollen ein lauffähiges Perl und Zugriff auf Kleinkram wie
318     C<ExtUtils::MakeMaker>, damit man sie übersetzen kann.
319 root 1.7
320 root 1.8 Zum Glück bietet C<ExtUtils::MakeMaker> extra Unterstützung für diesen
321     Fall. Das Extension-C<Makefile.PL> führe ich so aus:
322 root 1.7
323     $TOP/uperl/microperl -I$TOP/lib-perl Makefile.PL INSTALLDIRS=perl \
324     PERL_CORE=1 PERL_SRC=$TOP/uperl PERL_LIB=$TOP/lib-perl
325    
326     Anscheinend sind C<PERL_CORE=1> und C<SRC=> der Trick. Perl machts beim
327 root 1.8 Übersetzen genauso und es funktioniert, mehr Gedanken habe ich mir nicht
328 root 1.7 gemacht.
329    
330 root 1.8 Zum Übersetzen verwende ich folgenden C<make>-Aufruf:
331 root 1.7
332     make CC=$(CC) OPTIMIZE=$(OPTIMIZE) LINKTYPE=static CCCDLFLAGS= static
333    
334     Das und noch mehr ist in einem Skript namens C<s/make_ext> verewigt, das
335     nach dem Perl-Vorbild C<ext/util/make_ext> geschrieben wurde. Es macht
336 root 1.8 wenig mehr als die beiden Kommandos auszuführen: es baut automatisch
337 root 1.7 das C<Makefile>, falls es keines gibt und akzeptiert z.B. C<clean> als
338     Argument. Nichts, was man mit einem gescheiten C<Makefile> nicht sauberer
339 root 1.8 lösen könnte.
340 root 1.7
341 root 1.8 Dies baut für jede Extension, die XS benutzt, eine C<libextension.a>,
342 root 1.7 gegen die man linken muss.
343    
344     =head3 PM-Dateien von Erweiterungen
345    
346     Erweiterungen besitzen fast immer auch eigene C<.pm>-Dateien, die in das
347 root 1.8 fertige Executable gehören. Mein (nicht ganz perfekter) Weg, dies zu
348 root 1.7 erreichen war, C<make install> in den fertig gebauten Erweiterungsmodulen
349     zu machen. Dies installiert die C<.pm>-Dateien (und eventuell andere
350     Bibliotheksdateien) im C<lib-perl>-Verzeichnis, wo sie beim Bauen
351     automatisch gefunden werden. Ich darf dann blos nicht vergessen, sie ins
352     CVS einzuchecken.
353    
354     =head2 Linken und das Hauptprogramms
355    
356     Das Linken geht relativ einfach:
357    
358     goofed: src/main.o src/xsinit.o src/libfiles.o extensions
359     $(CC) $(CFLAGS) -o $@ src/xsinit.o \
360     `find lib-perl/auto -name '*.a' -print` \
361     src/main.o src/libfiles.o uperl/libuperl.a $(LIBS)
362    
363     Es werden einfach alle statischen Libraries, C<libuperl.a> und die
364 root 1.8 übersetzten Dateien C<libfiles.c>, C<xsinit.c> und C<main.c> zusammengelinkt.
365 root 1.7
366 root 1.8 C<main.c> enthält das Hauptprogramm. Es macht im wesentlichen
367 root 1.7 das gleiche, das auch Perl macht (hier ohne error-checking, das
368 root 1.8 Dokument C<perlembed> enthält genauere Erläuterungen zum Embedding
369 root 1.7 allgemein). Darin sind zwei wichtige Aufrufe:
370    
371     perl_parse (my_perl, xs_init, EMBED_ARGC, EMBED_ARGV, (char **) NULL);
372     perl_run (my_perl);
373    
374     C<perl_parse> parsed die Kommandozeilenargumente, genau wie C<perl>, und
375     initialisiert die eingelinkten Erweiterungsmodule.
376    
377 root 1.8 Üblicherweise (d.h. heutzutage) werden Erweiterungen als Shared-Objects
378 root 1.7 gebaut und dynamisch (mit C<DynaLoader> oder C<XSLoader>) geladen. Der
379     C<DynaLoader> generiert aus dem Package-Namen einen Dateinamen und
380 root 1.8 versucht, diese Datei als Shared-Object zu öffnen. Danach sucht es darin
381 root 1.7 eine Funktion namens C<boot_>Name, z.B. C<boot_Data__Dumper>), registriert
382 root 1.8 sie bei Perl und führt sie aus.
383 root 1.7
384     Diese Funktion registriert alle XS-Funktionen als Perl-Funktionen und
385 root 1.8 führt den C<BOOT:>-Abschnitt aus.
386 root 1.7
387     Eine statisch eingelinkte Erweiterung kann nicht dynamisch nach einem
388     Namen durchsicht werden, daher muss man die Funktionen "per Hand"
389     bei Perl registrieren (das Aufrufen geschieht erst beim Laden des
390     Perl-Moduls). Dies erledigt die C<xs_init>-Funktion in der Datei
391     C<xsinit.c>.
392    
393     Diese Datei kann man sich mit Hilfe des C<ExtUtils::Embed>-Moduls
394 root 1.8 schreiben lassen (siehe C<perlembed>), ich hatte aber wenig Glück,
395 root 1.7 dies automatisch zu tun (manche Module waren doppelt, andree haben
396     gefehlt). Seither Pflege ich sie per Hand. Die Datei sieht in etwa so aus:
397    
398     #include "EXTERN.h"
399     #define PERL_IN_MINIPERLMAIN_C
400     #include "perl.h"
401    
402     EXTERN_C void boot_Compress__LZF (pTHX_ CV *cv);
403     ...
404     EXTERN_C void boot_Spread (pTHX_ CV *cv);
405     EXTERN_C void boot_daemon (pTHX_ CV *cv);
406    
407     EXTERN_C void
408     xs_init (pTHX)
409     {
410     char *file = __FILE__;
411    
412     newXS ("daemon::bootstrap", boot_daemon, file);
413     newXS ("Spread::bootstrap", boot_Spread, file);
414     ...
415     newXS ("Compress::LZF::bootstrap", boot_Compress__LZF, file);
416     }
417    
418     Wenn das Parsing vorbei ist, baue ich noch C<@ARGV> auf - mein Aufruf von
419     C<perl_parse> benutzt ja nicht die echten Kommandozeilenargumente - und
420 root 1.8 rufe C<perl_run> auf, das die Kontrolle endgültig an Perl abgibt.
421 root 1.7
422     =head2 Bootstrapping von Perl
423    
424 root 1.8 Welches sind nun die Argumente für den Perl-Interpreter?
425 root 1.7
426     /* this array contains the initial startup code for the perl interpreter */
427     char *embed_argv[EMBED_ARGC] = {
428     "goofed",
429     "-e",
430     "\n"
431     " daemon->bootstrap (0);\n"
432     ... weiterer Perl-Code ...
433     };
434    
435     Das erste Argument ist der Name des Programms. Danach kommt eine
436     C<-e>-Option mit dem Startprogramm. Es sieht so aus:
437    
438     daemon->bootstrap (0);
439     PerlIO::scalar->bootstrap (0);
440     @INC = sub {
441     open my $fh, '<', \(daemon::libfile ($_[1]))
442     or die "FATAL: loader can't in-memory stream\n";
443     $fh;
444     };
445     require daemon;
446     daemon->init;
447    
448     Die ersten beiden Zeilen initialisieren die beiden Module C<daemon> (ein
449 root 1.8 Modul, in das ich die meisten Spezial-C-Funktionen für C<goofed> gepackt
450 root 1.7 habe) und (ausgerechnet!) C<PerlIO::scalar>.
451    
452 root 1.8 Ersteres enthält die C<libfile>-Funktion, mit dem ich die Perl-Sourcen
453     bekomme und letzteres wird von Perl benötigt um Memory-Streams (C<< open
454 root 1.7 FH, "<", \$scalar >>) zu supporten. Die beiden C<bootstrap>-Funktionen
455 root 1.8 registrieren allerdings I<nur> die XS-Funktionen, die zugehörigen
456 root 1.7 Perl-Module kann man zu diesem Zeitpunkt nicht laden.
457    
458 root 1.8 Um das zu ermöglichen, überschreibe ich C<@INC> mit einer
459 root 1.7 Funktion. Versucht Perl nun eine Datei zu laden durchsucht es C<@INC>,
460 root 1.8 findet die Funktion und führt sie aus. Diese holt sich die entsprechende
461     Datei und gibt einen Filehandle zurück. Perl liest und parsed sie dann.
462 root 1.7
463     Danach kann man die Perl-Module ganz normal mit C<use> oder C<require>
464     laden, was auch prompt gemacht wird, indem das C<daemon>-Modul geladen und
465 root 1.8 dessen C<init>-Funktion ausgeführt wird.
466 root 1.7
467     An diesem Punkt hat man ein Perl, in das man relativ einfach CPAN-Module
468 root 1.8 einbinden kann und keinerlei Abhängigkeiten im Filesystem besitzt,
469     schnell bootstrapped, support für binäre Objekte Bilder etc.) hat und
470     die gesamte Power von Perl zur Verfügung stellt.
471 root 1.7
472     =head1 Benutzung
473    
474     Nachdem ein solcher Aufwand getrieben wurde, wollte ich noch die Frage
475 root 1.8 klären, ob es sich gelohnt, Perl statt C zu benutzen.
476 root 1.7
477     Die Hauptaufgabe war es, einen Radius-Server zu bauen (wobei man mit
478 root 1.8 C<goofed> natürlich noch andere Aufgaben erfüllen kann, es dient also
479 root 1.7 eher als Anwendungsplattform :)
480    
481     Das Parsen von Radius-Paketen ist eine Byte-Pfriemelei, braucht aber sehr
482     viel Konfigurationsdaten. Das komplette Modul zum Parsen und Erzeugen von
483     Radius-Paketen ist (inklusive reichlich Dokumentation) 307 Zeilen lang.
484 root 1.8 Ähnlicher Code im I<Merit-Radiusd> besteht aus ca. 8000 Zeilen C. Ohne
485 root 1.7 Dokumentation.
486    
487     Allein hier hat sich Perl gelohnt. Richtig interessant wird es allerdings
488 root 1.8 bei den speziellen Anforderungen: Eine Authentifizierung benötigt ca.
489 root 1.7 zehn LDAP-Anfragen pro Request. Im alten Radius-Daemon waren diese
490 root 1.8 blockierend und haben den Großteil der Realzeit verbraucht.
491 root 1.7
492     In C<goofed> sehen sie zuerst auch blockierend aus:
493    
494     my $ldap_user = $self->{ldap}->search ("uuRadiusUser=$user, uuRadiusRealm=$realm, $ldap_root")
495     or return $self->nak ("user unknown");
496     ... tue etwas
497     my $ldap_profile = $self->{ldap}->search ("uuRadiusProfile=$profile, $ldap_root");
498     ... tue noch mehr
499     $self->{ldap}->search ("uuRadiusNAS=$nas_ip, uuRadiusRealm=$realm, $ldap_root")
500     or return $self->nak ("roaming not allowed");
501     ... usw.
502    
503     Auch hier wird eine Anfrage gebastelt und auf das Ergebnis gewartet. Der
504     Unterschied ist, das die Anfrage asynchron verschickt wird und in der
505 root 1.8 gleichen Zeit andere Radius-Pakete entgegengenommen werden können und
506     Anfragen gestellt bzw. verarbeitet werden können.
507 root 1.7
508     Da das LDAP-Protokoll selbst asynchron ist, besteht bei einem entsprechend
509 root 1.8 intelligenten LDAP-Server (ich kenne keinen) sogar die Möglichkeit,
510     einfache Anfragen sofort zu beantworten während komplizierte im
511 root 1.7 Hintergrund erledigt werden.
512    
513     Das LDAP-Modul verwendet nicht das C<Net:LDAP>-Modul von CPAN, da ich
514     dieses nicht stabil mit OpenLDAP zusammenbringen konnte, bzw. es sich
515     extrem gegen Event-basierte Programmierung gewehrt hat. Es benutzt
516     stattdessen direkt die C<libldap> von C<OpenLDAP> (durch Xs-Funktionen in
517     C<daemon.xs> implementiert).
518    
519     Das erste, was es macht, ist, eine Verbindung zum LDAP-Server aufzumachen
520     und einen Event-Handler darauf zu binden:
521    
522     $self->{ldap} = new ldap::conn $self->{host}, $self->{port} || 389
523     or die "unable to connect to ldap server $self->{host}:$self->{port}: $!\n";
524     ...
525     $self->{w} = Event->io (
526     fd => $self->{ldap}->fd,
527     poll => 'r',
528     cb => [$self, "_recvresp"],
529     );
530    
531     Normalerweise ist es mein Stil, Callback-Funktionen als anonyme
532     Sub-Funktionen auszulegen. Nur leider gibt dies mit aktuellen
533     Coro-Versionen manchmal memory-leaks, die ich nichtmla mit valgrind finde.
534    
535     Die C<_recvresp>-Methode holt einen Ergebnis-Record vom Server ab, baut
536     bei Abbruch die Verbindung neu auf, und ruft den entsprechenden Callback
537 root 1.8 für die ursprüngliche Anfrage auf.
538 root 1.7
539     Anfragen sind etwas aufwendiger: Einerseits mag es der LDAP-Server nicht,
540     wenn zu viele Anfragen gleichzeitig ausstehen (warum, weiss ich nicht,
541     aber da wenige Anwendungen asynchron Anfragen stellen, ist dies vielleicht
542 root 1.8 nicht gut getestet): es treten Fehler auf und sie müssen wiederholt
543 root 1.7 werden; andererseits darf immer nur einer gleichzeitig eine Anfrage
544     stellen (die C-Library ist zwar I<reentrant>, nicht aber der FileHandle,
545     auf den sie schreibt).
546    
547 root 1.8 Sowohl für die globale Limitierung als auch für das gleichzeitige
548 root 1.7 schreiben verwende ich eine C<Coro::Semaphore>:
549    
550     # in sub new
551     my $self = bless {
552     host => $host,
553     lock => (new Coro::Semaphore),
554     limit => (new Coro::Semaphore 20),
555     %arg,
556     }, $class;
557    
558     Der Abfragecode macht dann:
559    
560     $self->{limit}->down; # limit # of simult. requests
561     my $sem = new Coro::Semaphore 0;
562     $self->{lock}->down; # ensure that there is only a single writer
563    
564 root 1.8 Uff, drei Semaphoren! Die erste sorgt dafür, das nur eine bestimmte
565     Anzahl von Abfragen gleichzeitig ausstehen können. Sie wird erst nach dem
566 root 1.7 Lesen des Ergebnisses wieder freigegeben.
567    
568     Die zweite Sempahore wird schon gelockt erzeugt und wird nur benutzt, um
569     auf das Ergebnis zu warten (mehr dazu gleich).
570    
571 root 1.8 Und die dritte Semaphore-Operation läßt immer nur einen gleichzeitig
572 root 1.7 Anfragen zum Server schicken.
573    
574     Dies geschieht danach: (Achtung: stark vereinfacht :)
575    
576     my $id = $self->{ldap}->search ($base);
577     # registriere Callback
578     $self->{id}{$id} = sub {
579     ... parse Ergebnis
580     $sem->up;
581     }
582    
583 root 1.8 Danach werden die Semaphoren wieder gelöst:
584 root 1.7
585     $self->{lock}->up;
586     $sem->down; # sleep till result is there
587     $self->{limit}->up;
588    
589     Dies geschieht in umgekehrter Reihenfolge freigegeben (das ist notwendig,
590     um einen Deadlock zu verhindern) und definieren den Zeitablauf:
591    
592     Zuerst wird die Schreibzugriff-Semaphore freigegeben, da die Anfrage mit
593     C<search> zum Server geschickt wurde und nun andere Anfragen schicken
594 root 1.8 dürfen.
595 root 1.7
596     Danach wird auf das Ergebnis gewartet: Die C<$sem>-Semaphore ist gelockt
597 root 1.8 und wird erst im Callback wieder gelöst (C<< $sem->up >>), wenn das
598 root 1.7 Ergebnis eingetrudelt ist.
599    
600     Zuletzt wird die Semaphore, die die maximale Anzahl ausstehender Anfragen
601 root 1.8 begrenzt, gelöst, da ja die Anfrage durchgeführt wurde und das Ergebnis
602 root 1.7 vorliegt.
603    
604     Das sind 177 Zeilen (die XS-Schnittstelle zur OpenLDAP-Library nicht
605 root 1.8 mitgezählt). Dadurch ist dir LDAP-Funktionalität abgekapselt und leicht
606 root 1.7 einzeln testbar.
607    
608 root 1.8 Der Vorteil ist, daß man den eigentlichen Authentifizerungscode sehr
609     natürlich schreiben kann, nämlich sequentiell ohne zwischendurch
610     irgendwelche Locks beachten zu müssen oder irgendwelche Zustände
611     zwischenzuspeichern, und trotzdem in den Genuss voller Parallelität von
612 root 1.7 LDAP-Server und Radiusd gelangt. In der Praxis ist deshalb der LDAP-Server
613     der Engpass bei der Authentifizierung.
614    
615     =head1 Hinweise und Verweise
616    
617 root 1.8 Quellcode für C<goofed> ist leider nicht frei erhältlich. Ich hoffe
618     aber, das man ähnliches jetzt selbst nachbauen kann.
619 root 1.7
620     Handarbeit ist nicht immer das Beste. Man kann auch einfacher in den
621 root 1.8 Genuß von einzelnen Binaries kommen. Z.B. mit dem C<PAR>-Modul (von
622 root 1.7 CPAN), das aus allen Modulen eines Programms eine Art Archiv mit
623     Perl-Lader bauen kann. Es kommt dem obigen sehr nahe, da es ebenfalls ein
624     einzelnes Binary erzeugen kann. Allerdings entpackt es zur Laufzeit die
625     Dateien und kann auch keine Bibliotheken einlinken (Shared Objects bleiben
626     Shared Objects).
627    
628     Mit C<makeaperl> aus der Perl-Distribution kann man sich ein statisches
629     Perl-Binary mit allen Modulen bauen, die man braucht. Dies beinhaltet aber
630     nicht die Perl-Quellen dazu.
631 root 1.1
632