GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Pthreads und Vfork?

Ich versuche zu überprüfen, was wirklich mit pthreads passiert, während einer von ihnen vfork durchführt.
Die Spezifikation besagt, dass der übergeordnete „Thread of control“ „suspendiert“ ist, bis der untergeordnete Prozess exec* oder _exit aufruft.
Soweit ich weiß, besteht Konsens darin, dass der gesamte übergeordnete Prozess (d. h. mit all seinen Pthreads) ausgesetzt wird.
Ich möchte dies anhand eines Experiments bestätigen.
Bisher habe ich mehrere Experimente durchgeführt, die alle darauf hindeuten, dass andere pthreads laufen. Da ich keine Linux-Erfahrung habe, vermute ich, dass meine Interpretation dieser Experimente falsch ist, und das Lernen der wahren Interpretation dieser Ergebnisse könnte helfen, weitere Missverständnisse in meinem Leben zu vermeiden.
Hier sind also die Experimente, die ich gemacht habe:

Experiment I

#include<unistd.h>
#include<signal.h>
#include<errno.h>
#include<cstring>
#include<string>
#include<iostream>
using namespace std;
void * job(void *x){
  int pid=vfork();
  if(-1==pid){
    cerr << "failed to fork: " << strerror(errno) << endl;
    _exit(-3);
  }
  if(!pid){
    cerr << "A" << endl;
    cerr << "B" << endl;
    if(-1 == execlp("/bin/ls","ls","repro.cpp",(char*)NULL)){
      cerr << "failed to exec : " << strerror(errno) << endl;
      _exit(-4);//serious problem, can not proceed
    }
  }
  return NULL;
}
int main(){
  signal(SIGPIPE,SIG_IGN);
  signal(SIGCHLD,SIG_IGN);
  const int thread_count = 4;
  pthread_t thread[thread_count];
  int err;
  for(size_t i=0;i<thread_count;++i){
    if((err = pthread_create(thread+i,NULL,job,NULL))){
      cerr << "failed to create pthread: " << strerror(err) << endl;
      return -7;
    }
  }
  for(size_t i=0;i<thread_count;++i){
    if((err = pthread_join(thread[i],NULL))){
      cerr << "failed to join pthread: " << strerror(err) << endl;
      return -17;
    }
  }
}

Es gibt 44 pthreads, die alle vfork und exec im untergeordneten Prozess ausführen.
Jeder untergeordnete Prozess führt zwei Ausgabeoperationen zwischen vfork und exec „A“ und „B“ durch.
Die Theorie legt dies nahe die Ausgabe sollte ABABABABABA…ohne Verschachtelung lauten.
Die Ausgabe ist jedoch ein totales Durcheinander:zum Beispiel:

AAAA



BB
B

B

Experiment II

Da ich vermutete, dass die Verwendung von I/O lib nach vfork eine schlechte Idee sein könnte, habe ich die Funktion job() durch Folgendes ersetzt:

const int S = 10000000;
int t[S];
void * job(void *x){
  int pid=vfork();
  if(-1==pid){
    cerr << "failed to fork: " << strerror(errno) << endl;
    _exit(-3);
  }
  if(!pid){
    for(int i=0;i<S;++i){
      t[i]=i;
    }
    for(int i=0;i<S;++i){
      t[i]-=i;
    }
    for(int i=0;i<S;++i){
      if(t[i]){
        cout << "INCONSISTENT STATE OF t[" << i << "] = " << t[i] << " DETECTED" << endl;
      }
    }
    if(-1 == execlp("/bin/ls","ls","repro.cpp",(char*)NULL)){
      cerr << "failed to execlp : " << strerror(errno) << endl;
      _exit(-4);
    }
  }
  return NULL;
}

Dieses Mal führe ich zwei Schleifen aus, sodass die zweite die Ergebnisse der ersten rückgängig macht, also am Ende die globale Tabelle t[] sollte wieder im Anfangszustand sein (der per Definition nur aus Nullen besteht).
Wenn der Eintritt in den untergeordneten Prozess die anderen pthreads einfriert, wodurch sie nicht in der Lage sind, vfork aufzurufen, bis der aktuelle untergeordnete Prozess die Schleifen beendet hat, dann sollte das Array nur aus Nullen bestehen das Ende.
Und ich habe bestätigt, dass, wenn ich fork() anstelle von vfork() verwende, der obige Code keine Ausgabe erzeugt.
Wenn ich jedoch fork() in vfork() ändere, bekomme ich Unmengen von Inkonsistenzen an stdout gemeldet.

