프로세스를 시작하기 위해 프로그램을 사용하고 있습니다. 프로그램이 종료되면 stdin이 손실되어 프로세스도 종료되기를 원합니다.
나는 프로그램을 종료하고 proc/pid/fd로 가서 프로세스를 보았고 해당 프로그램의 stdin이 여전히 /dev/pts/2에 연결되어 있음을 발견했습니다.
이 경우 프로세스가 종료되지 않는 이유는 무엇입니까? 더 좋은 점은 표준 입력 파이프가 닫힐 때 프로그램이 닫히도록 하는 데 사용할 수 있는 래퍼나 기술이 있습니까?
답변1
stdin
파일 설명자입니다 0
. 프로세스의 파일 설명자를 닫는 것은 프로세스 자체에 의해서만 능동적으로 수행될 수 있습니다. 프로세스가 종료하기로 결정하면 stdin도 종료됩니다.
이제 프로세스의 stdin이 파이프의 읽기 끝인 경우 파이프의 다른 쪽 끝은 하나 이상의 다른 프로세스에 의해 열릴 수 있습니다. 다른 쪽 끝에 있는 모든 파일 설명자가 닫혀지면 파이프에서 읽으면 파이프에 아직 남아 있는 데이터를 읽지만 궁극적으로 (더 많은 데이터를 기다리는 대신) 아무것도 반환하지 않습니다. 이는 파일이 완료됨을 의미합니다.
cat
, cut
, ...와 같은 wc
표준 입력에서 읽는 응용 프로그램은 일반적으로 이런 일이 발생하면 종료됩니다. 왜냐하면 해당 응용 프로그램의 역할은 더 이상 입력이 없을 때 끝까지 입력을 처리하는 것이기 때문입니다.
입력이 끝날 때 애플리케이션이 종료되도록 하는 마법의 메커니즘은 없으며, 이러한 일이 발생하면 종료하기로 결정하는 것뿐입니다.
존재하다:
echo foo | cat
echo
를 쓰면 종료 "foo\n"
되어 파이프의 쓰기 끝이 닫히고 다른 쪽 끝은 read()
0바이트를 반환하여 더 이상 읽을 것이 없음을 나타내는 다음 종료하기로 결정합니다.cat
cat
cat
존재하다
echo foo | sleep 1
sleep
1초가 경과한 후에만 종료됩니다. 닫힌 파이프인 표준 입력은 그것과 아무 관련이 없으며 sleep
표준 입력에서 읽지도 않습니다.
파이프(또는 소켓)의 쓰기 면이 다릅니다.
읽기 측의 모든 fd가 닫혔을 때 쓰기 측에서 열린 fd에 쓰기를 시도하면 SIGPIPE가 프로세스로 전송되어 프로세스가 종료됩니다(신호를 무시하지 않는 한, 이 경우 ) write()
로 실패 합니다 EPIPE
.
하지만 이런 일은 글을 쓰려고 할 때만 발생합니다.
예를 들어:
sleep 1 | true
true
즉시 종료되고 읽기 측이 즉시 닫히 더라도 sleep
표준 출력에 쓰려고 시도하지 않으므로 종료되지 않습니다.
이제 /proc/fd/pid/n
출력에서 about이 빨간색으로 표시됩니다 ls -l --color
(예:질문의 첫 번째 버전), 이는 ls
해당 심볼릭 링크의 결과에 대해 실행하면 링크 대상의 유형을 결정하기 때문입니다.lstat()
readlink()
파이프, 소켓 또는 기타 네임스페이스의 파일이나 삭제된 파일에서 열린 파일 설명자의 경우 결과는 readlink
파일 시스템의 실제 경로가 아니므로 두 번째로 lstat()
완료된 작업은 ls
실패하고 ls
이를 깨진 기호 링크로 간주합니다. 끊어진 심볼릭 링크는 빨간색으로 나타납니다. 파이프의 다른 쪽 끝이 닫혀 있는지 여부에 관계없이 파이프의 양쪽 끝에 있는 fd에서 해당 결과를 얻을 수 있습니다. ls --color=always -l /proc/self/fd | cat
예를 들어 시도해 보세요.
fd가 손상된 파이프를 가리키는지 확인하려면 Linux에서 lsof
이 -E
옵션을 시도해 볼 수 있습니다.
$ exec 3> >(:) 4> >(sleep 999)
$ lsof -ad3-4 -Ep "$$"
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
zsh 13155 stephane 3w FIFO 0,10 0t0 5322414 pipe
zsh 13155 stephane 4w FIFO 0,10 0t0 5323312 pipe 392,sleep,0r
fd 3의 경우 lsof는 파이프의 읽기 측에서 다른 프로세스를 찾을 수 없습니다. 그러나 다음과 같은 결과가 나올 수 있습니다.
$ exec 5<&3
$ lsof -ad3-5 -Ep "$$"
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
zsh 13155 stephane 3w FIFO 0,10 0t0 5322414 pipe 13155,zsh,5w
zsh 13155 stephane 4w FIFO 0,10 0t0 5323312 pipe 392,sleep,0r
zsh 13155 stephane 5w FIFO 0,10 0t0 5322414 pipe 392,sleep,3w 13155,zsh,3w
fds 3과 5는 읽기 측에 fd가 없기 때문에 여전히 손상된 파이프입니다(lsof에는 sleep
깨진 파이프에 fd 3이 열려 있다는 사실이 어디에도 반영되지 않는다는 점에서 버그가 있는 것 같습니다).
표준 입력에서 열린 파이프가 마지막 기록기를 잃자마자(손상된) 프로세스를 종료하려면 다음을 수행할 수 있습니다.
run_under_watch() {
perl -MIO::Poll -e '
if ($pid = fork) {
$SIG{CHLD} = sub {
wait;
exit($? & 127 ? ($? & 127) + 128 : $? >> 8);
};
$p = IO::Poll->new; $p->mask(STDIN, POLLERR); $p->poll;
kill "TERM", $pid;
sleep 1;
kill "KILL", $pid;
exit(1);
} else {
exec @ARGV
}' "$@"
}
오류 조건에 대해 stdin을 모니터링하고(Linux에서는 파이프에 데이터가 있더라도 기록기가 남아 있지 않는 한 발생하는 것으로 보임) 발생하는 즉시 하위 명령을 종료합니다. 예를 들어:
sleep 1 | run_under_watch sleep 2
sleep 2
1초 후에 프로세스가 종료됩니다.
일반적으로 이렇게 하는 것은 약간 어리석은 일입니다. 이는 입력 끝을 처리할 시간이 생기기 전에 명령을 종료할 수 있음을 의미합니다. 예를 들어:
echo test | run_under_watch cat
cat
때로는 출력(또는 읽기) 시간이 되기 전에 종료되는 경우도 있습니다 "test\n"
. 이 문제를 해결할 수 있는 방법은 없습니다.관찰자명령이 입력을 처리하는 데 걸리는 시간을 알 수 있는 방법이 없습니다. 우리가 할 수 있는 것은 kill "TERM"
명령이 파이프에 남은 내용을 읽고 필요한 작업을 수행하기에 충분하기를 바라기 전에 유예 기간을 주는 것뿐입니다.