2017-10-11 8 views
1

다음 코드는 spawn()에 특정 프로세스를 포킹 할 때 read(fds[0]...)에서 spawn()으로 블록화하는 경우가 있습니다.pipe2 대신 파이프를 사용할 때 포크 후 파이프에서 블록 읽기

#include <fcntl.h> 
#include <unistd.h> 

#include <atomic> 
#include <mutex> 
#include <thread> 
#include <vector> 

void spawn() 
{ 
    static std::mutex m; 
    static std::atomic<int> displayNumber{30000}; 

    std::string display{":" + std::to_string(displayNumber++)}; 
    const char* const args[] = {"NullXServer", display.c_str(), nullptr}; 

    int fds[2]; 

    m.lock(); 
    pipe(fds); 
    int oldFlags = fcntl(fds[0], F_GETFD); 
    fcntl(fds[0], F_SETFD, oldFlags | FD_CLOEXEC); 
    oldFlags = fcntl(fds[1], F_GETFD); 
    fcntl(fds[1], F_SETFD, oldFlags | FD_CLOEXEC); 
    m.unlock(); 

    if (vfork() == 0) { 
    execvp("NullXServer", const_cast<char**>(args)); 
    _exit(0); 
    } 

    close(fds[1]); 
    int i; 
    read(fds[0], &i, sizeof(int)); 
    close(fds[0]); 
} 

int main() 
{ 
    std::vector<std::thread> threads; 
    for (int i = 0; i < 100; ++i) { 
    threads.emplace_back(spawn); 
    } 

    for (auto& t : threads) { 
    t.join(); 
    } 

    return 0; 
} 

참고 : 여기에 파이프를 만드는 것은 쓸데없는 일입니다. 교착 상태를 보여주기위한 것입니다. read(fds[0], ...)spawn()에 차단해서는 안됩니다. read이 호출되면 파이프의 모든 쓰기 엔드가 닫히므로 read이 즉시 반환되어야합니다. 부모 프로세스에서 파이프의 write-end가 명시 적으로 닫히고 자식 프로세스의 write-end가 파일 설명 자에 FD_CLOEXEC 플래그가 설정되어있어 암시 적으로 닫히면 execvp이 성공하자마자 파일 설명자가 닫힙니다 이 경우 항상 그렇습니다).

여기서 문제는 read()이 한 번 블로킹된다는 것입니다. 코드의 두 개는 적어도 결과 FD_CLOEXEC의 파이프 파일 디스크립터를위한 극히 미세하게 설정되고한다하더라도

pipe2(fds, O_CLOEXEC); 

수정 블로킹 판독이 :

m.lock(); 
pipe(fds); 
int oldFlags = fcntl(fds[0], F_GETFD); 
fcntl(fds[0], F_SETFD, oldFlags | FD_CLOEXEC); 
oldFlags = fcntl(fds[1], F_GETFD); 
fcntl(fds[1], F_SETFD, oldFlags | FD_CLOEXEC); 
m.unlock(); 

:

모두 교체.

불행히도 우리가 배포하는 모든 플랫폼에서 pipe2을 사용할 수 없습니다.

누구나 이 위의 코드에서 pipe 접근 방식을 사용하여 차단되는 이유에 대해 설명해 줄 수 있습니까?

좀 더 관찰하십시오 vfork() 블록을 충당하기 위해 뮤텍스 잠금을 확장

  • 읽기 차단을 해결합니다.
  • 하나의 시스템 호출이 실패하지 않습니다.
  • vfork() 대신 fork()을 사용하면 동일한 동작을 나타냅니다.
  • 스폰 된 프로세스가 중요합니다. 이 경우 'null'X 서버 프로세스가 특정 디스플레이에 생성됩니다. 예를 들어 여기서 'ls'를 포킹하는 것이 차단되지 않거나 블록이 발생할 확률이 상당히 낮습니다. 확실하지 않습니다.
  • 리눅스 2.6.18에서 4.12.8까지 재현 가능합니다. 그래서 이것은 내가 생각하기에 일종의 리눅스 커널 문제가 아닙니다.
  • GCC 4.8.2 및 GCC 7.2.0을 모두 사용하여 재현 가능합니다.

답변

1

그 이유는 후 여기

// Thread A 
int fds[2]; 

m.lock(); 
pipe(fds); 

파이프를 만드는 것이, 또 다른 스레드 수도 단지 vfork() 및 간부

// Thread B 
if (vfork() == 0) { 
    execvp("NullXServer", const_cast<char**>(args)); 
    _exit(0); 
} 

당신은 파일 기술자 플래그를 설정 직전 :

// Thread A again... 
int oldFlags = fcntl(fds[0], F_GETFD); 
fcntl(fds[0], F_SETFD, oldFlags | FD_CLOEXEC); 
oldFlags = fcntl(fds[1], F_GETFD); 
fcntl(fds[1], F_SETFD, oldFlags | FD_CLOEXEC); 
m.unlock(); 

이므로 B의 자식 프로세스가 스레드 A가 만든 파일 설명자를 상속합니다.

vfork()/execvp()을 포함하도록 뮤텍스를 확장하면이 효과를 마이그레이션하는 데 도움이됩니다.

m.lock(); 
pipe(fds); 
int oldFlags = fcntl(fds[0], F_GETFD); 
fcntl(fds[0], F_SETFD, oldFlags | FD_CLOEXEC); 
oldFlags = fcntl(fds[1], F_GETFD); 
fcntl(fds[1], F_SETFD, oldFlags | FD_CLOEXEC); 

if (vfork() == 0) { 
    execvp("NullXServer", const_cast<char**>(args)); 
    _exit(0); 
} 
m.unlock(); 
+0

물론 감사합니다. 지금 부끄러워 한 느낌 :) 물론 뮤텍스는 파일 설명자 플래그를 설정하는 동안 단 하나의 스레드 만 실제로 실행될 것이라고 보장하지 않습니다. 이는 어떤 이유로 든 가지고 있었던 인상입니다. –

+0

@TonvandenHeuvel 당황 할 필요가 없습니다. 동시 코드는 제대로 생각하기에는 너무 자주 피타입니다.) – Ctx