Hack Linux Fork Systemaufrufe

Kürzlich habe ich einen Beitrag mit dem Titel "Einführung in die Socket-API" veröffentlicht, um in C-Sprache zu lernen, und ich habe vorerst ein Programm mit TCP und UDP geschrieben. Es ist also an der Zeit für die Parallelisierungs- und Multiplexverarbeitung, die für die Netzwerkprogrammierung unverzichtbar sind. Ich denke an einen Umzug.

Dieses Mal werde ich mich also mit der Verarbeitung mehrerer Prozesse befassen. Ein wichtiger Systemaufruf zur Realisierung von Multiprozessen ist Fork. Ursprünglich wusste ich, dass der Systemaufruf, der unter Linux untergeordnete Prozesse erzeugt, Fork war, und ich wusste irgendwie, wie man ihn verwendet.

Ich wusste jedoch nicht, durch welche Art von Implementierung es realisiert wurde, daher habe ich den Quellcode zu diesem Zeitpunkt ordnungsgemäß überprüft.

Es gab jedoch Zeiten, in denen es schwierig war, nur dem Quellcode zu folgen. Geben Sie in einem solchen Fall vorerst den entsprechenden Code mit der Fork-Funktion an den statisch mit gcc verknüpften Objektcode aus und entsorgen Sie ihn mit objdump. Während des Zusammenbaus habe ich extrem einfache Arbeit geleistet, während ich den Fluss des Quellcodes mit der tatsächlich ausgegebenen Maschinensprache verglichen habe.

Ich verstehe es nicht zu 100% und bin skeptisch gegenüber der Interpretation, aber ich werde es weiterhin nach und nach untersuchen und aktualisieren, damit ich die richtige behalten kann.

Und wenn Sie damit vertraut sind, wäre ich Ihnen sehr dankbar, wenn Sie mir einen Rat geben könnten. Wir suchen auch Freunde, die den Open Source Source Code gemeinsam im Cafe w lesen können

Referenzierter Quellcode

Linux-Kernel: 2.6.11 proc-Befehle: procps-3.2.8 glibc:glibc-2.12.1 Die CPU ist x86_64.

Was ist ein Prozess?

Ein Prozess ist ein Adressraum zum Ausführen eines Programms und eine Sammlung von Informationen, die für die Verarbeitung erforderlich sind.

Das Programm wird normalerweise in einem Zusatzspeichergerät wie einer Festplatte gespeichert, aber es wird in den Speicher geladen und dabei ausgeführt.

Das wichtigste Programm, aus dem das Betriebssystem besteht, heißt Kernel, aber der Prozess wird von dem im Speicher geladenen Kernel verwaltet.

Wenn die CPU unter Linux ausgeführt wird, gibt es zwei Modi: Benutzermodus und Kernelmodus. Im Kernelmodus kann ohne Einschränkungen auf Systemressourcen zugegriffen werden, sodass die Verarbeitung in Bezug auf Hardware und das gesamte System vom Benutzer ausgeführt werden kann. Im Modus können Sie die Anwendungsverarbeitung in einem eindeutigen Adressraum durchführen, für den kein Zugriff auf solche Ressourcen erforderlich ist.

Durch Ausgeben von Systemaufrufen innerhalb des Prozesses ist es jedoch möglich, die CPU vorübergehend in den Kernelmodus zu schalten und CPU-abhängige Anweisungen auszuführen. Ein Systemaufruf ist übrigens eine bestimmte Gruppe von Prozessen, die im Kernelmodus ausgeführt werden und vom Prozess an das Betriebssystem angefordert werden.

Verwenden wir nun den Befehl ps mit Optionen, um die derzeit unter Linux vorhandenen Prozesse aufzulisten und anzuzeigen.

ps -ef f

root     21449     1  0 21:34 ?        Ss     0:00 php-fpm: master process (/etc/php-fpm.conf)
apache   21450 21449  0 21:34 ?        S      0:00  \_ php-fpm: pool www

Ein Prozess enthält Informationen in einer task_struct-Struktur, die als Prozessdeskriptor bezeichnet wird, und ein Mitglied vom Typ pid_t, das eine Eins-zu-Eins-Entsprechung mit dem Prozess namens Prozess-ID aufweist. In diesem Fall bedeuten 21449 und 21450 in der zweiten Spalte die Prozess-ID.

