Was ist das Besondere an Dateideskriptor 3 unter Linux?

Lesezeit: 4 Minuten

Benutzer-Avatar
Erkältung

Ich arbeite an einer Serveranwendung, die unter Linux und Mac OS X funktionieren soll. Das geht so:

  • Hauptanwendung starten
  • Verzweigung des Controller-Prozesses
  • Rufen Sie lock_down() im Controller-Prozess auf
  • Hauptanwendung beenden
  • Der Controller-Prozess verzweigt sich dann erneut und erstellt einen Arbeitsprozess
  • Schließlich verzweigt der Controller immer mehr Worker-Prozesse

Ich kann mich mit mehreren Methoden anmelden (z. B. Syslog oder eine Datei), aber im Moment denke ich über Syslog nach. Das “Lustige” ist, dass im Controller-Prozess niemals eine Syslog-Ausgabe zu sehen ist, es sei denn, ich füge den Abschnitt #ifdef unten ein.

Der Worker verarbeitet Protokolle fehlerfrei in Mac OS X und Linux mit oder ohne den Abschnitt ifdef’ed unten. Der Controller protokolliert auch fehlerfrei in Mac OS X ohne den Abschnitt #ifdef, aber unter Linux wird die ifdef benötigt, wenn ich eine Ausgabe in Syslog (oder die Protokolldatei für diese Angelegenheit) vom Controller-Prozess sehen möchte.

Also, warum ist das so?

static int
lock_down(void)
{
    struct rlimit rl;
    unsigned int n;
    int fd0;
    int fd1;
    int fd2;

    // Reset file mode mask
    umask(0);

    // change the working directory
    if ((chdir("https://stackoverflow.com/")) < 0)
        return EXIT_FAILURE;

    // close any and all open file descriptors
    if (getrlimit(RLIMIT_NOFILE, &rl))
        return EXIT_FAILURE;
    if (RLIM_INFINITY == rl.rlim_max)
        rl.rlim_max = 1024;

    for (n = 0; n < rl.rlim_max; n++) {
#ifdef __linux__        
        if (3 == n) // deep magic...
            continue;
#endif
        if (close(n) && (EBADF != errno))
            return EXIT_FAILURE;
    }

    // attach file descriptors 0, 1 and 2 to /dev/null
    fd0 = open("/dev/null", O_RDWR);
    fd1 = dup2(fd0, 1);
    fd2 = dup2(fd0, 2);
    if (0 != fd0)
        return EXIT_FAILURE;

    return EXIT_SUCCESS;
}

camh war nah dran, aber die Verwendung von closelog() war die Idee, die den Trick gemacht hat, also geht die Ehre an Jilles. Abgesehen vom Schließen eines Dateideskriptors unter Syslogs-Füßen muss jedoch noch etwas anderes passieren. Damit der Code funktioniert, habe ich kurz vor der Schleife einen Aufruf von closelog() hinzugefügt:

closelog();
for (n = 0; n < rl.rlim_max; n++) {
    if (close(n) && (EBADF != errno))
        return EXIT_FAILURE;
}

Ich habe mich auf ein wörtliches Verständnis der Handbuchseite verlassen und gesagt:

Die Verwendung von openlog() ist optional; es wird bei Bedarf automatisch von syslog() aufgerufen …

Ich habe dies so interpretiert, dass Syslog erkennen würde, ob der Dateideskriptor darunter geschlossen wurde. Anscheinend nicht. Ein explizites closelog() unter Linux war erforderlich, um Syslog mitzuteilen, dass der Deskriptor geschlossen wurde.

