데몬화 - 윈디하나의 솔라나라
|
솔라리스에서는 셸 로그아웃하면 종료된다.는 이야기를 들어서, 이와 같은 글을 작성했다. 결론적으로 OS 문제가 아니라 셸 문제다.
#include <unistd.h> int main(int argc, char **argv) { while (1) { sleep(10); } }
windy@wl ~ $ cc -o zzz zzz.c
windy@wl ~ $ ./zzz &
[1] xxxxx
windy@wl ~ $ Ctrl+D
logout 1)
1) 로그아웃 메시지는 나오지만 계속 접속이 유지되는 경우도 있다. 이럴 경우 강제로 터미널을 종료한다.
※ 솔라리스 9 x86 windy@wl ~ $ truss -p xxxxx sigsuspend(0x08047B48) (sleeping...) Received signal #14, SIGALRM, in sigsuspend() [caught] 1) sigsuspend(0x08047B48) Err#4 EINTR setcontext(0x080478CC) alarm(0) = 0 sigprocmask(SIG_UNBLOCK, 0x08047B28, 0x00000000) = 0 sigaction(SIGALRM, 0x08047A88, 0x00000000) = 0 alarm(0) = 0 sigaction(SIGALRM, 0x08047A88, 0x08047B08) = 0 sigprocmask(SIG_BLOCK, 0x08047B28, 0x08047B38) = 0 alarm(10) = 0 sigsuspend(0x08047B48) (sleeping...) ... windy@wl ~ $ ※ 솔라리스 10 x86 windy@wl ~ $ truss -p xxxxx nanosleep(0x08047D98, 0x08047DA0) (sleeping...) 1) nanosleep(0x08047D98, 0x08047DA0) = 0 nanosleep(0x08047D98, 0x08047DA0) (sleeping...) nanosleep(0x08047D98, 0x08047DA0) = 0 ... windy@wl ~ $1) 솔라리스 10과 솔라리스 9에서 실행해보면 truss(1) 결과가 다르다. (아마 다른 버전의 솔라리스, 다른 버전의 컴파일러로 했다면, truss(1)의 결과는 또 다를 것이다) 솔라리스 9은 시그널을 이용하고, 솔라리스 10은 nanosleep(2) 시스템콜을 이용한다. 동작 방식에 다른점이 있는 셈이다. 필자는 두 버전에서 모두 확인해봤으며 이 페이지에서 말하려는 결과는 같다.
bash
를 사용했다. 프로그래머가 조치를 취하지 않는 한, 로그아웃 했다고 해서 프로그램이 종료되지 않는다는 사실을 보였다. 로그아웃 한다고 해서 프로그램이 종료되는 것은 아니다.솔라리스때문이 아니다. 하지만 현실적으로 '솔라리스는 로그아웃 할 때 셸에서 실행한 프로그램이 종료된다'고 잘못 알고있는 경우가 많은데 이 이유는 다음절에서 설명한다. 부모 프로세스가 종료되었다고 자식 프로세스를 종료하는 운영체제는 없다.
windy@wl $ ./zzz & 20945 windy@wl $ exit
windy@wl ~ $ truss -p 20945
nanosleep(0x08047D18, 0x08047D20) (sleeping...)
nanosleep(0x08047D18, 0x08047D20) = 0
nanosleep(0x08047D18, 0x08047D20) (sleeping...)
Received signal #1, SIGHUP, in nanosleep() [default]
nanosleep(0x08047D18, 0x08047D20) Err#4 EINTR
bash의 경우 bash 가 종료될 때 자식 프로세스에게 SIGHUP 시그널을 보내지 않지만 (자세한 사항은 윈디하나의 솔라나라: Bash 참조) sh의 경우 sh가 종료될 때 SIGHUP 시그널을 보낸다. zzz 프로그램에서는 특별히 SIGHUP를 처리하지 않았으므로, 기본 처리기가 실행되며, SIGHUP의 기본 처리 방법은 '프로세스 종료'다. 따라서 로그 아웃시 프로세스가 종료되는 것이다. 따라서, zzz의 소스를 수정할 수 없는데 데몬으로 남아있으려면 SIGHUP를 대신 처리해주는 프로그램이 필요하다. 그것이 nohup(1) 이라는 프로그램이다. 이를 사용하면 아래와 같이 실행된다.
windy@wl $ nohup ./zzz & 출력을 nohup.out(으)로 보내는 중 21187 windy@wl $ exit
windy@wl ~ $ truss -p 21187
nanosleep(0x08047D18, 0x08047D20) (sleeping...)
nanosleep(0x08047D18, 0x08047D20) = 0
nanosleep(0x08047D18, 0x08047D20) (sleeping...)
Received signal #1, SIGHUP, in nanosleep() [ignored]
nanosleep(0x08047D18, 0x08047D20) (sleeping...)
nanosleep(0x08047D18, 0x08047D20) = 0
nanosleep(0x08047D18, 0x08047D20) (sleeping...)
SIGHUP은 동일하게 전달되지만 무시하도록 설정했기 때문에, 기본 처리기가 실행되지 않아 결과적으로 프로그램이 종료되지 않는다.
sh(1) 외에도 ksh(1) 도 SIGHUP 을 보낸다. csh(1), bash(1) 는 (별도로 세팅을 하지 않는다면) 시그널을 보내지 않는다. 리눅스 배포판의 기본 셸은 보통 bash 이며 sh 는 제공하지 않지만, 솔라리스 10 이하의 기본 셸은 sh 다. (솔라리스 11부터 bash가 기본 셸이다)
#include <stdio.h> #include <unistd.h> int main(int argc, char **argv) { while (1) { sleep(10); printf("HELLO\n"); fprintf(stderr, "ERROR\n"); } }아래와 같이 실행시킨 후 로그아웃 하자.
windy@wl ~ $ cc -o awake awake.c
windy@wl ~ $ ./awake & 1)
[1] xxxxx
windy@wl ~ $ logout
다른 콘솔을 열어 truss(1) 으로 확인해보면 아래와 같이 오류가 발생하고 있음을 알 수 있다.
windy@wl ~ $ truss -p xxxxx nanosleep(0xFEFFE800, 0xFEFFE808) = 0 write(1, " H E L L O\n", 6) = 6 write(2, " E R R O R\n", 6) = 6 nanosleep(0xFEFFE800, 0xFEFFE808) (sleeping...) nanosleep(0xFEFFE800, 0xFEFFE808) = 0 write(1, " H E L L O\n", 6) Err#5 EIO write(2, " E R R O R\n", 6) Err#5 EIO nanosleep(0xFEFFE800, 0xFEFFE808) (sleeping...)EIO 오류가 발생하긴 하지만 프로세스가 종료되지는 않는다. 오류를 처리하지 않았기 때문이다. 잘 만든 데몬은 이 오류를 처리 할 것이며, I/O 오류에 대한 가장 일반적인 처리방법은 프로그램 종료이다. (즉 I/O오류가 나면 프로그램이 종료되는 것은, 시스템이 원래 그래서가 아니라, 프로그래머가 종료되도록 만들었기 때문이다) 오류가 나지 않게 하려면 아래와 같이 실행해 보자.
windy@wl ~ $ ./awake > /tmp/awake 2>&1 & 2)
[1] xxxxx
windy@wl ~ $ logout
#include <stdio.h> #include <unistd.h> int main(int argc, char **argv) { while (1) { char str[50]; sleep(10); scanf("%s", str); } }
windy@wl ~ $ cc -o awake2 awake2.c windy@wl ~ $ ./awake2 & 1) [1] xxxxx windy@wl ~ $ logout windy@wl ~ $ ./awake2 < /dev/null & 2) [1] xxxxx windy@wl ~ $ logout1) scanf(3C) 가 실행될때 프로세스는 SIGTTIN에 의해 stop 되며 이때 셸을 종료하면 SIGTERM이 발생해 프로세스가 종료된다.
windy@wl ~ $ truss -p xxxxx Stopped by signal #26, SIGTTIN, in read() Received signal #15, SIGTERM, in read() [default] siginfo: SIGTERM pid=10178 uid=100 SI_USER read(0, 0xFE2D53BC, 1024) Err#4 EINTR
SIGTERM
이 발생하지 않도록 리다이렉션 하거나 SIGTERM
시그널을 캐치해 처리해준다. 또한 로그인 셸로 bash 가 아닌 sh 를 사용하는 경우, 셸이 로그아웃 될때 전달되는 SIGHUP
시그널을 처리(또는 무시)해야 한다.nohup ./daemon > /tmp/awake 2>&1 < /dev/null &
처럼 실행시켜줘도 될 것이다. 하지만 관리자 입장에서는 불편하다. 따라서 프로그래머에게 데몬으로 개발해줄 것을 요청하자. 다음 챕터에 샘플 소스가 나와있다.daemon.c | (1,281 바이트) |
/* Daemon App Example for Solaris WindyHana's Solanara: Daemonize http://www.solanara.net/solanara/daemon cc -o daemon daemon.c */ #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <stropts.h> #include <fcntl.h> #include <termio.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/wait.h> #include <signal.h> void signalhandler(int sig) { pid_t pid; int stat_loc; switch(sig) { case SIGHUP: break; case SIGCHLD: pid = wait(&stat_loc); break; } } int main(int argc, char* argv[]) { pid_t pid; int devttyfd; if ((pid = fork()) < 0) { perror("Cannot Fork!\n"); _exit(0); } if (pid > 0) { _exit(0); } setpgrp(); // reset process group id // redirect stdin, stdout, stderr to /dev/null. You can redirect to regular file. freopen("/dev/null", "r", stdin); freopen("/dev/null", "w", stdout); freopen("/dev/null", "w", stderr); // reset TTY if ((devttyfd = open("/dev/tty", O_WRONLY)) < 0) { ; // cannot open tty. do nothing. } else { (void) ioctl(devttyfd, TIOCNOTTY, (char *) 0); (void) close(devttyfd); } signal(SIGHUP, signalhandler); // set signal for SIGHUP signal(SIGCHLD, signalhandler); while (1) { sleep(10); // start daemon. } }
windy@wl ~ $ cc -o daemon daemon.c
windy@wl ~ $ ./daemon 1)
windy@wl ~ $ ps -ef | grep daemon
windy 27676 1 0 16:02:13 ? 0:00 ./daemon
windy@wl ~ $
1) 실행시 & 를 쓰지 않아도 이미 데몬으로 띄워져 있다. 당연히 로그아웃해도 문제 없다.
daemon.sh | (170 바이트) |
#!/bin/bash PID=$$ trap "echo SIGHUP trap" SIGHUP trap "echo SIGTERM trap" SIGTERM echo "Hit Ctrl+C to terminate." echo "PID is $PID" while [ true ]; do sleep 1 done
콘솔 1 콘솔 2 windy@wl ~ $ ./daemon.sh Hit Ctrl+C to terminate. PID is 22512 SIGHUP trap root@wl ~ # kill -HUP 22512 SIGTERM trap root@wl ~ # kill 22512
SIGHUP
시그널과 SIGQUIT
시그널을 무시하도록 해주는 프로그램이다. 아울러 입출력(stdout, stderr)을 nohup.out
으로 리다이렉트 해준다.nohup 커맨드 [커맨드의인자]
주어진 커맨드를 실행시킨후 SIGHUP, SIGQUIT를 무시하도록 하고, nohup.out 으로 출력을 리다이렉트한다.
nohup -p [-Fa] PID
주어진 PID를 가진 프로세스를 잡고 있는 또 다른 프로세스가 없고, SIGHUP, SIGQUIT 핸들러가 없다면 시그널을 무시하도록 변경 하고, nohup.out 으로 출력을 리다이렉트한다.
-a 옵션은 프로세스에 이미 SIGHUP과 SIGQUIT핸들러가 있어도 시그널을 무시하도록 변경한다.
-F 옵션은 프로세스를 이미 다른 프로세스가 잡고(Grab) 있어도 목표 프로세스를 잡는다.
nohup -g [-Fa] PGID
-g를 이용하면 프로세스의 그룹 단위로 프로세스를 잡을 수 있다. PGID는 프로세스의 그룹ID를 뜻한다. 나머지는 -p 옵션과 같다.
windy@wl ~/src $ wget https://github.com/bmc/daemonize/archive/refs/tags/release-1.7.8.tar.gz windy@wl ~/src $ tar xvfz daemonize-release-1.7.8.tar.gz windy@wl ~/src $ cd daemonize-release-1.7.8 windy@wl ~/src/daemonize-release-1.7.8 $ ./configure CFLAGS="-m64" LDFLAGS="-m64" windy@wl ~/src/daemonize-release-1.7.8 $ make windy@wl ~/src/daemonize-release-1.7.8 $ sudo make install
daemonize [-a] [-c directory] [-e stderr] [-o stdout] [-p pidfile] [-l lockfile] [-u user] [-v] path [arg] ...
RSS ATOM XHTML 5 CSS3 |
Copyright © 2004-2025 Jo HoSeok. All rights reserved. |