백그라운드 작업을 계속 생성하지만 wait 명령을 호출하지 않는 bash 스크립트와 관련하여 pid 누출 가능성을 확인하고 있었는데 우연히 (strace를 통해) Bash가 SIGCHLD를 모니터링하고 wait4(...)를 자동으로 호출한다는 사실을 알게 되었습니다. , 내 스크립트가 wait 명령을 호출하지 않더라도. 이것이 바로 PID 누출이 없는 이유입니다. 이는 좋은 일입니다. 하지만 그 배경 PID에 대해 대기 명령을 호출하면 어떻게 되는지 생각하기 시작했습니다. /proc에 존재하지 않으며 오류를 반환해야 합니다. Bash는 이를 어떻게 처리합니까? Bash 4.4.19 및 5.1.16에서 몇 가지 실험을 수행한 결과 Bash wait 명령이 실제로 백그라운드 작업 캐시에서 결과를 얻는다는 사실을 발견했습니다. 소스 코드도 확인했습니다. 예를 들면 다음과 같습니다.헤비스트라이크 5.1.16 , buildins/wait.def 라인 253을 참조하세요.
status = wait_for_single_pid (pid, wflags|JWAIT_PERROR);
그런 다음 job.c 라인 2611
r = bgp_search (pid);
의미는
/* Search for PID in the list of saved background pids; return its status if
found. If not found, return -1. We hash to the right spot in pidstat_table
and follow the bucket chain to the end. */
.
내 실험은
테스트 1:
bash <<'EOF'
bash -c 'sleep 1; exit 9' &
PID=$!
echo $PID
sleep 2
ls -d /proc/$PID
wait $PID
echo wait result: $?
EOF
결과 :
16079
ls: cannot access '/proc/16079': No such file or directory
wait result: 9
이는 Bash wait 명령이 캐싱을 사용한다는 증거입니다(저는 이를 확인하기 위해 strace도 사용했으며 wait4
-1을 반환하는 마지막 시스템 호출을 명확하게 보여줍니다.
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 9}], 0, NULL) = 397
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 399
wait4(-1, 0x7ffd79fc3fd0, WNOHANG, NULL) = -1 ECHILD (No child processes)
물론, disown -a
이전에 실행 했다면 코드 127: 을 반환할 것입니다 wait
. 이는 또한 백그라운드 pid가 백그라운드 작업 목록에서 제거되면 wait 명령이 올바른 코드로 종료되지 않는다는 것을 확인합니다.wait
wait: pid xxxxxx is not a child of this shell
이는 Bash wait 명령이 백그라운드 작업 관리 정보에 캐시된 결과를 사용하고 있다는 결론을 내립니다.
그렇다면 내 질문은 다음과 같습니다. 예를 들어 스크립트가 지속적으로 백그라운드 작업을 생성하는 경우
테스트 2:
while true; do
echo hi &
done
그러면 백그라운드 작업 캐시가 점점 커지는데 메모리 누수가 발생할까요?
이 스크립트를 테스트했는데 메모리 누수가 없는 것 같은데 왜 누수가 없나요?
편집: 좀 더 명확하게 말하자면, 위 스크립트는 메모리가 부족할 것으로 예상되지만 실제로는 내가 관찰한 대로 메모리가 부족하지 않습니다. 이유는 무엇입니까?
편집: 위의 질문은 test2
여전히 가장 흥미로운 질문입니다. 왜 메모리가 부족하지 않습니까?
편집: 또 다른 테스트를 했는데 몇 초 후에 메모리가 부족해졌습니다.
테스트 3:
bash <<'EOF'
while true; do
sleep 10 &
echo $!
done
EOF
밝혀지다
...
bash: fork: retry: Resource temporarily unavailable
bash: fork: retry: Resource temporarily unavailable
bash: fork: Interrupted system call
좋습니다. 이제 예상대로 작동합니다. 메모리가 부족합니다.
죄송합니다. 제 질문은 다음과 같습니다. 이것이 의도된 것입니까? 백그라운드 작업이 지속적으로 생성된다는 경고를 들어본 적이 없습니다. 지금까지 제가 아는 유일한 해결책은 disown
백그라운드 작업 관리를 중지하거나 (cmd&)
백그라운드 작업으로 관리하지 않고 프로세스를 시작하는 등의 다른 트릭을 사용하는 것입니다.
편집: 내 대답: 이것은 의도적으로 설계된 것입니다. 이는 Bash가 모든 활성 작업을 추적하고 짧은 시간 내에 활성 작업이 많으면 메모리가 부족하다는 의미입니다. 그러므로 이것은 와 모순되지 않습니다 test2
.
편집: Bash 백그라운드 작업 종료 코드 캐시가 활성 작업이 아닌 마지막 작업의 종료 코드를 캐시할 뿐만 아니라 모든 작업의 종료 코드도 캐시한다는 것을 보여주기 위해 또 다른 테스트를 추가했습니다.
테스트 4:
bash -x <<'EOF'
bash -c '/bin/sleep 3; exit 1' &
PID1=$!
bash -c '/bin/sleep 6; exit 2' &
PID2=$!
wait $PID1
echo exit code of first process is: $?
wait $PID2
echo exit code of second process is: $?
wait $PID1
echo Get exit code of first process again, result is: $?
EOF
결과 :
+ PID1=2357449
+ bash -c '/bin/sleep 3; exit 1'
+ PID2=2357450
+ wait 2357449
+ bash -c '/bin/sleep 6; exit 2'
+ echo exit code of first process is: 1
exit code of first process is: 1
+ wait 2357450
+ echo exit code of second process is: 2
exit code of second process is: 2
+ wait 2357449
+ echo Get exit code of first process again, result is: 1
Get exit code of first process again, result is: 1
답변1
첫째, 최대 PID(Resources에서 볼 수 있음 /proc/sys/kernel/pid_max
)가 일반적으로 32768로 제한되어 있으므로 메모리가 고갈될 수 없습니다. 따라서 더 많은 프로세스를 실행하더라도 결국 pid는 커널에 의해 회수되므로 커널은 최대 pid 수를 회수하게 됩니다. 메모리에 보관할 PID는 bash
< 32768입니다.
크기는 bash
또한 당신에 달려 있습니다nproc
(최대 사용자 프로세스 수) 제한.
다음 스크립트를 사용하면 bash에서 이것이 올바른지 쉽게 확인할 수 있습니다.
#!/bin/bash
declare -a pids_list=()
for i in {1..4196}; do
(exit 0) & waitpid=$! && wait $waitpid
pids_list+=($waitpid)
done
export KEPT=0 DISCARDED=0
for i in "${pids_list[@]}"
do
wait $i 2>/dev/null
if [ $? -ne 127 ] # If the child is not found in the jobs table, wait returns 127
then
let KEPT++
else
let DISCARDED++
KEPT=0
fi
done
echo KEPT=$KEPT DISCARDED=$DISCARDED
이 예에서는 백그라운드에서 4096+100=4196개의 작업을 실행하고 pids_list 배열에 pid를 유지하면서 각 작업이 완료될 때까지 기다립니다. 모든 작업이 완료된 후 pids_list 배열을 반복하여 bash가 여전히 상태를 유지하는지 확인합니다.
제 경우 기본 최대 프로세스 제한은 4096입니다.
$ ulimit -u
4096
이 코드를 스크립트 또는 소스 코드로 실행하면 처음 100개 PID의 상태를 삭제하고 마지막 4096개 PID만 메모리에 유지하는 것으로 확인됩니다.
$ check_pid_table.sh
KEPT=4096 DISCARDED=100
제한을 1024개로 줄이면 유지되는 프로세스 수는 이만큼입니다.
$ ulimit -u 1024
$ check_pid_table.sh
KEPT=1024 DISCARDED=3172
제한을 늘리면 모든 PID가 유지됩니다(그러나 다시 한 번 - 최대 제한까지 pid_max
).
$ ulimit -u 8192
$ check_pid_table.sh
KEPT=4196 DISCARDED=0
Bash에서 프로세스 테이블은 얼마나 많은 메모리를 차지합니까?
bash
또한 유지해야 하는 다양한 PID 수에 의해 소비되는 메모리 양을 확인할 수도 있습니다. 내가 여기서 사용하는 것은time(1)
bash
프로세스에서 사용하는 메모리를 확인하는 명령입니다 .
%M
time
확인 중Maximum resident set size of the process during its lifetime, in Kbytes.
$ ulimit -u 65536
$ for i in {1..8} {25..32}; do
> /usr/bin/time -f "number of procs=$i KB, memory=%M KB" bash -c '
> for (( i=$0 ; i>0 ; i-- )); do
> echo >/dev/null & wait $!
> done' $(($i*1024))
> done
number of procs=1 KB, memory=2904 KB
number of procs=2 KB, memory=2936 KB
number of procs=3 KB, memory=2968 KB
number of procs=4 KB, memory=3000 KB
number of procs=5 KB, memory=3032 KB
number of procs=6 KB, memory=3064 KB
number of procs=7 KB, memory=3096 KB
number of procs=8 KB, memory=3128 KB
number of procs=25 KB, memory=3672 KB
number of procs=26 KB, memory=3704 KB
number of procs=27 KB, memory=3736 KB
number of procs=28 KB, memory=3768 KB
number of procs=29 KB, memory=3796 KB
number of procs=30 KB, memory=3796 KB
number of procs=31 KB, memory=3796 KB
number of procs=32 KB, memory=3796 KB
각 1K 프로세스 블록이 메모리 소비를 약 32K 증가시키는 것을 볼 수 있습니다 bash
. 이는 각 프로세스 항목이 32비트를 사용한다는 의미입니다.
그러나 32KB(한도 max_pid
)에 가까워질수록 메모리가 정적이 되는 것을 볼 수 있습니다. 이는 내가 말했듯이 결국 pid가 재활용될 것이기 때문입니다(그리고 이미 내 시스템에서 많은 프로세스가 실행되고 있습니다).
답변2
누출이 아닙니다. 기억이 손실되지 않기 때문입니다. 쉘은 PID를 추적하므로 이론적으로는 결국 메모리가 부족해질 수 있지만 이 모든 것은 예상된 것이며 메모리 사용량을 관리합니다.
POSIX에서 셸은 활성 PID와 마지막으로 종료된 PID의 결과만 추적합니다.
wait [-n] [n ...]
지정된 각 하위 프로세스를 기다리고 종료 상태를 반환합니다. 각각은n
프로세스 ID이거나 작업 사양일 수 있습니다. 작업 사양이 제공되면 작업 파이프라인의 모든 프로세스가 대기됩니다. 지정하지 않으면n
현재 활성화된 모든 하위 프로세스가 대기되고 반환 상태는 0입니다.-n
이 옵션이 제공 되면wait
작업이 종료될 때까지 기다렸다가 종료 상태를 반환합니다.n
존재하지 않는 프로세스나 작업을 지정 하면 반환 상태는 127입니다. 그렇지 않은 경우 반환 상태는 마지막 대기 프로세스 또는 작업의 종료 상태입니다.
bash
그러나 당신은 (적어도 4.4.12에서 5.2까지) 정확합니다.POSIX와 호환되지 않음, bash --posix
동작조차도 POSIX와 호환되지 않습니다. 대신 모든 백그라운드 프로세스 상태가 유지됩니다. 이는 "test4"에서 성공적으로 시연되었습니다. POSIX 호환 방법을 사용하여 결과를 비교합니다 dash
.
exit code of first process is: 1
exit code of second process is: 2
Get exit code of first process again, result is: 127
bash
파일의 소스 코드 보기nojobs.c
특히 함수 alloc_pid_list
( wait_builtin
in에서 호출됨)wait.def
), 각 관리 PID에 대한 배열의 추가 12바이트 항목을 사용합니다 pid_list
. 다른 이유로 시스템 리소스를 소진하기 전에는 어레이 크기 증가로 인해 시스템 리소스가 소진될 가능성이 줄어듭니다.