최근에 ksh가 몇 초 동안 차단되면 ksh가 16K 바이트 이상을 표준 출력으로 인쇄한 후 일부 데이터를 잃을 수 있다는 사실을 발견했습니다.
이 test.sh
스크립트는 257*64(16448)바이트를 인쇄합니다.
#!/usr/bin/ksh
i=0
while [[ i -lt 257 ]]
do
x=$(file /tmp)
echo "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE"
i=$((i+1))
done |
while read datafile
do
echo $datafile
done
다음 테스트를 실행했습니다.
0 $ ./test.sh | wc -c
16448
0 $ ./test.sh | (sleep 3; wc -c)
16384
이 줄은 x=$(file /tmp)
두 번째 루프에 아무 것도 보내지 않지만 이 동작에 영향을 미치는 것으로 보입니다.
bash를 사용하면 예상대로 작동합니다.
나에게 이것은 ksh의 버그처럼 보입니다. 저는 솔라리스 5.10을 사용하고 있습니다. 해결책이나 해결 방법이 있습니까? 이 문제의 근본 원인은 무엇입니까? 나는 이것이 파이프 버퍼 크기와 관련이 있을 것이라고 추측합니다.
고마워요, 피터
편집하다:
따라서 run test 를 사용하면 truss
마지막 64바이트를 쓸 때 오류가 발생하는 것을 볼 수 있습니다.
ioctl(0, I_PEEK, 0x08046B40) = 0
Received signal #18, SIGCLD, in write() [caught]
siginfo: SIGCLD CLD_EXITED pid=6561 status=0x0000
write(1, " 0 1 2 3 4 5 6 7 8 9 A B".., 64) Err#4 EINTR
lwp_sigmask(SIG_SETMASK, 0x00020000, 0x00000000) = 0xFFBFFEFF [0x0000FFFF]
setcontext(0x08046670)
read(0, 0x0809064C, 1) = 0
ioctl(0, TCGETA, 0x08046B18) Err#22 EINVAL
dtksh를 사용하여 동일한 스크립트를 실행하는 방법은 다음과 같습니다. Stephane이 지적했듯이 실패한 쓰기는 다시 시도됩니다.
ioctl(0, I_PEEK, 0x08046694) = 1
read(0, " 0 1 2 3 4 5 6 7 8 9 A B".., 64) = 64
Received signal #18, SIGCLD, in write() [caught]
siginfo: SIGCLD CLD_EXITED pid=28276 status=0x0000
write(1, " 0 1 2 3 4 5 6 7 8 9 A B".., 64) Err#4 EINTR
lwp_sigmask(SIG_SETMASK, 0x00020000, 0x00000000) = 0xFFBFFEFF [0x0000FFFF]
waitid(P_ALL, 0, 0x08046500, WEXITED|WTRAPPED|WSTOPPED|WNOHANG) = 0
waitid(P_ALL, 0, 0x08046500, WEXITED|WTRAPPED|WSTOPPED|WNOHANG) Err#10 ECHILD
sigaction(SIGCLD, 0x08046510, 0x08046580) = 0
setcontext(0x08046430)
write(1, 0x080F0FD8, 64) (sleeping...)
write(1, " 0 1 2 3 4 5 6 7 8 9 A B".., 64) = 64
ioctl(0, I_PEEK, 0x08046694) = 0
답변1
이것은 의 버그처럼 보입니다 ksh
.
내가 의심하는 것은
x=$(file /tmp)
ksh
명령을 실행하고 파이프를 통해 출력을 읽는 새로운 프로세스를 생성 file
하고 종료될 때까지 기다리지 않습니다(ksh의 최신 버전을 포함하여 모든 최신 쉘이 이 작업을 수행함). 명령을 읽는 동안 EOF에 도달하면 거기에서 반환됩니다. 파이프.
이 동작은 다음을 실행하여 확인할 수 있습니다.
ksh -c 'x=$(exec sh -c "echo foo;exec >&-; sleep 10"); echo "$x"'
그리고 ksh
출력이 있는지 확인하고 즉시 반환 foo
하거나 10초 후에 반환합니다.
이 경우 file
명령이 종료되고 SIGCLD가 해당 상위(셸)로 전송된다는 의미입니다.뒤쪽에명령이 x=...
반환되었습니다.
쉘의 목적은 하위 프로세스의 종료를 요청하는 SIGCLD를 처리하는 것입니다. 쉘에 백그라운드에서 실행 중인 하위 프로세스가 있는 경우 언제든지 종료될 준비가 되어 있어야 합니다. SIGCLD 신호는 무시할 수 없는 다른 신호와 마찬가지로 시스템 호출을 차단합니다.중단된. 이를 위해 쉘은 다음 중 하나를 통해 준비되어야 합니다.차단하다잠재적으로 중단된 시스템 호출을 실행하는 동안 신호를 내보내거나 신호를 처리한 후 중단된 시스템 호출을 다시 시도합니다.
이 경우에는 아무 일도 일어나지 않은 것 같습니다. 대부분의 경우 write
내장 함수를 실행하는 ksh가 수행하는 시스템 호출은 echo
즉시 반환되므로 중단될 기회가 없지만, 시스템 호출은 stdout이 가리키는 파이프가 가득 차면 write
결국 차단됩니다. SIGCLD에 의해 중단되었습니다. ksh가 다시 시도하지 않습니다. 그게 오류입니다.
Linux에서도 다음을 실행하면 동일한 동작을 볼 수 있습니다.
strace -e write ksh -c 'i=0; while [ "$i" -lt 2000 ]; do : &
echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
i=$(($i+1)); done' | (sleep 3; wc)
그러면 우리는 다음을 봅니다:
write(1, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"..., 61) = ? ERESTARTSYS (To be restarted)
--- SIGCHLD (Child exited) @ 0 (0) ---
write(1, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"..., 61...
마찬가지로 :
명령을 종료하면 차단 write
시스템 호출이 중단되지만 이번에는 write
재시도됩니다.
해결 방법에는 내장 명령을 호출하기 전에 명령 대체를 피하거나 하위 쉘에서 명령을 실행하는 등 SIGCLD를 얻은 프로세스가 아닌 다른 프로세스에 의해 echo
대체가 수행되도록 하는 것이 포함될 수 있습니다.write
echo
(echo "012...")
편집하다: 출력을 자세히 살펴보면 truss
두 번째 루프의 추적이라는 것을 알 수 있습니다. 이는 다른 루프를 실행하는 프로세스와 별도의 프로세스에서 실행되도록 의도되었으므로 명령 종료 시 SIGCLD를 가져서는 안 됩니다 file
. 그러나 첫 번째 루프를 실행하는 하위 쉘이 종료되면 SIGCLD를 얻을 수 있습니다.
또한 테스트 결과에서 알 수 있듯이 ksh가 실제로 명령 대체를 위해 생성된 프로세스를 기다리고 있는 경우 수신된 SIGCLD 신호는 명령의 비동기 종료로 설명될 수 없습니다 file
.
외부 파이프가 가득 찼을 가능성이 더 높아 보이지만 두 while 루프 사이의 파이프는 그렇지 않습니다. SIGCLD는 echo
두 번째 루프의 차단 기간 동안 수신되고 첫 번째 루프가 종료될 때 발생합니다. 따라서 더 효율적인 솔루션은 하위 셸 echo
에서 각 명령을 실행하는 대신 하위 셸에서 두 번째 루프를 실행하는 것입니다 .
while ...; done | (while ...;done)