라즈베리파이에서 파이썬으로 작성한 데몬을 만들고 서비스를 등록할 일이 생겨 기록을 남겨본다.
1. 데몬 프로세스
데몬 프로세스는 PPID가 1(init)인 프로세스를 말하는 것으로 백그라운드에서 서비스를 제공하는 프로세스이다.
PPID가 1이라는 것은 서비스를 실행시킨 쉘이 종료되더라도 프로세스가 살아있다는 것을 의미한다.
데몬 프로세스를 만드는 법은 어렵지 않다.
위키에는 다음과 같이 나와있다.
- 프로세스를 제어하고 있는 터미널로부터 분리한다. (fork호출, 부모 프로세스는 exit호출)
- 프로세스를 세션 리더로 만든다.
- 프로세스를 프로세스 그룹의 리더로 만든다. (setsid() 호출)
- (한 번이나 두 번) 포크한 뒤 프로세스를 종료하여 자식 프로세스가 백그라운드에 남게 한다. 이 방법은 세션 리더를 만드는 데도 쓰이며, 부모 프로세스를 종료하지 않고 일반적인 작업을 수행할 수도 있다. 이 방법을 요약하여 ‘fork off and die’라고 한다.
- 루트 디렉터리(“/”)를 현재 작업 디렉터리로 만든다. (chdir(“/”) 호출)
- umask를 0으로 변경해서 호출한 쪽의 umask와 상관 없이 open(), creat() 등의 호출을 수행할 수 있도록 한다. (umask(0)호출)
- 상속받았으며, 부모 프로세스가 열고 있는 파일들을 자식 프로세스에서 모두 닫는다. 여기에는 0, 1, 2번 파일 서술자(각각 stdin, stdout, stderr)도 포함된다.
- 로그 파일이나 콘솔, 또는 /dev/null을 stdin, stdout, stderr로 설정한다.
무언가 어려워보이지만 실제 코드로는 그렇게 복잡하지도 않다.
import os, sys from datetime import datetime if __name__ == '__main__': # First Fork pid = os.fork() if pid > 0: # Parent Dies exit(0) else: # Change Working Directory os.chdir('/') os.setsid() os.umask(0) # Second Fork pid = os.fork() if pid > 0: # Parent Dies exit(0) else: # Daemon Process sys.stdout.flush() sys.stderr.flush() si = open(os.devnull, 'r') so = open(os.devnull, 'a+') se = open(os.devnull, 'a+') os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) # Process Begin proc = process() exit_code = proc.run() # Process End exit(exit_code)
물론 이것 말고도 python-daemon이라는 좋은 패키지가 있으니 사용하면 된다.
2. 서비스 등록
크게 어렵지 않다.
/etc/systemd/system에 서비스 파일을 등록하면 된다.
서비스 파일은 다음과 같이 작성한다.
[Unit] Description=blah blah process After=multi-user.target [Service] Type=forking ExecStart=/usr/bin/python /home/hooni/daemon.py Restart=always WorkingDirectory=/home/hooni [Install] WantedBy=multi-user.target
그 이후에는 systemctl을 통해 서비스를 관리하면 된다.
키워드는 다음과 같다.
$ systemctl daemon-reload $ systemctl enable my_daemon $ systemctl start my_daemon $ systemctl status my_daemon