'데몬 코딩 방법'에 해당되는 글 1건

데몬, systemd에 관한 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

리눅스 데몬(Daemon)

백그라운드에서 실행되는 일종의 프로세스이며 오랫동안 유지되는 프로세스입니다. 주로 시스템이 시작될때 같이 시작이 되서 시스템이 종료될때 같이 종료되는 경우가 대부분입니다. 백그라운드에서 돌면서 사용자가 원하는 서비스를 제공하는 프로세스를 의미합니다. 데몬은 일반 백그라운드 프로세스와는 다르게 제어 터미널이 없고 표준 입출력이 없습니다. 즉, 사용자로부터 직접적인 입력를 받지 않고 출력도 하지 않습니다.

데몬의 예는 뭐가 있을까요? 관례상 데몬은 프로세스 이름이 d로 끝납니다. 예를 들어 네트워크 인터페이스들을 감시하고 서버로부터 요청을 감지하는 inetd, 설정된 날짜와 시간이 되면 지정된 명령을 실행시켜주는 cron, 보안 원격 로그인을 제공하는 sshd와 같은 것들이 있지요. 이러한 데몬을 생각해보세요. 사용자가 입력하지도, 출력을 내보내지도 않습니다.

ps명령어를 통해서 어떤 데몬이 있는지 보도록 해봅시다.

   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
      0       1       1       1 ?             -1 Ss       0   1:42 /sbin/init splash
      0       2       0       0 ?             -1 S        0   0:00 [kthreadd]
      2       3       0       0 ?             -1 I<       0   0:00 [rcu_gp]
      2       4       0       0 ?             -1 I<       0   0:00 [rcu_par_gp]
      2       6       0       0 ?             -1 I<       0   0:00 [kworker/0:0H-kblockd]
      2       9       0       0 ?             -1 I<       0   0:00 [mm_percpu_wq]
      2      10       0       0 ?             -1 S        0   0:17 [ksoftirqd/0]
      2      11       0       0 ?             -1 I        0   0:51 [rcu_sched]
      2      12       0       0 ?             -1 S        0   0:01 [migration/0]
      2      13       0       0 ?             -1 S        0   0:00 [idle_inject/0]
      2      14       0       0 ?             -1 S        0   0:00 [cpuhp/0]
      2      15       0       0 ?             -1 S        0   0:00 [kdevtmpfs]
      2      16       0       0 ?             -1 I<       0   0:00 [netns]
      2      17       0       0 ?             -1 S        0   0:00 [rcu_tasks_kthre]
      2      18       0       0 ?             -1 S        0   0:00 [kauditd]
      2      19       0       0 ?             -1 S        0   0:00 [khungtaskd]
      2      20       0       0 ?             -1 S        0   0:00 [oom_reaper]
      2      21       0       0 ?             -1 I<       0   0:00 [writeback]
      2      22       0       0 ?             -1 S        0   0:00 [kcompactd0]
      2      23       0       0 ?             -1 SN       0   0:00 [ksmd]
      2      24       0       0 ?             -1 SN       0   0:00 [khugepaged]
      2      70       0       0 ?             -1 I<       0   0:00 [kintegrityd]
      2      71       0       0 ?             -1 I<       0   0:00 [kblockd]
      2      72       0       0 ?             -1 I<       0   0:00 [blkcg_punt_bio]
      2      73       0       0 ?             -1 I<       0   0:00 [tpm_dev_wq]
      2      74       0       0 ?             -1 I<       0   0:00 [ata_sff]
      2      75       0       0 ?             -1 I<       0   0:00 [md]
      2      76       0       0 ?             -1 I<       0   0:00 [edac-poller]
      2      77       0       0 ?             -1 I<       0   0:00 [devfreq_wq]
      2      78       0       0 ?             -1 S        0   0:00 [watchdogd]
      2      83       0       0 ?             -1 S        0   0:01 [kswapd0]
      2      84       0       0 ?             -1 S        0   0:00 [ecryptfs-kthrea]
      2      86       0       0 ?             -1 I<       0   0:00 [kthrotld]
      2      87       0       0 ?             -1 I<       0   0:00 [acpi_thermal_pm]
      2      88       0       0 ?             -1 S        0   0:00 [scsi_eh_0]
      2      89       0       0 ?             -1 I<       0   0:00 [scsi_tmf_0]
      2      90       0       0 ?             -1 S        0   0:00 [scsi_eh_1]
      2      91       0       0 ?             -1 I<       0   0:00 [scsi_tmf_1]
      2      93       0       0 ?             -1 I<       0   0:00 [vfio-irqfd-clea]
      2      95       0       0 ?             -1 I<       0   0:00 [ipv6_addrconf]
      2     104       0       0 ?             -1 I<       0   0:00 [kstrp]
      2     107       0       0 ?             -1 I<       0   0:00 [kworker/u3:0]
      2     120       0       0 ?             -1 I<       0   0:00 [charger_manager]
      2     121       0       0 ?             -1 I<       0   0:37 [kworker/0:1H-kblockd]
      2     166       0       0 ?             -1 S        0   0:00 [scsi_eh_2]
      2     167       0       0 ?             -1 I<       0   0:00 [scsi_tmf_2]
      2     187       0       0 ?             -1 S        0   0:15 [jbd2/sda5-8]
      2     188       0       0 ?             -1 I<       0   0:00 [ext4-rsv-conver]
      1     227     227     227 ?             -1 S<s      0   0:11 /lib/systemd/systemd-journald
      1     252     252     252 ?             -1 Ss       0   0:26 /lib/systemd/systemd-udevd

 

