파이프가 닫힐 때까지 읽기

파이프가 닫힐 때까지 읽기

저는 현재 운영 체제 소개에 대한 과제를 수행하고 있는데 재미있기도 하고 동시에 혼란스럽기도 합니다. 지금 파이프라인 작업 중입니다. 내 코드는 다음과 같습니다.

처음에 내 코드는 다음과 같았습니다.

// Child process - write
if (fork() == 0) {
    fprintf(stderr, "Child\r\n");
    close(1);
    dup(p[1]);
    close(p[0]);
    close(p[1]);
    runcmd(pcmd->left);
// Parent process - read
} else {
    wait(0);
    close(0);
    dup(p[0]);
    close(p[0]);
    close(p[1]);
    fprintf(stderr, "Parent\r\n");
    runcmd(pcmd->right);
}

이에 대한 내 생각 과정은 상위 프로세스가 하위 프로세스가 종료될 때까지 기다린 다음 파이프에서 데이터를 읽는 것뿐입니다. 나는 이 코드를 토론 페이지의 멘토에게 게시했고 그는 코드에 몇 가지 문제가 있다고 말했습니다. 그 중 하나는 다음과 같습니다.

  1. 하위 프로세스가 파이프를 차단할 만큼 오랫동안 입력으로 실행되면 상위 프로세스가 무기한 정지될 수 있습니다.

따라서 그는 wc데이터를 사용할 수 있을 때까지 파이프에서 기다렸다가 파이프가 닫힐 때까지 읽기를 시작하는 차단 읽기 명령을 사용하는 것이 올바른 구현이라고 언급했습니다.

파이프에 데이터가 있는 동안 파이프에서 데이터를 "읽는" 방법을 찾아보았지만 이를 해결하는 방법을 잘 모르겠습니다. 마지막으로 차단 파이프에서 영원히 대기할 수 있는 문제를 해결하기 위해 부모와 자식을 동시에 병렬로 실행했지만 이는 읽기 프로세스가 먼저 종료되고 모든 데이터를 읽지 못할 수 있음을 의미할 수 있습니다. 완료 전에 작성되었습니다. 이 문제를 어떻게 해결할까요?

    int p[2];
    pipe(p);
    // Child process - read
    if (fork() == 0) {
        fprintf(stderr, "Start child\r\n");
        close(0);
        dup(p[0]);
        close(p[0]);
        close(p[1]);
        fprintf(stderr, "Child\r\n");
        runcmd(pcmd->right);
    // Parent process - write
    } else {
        fprintf(stderr, "Start parent\r\n");
        close(1);
        dup(p[1]);
        close(p[0]);
        close(p[1]);
        fprintf(stderr, "Parent\r\n");
        runcmd(pcmd->left);
   }

편집: 또한 명령을 시도했지만 read버퍼와 읽을 예상 크기(?)가 필요하기 때문에 실제로 사용하는 방법을 잘 모르겠습니다. 들어오는 데이터의 크기를 모르는 경우 이를 검색하는 방법을 잘 모르겠습니다.

답변1

파이프라인은 간단합니다. 수영장 깊은 곳으로 뛰어들면 기분이 나빠질 것입니다. (또는 더 잘 가르치지 않은 것이 선생님의 잘못일 수도 있습니다.)

파이프에 더 익숙해지기 위해 매우 간단한 두 가지 프로그램을 작성하는 것이 좋습니다.

  1. 일부 텍스트를 표준 출력에 쓰고 종료하는 메서드입니다. “빠른 갈색 여우가 게으른 개를 뛰어넘었습니다.”처럼 간단한 것일 수도 있습니다. "Lorem ipsum dolor sat amet, consectetur adipiscing elit,...", 여러 번 반복되는 짧은 문자열(단일 문자일 수도 있음) - 원하는 대로 무엇이든 가능합니다. printf, write또는 fprintf(stdout, …)원하는 다른 기능을 사용하세요 .

    프로그램을 테스트하려면 쉘 프롬프트에서 실행하면 됩니다. 선택한 텍스트가 표시되고 종료되어야 합니다(셸 프롬프트로 돌아갑니다).

  2. 표준 입력에서 텍스트를 읽고 표준 출력에 씁니다. getc, gets또는 read원하는 다른 기능을 사용하세요 . 파일 끝에 도달하면 종료합니다. 파일 끝을 나타내는 방법을 보려면 사용하는 기능에 대한 매뉴얼 페이지를 확인하십시오.

    프로그램을 테스트하려면 텍스트 파일(이름 jon_file.txt)을 만들고 그 안에 텍스트를 넣으십시오. 다음과 같이 말하여 빠르게 수행할 수도 echo "Hello world" > jon_file.txt있고, 편집기를 사용할 수도 있습니다. 그런 다음 를 입력하면 prog2 < jon_file.txt파일 내용이 표시되고 종료됩니다(셸 프롬프트로 돌아갑니다).

