
백그라운드에서 실행되며 특정 서비스를 제공하는 장기 실행 프로세스 (예: httpd, sshd).
init(PID 1), 최신 리눅스에서는 systemd가 관리합니다.syslog나 journald를 통해 로그를 남깁니다.가장 중요한 차이점은 프로그래머가 얼마나 많은 코드를 직접 짜야 하는지에 대한 것 입니다.
| 비교 항목 | 고전 방식 (SysV / Old School) | 현대 방식 (systemd / New School) |
|---|---|---|
| 구현 난이도 | 복잡함 (직접 코딩 필요) | 매우 간단 (설정 파일 위임) |
| 프로세스 생성 | fork() 2번 (Double Fork) | systemd가 직접 실행 (Type=simple) |
| 터미널 제어 | setsid(), close(fd) 직접 호출 | systemd가 알아서 처리 |
| 로그 처리 | 파일 open 후 직접 기록 | printf만 하면 journald가 수집 |
| 관리 | PID 파일 직접 생성/삭제 | systemd가 PID 추적 및 관리 |
fork(): 부모를 종료시켜 쉘이 "끝났다"고 착각하게 만듦 (백그라운드 진입).setsid(): 새로운 세션을 만들어 터미널 제어권 박탈 (필수).chdir("/"): 작업 디렉토리를 루트로 이동 (USB 등을 마운트 해제 못 하는 문제 방지).umask(0): 파일 생성 권한 제약을 없앰.close(fd) & dup2: 표준 입출력(0,1,2)을 닫거나 /dev/null로 돌림.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;
}
gcc classic_daemon.c -o classic_daemon
./classic_daemon
# (아무런 메시지 없이 쉘 프롬프트가 바로 떨어져야 정상)
터미널(TTY)이 ?로 표시되어야 하며, PPID가 1(systemd/init)이어야 합니다.
ps -efj | grep classic_daemon
# 출력 예시:
# andrew 12345 1 12344 ? 00:00:00 ./classic_daemon
?: 터미널 없음 (성공).1: 부모가 init/systemd (성공).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>
setsid(): 이 호출이 없으면 데몬이 터미널을 붙잡고 있어서, 터미널을 닫으면 데몬도 같이 죽습니다. 필수 과정입니다./dev/null 리다이렉션: 데몬 내부에서 실수로 printf를 써도 프로그램이 죽지 않도록, 표준 출력을 "블랙홀"로 연결해두는 안전 장치입니다.chdir("/"): 데몬이 /home/user/test 폴더에서 실행된 채로 있으면, 관리자가 /home 파티션을 언마운트 할 수 없게 됩니다. 그래서 루트로 이동시킵니다.10.newdaemon.c 처럼 코드가 매우 단순해집니다. fork도, setsid도 필요 없습니다. Unit 파일(.service)이 모든 귀찮은 일을 대신합니다.
.service)[Service]
ExecStart=/home/pi/bin/10.newdaemon # 실행할 파일 경로
Type=simple # fork 안 하고 바로 실행함 (기본값)
Restart=on-failure # 죽으면 자동으로 다시 살려냄 (데몬의 영생)
journald)printf("Hello"); 만 해도 journalctl에 예쁘게 기록됩니다.journalctl -u [서비스명]: 해당 서비스 로그만 보기.journalctl -f: 실시간 로그 확인 (tail -f 유사).masked 상태는 서비스가 "/dev/null"로 링크되어 실행이 원천 봉쇄된 상태입니다.
sudo systemctl start mydaemon 실패 → Unit is masked.sudo systemctl unmask [서비스명]/etc/systemd/system/ 아래에 서비스 파일이 진짜 있는지 확인 (ls -l).sudo systemctl daemon-reload (설정 변경 사항 반영).sudo systemctl start [서비스명]새로운 데몬을 개발한다면 systemd 방식(Type=simple)을 권장하는 편이라고 합니다. 코드가 간결해지고, 로그 관리와 프로세스 감시(Watchdog)를 OS에 맡길 수 있어 훨씬 안정적입니다.
만약 데몬이 죽었을 때 이메일을 보내거나, 특정 시간(타이머)에만 실행되게 하려면 systemd의 Timer Unit을 확인해보면 좋습니다.