C Programmierung fork()?

2 Antworten

Im einfachsten Fall verwendet man fork() als eine "primitive" Form von Threads.

Eine weitere nicht unübliche Verwendung wäre auch um einen Prozessteil als "absoluten Hintergrundprozess" zu starten (z.B. als daemon).

Zu zweiterem hab ich vor einiger Zeit mal das gebastelt:

#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>

typedef int(*daemon_main_t)(int argc, char *argv[]);
int daemonize(daemon_main_t daemon_main, int argc, char *argv[]);

void daemon_stop(int signum){
    int fd;
    for(fd = sysconf(_SC_OPEN_MAX); fd >= 0; --fd){
        close(fd);
    }
    exit(0);
}

pid_t daemonize(daemon_main_t daemon_main, int argc, char *argv[]){
    pid_t pid, sid;
    
    pid_t result = -1;
    
    int pipefd[2];
    pipe(pipefd);
    
    pid = fork();
    if(pid < 0) return -1;
    else if(pid > 0){
        read(pipefd[0], &result, sizeof(pid_t));
        close(pipefd[0]);
        close(pipefd[1]);
        return result;
    }
    
    sid = setsid();
    if(sid < 0){
        result = -1;
        write(pipefd[1], &result, sizeof(pid_t));
        exit(errno);
    }
    
    pid = fork();
    if(pid < 0){
        result = -1;
        write(pipefd[1], &result, sizeof(pid_t));
        exit(errno);
    }
    else if(pid > 0) exit(0);
    
    if((chdir("/")) < 0){
        result = -1;
        write(pipefd[1], &result, sizeof(pid_t));
        exit(errno);
    }
    
    umask(0);
    
    signal(SIGINT, daemon_stop);
    signal(SIGTERM, daemon_stop);
    
    result = getpid();
    write(pipefd[1], &result, sizeof(pid_t));
    
    int fd;
    for(fd = sysconf(_SC_OPEN_MAX); fd >= 0; --fd){
        close(fd);
    }
    
    exit(daemon_main(argc, argv));
}

int _main(int argc, char *argv[]){
    /*TODO: daemonic activity*/
    return 0;
}

int main(int argc, char *argv[]){
    printf("daemonize: %d\n", daemonize(_main, argc, argv));
}

execl (und die anderen Funktionen der exec-Familie) werden dazu verwendet, um den aktuellen Prozess durch einen anderen zu ersetzen ohne ihn zu "beenden".

Eine der üblichsten Verwendungen dafür findet sich z.B. im Script/Programm /init eines initramfs das bei den meisten Linux-Distributionen üblich ist.

Grundsätzlich gilt dabei nämlich: "Wenn PID 0 stirbt, stirbt das System."

Also wird am Ende von /init z.B. exec switch_root /mnt/newroot /sbin/init aufgerufen, damit der Prozess mit der Prozess-ID 0 weiterhin existiert.

exec kann aber auch für andere Zwecke verwendet werden, z.B. "Launcher" (welcher Art auch immer).

Woher ich das weiß:eigene Erfahrung
KarlRanseierIII  22.06.2019, 03:30

Es gibt tatsächlich daemon() (aus BSD, aber z.B. auch in der glibc).

Bei PID0 mußte ich schmunzeln, ist mir aber auch schon passiert. Init ist immernoch PID1, PID0 gibt es maximal innerhalb des Schedulers (üblich als Kannung für den Leerlaufprozess bzw. Dummyprozess, um die Verwaltung zu vereinfachen).

PID1 ist Init, die Mutter aller Prozesse - Ob man PID0 dann eigentlich als Vater bezeichnen darf? Immerhin fault der auf der Couch rum, während "Mutti" sich um die "Kinderlein" kümmert :-D.

1
Isendrak  22.06.2019, 03:35
@KarlRanseierIII

Ach verdammt! Das ist die vermutlich seltsamste Version des "Off-by-One"-Fehlers, die ich je gesehen habe... ^^

Aber wir machen doch jetzt keine RTL-Nachmittagsserie daraus? "Zuhause bei den INITs"... ^^