데몬 코딩 룰

1. 데몬이 파일을 생성할때 적절한 권한이 있어야하는데 umask를 사용하여 파일 모드 생성 마스크를 설정합니다. 보통 0입니다. umask는 생성되는 파일의 생성모드를 정해주는데 umask가 현재 0x022라면 group의 w, other의 w 권한을 주지 않는 다는 것입니다. 따라서 파일을 rwxrwxrwx로 생성한다 하더라도 rwxr-xr-x로 생성이 된다는 겁니다. 

데몬이 파일을 올바르게 생성해야만 하므로 umask(0)을 지정합니다.

2. 자식 프로세스를 생성(fork)한 후 부모 프로세스는 종료(exit)합니다. 이렇게 자식 프로세스보다 부모 프로세스가 먼저 종료되면 initd이 자식 프로세스를 넘겨받게 됩니다. 자식 프로세스는 세션의 리더가 아니며 프로세스 그룹 리더도 아닙니다.

3. setsid를 호출해서 새로운 세션을 생성합니다. setsid는 다음과 같은 세가지 일이 일어납니다.


#include <unistd.h>
pid_t setsid(void);

호출 프로세스가 프로세스 그룹 리더가 아니면 새 세션을 생성합니다.
1. 호출 프로세스가 새 세션의 리더가 됩니다. 
2. 호출 프로세스는 새 프로세스 그룹의 리더가 됩니다. 
3. 호출 프로세스는 제어 터미널이 없습니다.

4. 현재 작업 디렉토리를 루트 디렉토리로 변경합니다. 

5. 필요하지 않은 모든 파일 디스크립터를 닫습니다. 어떻게 열려진 파일 디스크립터를 모두 알 수 있을까요? getrlimit이나 open_max를 사용하면 됩니다.

6. 표준 입출력을 모두 /dev/null로 지정합니다. 데몬은 사용자로부터 입력을 받을 필요가 없고, 출력, 에러를 직접 하지 않습니다. 데몬에는 제어 터미널 장치가 연관되어 있지 않고 출력이 표시될 대상이나 사용자로부터 입력을 받을 통로가 없습니다.

데몬 코딩

위의 코딩 룰 대로 코딩한 데몬 프로그램입니다.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/resource.h>
#include <syslog.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

