GNU/Linux >> Tutoriales Linux >  >> Linux

¿Pthreads y Vfork?

Estoy tratando de verificar qué sucede realmente con los subprocesos mientras uno de ellos ejecuta vfork.
La especificación dice que el "subproceso de control" principal está "suspendido" hasta que el proceso secundario llama a exec* o _exit.
Según tengo entendido, el consenso es que significa que todo el proceso principal (es decir, con todos sus subprocesos) está suspendido.
Me gustaría confirmarlo mediante un experimento.
Hasta ahora realizó varios experimentos, todos los cuales sugieren que se están ejecutando otros pthreads. Como no tengo experiencia con Linux, sospecho que mi interpretación de estos experimentos es incorrecta, y aprender la interpretación real de estos resultados podría ayudar a evitar más conceptos erróneos en mi vida.
Así que aquí están los experimentos que hice:

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

Hay 44 pthreads, todos los cuales realizan vfork y exec en el niño.
Cada proceso hijo realiza dos operaciones de salida entre vfork y exec "A" y "B".
La teoría sugiere que la salida debe decir ABABABABABA... sin anidar.
Sin embargo, la salida es un desastre total:por ejemplo:

AAAA



BB
B

B

Experimento II

Sospechando que usar I/O lib después de vfork podría ser una mala idea, reemplacé la función job() con lo siguiente:

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

Esta vez, realizo dos bucles de modo que el segundo deshace los resultados del primero, por lo que al final la tabla global t[] debe volver al estado inicial (que por definición es todo ceros).
Si al ingresar al proceso secundario se congelan los otros pthreads, lo que les impide llamar a vfork hasta que el elemento secundario actual termine los bucles, entonces la matriz debe ser todo ceros en el final.
Y confirmé que cuando uso fork() en lugar de vfork(), entonces el código anterior no produce ningún resultado.
Sin embargo, cuando cambio fork() a vfork() obtengo toneladas de inconsistencias reportadas a stdout.

Relacionado:¿Recorrer archivos con espacios en los nombres?

Experimento III

Un experimento más se describe aquí https://unix.stackexchange.com/a/163761/88901:implicó llamar a dormir, pero en realidad los resultados fueron los mismos cuando lo reemplacé con un largo for bucle.

Respuesta aceptada:

La página del manual de Linux para vork es bastante específico:

vfork() difiere de fork(2) en que el hilo de llamada se suspende hasta que el niño termine

No es todo el proceso, sino el hilo de llamada . Este comportamiento no está garantizado por POSIX u otros estándares, otras implementaciones pueden hacer cosas diferentes (hasta e incluso simplemente implementar vfork con un fork simple ).

(Rich Felker también señala que este comportamiento en vfork se considera peligroso).

Usando fork en un programa de subprocesos múltiples ya es bastante difícil razonar, llamar a vfork es al menos igual de malo. Sus pruebas están llenas de comportamiento indefinido, ni siquiera se le permite llamar a una función (y mucho menos hacer E/S) dentro del vfork ‘d child, excepto exec -escriba funciones y _exit (ni siquiera exit y regresar causa caos).

Aquí hay un ejemplo adaptado del tuyo que creo que es casi libre de comportamiento indefinido asumiendo un compilador/implementación que no genera llamadas de función para lecturas y escrituras atómicas en int s. (El único problema es escribir en start después del vfork – eso no está permitido.) El manejo de errores se eliminó para que sea 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);
}

La idea es esta:los subprocesos normales esperan (bucle ocupado) por la variable global atómica start ser 1 antes de incrementar un contador atómico global. El hilo que hace un vfork establece start a 1 en el hijo de vfork, luego espera (bucle ocupado de nuevo) a que los otros subprocesos hayan incrementado el contador.

Si los otros subprocesos se suspendieron durante vfork , nunca se podría hacer ningún progreso:los subprocesos suspendidos nunca incrementarían counter (habrían sido suspendidos antes de start se estableció en 1 ), por lo que el subproceso vforker se quedaría atascado en una espera infinitamente ocupada.


Linux
  1. ¿La diferencia entre [[ $a ==Z* ]] y [ $a ==Z* ]?

  2. ${!foo} ¿Y Zsh?

  3. Corte/Grep Y Df -h?

  4. Descripción general de FTP y SFTP

  5. Frambuesa Pi 4 y Kali

Solución de problemas y trampas de SELinux

Cargar y descargar

Grep y cola -f?

Git y enlaces duros

Buscar y copiar archivos

¿Preguntas sobre IPTables y DHCP?