Die task_struct-Struktur ist in include / linux / sched.h im Linux-Kernel definiert, aber da es sich um eine Struktur mit fast 200 Zeilen handelte, werde ich sie nicht zitieren. Es ist eine wichtige Struktur voller Informationen über den Prozess, daher ist es eine gute Idee, sie in den langen Herbstnächten zu lesen.

In der dritten Spalte wird die Prozess-ID des Prozesses angezeigt, der den Prozess erstellt hat. Einige Prozesse werden normalerweise in einem übergeordneten Prozess erzeugt.

Sie können sehen, dass der übergeordnete Prozess in der zweiten Zeile der Prozess in der ersten Zeile ist, die übergeordnete Prozess-ID des Prozesses in der ersten Zeile jedoch 1 ist.

Dies stellt einen Prozess namens init dar, der wie ein Pflegeelternteil übernimmt, wenn der übergeordnete Prozess nicht existiert oder der Elternteil vor dem Kind stirbt. Der Init wird vom Kernel erstellt und ist auch der Vorfahr aller Prozesse.

Wir werden den Status, in dem init das übergeordnete Element ist, überprüfen, indem wir das Programm später ausführen. Zuerst schreiben wir einfach ein Programm und führen es aus, das einen untergeordneten Prozess erzeugt.

python


#include <sys/types.h> /* pid_t */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {

    pid_t pid, ret;

    printf("start!\n");

    if((ret = fork()) == -1){
        perror("fork() failed.");
        exit(EXIT_FAILURE);
    }
    pid = getpid();

    printf("return from fork() is %d\n", ret);
    printf("pid is %d\n", pid);

    printf("end!\n");

    return 0;
}

Zuerst wird start! Angezeigt, und die Fork-Funktion führt einen Fork-Systemaufruf durch (der später angezeigt wird, aber intern klont). Fork () gibt -1 zurück, wenn es fehlschlägt. In diesem Fall wird die Programmausführung sofort beendet.

Wenn fork () erfolgreich ist, rufen Sie die Prozess-ID mit der Funktion getpid ab, um die ID des aktuellen Prozesses abzurufen und anzuzeigen. Hier wird das tgid-Element, das die Prozess-ID des Thread-Gruppenleiters ist, anstelle des pid-Elements erfasst, das die genaue Prozess-ID der Prozessdeskriptorstruktur angibt.

Der Systemaufruf getpid ruft die Funktion sys_getpid im Kernel auf, aber der Quellcode wird wie folgt in Zeile 967 von kernel / timer.c implementiert.

c:linux-2.6.11.1_kernel/timer.c


asmlinkage long sys_getpid(void)
{
    return current->tgid;
}

Die Variable current ist hier ein Zeiger auf den aktuellen Prozessdeskriptor, die Struktur vom Typ task_struct, abhängig vom Prozessor. Sie können sehen, dass es auf das tgid-Element zeigt, nicht auf das pid-Element des Strukturobjekts vom Typ task_struct.

Dieser Unterschied hat wichtige Auswirkungen, wenn Sie über Multithread-Programmierung nachdenken, nicht über Multiprozess-Programmierung. Behalten Sie ihn also im Auge.

Die Prozess-ID des Befehls ps zeigt auch das Mitglied tgid an. Das Folgende ist ein Teil des Quellcodes des Befehls ps. Um jedoch die Prozessliste anzuzeigen, laden Sie den Prozess unter / proc und setzen Sie die Prozess-ID in das tgid-Element und das tid-Element durch p, das ein Zeiger auf die proc_t-Struktur ist. Es wird gespeichert.

c:procps-3.2.8_proc/readproc.c


static int simple_nextpid(PROCTAB *restrict const PT, proc_t *restrict const p) {
  static struct direct *ent;        /* dirent handle */
  char *restrict const path = PT->path;
  for (;;) {
    ent = readdir(PT->procfs);
    if(unlikely(unlikely(!ent) || unlikely(!ent->d_name))) return 0;
    if(likely( likely(*ent->d_name > '0') && likely(*ent->d_name <= '9') )) break;
  }
  p->tgid = strtoul(ent->d_name, NULL, 10); 
  p->tid = p->tgid;
  memcpy(path, "/proc/", 6);
  strcpy(path+6, ent->d_name);  // trust /proc to not contain evil top-level entries
  return 1;
}

