나는 백그라운드에서 세 개의 프로그램을 실행하고 포그라운드에서 몇 개의 프로그램을 실행하는 쉘 스크립트를 가지고 있으며 trap
, 시작하고 실패할 경우 다시 시작할 수 wait
있도록 유닛 파일을 설정했습니다 .systemd
그러나 프로세스가 종료되면 해당 스크립트의 모든 항목이 종료되고 다시 시작되지는 않는다는 사실을 발견했습니다. 이 애플리케이션의 경우 둘 중 하나가 종료되면 다시 시작해야 합니다.
두 가지 합리적인 경로가 있습니다.
- 유닛 파일을 하이브하고 예외가 감지되고 모든 예외가 종료되도록 스크립트를 변경한 다음 스크립트를 다시 실행하세요. 나는 무엇을 해야할지 모르겠습니다.
- 세 가지 백그라운드 프로세스를 각각 별도의 파일이 있는 자체 단위로 구성합니다
.service
. 하지만.service
실패한 파일 중 하나를 종료하고 다시 시작하기 위해 파일을 작성하는 방법을 모르겠습니다 . 나는 그들의 종속성을 순서대로 시작하도록 정렬할 수 있다는 것을 알고 있지만 #2가 죽을 때 #1을 죽이거나 그 반대로 만드는 방법을 모릅니다.
나는 관리자를 작성하거나 프로그램이 그것을 알아내고 스스로 종료하도록 하고 싶지 않습니다. 그것이 목적입니다 systemd
. 나는 단지 올바른 주문을 놓치고 있기를 바라고 있습니다.
.서비스 파일:
[Unit]
Description=Foobar Interface
After=network.target
[Service]
Type=simple
WorkingDirectory=/home/user/scripts
ExecStart=/home/user/scripts/myscript.sh
Restart=always
[Install]
WantedBy=multi-user.target
쿵쿵 스크립트:
#!/usr/bin/env bash
tty_port=/dev/ttyUSB0
#Clean up any old running processes
pkill -f "cat ${tty_port}"
pkill transport
pkill backgroundprogram
#Configure the target
source /home/user/somescript.sh
foregroundprogram
#Set up the serial port
stty -F $tty_port 115200
#Read from the port in the background
cat $tty_port &
tty_pid=$!
#Wait for tty device to waken
sleep 15
#Send commands to tty device
echo "command1" > $tty_port
sleep 1
echo "command2" > $tty_port
sleep 1
#Start up the transport
/home/user/transport &>> /dev/null &
transport_pid=$!
#Wait a bit for the transport to start
sleep 1
#Start up the main process
/home/user/backgroundprogram &
background_pid=$!
#Wait a bit for it to start
sleep 1
#Finally, start the tty device
echo "command3" > $tty_port
trap "kill ${background_pid} ${tty_pid} ${transport_pid}; exit 1" INT
wait
이 모든 작업은 로그에 기록하는 등 작동하지만 세 프로세스 중 하나라도 실패하면 프로세스를 모두 종료하고 다시 시작하지 않고 계속 실행됩니다.
답변1
그러나 프로세스가 종료되면 해당 스크립트의 모든 항목이 종료되고 다시 시작되지는 않는다는 사실을 발견했습니다. 이 애플리케이션의 경우 둘 중 하나가 종료되면 다시 시작해야 합니다.
systemd는 아래 첨자가 아닌 쉘 스크립트를 모니터링합니다. 당신은하지 않을 것입니다생각하다systemd는 하위 프로세스의 종료에 응답합니다. 이로 인해 다시 시작됩니다.명령을 실행할 때마다. 생각해 보세요. 실행되는 쉘 스크립트가 있다면...
date
방금 자식 프로세스를 생성하고 실행한 다음 종료했습니다. 이로 인해 프로세스 감독자가 어떤 조치를 취하는 것을 원하지 않습니다.
systemd가 하위 프로세스를 모니터링하도록 하려면 각 프로세스에 대해 별도의 단위 파일을 생성하십시오.
- 직렬 포트를 구성하고 읽는 장치
- 하나를 위해
/home/user/transport
- 하나를 위해
/home/user/backgroundprogram
systemd 종속성을 사용하여 서비스의 올바른 시작 순서를 보장하고(한 서비스를 중지하면 모두 중지되도록 할 수 있음) 지시문을 사용하여 EnvironmentFile
파일에서 구성을 로드할 수 있습니다(예:).$tty_port
일부 설정 명령("tty 장치에 명령 보내기...")을 한 줄에 입력하거나 ExecStartPre
자체 Type=oneshot
서비스를 받을 수도 있습니다.
답변2
기본 스크립트를 별도의 서비스로 분할할 수 있으면 다음과 같이 쉽게 해결할 수 있습니다.
아래 예에는 s1, s2, s3의 세 가지 생성 서비스가 있으며 대상 s.target을 통해 그룹으로 제어합니다.
참고: 세 가지 서비스가 다음
과 같이 구성된 경우Requires
모두이 그룹에 참여하는 프로세스가 다시 시작됩니다.
또는 s.target 에서 구성하는 경우 Wants
그 중 하나가 충돌하고 다시 생성되면 해당 개별 프로세스만 다시 시작됩니다.
각 서비스에 대해 서비스 파일 s1, s2, s3을 만듭니다.
/etc/systemd/system/s1.service:
[Unit]
Description=my worker s1
After=network.target
Before=foobar.service
PartOf=s.target
[Service]
Type=simple
ExecStart=/usr/local/bin/s1.sh
Restart=always
(참고: 서비스가 동일한 경우하나 [이메일 보호됨]여러 파일 대신 파일. @ 및 %i를 사용하는 서비스 인스턴스는 매뉴얼을 참조하세요. )
이제 s1, s2 및 s3 서비스가 필요한 기본 대상(그룹) 파일을 만듭니다.
/etc/systemd/system/s.target:
[Unit]
Description=main s service
Requires=s1.service s2.service s3.service
# or
# Wants=s1.service s2.service s3.service
[Install]
WantedBy=multi-user.target
완벽한.
평소처럼 지금 달려야 합니다 systemctl daemon-reload
.
이제 서비스를 시작하고 systemctl start s.target
s1, s2 및 s3을 시작할 수 있습니다.
systemctl stop s.target
s1, s2 및 s3을 중지하여 세 가지 서비스를 모두 중지 할 수 있습니다 .
물론, 평소대로 개별 서비스를 시작/중지/다시 시작/상태 설정할 수 있습니다.
systemctl status s1
s1, s2 또는 s3 프로세스를 종료하면 자동으로 다시 생성됩니다(Restart=always).
을 사용하면 Requires
그룹의 모든 프로세스가 다시 시작됩니다.
PS: systemctl enable s.target
부팅 시 서비스를 시작하려면 다음을 실행하세요.
추신: 불행하게도 systemctl을 사용할 때 전체 "s1.service"를 입력하는 대신 "s1"과 같이 "s.target"에 약어 "s"를 사용할 수 없습니다. 이 그룹을 관리하려면 "s.target"을 입력해야 합니다.
답변3
#!/usr/bin/env/python3
# POSIX shell and bash < 4.3 doesn't want to do this.
# https://unix.stackexchange.com/questions/285156/exiting-a-shell-script-if-certain-child-processes-exit
#
# If you haven't written python3 before, be aware the string type
# is Unicode (UTF-8). Python 3.0 aborts on invalid UTF-8.
# Python 3.1 aims to round-trip invalid UTF-8 using "surrogateescape".
# Python 3.2 may accept non-UTF-8 encoding according to your locale.
# ...
#
# * Functions should be better tested.
#
# * Doesn't bother killing (and waiting for) child processes.
# Assumes systemd does it for us.
# Convenient, but I'm not 100% happy about it.
#
# * Otherwise direct translation of nasty script, e.g. use of "sleep".
import sys
import os
import time
tty_port = "/dev/ttyS0" # or: tty_port = sys.environ["tty_port"]
def die(msg):
sys.exit(msg)
# Run program in background
def bg(*argv):
pid = os.fork()
if pid == 0:
# Child process: exec or die
# Either way, we never return from this function.
try:
os.execvp(argv[0], argv)
except Exception as e:
# By convention, child always uses _exit()
sys._exit(e)
assert False
return pid
def __fg(*argv):
pid = bg(*argv)
(_, status) = os.waitpid(pid, 0)
return status
# Run program, wait for exit, die if the program fails
def fg(*argv):
status = __fg(*argv)
if os.WIFEXITED(status):
code = os.WEXITSTATUS(status)
if code != 0:
die("exit status {} from running {}".format(code, argv))
elif os.WIFSIGNALED(status):
die("signal {} when running {}"
.format(os.WTERMSIG(status), argv))
else:
assert False, "Unexpected result from waitpid()"
# Use with care.
# "Any user input that is employed as part of command should be carefully sanitized, to ensure that unexpected shell commands or command options are not executed."
#
def bg_shell(cmd):
return bg("/bin/sh", "-c", cmd)
def fg_shell(cmd):
return fg("/bin/sh", "-c", cmd)
fg("stty", "-F", tty_port, "115200")
tty_pid = bg("cat", tty_port)
print("\"cat {}\" started as pid {}".format(tty_port, tty_pid))
time.sleep(15)
tty_out = open(tty_port, "w")
def tty_print(msg):
tty_out.write(msg)
tty_out.flush()
tty_print("command1")
time.sleep(1)
tty_print("command2")
time.sleep(1)
transport_pid = bg_shell("exec /home/user/transport >/dev/null 2>&1")
print("transport started as pid {}".format(transport_pid))
time.sleep(1)
tty_print("command3")
time.sleep(1)
background_pid = bg("/home/user/backgroundprogram")
print("backgroundprogam started as pid {}".format(background_pid))
(pid, status) = os.wait()
# This could be modified to accept exit code 0 as a success,
# and/or accept exit due to SIGTERM as a success.
if os.WIFEXITED(status):
die("exit status {} from pid {}".format(os.WEXITSTATUS(status)), pid)
elif os.WIFSIGNALED(status):
die("signal {} when running {}".format(os.WTERMSIG(status), pid))
else:
assert False, "Unexpected result from wait()"
답변4
bash
최신 버전의 명령에는 백그라운드 프로세스가 끝날 때까지 기다렸다가 종료하는 wait
옵션이 있습니다 .-n
또한, 여전히 불분명한 이유로 cat
시작과 대기 사이에서 가끔 종료되지만, 지금까지는 종료를 알리지 않습니다 wait
. 그래서 jobs
기다리기 전에 명령을 추가했는데 whcih가 cat
종료되었는지 확인하는 것 같습니다. 그렇다면 대기는 나머지 두 프로세스에만 집중합니다. 아직 종료되지 않은 경우 세 프로세스 중 하나가 종료되면 대기가 종료됩니다.
따라서 내 스크립트의 마지막 줄은 wait
다음으로 대체됩니다.
jobs
wait -n
wait를 호출한 후 작업이 종료되면 wait가 종료되고 systemd는 나머지 하위 프로세스를 모두 종료하고 스크립트를 다시 시작합니다.