Vfork

pthreads 和 vfork

  • October 24, 2014

我正在嘗試檢查 pthread 在其中一個執行 vfork 時真正發生了什麼。規範說父“控制執行緒”被“暫停”,直到子程序呼叫 exec* 或 _exit。據我了解,共識是這意味著整個父程序(即:及其所有pthreads)被暫停。我想通過實驗來確認它。到目前為止,我進行了幾次實驗,所有這些都表明其他 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,所有這些執行緒都執行 vfork,並在子程序中執行 exec。每個子程序在 vfork 和 exec “A” 和 “B” 之間執行兩個輸出操作。該理論表明,輸出應為 ABABABABABABA ……沒有嵌套。然而,輸出是一團糟:例如:

AAAA



BB
B

B

實驗二

懷疑在 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[]應該回到初始狀態(根據定義全為零)。如果進入子程序凍結了其他 pthread 使它們無法呼叫 vfork 直到目前子程序完成循環,那麼數組最後應該全為零。我確認當我使用 fork() 而不是 vfork() 時,上面的程式碼不會產生任何輸出。但是,當我將 fork() 更改為 vfork() 時,會向標準輸出報告大量的不一致。

實驗三

這裡描述了另一個實驗https://unix.stackexchange.com/a/163761/88901 - 它涉及呼叫睡眠,但實際上當我用長for循環替換它時結果是相同的。

Linux 手冊頁vork非常具體:

vfork()不同之處在於fork(2)呼叫執行緒被掛起,直到子程序終止

這不是整個過程,而是呼叫執行緒。POSIX 或其他標準不保證這種行為,其他實現可能會做不同的事情(最多並包括簡單地vfork使用 plain實現fork)。

(Rich Felker 還注意到vfork 中的這種行為被認為是危險的。)

在多執行緒程序中使用fork已經很難推理了,呼叫vfork至少同樣糟糕。您的測試充滿了未定義的行為,您甚至不允許在 ’d 子級中呼叫函式(更不用說執行 I/O)了vfork,除了exec-type 函式和_exit(甚至不是exit,並且返回會導致混亂)。

這是一個改編自您的範例,我認為它幾乎沒有未定義的行為,假設編譯器/實現不輸出對ints 進行原子讀取和寫入的函式呼叫。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 子程序中執行set的1執行緒,然後等待(再次忙循環)其他執行緒增加計數器。vfork``start``1

如果其他執行緒在 期間掛起vfork,則永遠無法取得進展:掛起的執行緒永遠不會增加(它們在設置為counter之前已經掛起),因此 vforker 執行緒將陷入無限忙等待。start``1

引用自:https://unix.stackexchange.com/questions/163947