예를 들어, 다음과 유사한 쉘 스크립트가 있다고 가정하십시오.
longrunningthing &
p=$!
echo Killing longrunningthing on PID $p in 24 hours
sleep 86400
echo Time up!
kill $p
트릭을해야합니까? 프로세스가 조기에 종료되고 PID가 재활용되었을 수 있다는 점을 제외하고 일부 무고한 작업이 대신 신호 대기열에 폭탄을 가져옵니다. 실제로 이것은 문제가 될 수 있지만 그럼에도 불구하고 저를 걱정합니다. FS에서 PID를 유지하거나 제거하기 위해 장기적으로 해킹을 해킹하는 것이 좋지만 여기서 일반적인 상황을 생각하고 있습니다.
답변
가장 timeout
적합한 명령 을 사용하는 것이 가장 좋습니다.
timeout 86400 cmd
현재 (8.23) GNU 구현은 최소한 alarm()
자식 프로세스를 기다리는 동안 사용 하거나 이와 동등한 방식으로 작동 합니다. 돌아오고 나가는 SIGALRM
사이에 전달되는 것을 막지 않는 것 같습니다 (효과적으로 경보를 취소합니다 ). 작은 창 에서 stderr에 메시지를 작성할 수도 있습니다 (예를 들어, 자식이 코어를 덤프 한 경우). 경주 창을 더 확장 할 수 있습니다 (예를 들어 stderr가 전체 파이프 인 경우 무기한).waitpid()
timeout
timeout
나는 개인적으로 그 한계를 가지고 살 수 있습니다 (이것은 아마도 미래 버전에서 수정 될 것입니다). timeout
또한 올바른 종료 상태를보고하고 다른 코너 사례 (시작시 SIGALRM 차단 / 시작시 무시, 기타 신호 처리 등)를 처리하는 데주의를 기울여야합니다.
근사치로 다음 perl
과 같이 작성할 수 있습니다 .
perl -MPOSIX -e '
$p = fork();
die "fork: $!\n" unless defined($p);
if ($p) {
$SIG{ALRM} = sub {
kill "TERM", $p;
exit 124;
};
alarm(86400);
wait;
exit (WIFSIGNALED($?) ? WTERMSIG($?)+128 : WEXITSTATUS($?))
} else {exec @ARGV}' cmd
http://devel.ringlet.net/sysutils/timelimit/에timelimit
명령 이 있습니다 (GNU 를 몇 개월 앞두고 ).timeout
timelimit -t 86400 cmd
이 alarm()
메커니즘 은 유사한 메커니즘을 사용하지만 처리되지 않은 SIGCHLD
자식을 무시 하는 처리기를 설치 하여 자식 죽어가는 것을 감지합니다. 또한 실행하기 전에 알람 취소 waitpid()
(의 전달을 취소하지 않습니다 SIGALRM
이 계류중인 경우, 그러나이 표기되는 방법은, 나는 그것이 문제가되는 볼 수 없습니다) 및 죽이기 전에 호출 waitpid()
그래서 다시 PID를 죽일 수 없어 ( ).
netpipes 에도 timelimit
명령이 있습니다. 그 중 하나는 수십 년 전에 다른 모든 것보다 오래 걸리고 또 다른 접근법을 취하지 만 중지 된 명령에 대해서는 제대로 작동하지 않으며 1
시간 초과시 종료 상태를 반환합니다 .
질문에 대한보다 직접적인 답변으로 다음과 같은 작업을 수행 할 수 있습니다.
if [ "$(ps -o ppid= -p "$p")" -eq "$$" ]; then
kill "$p"
fi
즉, 프로세스가 여전히 우리의 자식인지 확인하십시오. 다시 말하지만, 프로세스가 종료되고 다른 프로세스에서 해당 pid를 재사용 할 수 있는 작은 경쟁 기간이 있습니다 ( ps
해당 프로세스의 상태 검색 및 종료 사이 kill
).
일부 껍질 ( zsh
, bash
, mksh
), 당신은 작업 사양 대신 PID를 전달할 수 있습니다.
cmd &
sleep 86400
kill %
wait "$!" # to retrieve the exit status
하나의 백그라운드 작업 만 생성하는 경우에만 작동합니다 (그렇지 않으면 올바른 jobspec을 얻는 것이 항상 안정적으로 가능한 것은 아닙니다).
이것이 문제라면 새로운 쉘 인스턴스를 시작하십시오.
bash -c '"$@" & sleep 86400; kill %; wait "$!"' sh cmd
자식 죽으면 쉘이 작업 테이블에서 작업을 제거하기 때문에 작동합니다. 쉘이 호출 kill()
할 때까지 SIGCHLD 신호가 처리되지 않았고 pid를 재사용 할 수 없거나 (기다리지 않았기 때문에) pid를 처리 할 수 없거나 처리되고 작업이 프로세스 테이블에서 제거되었으며 kill
오류를보고합니다. bash
의 kill
작업 테이블에 액세스하기 전에 SIGCHLD를 %
차단하고 확장 한 후에는 차단을 해제합니다 kill()
.
그 것을 피하기 위해 또 다른 옵션 sleep
후에도 주위를 어슬렁 과정 cmd
과, 죽은 bash
나 ksh93
에 파이프를 사용하는 read -t
대신 sleep
:
{
{
cmd 4>&1 >&3 3>&- &
printf '%d\n.' "$!"
} | {
read p
read -t 86400 || kill "$p"
}
} 3>&1
여전히 경쟁 조건이 있으며 명령의 종료 상태를 잃게됩니다. 또한 cmd
fd 4를 닫지 않는다고 가정합니다 .
다음 perl
과 같이 레이스없는 솔루션을 구현해 볼 수 있습니다 .
perl -MPOSIX -e '
$p = fork();
die "fork: $!\n" unless defined($p);
if ($p) {
$SIG{CHLD} = sub {
$ss = POSIX::SigSet->new(SIGALRM); $oss = POSIX::SigSet->new;
sigprocmask(SIG_BLOCK, $ss, $oss);
waitpid($p,WNOHANG);
exit (WIFSIGNALED($?) ? WTERMSIG($?)+128 : WEXITSTATUS($?))
unless $? == -1;
sigprocmask(SIG_UNBLOCK, $oss);
};
$SIG{ALRM} = sub {
kill "TERM", $p;
exit 124;
};
alarm(86400);
pause while 1;
} else {exec @ARGV}' cmd args...
(다른 유형의 코너 케이스를 처리하려면 개선해야합니다).
경쟁이없는 또 다른 방법은 프로세스 그룹을 사용하는 것입니다.
set -m
((sleep 86400; kill 0) & exec cmd)
그러나 프로세스 그룹을 사용하면 관련된 터미널 장치에 대한 I / O가있는 경우 부작용이 발생할 수 있습니다. 에 의해 생성 된 다른 모든 추가 프로세스를 종료하는 추가 이점이 cmd
있습니다.
답변
일반적으로 할 수 없습니다. 지금까지 제공된 모든 답변은 버그가있는 휴리스틱입니다. pid를 사용하여 신호를 안전하게 보낼 수있는 경우는 한 가지뿐입니다. 대상 프로세스가 신호를 전송할 프로세스의 직접적인 자식이고 부모가 아직 신호를 기다리지 않은 경우입니다. 이 경우, 종료 된 경우에도 부모가 기다릴 때까지 pid가 예약됩니다 ( “좀비 프로세스”입니다). 나는 껍질로 깨끗하게 할 수있는 방법을 모른다.
프로세스를 강제 종료하는 또 다른 안전한 방법은 마스터 측이 소유 한 의사 터미널로 설정된 제어 tty로 프로세스를 시작하는 것입니다. 그런 다음 터미널을 통해 신호를 보낼 수 있습니다 (예 : pty 의 문자 쓰기 SIGTERM
또는 SIGQUIT
pty 이상).
스크립팅에서 더 편리한 또 다른 방법은 명명 된 screen
세션 을 사용하고 명령을 화면 세션에 보내서 종료하는 것입니다. 이 프로세스는 스크린 세션에 따라 명명 된 파이프 또는 유닉스 소켓에서 발생하며 안전한 고유 이름을 선택하면 자동으로 재사용되지 않습니다.
답변
-
프로세스를 시작할 때 시작 시간을 저장하십시오.
longrunningthing & p=$! stime=$(TZ=UTC0 ps -p "$p" -o lstart=) echo "Killing longrunningthing on PID $p in 24 hours" sleep 86400 echo Time up!
-
프로세스를 종료하기 전에 중지하십시오 (정말 필수는 아니지만 경쟁 조건을 피하는 방법입니다. 프로세스를 중지하면 pid를 재사용 할 수 없음)
kill -s STOP "$p"
-
해당 PID가있는 프로세스의 시작 시간이 동일한 지 확인하고 그렇다면, 종료하고 그렇지 않으면 프로세스를 계속하십시오.
cur=$(TZ=UTC0 ps -p "$p" -o lstart=) if [ "$cur" = "$stime" ] then # Okay, we can kill that process kill "$p" else # PID was reused. Better unblock the process! echo "long running task already completed!" kill -s CONT "$p" fi
주어진 OS 에서 동일한 PID 및 시작 시간을 가진 프로세스가 하나만있을 수 있기 때문에 작동합니다 .
점검 중에 프로세스를 중지하면 경쟁 조건이 문제가되지 않습니다. 분명히 이것은 임의의 임의의 프로세스가 몇 밀리 초 동안 중단 될 수 있다는 문제가있다. 프로세스 유형에 따라 문제가 될 수도 있고 아닐 수도 있습니다.
개인적으로 나는 단순히 파이썬을 사용하고 psutil
PID 재사용을 자동으로 처리합니다.
import time
import psutil
# note: it would be better if you were able to avoid using
# shell=True here.
proc = psutil.Process('longrunningtask', shell=True)
time.sleep(86400)
# PID reuse handled by the library, no need to worry.
proc.terminate() # or: proc.kill()
답변
Linux 시스템에서는 pid 네임 스페이스를 유지하여 pid가 재사용되지 않도록 할 수 있습니다. /proc/$pid/ns/pid
파일을 통해 수행 할 수 있습니다 .
man namespaces
–이 디렉토리에있는 파일 중 하나를 파일 시스템의 다른 곳에 바인드 마운트 (참조
mount(2)
) 하면 현재 네임 스페이스에있는 모든 프로세스가 종료 되더라도 pid에 의해 지정된 프로세스의 해당 네임 스페이스가 유지됩니다.이 디렉토리의 파일 중 하나 (또는 이러한 파일 중 하나에 바인드 마운트 된 파일)를 열면 pid로 지정된 프로세스의 해당 네임 스페이스에 대한 파일 핸들이 리턴됩니다. 이 파일 설명자가 열려있는 한 네임 스페이스의 모든 프로세스가 종료 되더라도 네임 스페이스는 활성 상태로 유지됩니다. 파일 디스크립터는로 전달 될 수 있습니다
setns(2)
.
이름을 지정하여 프로세스 그룹 (기본적으로 임의의 수의 프로세스)을 분리 할 수 있습니다 init
.
man pid_namespaces
–새로운 공간에 만들어진 제 과정 (즉, 처리를 사용하여 생성
clone(2)
으로 CLONE_NEWPID 플래그 나 호출 후 처리에 의해 생성 된 제 자식unshare(2)
은 USING
CLONE_NEWPID 플래그) 있다 PID 1 및 인init
네임 스페이스에 대한 프로세스 ( 참조init(1)
) . 네임 스페이스 내에서 분리 된 하위 프로세스init(1)
는 동일한 PID 네임 스페이스 에서 하위의 조상 중 하나가prctl(2)
PR_SET_CHILD_SUBREAPER 명령을 사용하여 독립된 하위 프로세스 의 리퍼 로 표시 하지 않는 한이 프로세스 의 상위 프로세스가됩니다 .PID 네임 스페이스 의
init
프로세스가 종료되면 커널은 SIGKILL
신호 를 통해 네임 스페이스의 모든 프로세스를 종료합니다 . 이 동작은 프로세스가 PID 네임 스페이스 의 올바른 작동에 필수적 이라는 사실을 반영합니다 .init
이 util-linux
패키지는 네임 스페이스를 조작하는 데 유용한 많은 도구를 제공합니다. 예를 들어, unshare
사용자 네임 스페이스에 대한 권한을 아직 준비하지 않은 경우 수퍼 유저 권한이 필요합니다.
unshare -fp sh -c 'n=
echo "PID = $$"
until [ "$((n+=1))" -gt 5 ]
do while sleep 1
do date
done >>log 2>/dev/null &
done; sleep 5' >log
cat log; sleep 2
echo 2 secs later...
tail -n1 log
사용자 네임 스페이스를 정렬하지 않은 경우 권한을 즉시 삭제하여 임의의 명령을 안전하게 실행할 수 있습니다. 이 runuser
명령은 패키지에서 제공하는 또 다른 (비 setuid) 바이너리 util-linux
이며 통합하면 다음과 같습니다.
sudo unshare -fp runuser -u "$USER" -- sh -c '...'
…등등.
상기 예에서, 2 개 개의 스위치에 전달 불려하게 플래그 제 자식 프로세스를 생성하고 그 보장 상태 및 지시 플래그 PID를 스페이스를 만들.unshare(1)
--fork
sh -c
init
--pid
unshare(1)
이 sh -c
프로세스는 5 개의 백그라운드 하위 쉘을 생성합니다. 각 쉘은 무한 while
루프 로 true 를 반환 하는 한 계속해서 date
끝에 출력을 추가 합니다. 이 프로세스를 생성 한 후 추가 5 초 동안 호출 한 다음 종료됩니다.log
sleep 1
sh
sleep
-f
플래그가 사용되지 않으면 백그라운드 while
루프 중 어느 것도 종료되지 않지만 그와 함께 종료 된다는 점에 주목할 가치가 있습니다.
산출:
PID = 1
Mon Jan 26 19:17:45 PST 2015
Mon Jan 26 19:17:45 PST 2015
Mon Jan 26 19:17:45 PST 2015
Mon Jan 26 19:17:45 PST 2015
Mon Jan 26 19:17:45 PST 2015
Mon Jan 26 19:17:46 PST 2015
Mon Jan 26 19:17:46 PST 2015
Mon Jan 26 19:17:46 PST 2015
Mon Jan 26 19:17:46 PST 2015
Mon Jan 26 19:17:46 PST 2015
Mon Jan 26 19:17:47 PST 2015
Mon Jan 26 19:17:47 PST 2015
Mon Jan 26 19:17:47 PST 2015
Mon Jan 26 19:17:47 PST 2015
Mon Jan 26 19:17:47 PST 2015
Mon Jan 26 19:17:48 PST 2015
Mon Jan 26 19:17:48 PST 2015
Mon Jan 26 19:17:48 PST 2015
Mon Jan 26 19:17:48 PST 2015
Mon Jan 26 19:17:48 PST 2015
2 secs later...
Mon Jan 26 19:17:48 PST 2015
답변
longrunningthing
좀 더 데몬과 비슷한 방식으로 동작을 개선 하십시오 . 예를 들어 프로세스를 최소한 제한적으로 제어 할 수 있는 pidfile 을 만들 수 있습니다 . 래퍼를 포함하는 원래 바이너리를 수정하지 않고 여러 가지 방법으로이 작업을 수행 할 수 있습니다. 예를 들면 다음과 같습니다.
-
백그라운드에서 필요한 작업을 시작하고 (선택적 출력 리디렉션 사용) 간단한 프로세스 래퍼 스크립트를 사용하여이 프로세스의 PID를 파일에 쓴 다음 프로세스가 완료 될 때까지 기다렸다가 (사용
wait
) 파일을 제거하십시오. 대기 중 프로세스가 예를 들어kill $(cat pidfile)
래퍼는 pidfile이 제거되었는지 확인합니다.
-
모니터 래퍼 는 자체 PID를 어딘가에 배치 하고 여기에 전송 된 신호를 포착 (및 응답)합니다. 간단한 예 :
#!/bin/bash
p=0
trap killit USR1
killit () {
printf "USR1 caught, killing %s\n" "$p"
kill -9 $p
}
printf "monitor $$ is waiting\n"
therealstuff &
p=%1
wait $p
printf "monitor exiting\n"
이제 @R ..과 @ StéphaneChazelas가 지적했듯이 이러한 접근 방식은 종종 경쟁 조건을 갖거나 생성 가능한 프로세스 수에 제한을가합니다. 또한 longrunningthing
may 포크와 어린이가 분리 되는 경우를 처리하지 않습니다 (원래 질문의 문제가 아닐 수도 있음).
최근의 리눅스 커널 (세 몇 읽기)이 멋지게 사용하여 처리 할 수 cgroup을 , 즉 냉장고 – 나는 가정, 일부 현대적인 리눅스 시스템을 사용하는 init을 것입니다.
답변
리눅스 (그리고 몇 가지 다른 *의 nixes도)에서 실행중인 경우, 당신은 죽일하려는 프로세스가 계속 사용하는 경우 당신은 확인할 수 및 명령 줄은 긴 과정과 일치하는지 확인합니다. 같은 것 :
echo Time up!
grep -q longrunningthing /proc/$p/cmdline 2>/dev/null
if [ $? -eq 0 ]
then
kill $p
fi
다른 방법은 다음과 같이 종료하려는 프로세스가 얼마나 오래 실행되는지 확인하는 것 ps -p $p -o etime=
입니다. 에서이 정보를 추출하여 직접 할 수는 /proc/$p/stat
있지만 까다로울 수 있습니다 (시간은 지 피지로 측정되며 시스템 가동 시간 /proc/stat
도 사용해야합니다 ).
어쨌든 일반적으로 검사 후 프로세스를 종료 하기 전에 프로세스가 교체되지 않도록 보장 할 수 없습니다 .
답변
이것은 실제로 매우 좋은 질문입니다.
프로세스 고유성을 결정하는 방법은 (a) 메모리의 어디에 있는지; 그리고 (b) 그 기억에 포함 된 것. 구체적으로 말하면, 각 스레드의 텍스트 영역이 메모리에서 다른 위치를 차지할 것이므로 메모리에서 초기 호출을위한 프로그램 텍스트가 어디에 있는지 알고 싶습니다. 프로세스가 종료되고 동일한 pid로 다른 프로세스가 시작되면 새 프로세스의 프로그램 텍스트는 메모리에서 동일한 위치를 차지하지 않으며 동일한 정보를 포함하지 않습니다.
따라서 프로세스를 시작한 직후 md5sum /proc/[pid]/maps
결과를 저장하십시오. 나중에 프로세스를 종료하려면 다른 md5sum을 수행하고 비교하십시오. 일치하면 pid를 죽입니다. 그렇지 않다면하지 마십시오.
이것을 직접 보려면 두 개의 동일한 bash 쉘을 시작하십시오. /proc/[pid]/maps
그들 에 대해 조사하면 그들이 다르다는 것을 알게 될 것입니다. 왜? 비록 동일한 프로그램이지만 메모리에서 다른 위치를 차지하고 스택의 주소가 다릅니다. 따라서 프로세스가 종료되고 PID가 재사용 되는 경우 동일한 인수를 사용하여 동일한 명령을 다시 시작하더라도 “maps”파일은 달라지며 원래 프로세스를 처리하고 있지 않다는 것을 알게됩니다.
자세한 내용은 proc 매뉴얼 페이지 를 참조하십시오.
파일이 있음을 참고 /proc/[pid]/stat
하면 기본으로이 파일을 사용하는 것을 선호 그렇다면, 등의 과정, 부모 PID의 연령이 파일은 정적 정보와 동적 정보를 모두 포함 : 이미 다른 포스터의이 대답에 언급 된 것을 모든 정보가 들어 를 비교 한 다음을 시작 longrunningthing
하면 stat
파일 에서 다음 정적 필드를 추출하여 나중에 비교할 수 있도록 저장해야합니다.
pid, 파일 이름, 부모의 pid, 프로세스 그룹 ID, 제어 터미널, 시스템 부팅 후 시작된 시간 프로세스, 상주 세트 크기, 스택 시작 주소,
위와 같이 종합하면 위의 과정이 고유하게 식별되므로 다른 방법으로 진행할 수 있습니다. 실제로 “pid”및 “시스템 부팅 후 시간 프로세스 시작”이상을 확신 할 수 있습니다. stat
파일 에서 이러한 필드를 추출 하여 프로세스를 시작할 때 어딘가에 저장하십시오. 나중에 죽이기 전에 다시 추출하고 비교하십시오. 일치하면 원래 프로세스를보고있는 것입니다.