솔라리스는 셸 종료시 프로세스를 종료하지 않는다
대기만 하는 프로그램 zzz를 만들고 백그라운드로 실행한 후 셸을 로그아웃 한다.
zzz.c
#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 ~ $ 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의 결과는 또 다를 것이다) 솔라리스 9은 시그널을 이용하고, 솔라리스 10은 시스템콜을 이용한다. 동작 방식에 다른점이 있는 셈이다. 필자는 두 버전에서 모두 확인해봤으며 이 페이지에서 말하려는 결과는 같다.
- 일부러 간단한 프로그램을 만들어 예로 들었으며, 위에서는 로그인 셸로 bash 를 사용했다. 프로그래머가 조치를 취하지 않는 한, 로그아웃 했다고 해서 프로그램이 종료되지 않는다는 점을 기억해야 한다. 원칙적으로 [로그아웃 해도 프로그램이 종료되지 않는다]는 사실을 인지하자.
- 하지만 현실적으로 로그아웃 했는데 프로그램이 종료되는 프로그램이 많다. 이건 결과적으로 프로그래머가 그렇게 만들었기 때문이다. 따라서 이렇게 개발된 프로그램을 종료되지 않도록 해야 한다.
- 말하고자 하는건, 로그아웃시, 실행했던 프로그램이 종료되는 이유는 [솔라리스] 때문이 아니다.
로그인 셸이 sh 인 경우 셸이 로그아웃될때 SIGHUP를 차일드 프로세스에게 보낸다.
windy의 로그인 셸을 sh 로 변경해 로그인한 후, 위에서 만든 프로그램을 다시 실행해 보자.
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의 경우 기본값으로 SIGHUP 을 보내지 않지만(자세한 사항은 윈디하나의 솔라나라: Bash 참조) sh의 경우 셸이 종료될 때 SIGHUP를 보낸다. zzz 프로그램에서는 특별히 SIGHUP를 처리하지 않았으므로, default 핸들러가 실행되며, 기본 핸들러의 SIGHUP의 처리 방법은 프로세스 종료다. 따라서 로그아웃시 프로세스가 종료되는 것이다. 이런 경우에는 SIGHUP를 대신 처리해주는 프로그램이 필요한데 그것이 nohup 이라는 프로그램이다.
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...)
sh 외에도 ksh 도 SIGHUP 을 보낸다. csh, bash 는 시그널을 보내지 않는다. 솔라리스의 기본 셸은 sh다.
솔라리스는 stdout, stderr에서 작업시 로그아웃된 경우, 오류가 나지만 프로그램을 종료시키지 않는다.
stdout, stderr 으로 10초마다 한번씩 메시지를 출력하는 프로그램 awake 를 만들고 백그라운드로 실행한후 셸을 로그아웃 한다.
awake.c
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv) {
while (1) {
sleep(10);
printf("HELLO\n");
perror("ERROR\n");
}
}
windy@wl ~ $ cc -o awake awake.c
windy@wl ~ $ ./awake & 1)
[1] xxxxx
windy@wl ~ $ logout
windy@wl ~ $ ./awake > /tmp/awake 2>&1 & 2)
[1] xxxxx
windy@wl ~ $ logout
1) truss로 조사해보면, printf(), perror() 에서 I/O오류가 발생하지만 종료되지는 않는다. 위 프로그램에서 I/O오류를 처리하지 않았기 때문이다. 잘 만든 데몬은 이 오류를 처리 할 것이며, I/O 오류에 대한 가장 일반적인 처리방법은 프로그램 종료이다. (즉 I/O오류가 나면 프로그램이 종료되는 것은, 시스템이 원래 그래서가 아니라, 프로그래머가 종료되도록 만들었기 때문이다)
2) truss로 조사해보면, printf(), perror() 에서 I/O오류가 발생하지 않으며 잘 실행된다. 따라서 프로그래머가 I/O오류를 처리하는 코드를 넣었어도 종료되지 않았을 것이다.
솔라리스는 stdin에서 작업시 로그아웃 된 경우 SIGTERM 시그널을 보내 프로그램을 종료한다.
stdin에서 10초마다 한번씩 메시지를 입력받는 프로그램 awake2 를 만든다.
awake2.c
#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 ~ $ logout
1) scanf() 가 실행될때 프로세스는 SIGTTIN에 의해 stop 되며 이때 셸을 종료하면 SIGTERM이 발생해 프로세스가 종료된다.
2) scanf() 에서 I/O오류가 발생하지 않으며 잘 실행된다.
결론
- 셸이 종료되도 프로세스가 계속 떠 있게 하려면 우선 stdout, stderr 에 대한 I/O 오류를 없애야 하고, stdin 에 대해 SIGTERM 이 발생하지 않도록 리다이렉션 하거나 SIGTERM 시그널을 캐치해 처리해준다. 또한 로그인 셸로 bash 가 아닌 sh 를 사용하는 경우, 셸이 로그아웃 될때 전달되는 SIGHUP 시그널을 처리(또는 무시)해야 한다.
- [nohup ./daemon > /tmp/awake 2>&1 < /dev/null &] 처럼 실행시켜줘도 될 것이다. 하지만 관리자 입장에서는 불편하다. 따라서 아래와 같이 프로그래머에게 아래와 비슷한 방법으로 프로그래밍 해주길 요청한다.