Bash의 하위 프로세스로 SIGTERM 전달

Bash의 하위 프로세스로 SIGTERM 전달

다음과 비슷한 Bash 스크립트가 있습니다.

#!/bin/bash
echo "Doing some initial work....";
/bin/start/main/server --nodaemon

이제 스크립트를 실행하는 bash 셸이 SIGTERM 신호를 받으면 실행 중인 서버에도 SIGTERM을 보내야 합니다(이렇게 하면 트랩이 불가능해집니다). 그게 가능합니까?

답변1

노력하다:

#!/bin/bash 

_term() { 
  echo "Caught SIGTERM signal!" 
  kill -TERM "$child" 2>/dev/null
}

trap _term SIGTERM

echo "Doing some initial work...";
/bin/start/main/server --nodaemon &

child=$! 
wait "$child"

일반적으로 bash하위 프로세스가 실행되는 동안 모든 신호는 무시됩니다. 로 서버를 시작하면 &셸 작업 제어 시스템의 배경에 배치되고 $!서버의 PID( 및 와 함께 사용됨 wait) 가 저장됩니다 kill. 그런 다음 호출은 wait지정된 PID(서버)가 있는 작업이 완료될 때까지 기다립니다.또는 방출될 신호.

쉘이 이를 수신하면 SIGTERM(또는 서버가 독립적으로 종료될 때) 호출이 리턴됩니다 wait(서버의 종료 코드로 종료하거나 신호가 수신된 경우 신호 번호 + 128로 종료). 그 후, 쉘이 SIGTERM을 수신하면 _term종료하기 전에 SIGTERM 트랩 핸들러로 지정된 함수를 호출합니다(여기서 우리는 정리를 수행하고 신호를 서버 프로세스에 수동으로 전파합니다 kill).

답변2

Bash는 SIGTERM과 같은 신호를 현재 대기 중인 프로세스에 전달하지 않습니다. 스크립트를 종료하고 싶다면계속해서 입력하세요서버(서버를 직접 시작한 것처럼 신호 및 기타 모든 것을 처리할 수 있도록 허용)를 사용해야 합니다 exec.쉘을 열려는 프로세스로 바꾸십시오.:

#!/bin/bash
echo "Doing some initial work....";
exec /bin/start/main/server --nodaemon

어떤 이유로든 셸을 보존해야 하는 경우(예: 서버 종료 후 일부 정리 작업이 필요한 경우) , trapwait를 조합하여 사용해야 합니다 kill. 바라보다센서 스미스의 답변.

답변3

안드레아스 웨슨은 다음과 같이 지적합니다.OP의 예와 같이 호출에서 돌아올 필요가 없는 경우 명령을 통해 호출하는 것만으로도 exec충분합니다(@Stuart P. Bentley의 답변). 그렇지 않으면 "전통적인" trap 'kill $CHILDPID' TERM(@cuonglm의 답변)이 시작되지만 wait트랩 핸들러가 실행된 후에 호출이 실제로 반환되며 이는 하위 프로세스가 실제로 종료되기 전에 여전히 가능합니다. 따라서 "추가" 호출을 하는 것이 좋습니다 wait(@user1463361의 답변).

이는 개선되었지만 여전히 경쟁 조건을 생성합니다. 즉, 신호 전송자가 TERM 신호 전송을 다시 시도하지 않는 한 프로세스가 종료되지 않을 수 있습니다. 취약점 창은 트랩 핸들러 등록과 하위 프로세스의 PID 기록 사이에 있습니다.

다음은 취약점을 제거합니다(재사용을 위해 함수에 패키지됨).

prep_term()
{
    unset term_child_pid
    unset term_kill_needed
    trap 'handle_term' TERM INT
}

handle_term()
{
    if [ "${term_child_pid}" ]; then
        kill -TERM "${term_child_pid}" 2>/dev/null
    else
        term_kill_needed="yes"
    fi
}

wait_term()
{
    term_child_pid=$!
    if [ "${term_kill_needed}" ]; then
        kill -TERM "${term_child_pid}" 2>/dev/null 
    fi
    wait ${term_child_pid} 2>/dev/null
    trap - TERM INT
    wait ${term_child_pid} 2>/dev/null
}

# EXAMPLE USAGE
prep_term
/bin/something &
wait_term

답변4

위 답변에 대한 몇 가지 추가 사항은 다음과 같습니다.

  1. "&"를 사용하여 백그라운드에서 프로세스를 시작하고 제안된 대로 해당 PID를 기다립니다.@cuonglm자식 프로세스가 실행되는 동안 핸들러가 실행되도록 허용하지만 자식 프로세스가 분리되자마자 stdin이 닫히기 때문에 자식 프로세스는 입력을 캡처하는 기능을 잃게 됩니다. stdin을 계속 열어 두려면 하위 프로세스에 파이프되는 무한 루프를 추가하면 됩니다. 바라보다이 게시물.

그런 다음 현재 셸에서 입력을 읽고 이를 자식 프로세스의 proc 파일에 기록하여 표준 입력에 입력합니다.

(while true; do sleep 10000; done) | /bin/start/main/server --nodaemon &
child_pid=$!
while :
do
    result=$(kill -0 $mypid > /dev/null 2>&1)
    if [ $? -ne 0 ] ; then
        # process is gone
        break
    else
        # read input in the current shell and store it in a variable. The timeout only works with Bash, not with Bourne-Shell. You will need to find a way to read stdin instead and sleep 1 sec between each loop
        read -t 1 input 
        
        # echo the input to the proc file of the runuser process so it goes to its stdin
        echo $input > /proc/$child_pid/fd/0 2>/dev/null 
    fi
done
wait $child_pid

참고: 이는 Linux에서는 잘 작동하지만 다른 Unix 플랫폼에서는 약간의 조정이 필요할 수 있습니다.

편집하다: 더 간단한 접근 방식은 표준 입력을 파일 설명자에 복사한 다음 백그라운드 프로세스의 표준 입력으로 사용할 수 있는 것입니다.

exec 3<&0
/bin/start/main/server --nodaemon <&3 &
  1. exec는 @이 제안한 두 번째 솔루션입니다.스튜어트 벤틀리그러나 때로는 새 PID를 사용하여 프로세스를 생성해야 하거나 사용 중인 명령에서 새 PID 또는 새 PGID를 사용하여 프로세스를 선택하고 생성하는 것을 허용하지 않을 수 있습니다(-l을 사용하는 runuser의 경우). 옵션).

  2. 1)과 2)의 대안은 특정 PID를 대상으로 하는 대신 프로세스 그룹에 신호를 보내는 것입니다.

이는 하위 프로세스의 PID 앞에 빼기 기호(-)를 사용하여 Kill을 사용하여 수행할 수 있습니다.

kill -TERM -$child_pid

Bash를 사용하면 백그라운드에서 프로세스를 시작하지 않고도 프로세스 그룹을 대상으로 하는 신호를 포착할 수 있습니다. 이 방법은 stdin을 읽는 자식 프로세스의 능력을 잃지 않습니다. 핸들러를 사용하면 신호를 자식에게 전달할 수 있으므로 자식이 다른 프로세스 그룹에서 실행되는 경우에도 좋은 솔루션입니다. 제한 사항은 그룹의 다른 구성원도 신호를 수신한다는 것인데, 이는 상황에 따라 문제가 될 수도 있고 그렇지 않을 수도 있습니다.

관련 정보