fork gibt 0 für untergeordnete Prozesse und die Prozess-ID von untergeordneten Prozessen für übergeordnete Prozesse zurück. Das Folgende ist das Ausführungsergebnis des Programms und der Status des ausgeführten Befehls ps.

Ausführungsergebnis


start!
return from fork() is 21526
pid is 21525
end!
return from fork() is 0
pid is 21526
end!

Status des Befehls ps


tajima   21181 21180  0 20:51 pts/0    Ss     0:00  |       \_ -bash
tajima   21525 21181  0 22:24 pts/0    S+     0:00  |           \_ ./a.out
tajima   21526 21525  0 22:24 pts/0    S+     0:00  |               \_ ./a.out

Es hängt vom Scheduler ab, ob der untergeordnete Prozess oder der übergeordnete Prozess ausgeführt wird. In diesem Fall können Sie jedoch sehen, dass der übergeordnete Prozess und der untergeordnete Prozess in dieser Reihenfolge ausgeführt wurden.

Sie werden feststellen, dass der Start! Hier nur einmal angezeigt wird. Dies liegt daran, dass der neue Prozess unmittelbar nach dem Aufruf des Fork-Systems ausgeführt wird. Sie werden den Grund später verstehen, da wir die interne Verarbeitung der Gabel verfolgen werden.

Lassen Sie uns nun das vorherige Programm wie folgt ändern. Ob es sich um einen übergeordneten oder einen untergeordneten Prozess handelt, wird durch den Rückgabewert der Funktion getpid bestimmt. Der Ruhezustand wird jedoch für den übergeordneten Prozess für 10 Sekunden und für den untergeordneten Prozess für 30 Sekunden ausgeführt.

python


    pid = getpid();

    if(ret == 0){ 
        sleep(30);
    } else {
        sleep(10);
    }   

    printf("return from fork() is %d\n", ret);
    printf("pid is %d\n", pid);

Wenn Sie unmittelbar nach dem Starten des Programms mit dem Befehl ps nachsehen,

tajima   22501 22438  0 21:32 pts/0    S+     0:00  |           \_ ./a.out
tajima   22502 22501  0 21:32 pts/0    S+     0:00  |               \_ ./a.out

Das Ergebnis ist das gleiche wie zuvor, aber wenn der übergeordnete Prozess aus dem Ruhezustand aufwacht und die Ausführung des Programms beendet, führen Sie den Befehl ps erneut aus. tajima 22502 1 0 21:32 pts/0 S 0:00 ./a.out Sie können sehen, dass das übergeordnete Element des untergeordneten Prozesses der Prozess mit der Prozess-ID 1, dh init, ist.

In einigen Fällen möchten Sie möglicherweise einen synchronisierten Prozess zwischen Eltern und Kind ausführen.

Es gibt verschiedene Möglichkeiten, dies zu tun, z. B. darauf zu warten, dass der übergeordnete Prozess den untergeordneten Prozess beendet, oder das Ende des untergeordneten Prozesses mit einem Signal zu erkennen.

Das folgende Programm führt den Systemaufruf waitpid () aus, um die Ausführung des übergeordneten Prozesses zu blockieren, bis der untergeordnete Prozess beendet wird.

Nachdem der untergeordnete Prozess beendet wurde, wird der übergeordnete Prozess 10 Sekunden später zerstört.

python


#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {

    pid_t pid, ret;

    printf("start\n");

    if((ret = fork()) == -1){
        perror("fork() failed.");
        exit(EXIT_FAILURE);
    }   
    pid = getpid();

    if(ret == 0){ 
        sleep(30);
    } else {
        if(waitpid(-1, NULL, 0) < 0) {
            perror("waitpid() failed.");
            exit(EXIT_FAILURE);
        }   
        sleep(10);
    }   

    printf("return from fork() is %d\n", ret);
    printf("pid is %d\n", pid);

}

waitpid () kann den Beendigungsstatus eines untergeordneten Prozesses erhalten, indem ein Zeiger vom Typ int als Argument an das zweite Argument übergeben wird, oder sein Verhalten ändern, indem im dritten Argument eine optionale logische Summe angegeben wird.

