pthreads e vfork

3

Estou tentando verificar o que realmente acontece com os pthreads, enquanto um deles executa o vfork. A especificação diz que o "encadeamento de controle" pai é "suspenso" até que o processo filho chame exec * ou _exit. Pelo que entendi, o consenso é que isso significa que todo o processo pai (ou seja: com todos os seus pthreads) está suspenso. Eu gostaria de confirmar isso usando um experimento. Até agora eu realizei vários experimentos, todos sugerindo que outros pthreads estão rodando. Como não tenho experiência com linux, suspeito que minha interpretação desses experimentos esteja errada, e aprender a interpretação real desses resultados poderia ajudar a evitar mais equívocos em minha vida. Então, aqui estão os exeprimentos que fiz:

Experiência 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;
    }
  }
}

Existem 44 pthreads, todos executando vfork e exec na criança. Cada processo filho executa duas operações de saída entre o vfork e exec "A" e "B". A teoria sugere que a saída deve ler ABABABABABA ... sem aninhamento. No entanto, a saída é uma bagunça total: por exemplo:

AAAA



BB
B

B

Experiência II

Suspeitando que usar a biblioteca de E / S após o vfork poderia ser uma má ideia, substituí a função job () com o seguinte:

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

Desta vez, executo dois loops de modo que o segundo desfaça os resultados do primeiro, então, no final, a tabela global t[] deve estar de volta ao estado inicial (que, por definição, é todo zeros). Se entrar no processo filho congela os outros pthreads, tornando-os incapazes de chamar o vfork até que o filho atual termine os loops, então o array deve ter todos os zeros no final. E eu confirmei que quando eu uso fork () em vez de vfork (), o código acima não produz nenhuma saída. No entanto, quando eu mudo fork () para vfork () eu recebo toneladas de inconsistências relatadas para stdout.

Experiência III

Mais uma experiência é descrita aqui link - envolvia a chamada do sono, mas na verdade os resultados eram os mesmos quando Eu o substitui por um longo loop for .

    
por qbolec 24.10.2014 / 09:55

1 resposta

3

A página man do Linux para vork é bem específica:

vfork() differs from fork(2) in that the calling thread is suspended until the child terminates

Não é todo o processo, mas, de fato, o thread de chamada. Esse comportamento não é garantido pelo POSIX ou outros padrões, outras implementações podem fazer coisas diferentes (até e inclusive simplesmente implementando vfork com um fork simples).

(Rich Felker também observa esse comportamento em vfork considerado perigoso .)

Usar fork em um programa multi-thread é difícil o suficiente para já estar em causa, chamando vfork é pelo menos tão ruim. Seus testes estão cheios de comportamento indefinido, você nem está autorizado a chamar uma função (muito menos E / S) dentro da criança vfork 'd, exceto as funções exec -type e _exit (nem mesmo exit e retornando causa caos).

Aqui está um exemplo adaptado do seu, que acredito ser quase livre de comportamento indefinido, supondo um compilador / implementação que não produza chamadas de função para leituras e gravações atômicas em int s. (O único problema é a gravação em start após o vfork - isso não é permitido.) Manipulação de erros elidida para mantê-lo curto.

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

A idéia é a seguinte: os threads normais aguardam (busy-loop) para que a variável global atômica start seja 1 antes de incrementar um contador atômico global. O encadeamento que faz um vfork define start para 1 no filho do vfork, então espera (ocupado-loop novamente) pelos outros encadeamentos para ter incrementado o contador.

Se os outros tópicos estivessem suspensos durante vfork , nenhum progresso poderia ser feito: os threads suspensos nunca aumentariam counter (eles teriam sido suspensos antes que start fosse definido como 1 ), o thread de vforker ficaria preso em uma infinita espera ocupada.

    
por 24.10.2014 / 12:21

Tags