데몬화 - 윈디하나의 솔라나라

목차

개요

셸이 종료될 때

예제는 로그인 셸을 BASH 로 변경한 후 확인해야 함
아래 문서는 윈디하나의 솔라나라: 솔라리스 11 기본 설정에 나와있는 설정을 모두 했을 경우에 대한 예시다. 특히 로그인 셸을 BASH 로 한 후 테스트해야 한다.
  1. 솔라리스는 셸 종료시 프로세스를 종료하지 않는다

    대기만 하는 프로그램 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 ~ $ 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) 시스템콜을 이용한다. 동작 방식에 다른점이 있는 셈이다. 필자는 두 버전에서 모두 확인해봤으며 이 페이지에서 말하려는 결과는 같다.

  2. 로그인 셸이 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의 경우 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가 기본 셸이다)

  3. 솔라리스는 stdout, stderr에서 작업시 로그아웃된 경우, 오류가 나지만 프로그램을 종료시키지 않는다.

    stdout(3), stderr(3) 으로 10초마다 한번씩 메시지를 출력하는 프로그램 awake 를 만들고 백그라운드로 실행한후 셸을 로그아웃 한다.
    awake.c
    #include <stdio.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(0xFEFFEB00, 0xFEFFEB08) (sleeping...)
    nanosleep(0xFEFFEB00, 0xFEFFEB08)               = 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
    ...
    
    오류가 발생하긴 하지만 프로세스가 종료되지는 않는다. 오류를 처리하지 않았기 때문이다. 잘 만든 데몬은 이 오류를 처리 할 것이며, I/O 오류에 대한 가장 일반적인 처리방법은 프로그램 종료이다. (즉 I/O오류가 나면 프로그램이 종료되는 것은, 시스템이 원래 그래서가 아니라, 프로그래머가 종료되도록 만들었기 때문이다) 오류가 나지 않게 하려면 아래와 같이 실행해 보자.
    windy@wl ~ $ ./awake > /tmp/awake 2>&1 & 2)
    [1] xxxxx
    windy@wl ~ $ logout
    
  4. 솔라리스는 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(3C) 가 실행될때 프로세스는 SIGTTIN에 의해 stop 되며 이때 셸을 종료하면 SIGTERM이 발생해 프로세스가 종료된다.
    2) scanf(3C) 에서 I/O오류가 발생하지 않으며 잘 실행된다.
  5. 결론

C언어로 데몬 만들기 위한 예제

솔라리스에 특화되어있다. 인터넷에 공개되어있는 여러 소스를 조합해 만들었다. 이 프로그램 소스는 첫번째 예제의 데몬화 된 예제이다.
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) 실행시 & 를 쓰지 않아도 이미 데몬으로 띄워져 있다. 당연히 로그아웃해도 문제 없다.

셸 스크립트에서의 시그널 처리

nohup

daemonize

RSS ATOM XHTML 5 CSS3