GNU/Linux >> Linux Esercitazione >  >> Linux

Pthreads e Vfork?

Sto cercando di verificare cosa succede realmente ai pthread mentre uno di essi esegue vfork.
La specifica dice che il "thread of control" genitore è "sospeso" fino a quando il processo figlio non chiama exec* o _exit.
A quanto ho capito, il consenso è che significa che l'intero processo padre (ovvero:con tutti i suoi pthread) è sospeso.
Vorrei confermarlo utilizzando un esperimento.
Finora ho eseguito diversi esperimenti, i quali suggeriscono che altri pthread sono in esecuzione. Dato che non ho esperienza con Linux, sospetto che la mia interpretazione di questi esperimenti sia sbagliata e apprendere la vera interpretazione di questi risultati potrebbe aiutare a evitare ulteriori idee sbagliate nella mia vita.
Quindi ecco gli esempi che ho fatto:

Esperimento 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;
    }
  }
}

Ci sono 44 pthread, che eseguono tutti vfork ed exec nel figlio.
Ogni processo figlio esegue due operazioni di output tra vfork ed exec "A" e "B".
La teoria suggerisce che l'output dovrebbe leggere ABABABABBABA...senza annidamento.
Tuttavia l'output è un disastro totale:ad esempio:

AAAA



BB
B

B

Esperimento II

Sospettando che l'utilizzo di I/O lib dopo vfork possa essere una cattiva idea, ho sostituito la funzione job() con la seguente:

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;
}

Questa volta, eseguo due cicli in modo tale che il secondo annulli i risultati del primo, quindi alla fine la tabella globale t[] dovrebbe tornare allo stato iniziale (che per definizione è tutti zeri).
Se l'immissione del processo figlio blocca gli altri pthread rendendoli incapaci di chiamare vfork fino a quando il figlio corrente non termina i cicli, l'array dovrebbe essere tutti zeri in la fine.
E ho confermato che quando uso fork() invece di vfork(), il codice sopra non produce alcun output.
Tuttavia, quando cambio fork() in vfork() ottengo tonnellate di incongruenze segnalate a stdout.

Correlati:scorrere i file con spazi nei nomi??

Esperimento III

Un altro esperimento è descritto qui https://unix.stackexchange.com/a/163761/88901 – si trattava di chiamare il sonno, ma in realtà i risultati erano gli stessi quando l'ho sostituito con un lungo for ciclo.

Risposta accettata:

La pagina man di Linux per vork è abbastanza specifico:

vfork() differisce da fork(2) in quanto il thread chiamante è sospeso fino alla cessazione del figlio

Non è l'intero processo, ma in effetti il ​​thread della chiamata . Questo comportamento non è garantito da POSIX o altri standard, altre implementazioni possono fare cose diverse (fino alla semplice implementazione di vfork con un semplice fork ).

(Rich Felker nota anche questo comportamento in vfork considerato pericoloso.)

Usando fork in un programma multi-thread è già abbastanza difficile ragionare, chiamando vfork è almeno altrettanto male. I tuoi test sono pieni di comportamenti indefiniti, non ti è nemmeno permesso chiamare una funzione (per non parlare di fare I/O) all'interno di vfork 'd figlio, ad eccezione di exec -digita le funzioni e _exit (nemmeno exit , e il ritorno provoca caos).

Ecco un esempio adattato dal tuo che credo sia quasi privo di comportamenti indefiniti presupponendo un compilatore/implementazione che non genera chiamate di funzioni per letture e scritture atomiche su int S. (L'unico problema è la scrittura su start dopo il vfork – non è consentito.) La gestione degli errori è stata eliminata per mantenerlo breve.

#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);
}

L'idea è questa:i thread normali aspettano (ciclo occupato) la variabile globale atomica start essere 1 prima di incrementare un contatore atomico globale. Il thread che esegue un vfork imposta start a 1 nel figlio vfork, quindi attende (di nuovo ciclo occupato) che gli altri thread abbiano incrementato il contatore.

Se gli altri thread sono stati sospesi durante vfork , nessun progresso potrebbe mai essere fatto:i thread sospesi non incrementerebbero mai counter (sarebbero stati sospesi prima di start era impostato su 1 ), quindi il thread vforker sarebbe bloccato in un'attesa di occupato infinita.


Linux
  1. Il risultato di Ls * , Ls ** e Ls ***?

  2. Centos 4.8 e Glibc 2.5?

  3. Grep e coda -f?

  4. pthreads mutex vs semaforo

  5. Trova e copia i file

Risoluzione dei problemi e insidie ​​di SELinux

Carica e scarica

Git e collegamenti reali

La differenza tra fork(), vfork(), exec() e clone()

Pipe, dup2 e exec()

Domande su IPTables e DHCP?