pipe, 또는 화려한 이름으로 부르지 마세요 . 또는 dup로 부르지도 마세요 . (언제 무슨 일이 일어나고 있는지 확실히 이해할 수 있도록 디버깅 및/또는 감사 코드를 포함해야 합니다.) 그런 다음 를 실행 하면 예상한 결과를 얻을 수 있습니다.opencloseprog1 | prog2

이제 sleep프로그램에 호출을 추가하여 이를 "중단"해 보십시오. 깨뜨렸다면 어떻게 했는지 알려주세요. 거의 불가능합니다. 한 프로그램(또는 두 개의 프로그램)을 앉아서 기다리려는 것보다 더 오래 잠자지 않으면 항상 prog2작성된 모든 데이터가 출력 됩니다 prog1.

위의 예에서 명확하지 않은 경우: 부모 프로세스와 자식 프로세스(또는 일반적으로 파이프 양쪽의 프로세스)가 "동시에" 실행되도록 하는 것이 올바른 일입니다. 1Reader는   파이프에 데이터가 없기 때문에 "먼저 종료"되지 않습니다.지금. 위의 연습에서 알 수 있듯이, 프로그램이 현재 데이터가 없는 파이프에서 데이터를 읽으려고 시도하면 read시스템 호출은 프로그램이 데이터가 도착할 때까지 기다리도록 강제합니다. 파이프에 데이터가 없을 때까지 리더는 종료되지 않습니다. 더 이상 오지 않고,한 번.2   (이 시점에서 read파일 끝이 반환됩니다.) "더 이상 데이터가 제공되지 않음" 조건은 작성자가 파이프를 닫는 것(또는 종료하는 것, 열려 있는 모든 파일 설명자가 exit호출되기 때문에 동일함)으로 표시됩니다.close

이 시점에서 왜 시스템 호출에 신경을 쓰는지 이해가 안 갑니다 read. 하지만 아직 시스템 호출을 사용하는 방법을 모른다면 선생님이 지침을 따르지 않는 방식으로 자료를 제시하고 있다는 의심이 확고해집니다. 논리적 순서. (명령 read이 아닌 시스템 호출을 의미한다고 가정합니다 read.) 프로그램이 이해되는 유일한 방법은 위의 프로그램 runcmd(pcmd->right)과 같은 일부 방법을 통해 표준 입력에서 읽는 것 입니다. prog2귀하의 프로그램은 쉘이 하는 일, 즉 파이프를 설정하고 프로그램을 실행시키는 일을 하는 것처럼 보입니다. 이 수준에서는 귀하의 프로그램이 (우리에게 보여준 범위 내에서) I/O(읽기 또는 쓰기)를 수행할 이유가 없습니다.
__________
1관련 자료:파이프라인 명령은 어떤 순서로 실행되나요?
2 물론 이는 지나친 단순화이다. 곧 배우게 되겠지만, 아직 그렇게 하지 않았다면 파이프에 데이터가 없을 때 리더가 종료되도록 설계할 수 있습니다.지금- 하지만 이는 기본 동작이 아닙니다. 또는 다른 조건(예: q파이프에서 을 읽는 경우)에서 리더가 종료되도록 설계할 수도 있습니다. 아니면 신호 등에 의해 사망할 수도 있습니다.


