2014-12-08 15 views
18

Ich habe ein System, in dem zwei identische Prozesse ausgeführt werden (nennen wir sie Replikate). Wenn ein Replikat signiert wird, wird es sich mit dem Aufruf fork() duplizieren. Ein dritter Prozess wählt einen der Prozesse aus, um zufällig zu töten, und signalisiert dann dem anderen, einen Ersatz zu erzeugen. Funktional funktioniert das System gut; Es kann Repliken den ganzen Tag außer dem Leistungsproblem töten/respawnen.Gabel() undicht? Es dauert länger und länger, um einen einfachen Prozess zu verzweigen

Der Anruf fork() dauert länger und länger. Das folgende ist das einfachste Setup, das das Problem weiterhin anzeigt. Der Zeitpunkt wird in der folgenden Grafik dargestellt: fork timing

Die Replik des Codes ist die folgende:

void restartHandler(int signo) { 
// fork 
    timestamp_t last = generate_timestamp(); 
    pid_t currentPID = fork(); 


    if (currentPID >= 0) { // Successful fork 
    if (currentPID == 0) { // Child process 
     timestamp_t current = generate_timestamp(); 
     printf("%lld\n", current - last); 

     // unblock the signal 
     sigset_t signal_set; 
     sigemptyset(&signal_set); 
     sigaddset(&signal_set, SIGUSR1); 
     sigprocmask(SIG_UNBLOCK, &signal_set, NULL); 

     return; 
    } else { // Parent just returns 
     waitpid(-1, NULL, WNOHANG); 
     return; 
    } 
    } else { 
    printf("Fork error!\n"); 
    return; 
    } 
} 

int main(int argc, const char **argv) { 
    if (signal(SIGUSR1, restartHandler) == SIG_ERR) { 
    perror("Failed to register the restart handler"); 
    return -1; 
    } 

    while(1) { 
    sleep(1); 
    } 

    return 0; 
} 

Je länger das System läuft, desto schlimmer wird es.

Es tut uns leid, eine bestimmte Frage zu haben, aber hat jemand irgendeine Idee/Hinweise, was vor sich geht? Es scheint mir, dass es im Kernel ein Ressourcenleck gibt (also das Linux-Kernel-Tag), aber ich weiß nicht, wo ich anfangen soll.

Was habe ich versucht:

  • kmemleak Versuchte, die nichts zu fangen haben. Dies bedeutet, dass bei einem Speicherleck immer noch erreichbar ist.
  • /proc/<pid>/maps wächst nicht.
  • Derzeit läuft der 3.14 Kernel mit RT-Patch (beachten Sie, dies passiert mit nicht-RT-und RT-Prozesse), und haben auch am 3.2 versucht.
  • Zombie-Prozesse sind kein Problem. Ich habe versucht, eine Version, in der ich einen anderen Prozess als ein Unterreader mit prctl
  • Ich bemerkte zuerst diese Verlangsamung in einem System, in dem die Timing-Messungen sind außerhalb des neu gestarteten Prozesses; gleiches Verhalten.

Irgendwelche Hinweise? Alles, was ich zur Verfügung stellen kann, um zu helfen? Vielen Dank!

+1

Das untergeordnete Element ist nur eine exakte Kopie bis zu dem Zeitpunkt, zu dem der neue Prozess gestartet wird, bzw. bis zum ersten Speicherbefehl. Danach muss der Speicher Kopien erstellen und schreiben. –

+0

Im ganzen System komme ich um dies mit Hilfe von mlockall und dann gehen/Proc/Pid/Maps Dummy schreibt. Sowohl im Gesamtsystem als auch im Code, den ich hier anbiete, bleibt das Performance-Problem mit der Gabel bestehen. – superdesk

+0