void daemonize(const char *cmd){
        int i,fd0,fd1,fd2;
        pid_t pid;
        struct rlimit rl;
        struct sigaction sa;

        //umask를 0으로
        umask(0);

        if(getrlimit(RLIMIT_NOFILE,&rl)<0){
                printf("%s: can't get file limit\n",cmd);
                exit(0);
        }

        //자식 프로세스 생성 후 부모는 종료
        if((pid=fork())<0){
                printf("%s: can't fork\n",cmd);
                exit(0);
        }else if(pid !=0)
                exit(0);

        //세션을 만들고 리더가 됨.
        setsid();

        //SIGHUP 무시, 제어터미널 연결이 끊겨도 무시
        sa.sa_handler=SIG_IGN;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags=0;
        if(sigaction(SIGHUP,&sa,NULL)<0){
                printf("%s: can't ignore SIGHUP\n",cmd);
                exit(0);
        }
        if((pid=fork())<0){
                printf("%s:can't fork\n",cmd);
                exit(0);
        }
        else if(pid!=0)
                exit(0);


        // 작업 디렉토리를 /로 변경
        if(chdir("/") < 0){
                printf("%s: can't change directory to /\n",cmd);
                exit(0);
        }

        //열려있는 파일 디스크립터 닫기
        if(rl.rlim_max == RLIM_INFINITY)
                rl.rlim_max = 1024;
        for(i=0;i<rl.rlim_max;i++)
                        close(i);

        //모두 닫았으니 fd는 0부터 return되며 모두 /dev/null로 직행
        fd0=open("/dev/null",O_RDWR);
        fd1=dup(0);
        fd2=dup(0);

        openlog(cmd,LOG_CONS,LOG_DAEMON);
        if(fd0 != 0 || fd1 != 1 || fd2 != 2){
                syslog(LOG_LOCAL0," unexpected fd : %d %d %d",fd0,fd1,fd2);
                exit(1);
        }

        syslog(LOG_LOCAL0,"helloworld! [%s]start",cmd);
}


int main(int argc,char* argv[]){
        daemonize(argv[0]);

        //100초후 데몬 종료
        sleep(100);
}

 

이후에 compile하고 실행시켜봅시다. 


# gcc daemon.c -o dd
# ./dd

아무 변화가 없죠. 이제 ps -axj로 실행시켜보세요.


# ps -efj
...
      1  105975  105974  105974 ?             -1 S        0   0:01 ./dd
 104369  105977  105977  103273 pts/2     105977 R+       0   0:00 ps -axj
...

 

./dd라는 프로세스가 보이네요. 만약 정상적인 프로세스라면 부모 pid도 같이 보여야합니다. 찾아볼까요?


# ps -axj | grep 105974 
      1  105980  105974  105974 ?             -1 S        0   0:01 ./dd

없음을 알 수 있네요. 부모프로세스는 105974인데 먼저 종료가 되었죠. 그렇기 때문에 initd가 그 프로세스를 짬처리 당했고 자식 프로세스는 setsid로 새로운 세션의 리더가 됩니다. 

이러한 사실들을 보면 daemon이 올바르게 초기화되었다는 것을 알 수 있습니다.

 

로그 기록

데몬은 입출력 통로가 없기 때문에 사용자에게 특정한 사실을 알리기 위해서는 파일에 기록하는 식으로 알리게 됩니다.  이때 만약 데몬마다 로그파일이 다르다면 시스템 관리자 입장에서는 어떤 로그파일이 무슨 데몬의 로그인지 확인하기가 까다롭게 됩니다.

그래서 로그를 중앙 집중적으로 관리하게 되고 이때 이 로그를 관장하는 데몬이 syslogd입니다. syslogd를 사용하기 위해서는 우선 syslog가 설치가 되어있어야겠죠. 

이후에는 /etc/syslog.conf 파일 맨 아래에 다음과 같은 설정을 합니다. 

local0.*        /var/log/dd_info

이제 변경사항을 반영하기 위해서 syslogd를 재시작합니다.

# /etc/init.d/syslog restart

 

이제 우리의 데몬을 실행시켜보도록 합시다. 이제 /var/log/dd_info에 우리의 로그가 나오는것을 확인할 수 있을 것입니다.

반응형
블로그 이미지

REAKWON

와나진짜

,