Dieses Mal wird davon ausgegangen, dass die Steuerung aufgrund der Statusänderung der Beendigung des untergeordneten Prozesses an den übergeordneten Prozess zurückgegeben wird. Es ist jedoch möglich, nicht nur die Beendigung, sondern auch die Statusänderung wie Stopp und Neustart zu erkennen.

Wie üblich wird beim Ausführen des Befehls ps für eine Weile Folgendes angezeigt:

tajima   22438 22437  0 21:28 pts/0    Ss     0:00  |       \_ -bash
tajima   22609 22438  0 22:33 pts/0    S+     0:00  |           \_ ./a.out
tajima   22610 22609  0 22:33 pts/0    S+     0:00  |               \_ ./a.out

Sie können sehen, dass Folgendes angezeigt wird, wenn der untergeordnete Prozess endet.

tajima   22438 22437  0 21:28 pts/0    Ss     0:00  |       \_ -bash
tajima   22609 22438  0 22:33 pts/0    S+     0:00  |           \_ ./a.out

Zusätzlich befindet sich der untergeordnete Prozess in einem Zombie-Prozess, bis der übergeordnete Prozess einen Wartesystemaufruf aufruft, um den Prozess zu bereinigen. init räumt auf, indem er einen wait4-Systemaufruf ausgibt, um Eltern dieses verlorenen Zombies zu werden und die Seele freizulassen.

Hackgabel

Schauen wir uns nun die Gabelimplementierung an. Das Folgende ist eine zerlegte Version einer ausführbaren Datei, die statisch mit einem Programm verknüpft ist, das die Fork-Funktion verwendet.

fork.S


0000000000400494 <main>:
  400494:   55                      push   %rbp   
  400495:   48 89 e5                mov    %rsp,%rbp
  400498:   e8 f3 d1 00 00          callq  40d690 <__libc_fork>
  40049d:   b8 00 00 00 00          mov    $0x0,%eax
  4004a2:   c9                      leaveq 
  4004a3:   c3                      retq   

Wir rufen eine Funktion mit dem Symbol __libc_fork auf. Wenn Sie also __libc_fork in derselben Datei folgen, ist es genau das, was die CPU tut, aber es ist schwer, es plötzlich zu lesen. Überprüfen Sie daher zuerst die Implementierung mit dem Quellcode der Sprache C. Ich werde.

c:glibc-2.12.1_nptl/sysdeps/unix/sysv/linux/fork.c