Eine weitere Sache, die mich immer noch verwirrt, ist, dass die Nichtverwendung von closelog() den ersten Fork-Prozess (den Controller) sogar daran hinderte, eine Protokolldatei zu öffnen und zu verwenden. Die folgenden gegabelten Prozesse könnten Syslog oder eine Protokolldatei ohne Probleme verwenden. Vielleicht gibt es im Dateisystem einen Caching-Effekt, der dazu führt, dass der erste gegabelte Prozess eine unzuverlässige “Idee” davon hat, welche Dateideskriptoren verfügbar sind, während der nächste Satz von gegabelten Prozessen ausreichend verzögert wird, um davon nicht betroffen zu sein?

  • +1 für // deep magic... 🙂 Wer braucht Harry Potter, wenn man den Linux-Kernel hat?

    – David Z

    20. August 2010 um 22:33 Uhr

  • Also, was ist zu diesem Zeitpunkt auf fd 3?

    – Gilles ‘SO- hör auf, böse zu sein’

    20. August 2010 um 22:42 Uhr

  • @David. Nichts mit dem Kernel zu tun. Nicht einmal fd 0, 1 und 2 sind speziell für den Kernel.

    – camh

    20. August 2010 um 23:59 Uhr

  • @camh: Das weiß ich. Es ist nur ein Witz. Fühlen Sie sich frei, “Standard-Dateideskriptorzuweisungen” zu ersetzen, wenn Sie sich dadurch besser fühlen.

    – David Z

    21. August 2010 um 0:02 Uhr

Das Besondere an Dateideskriptor 3 ist, dass er normalerweise der erste Dateideskriptor ist, der von einem Systemaufruf zurückgegeben wird, der einen neuen Dateideskriptor zuweist, da 0, 1 und 2 normalerweise für stdin, stdout und stderr eingerichtet sind.

Das bedeutet, wenn eine von Ihnen aufgerufene Bibliotheksfunktion einen Dateideskriptor für ihre eigenen internen Zwecke zuweist, um ihre Funktionen auszuführen, erhält sie fd 3.

Der Bibliotheksaufruf openlog(3) muss geöffnet werden /dev/log um mit dem Syslog-Daemon zu kommunizieren. Wenn Sie anschließend alle Dateideskriptoren schließen, können Sie die Syslog-Bibliotheksfunktionen beschädigen, wenn sie nicht so geschrieben sind, dass sie damit umgehen.

Der Weg, dies unter Linux zu debuggen, ist die Verwendung von strace um die tatsächlich durchgeführten Systemaufrufe zu verfolgen; die Verwendung eines Dateideskriptors für Syslog wird dann offensichtlich:

$ cat syslog_test.c
#include <stdio.h>
#include <syslog.h>

int main(void)
{
    openlog("test", LOG_PID, LOG_LOCAL0);
    syslog(LOG_ERR, "waaaaaah");
    closelog();
    return 0;
}
$ gcc -W -Wall -o syslog_test syslog_test.c
$ strace ./syslog_test
...
socket(PF_FILE, SOCK_DGRAM, 0)          = 3
fcntl64(3, F_SETFD, FD_CLOEXEC)         = 0
connect(3, {sa_family=AF_FILE, path="/dev/log"}, 16) = 0
send(3, "<131>Aug 21 00:47:52 test[24264]"..., 42, MSG_NOSIGNAL) = 42
close(3)                                = 0
exit_group(0)                           = ?
Process 24264 detached

syslog(3) kann einen Dateideskriptor für den Socket von syslogd offen halten; Dies unter den Füßen zu schließen, wird wahrscheinlich Probleme verursachen. Ein Aufruf von closelog(3) kann helfen.

Syslog bindet beim Start an einen bestimmten Deskriptor. Meistens Deskriptor 3. Wenn Sie es schließen, werden keine Protokolle angezeigt.

syslog-ng -d -v

Gibt Ihnen mehr Informationen darüber, was es hinter den Kulissen tut.

Die Ausgabe sollte in etwa so aussehen:

binding fd 3, inetaddr: 0.0.0.0, port: 514
io.c: Preparing fd 3 for reading
io.c: Preparing fd 4 for reading
binding fd 5, unixaddr: /dev/log
io.c: listening on fd 5

1205330cookie-checkWas ist das Besondere an Dateideskriptor 3 unter Linux?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy