[Linux] 데몬 프로세스 생성 정리

mommers·2026년 2월 4일

Linux

목록 보기
41/59

데몬 프로세스 생성 (Daemon)

1. 데몬(Daemon)이란?

백그라운드에서 실행되며 특정 서비스를 제공하는 장기 실행 프로세스 (예: httpd, sshd).

  • 제어 터미널(Controlling Terminal)이 없음: 사용자의 입력을 직접 받지 않습니다.
  • 부모 프로세스: 과거에는 init(PID 1), 최신 리눅스에서는 systemd가 관리합니다.
  • 로그: 표준 입출력이 없으므로 syslogjournald를 통해 로그를 남깁니다.

2. 데몬 생성 방식 비교(SysV vs systemd)

가장 중요한 차이점은 프로그래머가 얼마나 많은 코드를 직접 짜야 하는지에 대한 것 입니다.

비교 항목고전 방식 (SysV / Old School)현대 방식 (systemd / New School)
구현 난이도복잡함 (직접 코딩 필요)매우 간단 (설정 파일 위임)
프로세스 생성fork() 2번 (Double Fork)systemd가 직접 실행 (Type=simple)
터미널 제어setsid(), close(fd) 직접 호출systemd가 알아서 처리
로그 처리파일 open 후 직접 기록printf만 하면 journald가 수집
관리PID 파일 직접 생성/삭제systemd가 PID 추적 및 관리

3. 고전적 데몬 생성 (Coding the Hard Way)

  1. fork(): 부모를 종료시켜 쉘이 "끝났다"고 착각하게 만듦 (백그라운드 진입).
  2. setsid(): 새로운 세션을 만들어 터미널 제어권 박탈 (필수).
  3. chdir("/"): 작업 디렉토리를 루트로 이동 (USB 등을 마운트 해제 못 하는 문제 방지).
  4. umask(0): 파일 생성 권한 제약을 없앰.
  5. close(fd) & dup2: 표준 입출력(0,1,2)을 닫거나 /dev/null로 돌림.

3.1 고전적 데몬 예제1

SysV는 Double Fork를 통해 부모와 연결을 끊고, 터미널 제어권을 완전히 박탈합니다.

  • classic_daemon.c

이 코드는 Double Fork 기법을 사용하여 터미널 제어권을 완벽하게 제거하고, /dev/null로 표준 입출력을 돌린 뒤, syslog를 통해 로그를 남깁니다.

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

// 실제 서비스 로직 (5초마다 로그 기록)
void main_service_loop() {
    int count = 0;
    while (1) {
        // 터미널이 없으므로 printf 대신 syslog 사용
        syslog(LOG_INFO, "My Classic Daemon is alive! Count: %d", count++);
        sleep(5);
    }
}

// 데몬화 함수 (핵심)
static void skeleton_daemon() {
    pid_t pid;

    // 1. First Fork: 부모 프로세스 종료 (백그라운드 전환)
    pid = fork();
    if (pid < 0) exit(EXIT_FAILURE);
    if (pid > 0) exit(EXIT_SUCCESS);

    // 2. 새로운 세션 생성 (터미널 제어권 박탈)
    if (setsid() < 0) exit(EXIT_FAILURE);

    // 3. 시그널 처리 (SIGHUP 무시)
    signal(SIGCHLD, SIG_IGN);
    signal(SIGHUP, SIG_IGN);

    // 4. Second Fork: 세션 리더가 아님을 보장 (터미널 재할당 방지)
    pid = fork();
    if (pid < 0) exit(EXIT_FAILURE);
    if (pid > 0) exit(EXIT_SUCCESS);

    // 5. 파일 모드 마스크 초기화 (권한 문제 방지)
    umask(0);

    // 6. 작업 디렉토리 변경 (마운트된 파일시스템 잠금 방지)
    if (chdir("/") < 0) {
        syslog(LOG_ERR, "Could not change working directory to /");
        exit(EXIT_FAILURE);
    }

    // 7. 표준 입출력 닫기 및 /dev/null 리다이렉션
    // (printf 등이 에러 없이 동작하도록 구멍으로 연결)
    int x;
    for (x = sysconf(_SC_OPEN_MAX); x >= 0; x--) {
        close(x);
    }

    // fd 0, 1, 2를 /dev/null로 연결
    int fd0 = open("/dev/null", O_RDWR); // stdin
    int fd1 = dup(0);                    // stdout
    int fd2 = dup(0);                    // stderr
    
    // syslog 초기화
    openlog("classic_daemon", LOG_PID, LOG_DAEMON);
}