pid_t
__libc_fork (void)
{
  pid_t pid;
  struct used_handler
  {
    struct fork_handler *handler;
    struct used_handler *next;
  } *allp = NULL;

  /* Run all the registered preparation handlers.  In reverse order.
     While doing this we build up a list of all the entries.  */
  struct fork_handler *runp;
  while ((runp = __fork_handlers) != NULL)
    {   
      /* Make sure we read from the current RUNP pointer.  */
      atomic_full_barrier (); 

      unsigned int oldval = runp->refcntr;

      if (oldval == 0)
    /* This means some other thread removed the list just after
       the pointer has been loaded.  Try again.  Either the list
       is empty or we can retry it.  */

/*Kürzung*/

#ifdef ARCH_FORK
  pid = ARCH_FORK (); 
#else
# error "ARCH_FORK must be defined so that the CLONE_SETTID flag is used"
  pid = INLINE_SYSCALL (fork, 0); 
#endif
/*Kürzung*/

Wenn Sie dem Makro ARCH_FORK folgen, werden Sie auf ein Makro namens INLINE_SYSCALL stoßen, also werden wir es weiter verfolgen.

c:glibc-2.12.1_nptl/sysdeps/unix/sysv/linux/x86_64/fork.c


#define ARCH_FORK() \
  INLINE_SYSCALL (clone, 4,                           \
          CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD, 0,     \
          NULL, &THREAD_SELF->tid)

c:glibc-2.12.1_sysdeps/unix/sysv/linux/x86_64/sysdep.h


# define INLINE_SYSCALL(name, nr, args...) \
  ({                                          \
    unsigned long int resultvar = INTERNAL_SYSCALL (name, , nr, args);        \
    if (__builtin_expect (INTERNAL_SYSCALL_ERROR_P (resultvar, ), 0))         \
      {                                       \
    __set_errno (INTERNAL_SYSCALL_ERRNO (resultvar, ));           \
    resultvar = (unsigned long int) -1;                   \
      }                                       \
    (long int) resultvar; })

# define INTERNAL_SYSCALL(name, err, nr, args...) \
  INTERNAL_SYSCALL_NCS (__NR_##name, err, nr, ##args)

# define INTERNAL_SYSCALL_NCS(name, err, nr, args...) \
  ({                                          \
    unsigned long int resultvar;                          \
    LOAD_ARGS_##nr (args)                             \
    LOAD_REGS_##nr                                \
    asm volatile (                                \
    "syscall\n\t"                                 \
    : "=a" (resultvar)                                \
    : "0" (name) ASM_ARGS_##nr : "memory", "cc", "r11", "cx");            \
    (long int) resultvar; })

Das Makro INLINE_SYSCALL wird weiter intern in eine Makrofunktion namens INTERNAL_SYSCALL_NCS konvertiert.

Darin wird der Wert von __NR_clone im Rax-Register in der Beschreibung des Inline-Assemblers gespeichert. Sie können sehen, dass der Systemaufruf des Klons ausgeführt wird.

Übrigens bedeutet der Ausgangsoperand = a, dass er in das Rax-Register ausgegeben wird, und der Eingangsoperand 0 bedeutet, dass das Eingangsregister auch das gleiche Rax-Register wie der Ausgang ist.

c:linux-2.6.11.1_sysdeps/unix/sysv/linux/x86_64/sysdep.h


#define __NR_clone                              56

Sie können sehen, dass die Rufnummer des Klonsystems auf Prozessoren der x86_64-Serie 56 lautet. Lassen Sie uns den Beweis mit dem Code im Assembler früher überprüfen.

fork.S


  40d736:   b8 38 00 00 00          mov    $0x38,%eax
  40d73b:   0f 05                   syscall

Die Hexadezimalzahl 0x38 wird im eax-Register (rax) gespeichert. Dies bedeutet die Dezimalzahl 56.

Der Systemaufruf clone () wird von der Funktion sys_clone implementiert, die intern die Funktion do_fork aufruft.

c:linux-2.6.11.1arch/x86_64/kernel/process.c


asmlinkage long sys_clone(unsigned long clone_flags, unsigned long newsp, void __user *parent_tid, void __user *child_tid, struct pt_regs *regs)
{
    if (!newsp)
        newsp = regs->rsp;
    return do_fork(clone_flags, newsp, regs, 0, parent_tid, child_tid);
}

c:linux-2.6.11.1_kernel/fork.c_1124-1134


long do_fork(unsigned long clone_flags,
          unsigned long stack_start,
          struct pt_regs *regs,
          unsigned long stack_size,
          int __user *parent_tidptr,
          int __user *child_tidptr)
{
    struct task_struct *p;
    int trace = 0; 
    long pid = alloc_pidmap();

Verwalten Sie die PID-Verwendung mit long pid = alloc_pidmap (); Holen Sie sich eine neue PID für einen untergeordneten Prozess aus einem Array namens pidmap_array.

c:linux-2.6.11.1_kernel/pid.c


typedef struct pidmap {                                                
    atomic_t nr_free;                                                  
    void *page;                                                        
} pidmap_t;       

static pidmap_t pidmap_array[PIDMAP_ENTRIES] =                         
     { [ 0 ... PIDMAP_ENTRIES-1 ] = { ATOMIC_INIT(BITS_PER_PAGE), NULL } };

pidmap_array hat eine Struktur namens struct pidmap als Element und ist auf den Typ pidmap_t typisiert.

c:linux-2.6.11.1_kernel/fork.c_1142-1143



    p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, pid);

Die Funktion copy_process gibt einen Zeiger auf die Struktur task_struct zurück, die eine Kopie des Prozessdeskriptors des übergeordneten Prozesses ist. Im Folgenden werde ich nur die Hauptpunkte betrachten.

c:linux-2.6.11.1_kernel/fork.c_820


p = dup_task_struct(current); 

current zeigt auf einen Zeiger auf die task_struct-Struktur des aktuellen Prozesses, kopiert diesen Inhalt jedoch in die task_struct-Struktur des untergeordneten Prozesses und gibt diesen Zeiger zurück. Danach werden verschiedene Initialisierungsprozesse für den Zeiger auf die task_struct-Struktur des kopierten untergeordneten Prozesses ausgeführt.

Anschließend wird die Funktion copy_thread mit dem Zeiger auf die Struktur pt_regs als Argument zum Festlegen des Kernelmodusstapels des untergeordneten Prozesses aufgerufen. Die Struktur pt_regs speichert den Wert des Registers, wenn der Kernelmodus aufgerufen wird.

include/asm-x86_64/ptrace.h_39-65


struct pt_regs {
    unsigned long r15;
    unsigned long r14;
    unsigned long r13;
    unsigned long r12;
    unsigned long rbp;
    unsigned long rbx;
/* arguments: non interrupts/non tracing syscalls only save upto here*/
    unsigned long r11;
    unsigned long r10;  
    unsigned long r9; 
    unsigned long r8; 
    unsigned long rax;
    unsigned long rcx;
    unsigned long rdx;
    unsigned long rsi;
    unsigned long rdi;
    unsigned long orig_rax;
/* end of arguments */  
/* cpu exception frame or undefined */
    unsigned long rip;
    unsigned long cs; 
    unsigned long eflags; 
    unsigned long rsp; 
    unsigned long ss; 
/* top of stack page */ 
};

c:linux-2.6.11.1_arch/x86_64/kernel/process.c_366-386


int copy_thread(int nr, unsigned long clone_flags, unsigned long rsp, 
        unsigned long unused,
    struct task_struct * p, struct pt_regs * regs)
{
    int err;
    struct pt_regs * childregs;
    struct task_struct *me = current;

    childregs = ((struct pt_regs *) (THREAD_SIZE + (unsigned long) p->thread_info)) - 1;

    *childregs = *regs;

    childregs->rax = 0;
    childregs->rsp = rsp;
    if (rsp == ~0UL) {
        childregs->rsp = (unsigned long)childregs;
    }   

    p->thread.rsp = (unsigned long) childregs;
    p->thread.rsp0 = (unsigned long) (childregs+1);
    p->thread.userrsp = me->thread.userrsp; 

childregs = ((struct pt_regs *) (THREAD_SIZE + (unsigned long) p-> thread_info)) --1; Dieser Prozess platziert die pt_regs-Struktur an der obersten Adresse des Kernelstapels in dem dem Prozess zugewiesenen Speicherbereich. Es ist die Verarbeitung von. Ich denke, dass es eine Implementierung ist, dass die Adresse, die durch Subtrahieren der Größe 1 der pt_regs-Struktur erhalten wird, zum Speicherort von pt_regs wird.

Bei Childregs wird der gesamte Inhalt von Regs einmal kopiert, und dann wird der Wert des Registers teilweise geändert.

Was Sie hier beachten sollten, ist childregs-> rax = 0. Ich habe 0 in das Rax-Register eingetragen. In der Sprache C soll der Rückgabewert im rax-Register oder im eax-Register zurückgegeben werden. Im Fall eines untergeordneten Prozesses ist dies der Rückgabewert, der unverändert an den Benutzerprozessbereich zurückgegeben wird.

Die Adresse der obersten Ebene des Stapels wird in p-> thread.rsp0 gespeichert.

c:linux-2.6.11.1_kernel/fork.c_32-38


        if ((p->ptrace & PT_PTRACED) || (clone_flags & CLONE_STOPPED)) {                                                                      
            /*
             * We'll start up with an immediate SIGSTOP.              
             */ 
            sigaddset(&p->pending.signal, SIGSTOP);                   
            set_tsk_thread_flag(p, TIF_SIGPENDING);                   
        }           

Wenn der untergeordnete Prozess vom Debugger überwacht wird oder das CLONE_STOPPED-Flag gesetzt ist, sollte die Ausführung des untergeordneten Prozesses gestoppt werden.

c:linux-2.6.11.1_kernel/fork.c_40-43


        if (!(clone_flags & CLONE_STOPPED))
            wake_up_new_task(p, clone_flags);                         
        else
            p->state = TASK_STOPPED;

Wenn das CLONE_STOPPED-Flag nicht gesetzt ist, führen Sie die Funktion wake_up_new_task aus, um die Planung des Eltern-Kind-Prozesses ordnungsgemäß anzupassen. Wenn das Flag CLONE_STOPPED gesetzt ist, setzen Sie das Flag TASK_STOPPED im Statuselement.

c:linux-2.6.11.1_kernel/fork.c_45-48


        if (unlikely (trace)) {
            current->ptrace_message = pid; 
            ptrace_notify ((trace << 8) | SIGTRAP);
        }    

Dies ist ein Prozess für den Debugger. Dieser Prozess ermöglicht es dem Debugger, die übergeordneten und untergeordneten Prozesse ordnungsgemäß zu verfolgen.

c:linux-2.6.11.1_kernel/fork.c_50-54


        if (clone_flags & CLONE_VFORK) {
            wait_for_completion(&vfork);
            if (unlikely (current->ptrace & PT_TRACE_VFORK_DONE))
                ptrace_notify ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP);
        }  

Dies ist der Prozess, wenn der Systemaufruf vfork () verwendet wird. Da vfork denselben Speicheradressraum mit den übergeordneten und untergeordneten Prozessen teilt, wird die Ausführung des übergeordneten Prozesses gestoppt, bis der untergeordnete Prozess beendet wird. Wie später beschrieben wird, ist dies eines der von Linux implementierten Geräte, um die Verschwendung von Replikation durch Gabeln zu vermeiden.

c:linux-2.6.11.1_arch/x86_64/kernel/process.c


asmlinkage long sys_vfork(struct pt_regs *regs)
{
    return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs->rsp, regs, 0,
            NULL, NULL);
}

c:linux-2.6.11.1_kernel/fork.c_55-59


    } else {
        free_pidmap(pid);
        pid = PTR_ERR(p);
    }
    return pid;

Wenn die Verarbeitung mit der Funktion copy_process fehlschlägt, wird die für den untergeordneten Prozess erfasste Prozess-ID durch Setzen eines nicht verwendeten Flags in pidmap_array freigegeben und dann in einen Fehlercode konvertiert und in pid gespeichert. Wenn dies nicht fehlschlägt, wird die Prozess-ID des untergeordneten Prozesses zurückgegeben, der so wie er ist erhalten wurde.

Anschließend kehrt der Prozess im Benutzermodus zu "__libc_fork" zurück, verzweigt im übergeordneten und im untergeordneten Prozess, je nachdem, ob die PID 0 ist, und führt eine entsprechende Nachbearbeitung durch. Anschließend wird die Prozess-ID an den Benutzerprozess zurückgegeben.

Ist eine Kopie des übergeordneten Prozesses nicht nutzlos?

Linux verwendet eine Methode zum Erstellen eines neuen Prozesses, indem die Ressourcen des übergeordneten Prozesses auf den untergeordneten Prozess kopiert werden. Normalerweise verwendet der untergeordnete Prozess jedoch einen Systemaufruf wie execve, um einen anderen Adressraum als den übergeordneten Prozess zu verarbeiten. Dieser Duplizierungsprozess scheint ineffizient zu sein, wie Sie es oft tun.

Um diese Probleme zu beheben, verwenden beide Prozesse dieselbe physische Seite und die Kernel-Datenstruktur pro Prozess, z. B. Copy-on-Write, bei der eine neue Seite nur zugewiesen wird, wenn sich eine ändert, und einen offenen Dateideskriptor. Wir versuchen mit solchen Methoden die Effizienz zu verbessern.

Wenn ich das nächste Mal über die Einführung in die Socket-API zum Erlernen der C-Sprache schreibe, programmiere ich mit mehreren Prozessen.

Recommended Posts

Hack Linux Fork Systemaufrufe
[Linux] [Grundeinstellungen] Systemeinstellungen
Hacken Sie einen Linux-Dateideskriptor
Linux-Systemarchitektur [Run Level]
Ich habe versucht, Linux Systemaufrufe und Scheduler hinzuzufügen
Verstehen Sie das Linux-Audit-System Audit
Linux-Hauptpaketverwaltungssystem