Verwandte:Durchlaufen von Dateien mit Leerzeichen im Namen?

Experiment III

Ein weiteres Experiment ist hier beschrieben https://unix.stackexchange.com/a/163761/88901 – es beinhaltete das Aufrufen von sleep, aber tatsächlich waren die Ergebnisse dieselben, als ich es durch einen langen for Schleife.

Akzeptierte Antwort:

Die Linux-Manpage für vork ist ziemlich spezifisch:

vfork() unterscheidet sich von fork(2) darin der aufrufende Thread wird ausgesetzt, bis das untergeordnete Element beendet wird

Es ist nicht der ganze Prozess, sondern der aufrufende Thread . Dieses Verhalten wird von POSIX oder anderen Standards nicht garantiert, andere Implementierungen können andere Dinge tun (bis hin zur einfachen Implementierung von vfork mit einem einfachen fork ).

(Rich Felker stellt auch fest, dass dieses Verhalten in vfork als gefährlich angesehen wird.)

Verwenden von fork in einem Multi-Thread-Programm ist es schon schwer genug, darüber nachzudenken, vfork aufzurufen ist mindestens genauso schlimm. Ihre Tests sind voll von undefiniertem Verhalten, Sie dürfen nicht einmal eine Funktion innerhalb der vfork aufrufen (geschweige denn I/O ausführen). ‘d Kind, außer für exec -type-Funktionen und _exit (nicht einmal exit , und die Rückkehr verursacht Chaos).

Hier ist ein Beispiel, das von Ihrem übernommen wurde und meiner Meinung nach fast ist frei von undefiniertem Verhalten unter der Annahme eines Compilers/einer Implementierung, die keine Funktionsaufrufe für atomare Lese- und Schreibvorgänge auf int ausgibt s. (Das einzige Problem ist das Schreiben in start nach dem vfork – das ist nicht erlaubt.) Fehlerbehandlung wurde weggelassen, um es kurz zu halten.

#include<unistd.h>
#include<signal.h>
#include<errno.h>
#include<atomic>
#include<cstring>
#include<string>
#include<iostream>

std::atomic<int> start;
std::atomic<int> counter;
const int thread_count = 4;

void *vforker(void *){
  std::cout << "vforker starting\n";
  int pid=vfork();
  if(pid == 0){
    start = 1;
    while (counter < (thread_count-1))
      ;
    execlp("/bin/date","date",nullptr);
  }
  std::cout << "vforker done\n";
  return nullptr;
}

void *job(void *){
  while (start == 0)
    ;
  counter++;
  return NULL;
}

int main(){
  signal(SIGPIPE,SIG_IGN);
  signal(SIGCHLD,SIG_IGN);
  pthread_t thread[thread_count];
  counter = 0;
  start   = 0;

  pthread_create(&(thread[0]), nullptr, vforker, nullptr);
  for(int i=1;i<thread_count;++i)
    pthread_create(&(thread[i]), nullptr, job, nullptr);

  for(int i=0;i<thread_count;++i)
    pthread_join(thread[i], nullptr);
}

Die Idee ist folgende:Die normalen Threads warten (busy-loop) auf die atomare globale Variable start 1 sein bevor ein globaler Atomzähler inkrementiert wird. Der Thread, der einen vfork ausführt setzt start zu 1 im vfork-Kind und wartet dann (erneut Busy-Loop), bis die anderen Threads den Zähler erhöht haben.

Wenn die anderen Threads während vfork angehalten wurden , konnte niemals ein Fortschritt erzielt werden:Die angehaltenen Threads würden den counter niemals erhöhen (Sie wären vor start gesperrt worden wurde auf 1 gesetzt ), sodass der vforker-Thread in einem endlosen Wartestatus feststecken würde.


Linux
  1. Das Ergebnis von Ls * , Ls ** und Ls ***?

  2. Der Unterschied zwischen [[ $a ==Z* ]] und [ $a ==Z* ]?

  3. Centos 4.8 und Glibc 2.5?

  4. Grep und Schwanz -f?

  5. Überblick über FTP und SFTP

SELinux Fehlerbehebung und Fallstricke

Hoch- und Herunterladen

pthreads mutex gegen semaphor

Boost und Autoconf

Suchen und kopieren Sie Dateien

Fragen zu IPTables und DHCP?