데몬화 - WindyHana's Solanara

목차

개요

셸이 종료될 때

  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 ~ $ 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은 시스템콜을 이용한다. 동작 방식에 다른점이 있는 셈이다. 필자는 두 버전에서 모두 확인해봤으며 이 페이지에서 말하려는 결과는 같다.
  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의 경우 기본값으로 SIGHUP 을 보내지 않지만(자세한 사항은 윈디하나의 솔라나라: Bash 참조) sh의 경우 셸이 종료될 때 SIGHUP를 보낸다. zzz 프로그램에서는 특별히 SIGHUP를 처리하지 않았으므로, default 핸들러가 실행되며, 기본 핸들러의 SIGHUP의 처리 방법은 프로세스 종료다. 따라서 로그아웃시 프로세스가 종료되는 것이다. zzz의 소스를 수정할 수 없는 경우, 데몬으로 남아있으려면 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 는 시그널을 보내지 않는다. 리눅스의 기본 셸은 보통 bash이지만, 솔라리스 10 이하의 기본 셸은 sh다. (솔라리스 11부터 bash가 기본 셸이다)
  3. 솔라리스는 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오류를 처리하는 코드를 넣었어도 종료되지 않았을 것이다.
  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() 가 실행될때 프로세스는 SIGTTIN에 의해 stop 되며 이때 셸을 종료하면 SIGTERM이 발생해 프로세스가 종료된다.
    2) scanf() 에서 I/O오류가 발생하지 않으며 잘 실행된다.
  5. 결론

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

솔라리스에 특화되어있다. 인터넷의 여러 소스를 조합해 만들었다. 이 프로그램 소스는 첫번째 예제의 데몬화 된 예제이다.
daemon.c
 다운로드 (1,139 바이트)
/*
	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 <signal.h>

void signalhandler(int sig)
{
	switch(sig) {
	case SIGHUP:
		break;
	}
}

int main(int argc, char* argv[]) {
	int 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

	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

Twitter RSS IconTexto 올바른 XHTML 1.0 Transitional 입니다 올바른 CSS입니다!