int main() {
    // 데몬 만들기 시작
    skeleton_daemon();

    // 여기까지 오면 완벽한 데몬 상태임
    syslog(LOG_NOTICE, "Daemon started successfully.");
    
    // 실제 서비스 시작
    main_service_loop();

    // 종료 (실제로는 도달하지 않음)
    closelog();
    return EXIT_SUCCESS;
}

3-2) 실행 및 확인 방법

① 컴파일

gcc classic_daemon.c -o classic_daemon

② 실행

./classic_daemon
# (아무런 메시지 없이 쉘 프롬프트가 바로 떨어져야 정상)

③ 동작 확인 (PS 확인)

터미널(TTY)이 ?로 표시되어야 하며, PPID1(systemd/init)이어야 합니다.

ps -efj | grep classic_daemon
# 출력 예시:
# andrew   12345     1  12344 ?        00:00:00 ./classic_daemon
  • TTY = ?: 터미널 없음 (성공).
  • PPID = 1: 부모가 init/systemd (성공).

④ 로그 확인 (Syslog)

printf 내용이 화면에 안 나오고 시스템 로그에 기록됩니다.

# Ubuntu/Debian 계열
tail -f /var/log/syslog | grep classic_daemon

# 출력 예시:
# Feb  1 16:30:00 hostname classic_daemon[12345]: Daemon started successfully.
# Feb  1 16:30:00 hostname classic_daemon[12345]: My Classic Daemon is alive! Count: 0
# Feb  1 16:30:05 hostname classic_daemon[12345]: My Classic Daemon is alive! Count: 1

⑤ 종료 방법

데몬은 스스로 죽지 않으므로 kill 명령어로 종료해야 합니다.

killall classic_daemon
# 또는
kill <PID>

3-3) 핵심 코드

  1. setsid(): 이 호출이 없으면 데몬이 터미널을 붙잡고 있어서, 터미널을 닫으면 데몬도 같이 죽습니다. 필수 과정입니다.
  2. /dev/null 리다이렉션: 데몬 내부에서 실수로 printf를 써도 프로그램이 죽지 않도록, 표준 출력을 "블랙홀"로 연결해두는 안전 장치입니다.
  3. chdir("/"): 데몬이 /home/user/test 폴더에서 실행된 채로 있으면, 관리자가 /home 파티션을 언마운트 할 수 없게 됩니다. 그래서 루트로 이동시킵니다.

4. systemd 기반 데몬 (Configuring the Easy Way)

10.newdaemon.c 처럼 코드가 매우 단순해집니다. fork도, setsid도 필요 없습니다. Unit 파일(.service)이 모든 귀찮은 일을 대신합니다.

핵심 Unit 파일 설정 (.service)

[Service]
ExecStart=/home/pi/bin/10.newdaemon  # 실행할 파일 경로
Type=simple                          # fork 안 하고 바로 실행함 (기본값)
Restart=on-failure                   # 죽으면 자동으로 다시 살려냄 (데몬의 영생)

로깅의 혁신 (journald)

  • 바이너리 저장: 텍스트가 아닌 바이너리로 저장되어 검색 속도가 빠르고 위변조가 어렵습니다.
  • 자동 수집: 프로그램에서 printf("Hello"); 만 해도 journalctl에 예쁘게 기록됩니다.
  • 명령어:
    • journalctl -u [서비스명]: 해당 서비스 로그만 보기.
    • journalctl -f: 실시간 로그 확인 (tail -f 유사).

5. 트러블슈팅: "Unit is masked" 에러

masked 상태는 서비스가 "/dev/null"로 링크되어 실행이 원천 봉쇄된 상태입니다.

  • sudo systemctl start mydaemon 실패 → Unit is masked.
  • 의도적인 차단 혹은 잘못된 심볼릭 링크 때문
  • 해결 순서:
    1. 해제 : sudo systemctl unmask [서비스명]
    2. 확인 : /etc/systemd/system/ 아래에 서비스 파일이 진짜 있는지 확인 (ls -l).
    3. 갱신 : sudo systemctl daemon-reload (설정 변경 사항 반영).
    4. 시작 : sudo systemctl start [서비스명]

정리

  • 새로운 데몬을 개발한다면 systemd 방식(Type=simple)을 권장하는 편이라고 합니다. 코드가 간결해지고, 로그 관리와 프로세스 감시(Watchdog)를 OS에 맡길 수 있어 훨씬 안정적입니다.

  • 만약 데몬이 죽었을 때 이메일을 보내거나, 특정 시간(타이머)에만 실행되게 하려면 systemd의 Timer Unit을 확인해보면 좋습니다.

profile
임베디드 개발자가 되기 위해 공부중입니다!

0개의 댓글