GNU/Linux >> Znalost Linux >  >> Linux

Pthreads a Vfork?

Snažím se zkontrolovat, co se skutečně děje s pthready, zatímco jeden z nich provádí vfork.
Specifikace říká, že nadřazené „kontrolní vlákno“ je „pozastaveno“, dokud podřízený proces nezavolá exec* nebo _exit.
Jak jsem pochopil, shoda je v tom, že to znamená, že celý nadřazený proces (to znamená:se všemi jeho pvlákny) je pozastaven.
Rád bych to potvrdil pomocí experimentu.
Zatím provedl několik experimentů, z nichž všechny naznačují, že běží další pthreads. Protože nemám s linuxem žádné zkušenosti, mám podezření, že moje interpretace těchto experimentů je nesprávná, a naučit se skutečnou interpretaci těchto výsledků by mohlo pomoci vyhnout se dalším mylným představám v mém životě.
Takže zde jsou experimenty, které jsem provedl:

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

Existuje 44 pthreadů, z nichž všechna provádějí vfork a exec v potomkovi.
Každý podřízený proces provádí dvě výstupní operace mezi vfork a exec „A“ a „B“.
Teorie naznačuje, že výstup by měl číst ABABABABABA…bez vnořování.
Výstup je však naprostý nepořádek:například:

AAAA



BB
B

B

Experiment II

S podezřením, že použití I/O lib po vforku může být špatný nápad, nahradil jsem funkci job() následujícím:

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

Tentokrát provedu dvě smyčky tak, že druhá zruší výsledky první, takže na konci globální tabulka t[] by měl být zpět do počátečního stavu (což jsou podle definice samé nuly).
Pokud vstup do podřízeného procesu zamrzne ostatní pthreads, takže nemohou volat vfork, dokud aktuální potomek nedokončí smyčky, pak by pole mělo mít samé nuly na konec.
A potvrdil jsem, že když použiji fork() místo vfork(), pak výše uvedený kód neprodukuje žádný výstup.
Když však změním fork() na vfork(), dostanu tun nesrovnalostí hlášených stdout.

Související:Procházení souborů s mezerami v názvech?

Experiment III

Ještě jeden experiment je popsán zde https://unix.stackexchange.com/a/163761/88901 – zahrnoval volání spánku, ale ve skutečnosti byly výsledky stejné, když jsem jej nahradil dlouhým for smyčka.

Přijatá odpověď:

Manuálová stránka Linuxu pro vork je zcela konkrétní:

vfork() se liší od fork(2) v tom volajícím vlákně je pozastavena, dokud dítě neskončí

Nejde o celý proces, ale ve skutečnosti o volající vlákno . Toto chování není zaručeno POSIX ani jinými standardy, jiné implementace mohou dělat různé věci (až po jednoduchou implementaci vfork s obyčejnou fork ).

(Rich Felker také zaznamenal toto chování ve vforku považovaném za nebezpečné.)

Pomocí fork ve vícevláknovém programu je už dost těžké uvažovat o volání vfork je minimálně stejně špatný. Vaše testy jsou plné nedefinovaného chování, dokonce nemáte povoleno volat funkci (natož dělat I/O) uvnitř vfork 'd child, kromě exec -type funkce a _exit (ani exit a návrat způsobí chaos).

Zde je příklad převzatý z vašeho, o kterém se domnívám, že je téměř bez nedefinovaného chování za předpokladu, že kompilátor/implementace nevydává na výstupu funkce volání po atomickém čtení a zápisu na int s. (Jedním problémem je zápis do start za vfork – to není povoleno.) Zpracování chyb se vyloučilo, aby to bylo krátké.

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

Myšlenka je tato:normální vlákna čekají (zaneprázdněná smyčka) na atomickou globální proměnnou start být 1 před zvýšením globálního atomového čítače. Vlákno, které dělá vfork nastaví start1 v podřízeném vfork, pak čeká (opět zaneprázdněná smyčka), až ostatní vlákna zvýší počítadlo.

Pokud byla ostatní vlákna pozastavena během vfork , nebylo možné dosáhnout žádného pokroku:pozastavená vlákna by nikdy nezvýšila counter (byly by pozastaveny před start byl nastaven na 1 ), takže vlákno vforker by uvízlo v nekonečném čekání.


Linux
  1. Rozdíl mezi [[ $a ==Z* ]] a [ $a ==Z* ]?

  2. ${!foo} A Zsh?

  3. Centos 4.8 a Glibc 2.5?

  1. Raspberry Pi 4 a Kali

  2. WSL2 a Kali

  3. pthreads mutex vs semafor

  1. Najděte a zkopírujte soubory

  2. Změnit vlastníka a skupinu v C?

  3. Pipes, dup2 a exec()