P.S.: Heisst das etwa, ich hab (schon wieder) das Rad neu erfunden (aber diesmal "versehentlich")? o.O

0
KarlRanseierIII  22.06.2019, 03:38
@Isendrak

Naja, ich weiß aus dem Stegreif nicht wie portabel daemon() ist, da es bei BSD 4.4(?) eingeführt wurde, nicht aber in POSIX1 landete.

Die manpage sagt:

           Since glibc 2.21:
               _DEFAULT_SOURCE
           In glibc 2.19 and 2.20:
               _DEFAULT_SOURCE || (_XOPEN_SOURCE && _XOPEN_SOURCE < 500)
           Up to and including glibc 2.19:
               _BSD_SOURCE || (_XOPEN_SOURCE && _XOPEN_SOURCE < 500)

Ich glaube das RTL-Publikum wäre mit der Sitcom wohl überfordert :-D.

Ah, die BUG-Section ist interessant, es wird kein Doppel-Fork durchgeführt. Nein, DU hast die Funktionalität nicht repliziert.

0
Isendrak  22.06.2019, 03:48
@KarlRanseierIII

Hab ich vor ca. 1,5 Minuten auch gelesen.

Wobei ich sagen muss, dass mir an meiner Version die "Kleinigkeit" gefällt, dass die Funktion bei Erfolg nicht 0 sondern die PID des endgültigen Daemons zurückgibt. ^^

P.S.: Okay, dann halt nicht RTL sondern... ähm... ah ja! "N24 - Die Familiengeheimnisse der INITs". Und natürlich wird auch die Militärvergangenheit des Großvaters "Kernel (oder Colonel?) Linux" aufgedeckt. ^^

1
KarlRanseierIII  22.06.2019, 04:03
@Isendrak

Und die Sonderausgabe ist dann:

Who the fsck ist General Failure and why is he reading my harddrive? Wenn der böse Onkel die Tante Storage-Stack ...äääääh, den Rest überlasse ich der Fantasie.

Apropos, wenn man man 7 daemon liest, ist ein Doppelfork nur bei SysV Semantik nötig, da sollte man aber noch deutlich mehr aufräumen. Heute sorgt das Initsystem für die vorbereitete Umgebung.

1
Isendrak  22.06.2019, 04:08
@KarlRanseierIII
den Rest überlasse ich der Fantasie

Da muss ich nochmal im Keller nachschauen, ich glaub da hab ich noch ne Flasche 19XXer Fantasie. (Wäre bei jeglicher weiterer Ausführung dieses Teilthemas auch dringend notwendig.) ^^

Apropos

man apropos ^^

Wie ist das denn bei "Very-Late-Start-Daemons" (Daemons, die erst nach dem Login "manuell" gestartet werden) oder vergleichbarem?

0
KarlRanseierIII  22.06.2019, 04:16
@Isendrak

Na, solange man sie via Initsystem startet macht es wohl keinen Unterschied.

Interessanter dürfte es werden, wenn Du den daemon direkt von der Shell aus startest und trotzdem detachen möchtest, dann kommst Du wohl um die SysV-Semantik nicht herum. (Mit direkt meine ich, daemon-binary <params>)

Deswegen solltest Du ohnehin beides implementieren.

1

Weil so Daten vom Elternprozess in das Kind vererbt werden können, bzw. auch an Geschwisteter der Kinder.

Das betrifft insbesondere Filedeskriptoren, die unter anderem auch für IPC genutzt werden können. (Pipes als klassisches Beispiel)

Gansa 
Fragesteller
 22.06.2019, 00:20

Aber wieso muss ich das vereben ? kann ich nicht ganz normal ein Prozess verwedenen? ich verstehe nicht wieso ich den prozess kopieren muss

0
KarlRanseierIII  22.06.2019, 00:29
@Gansa

Letztlich wurde die Semantik eben so festgelegt.

Überlege Dir einfach mal, wie Du z.B. folgendes realisierst:

find <path> -type f|wc -l

Wie sorgst Du dafür, daß der Standard-Out von find mit dem Standard-In von wc verbunden wird?

0