pthread와 vfork

pthread와 vfork

스레드 중 하나가 vfork를 수행할 때 pthread에 정확히 어떤 일이 발생하는지 확인하려고 합니다. 사양에는 하위 프로세스가 exec* 또는 _exit를 호출할 때까지 상위 "제어 스레드"가 "정지"된다고 명시되어 있습니다. 내가 이해한 바에 따르면 이는 전체 상위 프로세스(즉, 모든 pthread)가 일시 중지된다는 의미입니다. 나는 이것을 실험적으로 확인하고 싶었다. 나는 지금까지 여러 실험을 실행했는데, 모두 다른 pthread가 실행 중임을 나타냅니다. 나는 Linux 경험이 없기 때문에 이러한 실험에 대한 나의 해석이 잘못되었다고 의심하며 이러한 결과에 대한 진정한 설명을 아는 것은 내 인생에서 더 이상 오해를 피하는 데 도움이 될 수 있습니다. 제가 했던 실험은 다음과 같습니다.

실험 하나

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

44개의 pthread가 있으며 모든 pthread는 vfork를 실행하고 하위 스레드에서 exec를 실행합니다. 각 하위 프로세스는 vfork와 exec "A" 및 "B" 사이에서 두 가지 출력 작업을 수행합니다. 이론에 따르면 출력은 중첩 없이 ABABABABA...여야 합니다. 그러나 출력은 완전히 엉망입니다. 예를 들면 다음과 같습니다.

AAAA



BB
B

B

실험 2

vfork 이후에 I/O lib를 사용하는 것이 좋지 않을 수 있다고 의심하여 ​​job() 함수를 다음으로 대체했습니다.

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

이번에는 두 개의 루프를 실행하고 두 번째 루프는 첫 번째 루프의 결과를 실행 취소하므로 결국 전역 테이블은 t[]초기 상태(정의상 모두 0임)로 반환되어야 합니다. 하위 프로세스를 입력하면 다른 pthread가 정지되어 현재 하위 프로세스가 루프를 완료할 때까지 vfork를 호출할 수 없게 되는 경우 배열은 결국 모두 0이 되어야 합니다. vfork() 대신 fork()를 사용하면 위 코드에서 출력이 생성되지 않는 것을 확인했습니다. 그러나 fork()를 vfork()로 변경하면 표준 출력에 많은 불일치가 보고됩니다.

실험 3

여기에 또 다른 실험이 설명되어 있습니다.https://unix.stackexchange.com/a/163761/88901- sleep을 호출하는 것과 관련이 있지만 실제로 긴 루프로 바꾸면 결과는 동일합니다 for.

답변1

리눅스 매뉴얼 페이지vork매우 구체적:

vfork()차이점 fork(2)은 호출하는 것입니다.철사하위 프로세스가 종료될 때까지 일시 중지됩니다.

그것은 과정이 아니라 실제 소명이다철사. 이 동작은 POSIX 또는 다른 표준에 의해 보장되지 않으며 다른 구현에서는 다르게 수행될 수 있습니다(기껏해야 단순히 vfork일반 구현을 사용하는 것을 포함 fork).

(리치 펠커도 여기에 있습니다.vfork는 위험한 것으로 간주됩니다.)

다중 스레드 프로그램에서 사용하는 것에 fork대해 이미 추론하기는 어렵습니다 . 호출도 vfork적어도 그만큼 나쁩니다. 귀하의 테스트는 정의되지 않은 동작으로 가득 차 있으며, -type 함수를 vfork제외하고는 'd 하위 항목에서 함수를 호출하는 것조차 허용되지 않습니다 (I/O 수행은 물론) . (그것은 불가능하며 반환하면 혼란을 야기합니다)exec_exitexit

다음은 귀하의 예를 적용한 것입니다.거의int컴파일러/구현이 s에 대한 원자성 읽기 및 쓰기에 대한 함수 호출을 내보내지 않는다고 가정하면 정의되지 않은 동작은 없습니다 . (한 가지 문제는 - start뒤에 쓰는 것이 vfork허용되지 않는다는 것입니다.) 짧게 유지하기 위해 오류 처리를 생략합니다.

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

아이디어는 다음과 같습니다. 일반 스레드는 start전역 원자 카운터를 증가시키기 전에 원자 전역 변수를 기다립니다(사용 중 루프). vfork 자식에서 설정된 스레드를 실행 1한 다음 다른 스레드가 카운터를 증가시킬 때까지 기다립니다(다시 사용 중 루프).vforkstart1

기간 동안 다른 스레드가 중단되면 vfork진행이 이루어지지 않습니다. 중단된 스레드는 절대 증가하지 않으므로( counter로 ) vforker 스레드는 무한한 바쁜 대기 상태에 갇히게 됩니다.start1

관련 정보