6개월 후 이 답변을 되돌아보면 제가 실제로 전체 질문을 다루지 않았음을 알 수 있습니다. 전반부는 다루었지만 전반부는 다루지 않았습니다. 그래서 위의 내용을 계속해서,

  1. 첫 번째 프로그램을 수정하고 다음을 작성하세요.많은최소 100,000(10 5 ) 또는 102400(2 10 ×10 2 ) 문자의 데이터가 표준 출력으로 전송됩니다. 또한 아직 수행하지 않은 경우 일부 지속적인 상태 정보를 stderr에 기록하도록 수정하세요. 예를 들어 ".1000(또는 1024)자마다 " "를 stderr로 보내고, !\n완료되면 " "를 stderr로 보냅니다.

    이를 테스트하려면 prog1 > /dev/null위의 조언을 따랐다면 100점(.) 다음에 !개행 문자가 옵니다. sleep() 에서 호출이나 시간이 많이 소요되는 기타 기능을 조작 하지 않는 경우 prog1이 출력은 매우 빠르게 나타납니다.

    그런 다음 prog1 | wc -c. 위에서 설명한 대로 stderr 상태 정보와  100000stdout 102400에 쓴 바이트 수가 표시되어야 합니다. (이것은 wc -c표준 입력(파이프)에서 읽은 바이트 수를 보고하는 의 출력이 됩니다 .)

  2. sleep시작하기 10~20초 전에 읽기를 시작 하도록 두 번째 프로그램을 수정하세요 .

    이를 테스트하려면 prog2 < jon_file.txt다시 실행하세요. 분명히 에 지정한 시간 동안 일시 중지 sleep()한 다음 파일 내용을 표시하고 종료해야 합니다(셸 프롬프트로 돌아갑니다).

지금 실행하세요 prog1 | prog2 > /dev/null. 하지만 그렇게 하기 전에 무슨 일이 일어날지 추측해 볼 수도 있습니다.

    ︙

    ︙

    ︙

나는 그것이 몇 개의 점을 인쇄할 것으로 예상했습니다. 아마도 8, 아마도 64 또는 65, 어쩌면 다른 숫자일 수도 있습니다. 그런 다음 일시 중지하고 나머지 점을 인쇄하고 ... 읽지 않았 더라도 즉시 시작할 수 있기 !때문입니다. 아직 쓰고 있어요. 파이프는 읽기를 시작할 준비가 될 때까지 데이터를 보유할 수 있지만 특정 지점까지만 가능합니다. 파이프에는 버퍼링 제한이 있습니다. 이는 8000(또는 8192), 64000(또는 65536) 또는 기타 숫자일 수 있습니다. 파이프가 가득 차면 시스템은 강제로 대기합니다 . 읽기가 시작 되면 파이프를 비워 파이프에 더 많은 데이터를 위한 공간을 제공하므로 쓰기를 다시 시작할 수 있습니다.prog1prog2prog2prog1prog2prog1

처음에 위 동작이 표시되지 않으면 숫자를 200,000바이트, 30초 등으로 늘려보세요.

따라서 선생님이 계획의 첫 번째 초안을 비판할 때 그의 말이 옳습니다. (또는 그의 말이 완전히 맞고 당신이 그를 잘못 인용했을 수도 있습니다.) 아시다시피, 이 버전의 프로그램은 프로그램 (파이프 작성기)이 시작 (파이프 판독기) runcmd(pcmd->left)되기 전에 완료될 때까지 기다립니다. runcmd(pcmd->right)그런데 왼쪽 프로그램이 100,000바이트를 출력한다면 어떻게 될까요? 파이프를 채운 다음 더 쓸 수 있을 때까지 기다립니다. 그러나 "누군가"가 파이프에서 읽고 저장소 버퍼를 모두 소모할 때까지는 더 이상 쓸 수 없습니다. 그러나 주 프로그램은 파이프 작성기가 완료될 때까지 파이프 판독기를 시작하지 않습니다. 모든 사람은 다른 사람이 뭔가를 하기를 기다리고 있지만, 첫 번째 사람이 그 일을 해내기 전까지는 그 일을 하지 않습니다. ("돈만 주면 보석을 주겠다."/"아니요.아픈나중에 돈 줘보석을 주세요. ") 그렇습니다. 결론: 파이프가 가득 차서 데이터를 읽는 프로세스가 없어 파이프를 통한 데이터 이동이 중지되면 두 프로세스 모두 무기한 정지됩니다.

이 상태는 문화적으로 무심코 다음과 같이 불립니다.캐치 22. 컴퓨터 과학에서 공식적인 이름은 다음과 같습니다.이중 자물쇠, 비공식적으로 호출됨치명적인 포옹.

관련 정보