저는 현재 운영 체제 소개에 대한 과제를 수행하고 있는데 재미있기도 하고 동시에 혼란스럽기도 합니다. 지금 파이프라인 작업 중입니다. 내 코드는 다음과 같습니다.
처음에 내 코드는 다음과 같았습니다.
// 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);
}
이에 대한 내 생각 과정은 상위 프로세스가 하위 프로세스가 종료될 때까지 기다린 다음 파이프에서 데이터를 읽는 것뿐입니다. 나는 이 코드를 토론 페이지의 멘토에게 게시했고 그는 코드에 몇 가지 문제가 있다고 말했습니다. 그 중 하나는 다음과 같습니다.
- 하위 프로세스가 파이프를 차단할 만큼 오랫동안 입력으로 실행되면 상위 프로세스가 무기한 정지될 수 있습니다.
따라서 그는 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
파이프라인은 간단합니다. 수영장 깊은 곳으로 뛰어들면 기분이 나빠질 것입니다. (또는 더 잘 가르치지 않은 것이 선생님의 잘못일 수도 있습니다.)
파이프에 더 익숙해지기 위해 매우 간단한 두 가지 프로그램을 작성하는 것이 좋습니다.
일부 텍스트를 표준 출력에 쓰고 종료하는 메서드입니다. “빠른 갈색 여우가 게으른 개를 뛰어넘었습니다.”처럼 간단한 것일 수도 있습니다. "Lorem ipsum dolor sat amet, consectetur adipiscing elit,...", 여러 번 반복되는 짧은 문자열(단일 문자일 수도 있음) - 원하는 대로 무엇이든 가능합니다.
printf
,write
또는fprintf(stdout, …)
원하는 다른 기능을 사용하세요 .프로그램을 테스트하려면 쉘 프롬프트에서 실행하면 됩니다. 선택한 텍스트가 표시되고 종료되어야 합니다(셸 프롬프트로 돌아갑니다).
표준 입력에서 텍스트를 읽고 표준 출력에 씁니다.
getc
,gets
또는read
원하는 다른 기능을 사용하세요 . 파일 끝에 도달하면 종료합니다. 파일 끝을 나타내는 방법을 보려면 사용하는 기능에 대한 매뉴얼 페이지를 확인하십시오.프로그램을 테스트하려면 텍스트 파일(이름
jon_file.txt
)을 만들고 그 안에 텍스트를 넣으십시오. 다음과 같이 말하여 빠르게 수행할 수도echo "Hello world" > jon_file.txt
있고, 편집기를 사용할 수도 있습니다. 그런 다음 를 입력하면prog2 < jon_file.txt
파일 내용이 표시되고 종료됩니다(셸 프롬프트로 돌아갑니다).
pipe
, 또는 화려한 이름으로 부르지 마세요 . 또는 dup
로 부르지도 마세요 . (언제 무슨 일이 일어나고 있는지 확실히 이해할 수 있도록 디버깅 및/또는 감사 코드를 포함해야 합니다.) 그런 다음 를 실행 하면 예상한 결과를 얻을 수 있습니다.open
close
prog1 | prog2
이제 sleep
프로그램에 호출을 추가하여 이를 "중단"해 보십시오. 깨뜨렸다면 어떻게 했는지 알려주세요. 거의 불가능합니다. 한 프로그램(또는 두 개의 프로그램)을 앉아서 기다리려는 것보다 더 오래 잠자지 않으면 항상 prog2
작성된 모든 데이터가 출력 됩니다 prog1
.
위의 예에서 명확하지 않은 경우: 부모 프로세스와 자식 프로세스(또는 일반적으로 파이프 양쪽의 프로세스)가 "동시에" 실행되도록 하는 것이 올바른 일입니다. 1Reader는 파이프에 데이터가 없기 때문에 "먼저 종료"되지 않습니다.지금. 위의 연습에서 알 수 있듯이, 프로그램이 현재 데이터가 없는 파이프에서 데이터를 읽으려고 시도하면 read
시스템 호출은 프로그램이 데이터가 도착할 때까지 기다리도록 강제합니다. 파이프에 데이터가 없을 때까지 리더는 종료되지 않습니다.
더 이상 오지 않고,한 번.2 (이 시점에서 read
파일 끝이 반환됩니다.) "더 이상 데이터가 제공되지 않음" 조건은 작성자가 파이프를 닫는 것(또는 종료하는 것, 열려 있는 모든 파일 설명자가 exit
호출되기 때문에 동일함)으로 표시됩니다.close
이 시점에서 왜 시스템 호출에 신경을 쓰는지 이해가 안 갑니다 read
. 하지만 아직 시스템 호출을 사용하는 방법을 모른다면 선생님이 지침을 따르지 않는 방식으로 자료를 제시하고 있다는 의심이 확고해집니다. 논리적 순서. (명령 read
이 아닌 시스템 호출을 의미한다고 가정합니다 read
.) 프로그램이 이해되는 유일한 방법은 위의 프로그램 runcmd(pcmd->right)
과 같은 일부 방법을 통해 표준 입력에서 읽는 것 입니다. prog2
귀하의 프로그램은 쉘이 하는 일, 즉 파이프를 설정하고 프로그램을 실행시키는 일을 하는 것처럼 보입니다. 이 수준에서는 귀하의 프로그램이 (우리에게 보여준 범위 내에서) I/O(읽기 또는 쓰기)를 수행할 이유가 없습니다.
__________
1관련 자료:파이프라인 명령은 어떤 순서로 실행되나요?
2 물론 이는 지나친 단순화이다. 곧 배우게 되겠지만, 아직 그렇게 하지 않았다면 파이프에 데이터가 없을 때 리더가 종료되도록 설계할 수 있습니다.지금- 하지만 이는 기본 동작이 아닙니다. 또는 다른 조건(예: q
파이프에서 을 읽는 경우)에서 리더가 종료되도록 설계할 수도 있습니다. 아니면 신호 등에 의해 사망할 수도 있습니다.
6개월 후 이 답변을 되돌아보면 제가 실제로 전체 질문을 다루지 않았음을 알 수 있습니다. 전반부는 다루었지만 전반부는 다루지 않았습니다. 그래서 위의 내용을 계속해서,
첫 번째 프로그램을 수정하고 다음을 작성하세요.많은최소 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 상태 정보와100000
stdout102400
에 쓴 바이트 수가 표시되어야 합니다. (이것은wc -c
표준 입력(파이프)에서 읽은 바이트 수를 보고하는 의 출력이 됩니다 .)sleep
시작하기 10~20초 전에 읽기를 시작 하도록 두 번째 프로그램을 수정하세요 .이를 테스트하려면
prog2 < jon_file.txt
다시 실행하세요. 분명히 에 지정한 시간 동안 일시 중지sleep()
한 다음 파일 내용을 표시하고 종료해야 합니다(셸 프롬프트로 돌아갑니다).
지금 실행하세요 prog1 | prog2 > /dev/null
. 하지만 그렇게 하기 전에 무슨 일이 일어날지 추측해 볼 수도 있습니다.
︙
︙
︙
나는 그것이 몇 개의 점을 인쇄할 것으로 예상했습니다. 아마도 8, 아마도 64 또는 65, 어쩌면 다른 숫자일 수도 있습니다. 그런 다음 일시 중지하고 나머지 점을 인쇄하고 ... 읽지 않았 더라도 즉시 시작할 수 있기 !
때문입니다. 아직 쓰고 있어요. 파이프는 읽기를 시작할 준비가 될 때까지 데이터를 보유할 수 있지만 특정 지점까지만 가능합니다. 파이프에는 버퍼링 제한이 있습니다. 이는 8000(또는 8192), 64000(또는 65536) 또는 기타 숫자일 수 있습니다. 파이프가 가득 차면 시스템은 강제로 대기합니다 . 읽기가 시작 되면 파이프를 비워 파이프에 더 많은 데이터를 위한 공간을 제공하므로 쓰기를 다시 시작할 수 있습니다.prog1
prog2
prog2
prog1
prog2
prog1
처음에 위 동작이 표시되지 않으면 숫자를 200,000바이트, 30초 등으로 늘려보세요.
따라서 선생님이 계획의 첫 번째 초안을 비판할 때 그의 말이 옳습니다. (또는 그의 말이 완전히 맞고 당신이 그를 잘못 인용했을 수도 있습니다.) 아시다시피, 이 버전의 프로그램은 프로그램 (파이프 작성기)이 시작 (파이프 판독기) runcmd(pcmd->left)
되기 전에 완료될 때까지 기다립니다. runcmd(pcmd->right)
그런데 왼쪽 프로그램이 100,000바이트를 출력한다면 어떻게 될까요? 파이프를 채운 다음 더 쓸 수 있을 때까지 기다립니다. 그러나 "누군가"가 파이프에서 읽고 저장소 버퍼를 모두 소모할 때까지는 더 이상 쓸 수 없습니다. 그러나 주 프로그램은 파이프 작성기가 완료될 때까지 파이프 판독기를 시작하지 않습니다. 모든 사람은 다른 사람이 뭔가를 하기를 기다리고 있지만, 첫 번째 사람이 그 일을 해내기 전까지는 그 일을 하지 않습니다. ("돈만 주면 보석을 주겠다."/"아니요.아픈나중에 돈 줘너보석을 주세요. ") 그렇습니다. 결론: 파이프가 가득 차서 데이터를 읽는 프로세스가 없어 파이프를 통한 데이터 이동이 중지되면 두 프로세스 모두 무기한 정지됩니다.
이 상태는 문화적으로 무심코 다음과 같이 불립니다.캐치 22. 컴퓨터 과학에서 공식적인 이름은 다음과 같습니다.이중 자물쇠, 비공식적으로 호출됨치명적인 포옹.