내가 아는 한, vfork
호출될 때 하위 프로세스는 상위 프로세스와 동일한 주소 공간을 사용하며 상위 프로세스의 ss 변수에서 하위 프로세스가 변경한 내용은 상위 프로세스에 반영됩니다. 내 질문은 다음과 같습니다
- 자식 프로세스가 생성되면 부모 프로세스가 일시 중지됩니까?
- 그렇다면 왜 그렇습니까?
- 스레드처럼 병렬로 실행할 수 있나요? 결국 스레드와 프로세스는 모두 동일한
clone()
함수를 호출합니다.
약간의 조사와 인터넷 검색 끝에 상위 프로세스가 실제로 중단되는 것이 아니라 호출 스레드가 중단된다는 사실을 알게 되었습니다. 그럼에도 불구하고 자식 프로세스가 실행될 때 exit()
또는 exec()
부모 프로세스는 자식 프로세스가 종료되었음을 어떻게 알 수 있습니까? 자식 프로세스에서 돌아오면 어떻게 되나요?
답변1
귀하의 문제는 부분적으로 잘못된 명명 규칙에 기초합니다. 커널의 "제어 스레드"는 사용자의 프로세스입니다. 따라서 vfork "호출 스레드가 중단되었습니다"를 읽을 때 "멀티스레드 프로세스"의 "스레드"보다는 "프로세스"(또는 원하는 경우 "무거운 스레드")를 생각하십시오.
- 예, 상위 프로세스가 일시 중지되었습니다.
vfork
의미 체계는 하나의 프로세스(가장 일반적으로 셸)가 분기되어 일부 파일 설명자를 엉망으로 만들고 exec
다른 프로세스가 그 자리를 대신하는 매우 일반적인 상황에 대해 정의됩니다. 커널 사람들은 exec
복사된 페이지만 버리기 때문에 복사를 건너뛰면 페이지 복사 오버헤드를 많이 줄일 수 있다는 것을 깨달았습니다 . vforked 하위 프로세스에는 커널에 자체 파일 설명자 테이블이 있으므로 이에 대한 작업은 상위 프로세스에 영향을 주지 않으며 의미 체계는 fork
변경되지 않습니다.
- 왜? 포크/실행은 일반적이고 비용이 많이 들고 낭비적이기 때문입니다.
"커널 제어 스레드"에 대한 보다 정확한 정의를 고려하면 병렬로 실행될 수 있는지 여부에 대한 대답은 다음과 같습니다.
- 아니요. 상위 프로세스는 하위 프로세스가 종료되거나 실행될 때까지 커널에 의해 차단됩니다.
부모는 자녀가 자퇴했다는 것을 어떻게 알 수 있나요?
- 이는 사실이 아니며, 커널은 하위 프로세스가 떠날 때까지 상위 프로세스가 CPU를 가져오는 것을 알고 방지합니다.
마지막 질문에 대해서는 커널이 반환과 관련된 하위 스택 작업을 감지하고 잡을 수 없는 신호로 하위 프로세스에 신호를 보내거나 직접 종료하는 것으로 의심되지만 자세한 내용은 모릅니다.
답변2
저는 Linux 프로그래머는 아니지만 오늘도 같은 문제에 직면하여 다음 테스트를 수행했습니다.
#include<unistd.h>
#include<signal.h>
#include<errno.h>
#include<fcntl.h>
#include<cassert>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<iostream>
#include<fstream>
#include<sstream>
#include<list>
using namespace std;
int single_talk(int thread_id){
fprintf(stderr,"thread %d before fork @%d\n",thread_id,time(0));
int pid=vfork();
if(-1==pid){
cerr << "failed to fork: " << strerror(errno) << endl;
_exit(-3);//serious problem, can not proceed
}
sleep(1);
fprintf(stderr,"thread %d fork returned %d @%d\n",thread_id,pid,time(0));
if(pid){//"CPP"
fprintf(stderr,"thread %d in parent\n",thread_id);
}else{//"PHP"
sleep(1);
fprintf(stderr,"thread %d in child @%d\n",thread_id,time(0));
if(-1 == execlp("/bin/ls","ls",(char*)NULL)){
cerr << "failed to execl php : " << strerror(errno) << endl;
_exit(-4);//serious problem, can not proceed
}
}
}
void * talker(void * id){
single_talk(*(int*)id);
return NULL;
}
int main(){
signal(SIGPIPE,SIG_IGN);
signal(SIGCHLD,SIG_IGN);
const int thread_count = 44;
pthread_t thread[thread_count];
int thread_id[thread_count];
int err;
for(size_t i=0;i<thread_count;++i){
thread_id[i]=i;
if((err = pthread_create(thread+i,NULL,talker,thread_id+i))){
cerr << "failed to create pthread: " << strerror(err) << endl;
exit(-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;
exit(-17);
}
}
}
으로 컴파일 g++ -pthread -o repro repro.cpp
하고 실행했습니다 ./repro
. 출력에서 볼 수 있는 것은 모든 것이 차례로 동시에 발생한다는 것입니다. 먼저 모든 pthread가 vfork를 실행한 다음 모두 1초 동안 기다린 다음 하위 프로세스 현실에서 "깨어나고" 모든 하위 프로세스가 exec()를 실행하고 결국 모든 상위 프로세스가 실행됩니다. 깨우다.
나에게 이것은 하나의 pthread가 vfork를 호출하면 다른 pthread가 중단되지 않는다는 것을 증명합니다. 중단되면 exec()가 호출될 때까지 vfork()를 호출할 수 없습니다.
답변3
자식 프로세스가 생성되면 부모 프로세스가 일시 중지됩니까?
예.
그렇다면 왜?
상위 프로세스와 하위 프로세스가 주소 공간을 공유하기 때문입니다. 특히 스택의 주소 공간. 상위 프로세스가 계속하려고 하면 다른 함수를 호출하고 하위 프로세스의 호출 스택을 삭제할 수 있습니다. 따라서 exec()
또는_exit()
스레드처럼 병렬로 실행할 수 있나요? 결국 스레드와 프로세스 모두 동일한 clone() 함수를 호출합니다.
물론. 호출 스레드만 일시 중단됩니다. 찾다.
상위 프로세스는 하위 프로세스가 종료되었음을 어떻게 알 수 있습니까?
대신 wait4()
에 부모에게 계속 진행하는 방법을 묻습니다. 그러나 실제로는 그렇지 않습니다. 정의된 두 이벤트 중 하나가 발생하면 커널은 해당 이벤트를 다시 실행합니다.
자식 프로세스에서 돌아오면 어떻게 되나요?
vfork()
릴리스된 스택 프레임으로 돌아가서 다음은 return;
정의되지 않은 동작에 대한 빠른 경로입니다.
답변4
자식 프로세스에서 돌아오면 어떻게 되나요?
"그러나 하위 컨텍스트에서 실행되는 동안 vfork()를 호출하는 프로시저에서 반환하면 vfork()에서 최종 반환이 더 이상 존재하지 않는 스택 프레임으로 반환되기 때문에 작동하지 않습니다."
나는 이것을 문자 그대로 받아들이지 않습니다. 스택 영역의 바이트는 각 POP(또는 RETURN) 명령 후에 실제로 사라지지 않습니다. 그러나 뒤로 돌아가서 계속 실행하고 PUSH(또는 CALL) 명령을 실행하면 스택의 이전 값이 대체됩니다.
vfork()
이는 호출한 다음 프로세스의 메모리를 수정하기 위해 무엇이든 수행할 경우 자주 발생할 수 있는 이상한 일의 대표적인 예일 뿐입니다.
[기술적으로는 Linux에서와 동일한 동작을 가정합니다. vfork()의 다른 동작은 POSIX에서 기술적으로 허용됩니다. 누군가가 fork() ]와 동등한 vfork()를 제공하는 것 외에 POSIX 기술의 유연성을 사용하는 방법을 찾았는지 모르겠습니다.