Haben Sie den Befehl ps verwendet, um zu sehen, was gerade ausgeführt wird (in der Regel wiederholt das Dienstprogramm 'top' das für Sie) und wie viel Zeit wird in jedem laufenden Prozess verbraucht? In meinem Code sehe ich keine Weitergabe des Signals user1, so dass es zweifelhaft ist, dass alle untergeordneten Prozesse getötet werden. – user3629249

Antwort

2

Die Verlangsamung wird durch eine Anhäufung anonymer vmas verursacht und ist ein bekanntes Problem. Das Problem ist offensichtlich, wenn es eine große Anzahl von fork() Aufrufen gibt und die Eltern vor den Kindern verlassen. Der folgende Code erstellt das Problem (source Daniel Forrest):

#include <unistd.h> 

int main(int argc, char *argv[]) 
{ 
    pid_t pid; 
    while (1) { 
    pid = fork(); 
    if (pid == -1) { 
     /* error */ 
     return 1; 
    } 
    if (pid) { 
     /* parent */ 
     sleep(2); 
     break; 
    } 
    else { 
     /* child */ 
     sleep(1); 
    } 
    } 
    return 0; 
} 

Das Verhalten kann durch Überprüfung anon_vma in /proc/slabinfo bestätigt werden.

Es gibt einen Patch (source), der die Länge der kopierten anon_vma_chain auf fünf begrenzt. Ich kann bestätigen, dass der Patch das Problem behebt.

Für, wie ich schließlich das Problem fand, fing ich schließlich gerade an, printk Anrufe im fork Code zu setzen und die Zeiten zu überprüfen, die in dmesg gezeigt werden. Schließlich sah ich, dass es der Anruf zu anon_vma_fork war, der länger und länger dauerte. Dann war es eine schnelle Sache von Google-Suche.

Es hat eine ziemlich lange Zeit gedauert, so würde ich noch irgendwelche Vorschläge für einen besseren Weg schätzen, das Problem aufzuspüren. Und all denen, die bereits Zeit damit verbracht haben, mir zu helfen, Danke.

0

Nur eine Idee: vielleicht hängt es mit MMU oder Cache zusammen? Soweit ich weiß, füllt der Kernel bei fork() entsprechende Tabelleneinträge mit Verweisen auf dieselben physikalischen RAM-Seiten. Du schreibst, Du machst Dummy schreibt, aber machst Du sie zu ausführbaren Segmenten (wenn ja, wie, denn diese sollten schreibgeschützt sein)? Aus dem Graph scheint die Leistung an einigen Punkten zuzunehmen (512? 512 * 3? 512 * 4?). Es lässt mich vermuten, dass das System (Kernel ?, Hardware?) Sich des Problems bewusst ist und einige Workarounds verwendet (dupliziere Antries bei MMU für dieselbe physische Seite? Einige Datenstrukturen sind aufgeteilt?).

1

Vielleicht könnten Sie versuchen, den generischen wait() - Aufruf anstelle von waitpid() zu verwenden? Es ist nur eine Vermutung, aber ich habe gehört, dass es von einem Professor im Undergrad besser war. Haben Sie auch versucht mit address sanitizer

Auch können Sie GDB verwenden, um einen Kindprozess als auch zu debuggen (wenn Sie das nicht bereits versucht haben). Sie können den Follow-Fork-Modus verwenden:

set follow-fork-mode child 

, aber das ist nur in der Lage, die Eltern zu debuggen.Sie können beide debuggen, indem Sie die pid des Kindes Prozess bekommen, schlafen() aufrufen, nachdem Forking dann:

attach <child process pid> 

dann rufen:

detach 

Dies ist nützlich, weil Sie Speicherlecks in valgrind Dump kann. Rufen Sie einfach valgrind mit

valgrind --vgdb-error=0...<executable> 

dann einige relevante Haltepunkte setzen, und weiter durch das Programm, bis Sie Ihre Haltepunkte treffen dann auf Lecks suchen:

monitor leak_check full reachable any 

dann:

monitor block_list <loss_record_nr> 
Verwandte Themen