-
시스템 프로그래밍 실습 6주차 : DaemonSystem Programming/Ubuntu Linux 2021. 10. 3. 00:40728x90
Daemon
목차
Process Execution Type
Session
Daemon
- Daemon Coding Rules
- Daemon Errors를 위한 Logging -> syslog daemon!
- 정기적으로 프로그램을 실행시켜 주는 소프트웨어 -> cron daemon!
Process Execution Type
Process에는 두 가지 종류가 있다
(1) Foreground process : Shell이 process가 끝나는 것을 기다려야 한다
(2) Background process : Shell이 process가 끝나는 것 기다리지 않고, Command line 끝에 &를 붙여 Background process를 생성한다
Session
Process Group
- 관련 있는 프로세스를 묶은 것으로 프로세스 그룹ID가 부여됨
- 작업 제어 기능을 제공하는 쉘은 명령을 파이프로 연결하여 프로세스 그룹 생성 가능
- 프로세스 그룹을 구성하는 프로세스 중 하나가 그룹 리더가 됨
- 프로세스 그룹 리더의 PID 가 PGID
- PGID : getpgrp(), getpgid()
- PGID 변경 : setpgid()Session이란 Process Group의 Collection!
- 사용자가 로그인해 작업하고 있는 터미널 단위로 프로세스 그룹을 묶은 것
- Session에 있는 Process Group은 하나의 foreground process 그룹 또는 하나 이상의 background process 그룹으로 나뉜다- 세션 검색 : getsid( )
: 새로운 세션을 생성 시 해당 프로세스는 세션 리더가 되면 세션 리더의 PID가 세션ID - 세션 생성 : setsid( ))
https://codingsmu.tistory.com/48
Daemon
멀티태스킹 운영 체제에서 데몬은 사용자가 직접적으로 제어하지 않고, background에서 돌면서 여러 작업을 하는 프로그램을 말한다.
그러나 background 프로그램과 Daemon 프로그램은 엄연한 차이가 있다!
1) No controlling terminal [터미널이 없다]
: Controlling terminal이 있는 process는 ctrl+c 등으로 의도하지 않게 끝날 수 있기 때문이다
2) 시스템이 시동할 때 보통 시작하고, 계속해서 돈다
: 어떤 daemon들은 user terminal으로부터 시작할 수 있다
3) 관습적으로 daemon의 이름은 'd'로 끝난다
4) Windows에서는 Daemon은 service라고 부르기도 한다
+) 대개 부모 프로세스를 갖지 않으며, 즉 PPID가 1이며, 따라서 프로세스 트리에서 init 바로 아래에 위치!
+) 이런 데몬들은 네트워크 요청, 하드웨어 동작, 여타 프로그램에 반응하는 기능을 담당하게 된다 ( 그 밖에도 몇몇 리눅스에 있는 devfsd처럼 하드웨어 설정이나, cron처럼 주기적인 작업을 실행하는 등 기타 다양한 목적으로 사용)
* Daemon의 예시
Daemon Coding Rules
데몬이 되는 방법은 일반적으로 (1) 자식 프로세스를 fork()하여 생성하고, (2) 자식을 분기한 자신을 죽이면서 (3) init이 고아가 된 자식 프로세스를 자기 밑으로 데려가도록 하는 방식이다. 이러한 방법을 ‘fork off and die’라 부르기도 한다.
-> 유닉스에서 (1) 부모 프로세스가 PID 1(init)이고, (2) 제어하는 터미널이 없을 때 그 프로세스를 데몬이라 할 수 있다.
Daemon 만드는 방법
1) 프로세스를 제어하고 있는 터미널로부터 분리한다. (fork호출, 부모 프로세스는 exit호출)
2) 프로세스를 세션 리더로 만든다.
3) 프로세스를 프로세스 그룹의 리더로 만든다. (setsid() 호출)
4) (한 번이나 두 번) 포크한 뒤 프로세스를 종료하여 자식 프로세스가 백그라운드에 남게 한다. 이 방법은 세션 리더를 만드는 데도 쓰이며, 부모 프로세스를 종료하지 않고 일반적인 작업을 수행할 수도 있다. 이 방법을 요약하여 ‘fork off and die’라고 한다.
5) 루트 디렉터리("/")를 현재 작업 디렉터리로 만든다. (chdir("/") 호출)
6) umask를 0으로 변경해서 호출한 쪽의 umask와 상관 없이 open(), creat() 등의 호출을 수행할 수 있도록 한다. (umask(0)호출)
7) 상속받았으며, 부모 프로세스가 열고 있는 파일들을 자식 프로세스에서 모두 닫는다. 여기에는 0, 1, 2번 파일 서술자(각각 stdin, stdout, stderr)도 포함된다.
8) 로그 파일이나 콘솔, 또는 /dev/null을 stdin, stdout, stderr로 설정한다.1) 부모 프로세스에서 fork()와 exit() 호출!
- 부모 프로세스는 그냥 daemon 프로세스를 만드는 역할 -> 그래서 fork()로 자식 프로세스 생성 뒤, 부모 프로세스는 exit() !
- daemon을 위한 새로운 Session 생성의 전제 조건이다!
2) setsid()를 통해 새로운 Session을 만든다
- Daemon 프로세스를 세션 리더로 만든다.
- Daemon 프로세스를 프로세스 그룹의 리더로 만든다. (setsid() 호출)• pid_t setsid (void)
» setsid( ) makes process the leader of the process group and session
» On success, return the session ID of calling process
» On error, return -13) 루트 디렉터리("/")를 현재 작업 디렉터리로 만든다. (chdir("/") 호출)
- Current working directory can be unmounted
4) file의 mode mask를 설정한다 (umask)
- umask는 컴퓨팅에서 새로 만들어진 파일에 파일 권한을 어떻게 설정할지를 제어하는 마스크 설정을 결정하는 명령!
- If umask value is 1, clear value; otherwise, keep value
파일 기본 허가권 : 666( rw- rw- rw- )
디렉터리 기본 허가권 : 777( rwx rwx rwx )
각 기본 허가권에 umask 값을 빼서 파일 권한 설정을 할 수 있다! ( 즉, umask 값과 새 폴더 퍼미션 값을 더하면 777이 된다. (예: 022 + 755 = 777) )
umask 값이 0002 라면 파일의 초기 접근 권한은 664, 디렉터리의 초기 접근 권한은 775!
ex) file : 666 - 002 = 664, directory : 777 - 002 = 775
- Daemon Process가 파일들을 만들 때, 파일 권한을 설정하고 싶을 수 있으니까
- umask를 0으로 변경해서 호출한 쪽의 umask와 상관 없이 open(), creat() 등의 호출을 수행할 수 있도록 한다. (umask(0)호출)
* 파일 접근 권한
- 파일 접근 권한을 8진수 3자리 표현, 또는 2진수 9자리 표현이 가능하다!
- 8진수로 표현할 때, 각 자리 숫자는 순서대로 파일 소유자, 그룹원, 외부 사용자의 권한을 의미한다
- 2진수로 표현할 때, 각 숫자 3개씩은 순서대로 파일 소유자, 그룹원, 외부 사용자의 권한을 의미한다
- 2진수로 표현할 때, 숫자 3개는 각각 r, w, x의 권한 유무를 의미한다
- 8진수 숫자를 2진수로 바꾸면 동일한 의미
5) 모든 파일 디스크립터를 닫는다
- Daemon이 부모로부터 상속받고 있는 어떠한 descripter들로부터 해방시키기 위해
6) 0, 1, 2번 파일 디스크립터(각각 표준 입력 stdin, 출력 stdout, 에러 stderr)를 열고 /dev/null로 바꾼다
- Daemon은 표준 입력 stdin, 출력 stdout, 에러 stderr이 필요하지 않다
- Many library functions assume that the first three descriptors are open
예시 1)
예시 2)
#include <sys/types.h> #include <sys/stat.h> #include <stdlib.h> #include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <linux/limits.h> int main(void) { pid_t pid; int i; /* 새로운 프로세스 생성 */ pid = fork(); if (pid == -1) return -1; else if (pid != 0) exit(EXIT_SUCCESS); /* 새로운 세션과 프로세스 그룹을 생성, 세션 리더로 만듬 */ if (setsid() == -1) return -1; /* 작업 디렉토리를 루트 디렉토리로 변경 */ if (chdir("/") == -1) return -1; /* 모든 파일 디스크립터(fd)를 닫음 */ for (i=0; i<NR_OPEN; i++) close(i); /* 표준 입력, 출력, 에러 파일을 /dev/null 로 리다이렉션 */ open("/dev/null", O_RDWR); dup(0); dup(0); /* 데몬에서 수행할 작업... */ return 0; }
https://ko.wikipedia.org/wiki/%EB%8D%B0%EB%AA%AC_(%EC%BB%B4%ED%93%A8%ED%8C%85)
https://elechole.tistory.com/231
http://tmmse.xyz/2017/10/19/daemon/
https://reakwon.tistory.com/118
https://leeeeye321.tistory.com/86
https://bamdule.tistory.com/171
https://cheershennah.tistory.com/162
https://jhnyang.tistory.com/63
Daemon Errors를 위한 Logging by syslog Daemon
터미널이 없는 Daemon에서 error message는 어떻게 다루는가?! -> "syslog Daemon"을 통해!
closelog, openlog, syslog, vsyslog 4개의 함수를 통해서 시스템 logger에게 메시지를 보낸다
기본적으로 로그가 쓰여지는 파일의 경로는 /var/log/syslog
syslog의 동작을 설정하기 위해서는 syslog의 설정 파일인 /etc/syslog.conf에 기록!
* syslog Daemon이랑 소통하는 방법 !
1) syslog( ) 함수 부르기
2) UDP port 514에 log 메세지 보내기 (오직 TCP/IP 네트워크에 의해 host와 연결된 user process만! )
3) Kernel routines
- facilty : 로그 생성 서비스 / 메시지의 출력처
- facility를 사용하는 것으로 메시지의 출력이 어디서부터 왔는지 로그의 출력처를 제한하는 것이 가능
» Refer to the configuration file (/etc/rsyslog.d)
» Logs with different facilities are written to different files- priority는 메세지의 우선도
- action : 로그를 어디에 남길 것인지를 결정 ( 로그파일, 콘솔, 원격 로그 서버, 특정 사용자 등에 로그를 남길 수 있음)
» 로그파일일 경우 => "경로지정 ex) /var/log/secure"» 콘솔일 경우 => "/dev/console"
» 원격 로그 서버일 경우 => "@호스트주소 (@192.168.1.1)"
» 지정된 사용자 스크린에 출력 => "user ID"
» 현재로그인되어 있는 모든 사용자의 스크린에 메시지 보냄 => "*"
syslog() 함수
- log message를 생성한다 / 로그 메시지를 실제로 정의하는 함수
- priority는 로그의 중요도를 표시 / facility와 level의 결합이다
커널에서 dmesg 출력으로 사용되는 printk() 함수에서 포맷 스트링 바로 앞쪽에 prefix로 붙여 중요도를 넣어주는 것과 동일한 역할
- format : 포맷을 설정하는 파라미터 / format의 자리에 실제로 넣어줄 로그의 내용이 들어간다
( format argument and any remaining are passed to the vsprintf( ) for formatting )
setlogmask() 함수
- process를 위해 log priority mask를 설정한다
- 특정 레벨의 로그를 무시하게 함
- 비트 마스크 형태로 지정하는데, 비트가 ON 이면 정상적으로 처리하고 비트가 OFF면 해당 레벨의 메시지를 무시
ex) LOG_MASK(LOG_EMERG) | LOG_MASK(LOG_ERROR)
- 이전의 mask value를 return
* System Logger 열고 닫기
openlog() 함수
- systemo logger로 연결한다
- 로그를 쓸 때 openlog() 를 통해 로그 파일 디스크립터를 얻는다
- ident는 program의 이름이다 / 모든 로그의 앞에 공통적으로 붙여지는 prefix와 같은 역할
- option은 logger 연결 옵션 / 여러 옵션들을 특정한 한 bitmask이다
- facilty는 메시지를 기록하는 프로그램의 유형을 정한다. / 어떤 타입의 프로그램이 메세지를 logging 했는지 특정할 때 사용된다
closelog() 함수
- system logger에 쓸 때 사용되는 file descripter를 닫는다
- 로그 기록이 완료되었다면, closelog() 를 통해 로그 파일 디스크립터를 정상적으로 닫아줘야한다
예시 1)
예시 2)
#include <syslog.h> #include <stdio.h> int main(int argc, char** argv) { if (argc < 2) { printf("Usage: %s <message>", argv[0]); return 1 } /* logging */ openlog("mylog", LOG_CONS, LOG_USER); syslog(LOG_INFO, argv[1]); closelog(); return 0; }
https://cornswrold.tistory.com/34
https://www.linux.co.kr/lecture/lec_linux_01/lec-data/11data.pdf
https://rrhh234cm.tistory.com/472
https://medium.com/@hyoje420/c-c-syslog-h-%EC%82%AC%EC%9A%A9%EB%B2%95-807c523a0af4
정기적으로 프로그램을 실행시켜 주기 by cron daemon
Cron(크론)이라는 데몬이 원하는 시간에 원하는 명령(프로그램)을 수행하도록 만든 명령 리스트를 Crontab(크론탭)이라고 하고, 이러한 명령 리스트를 만드는 작업을 Crontab(크론탭) 작업
- 정기적으로 같은 작업을 할 때 사용
- 일정 시간마다 자동으로 실행
- system 시작할 때 자동 시작!
- 윈도우 환경의 작업 스케줄러와 비슷한 기능
- crontab ( Cron configuration file )은 스케줄 시간과 실행할 파일의 경로를 관리하고, cron은 crontab을 실행
: cron daemon이 실행되는 경로와 동일!
: 형식 minute(0~59) hour(0~23) executable_file
: cron이 실행할 설정 파일인 crontab이 저장되어 있는 장소는 3곳
- 일반 사용자 : /var/spool/cron/crontabs
- root 사용자 : /etc/crontab
- 먼저 터미날(shell)에서 접속합니다. 그래야 명령을 내릴 수 있으므로.
- crontab -e 명령어 입력
- 아마 처음으로 시스템에서 crontab -e 명령어를 사용하면 no crantab for root라는 메세지가 나오면서 편집기를 선택하하고 나옵니다.
- 편집기는 ed, nano, vim basic, vim tiny 중에서 선택할 수 있습니다. 전는 easiest라고 쓰여진 nano편집기를 선택했습니다.
이 편집기는 GUI 편집기 특징을 어느 정도 갖추고 있어서 접근성이 좋았습니다.
▽ nginx 우분투에서 크론탭 등록시 처음 사용 할 편집기 선택 화면
- 편집기에서 명령어를 입력합니다.
. 30 4 * dbbackup.sh 와 같은 것 - 저장해서 빠져나옵니다.
. nano 편집기에서 저장 명령어는 CTRL+O이죠
. 빠져 나오는 명령은 CTRL+X - 편집기를 빠져나오면 ‘crontab: installing new crontab’이라는 메세지 뜹니다.
그러면 성공한 것이죠 - 이 명령이 등록되면 /var/spool/cron/crontabs/ 폴더에 root라는 파일이 생성됩니다.
▽ nginx 우분투에서 크론탭 등록 시 나노(nano) 편집 시용 모습
여기에서 crontab -e 명령어를 사용해 등록을 했다면 이와 관련된 다른 명령어가 있을 것입니다. crantab에는 crontab -e옵션 외 -l, -r옵션이 있는데요.
- crontab -e : 명령을 등록, 편집 – 맨 처음에 사용 시 편집기를 선택할 수 있다.
- crontab -d : 등록된 명령을 삭제
- crontab -l : 현재 등록된 리스트 출력
- crontab -l -u otheruser : otheruser 사용자가 등록한 crontab 리스트 출력
- crontab -r : 현재 사용자가 등록한 crontab 전체 삭제
로그 남기기
크론 작업 결과를 로그로 남겨 나중에 분석하고 싶다면 아래와 같은 방법으로 로그를 남기라는 명령을 줍니다.
* * * * * doitnow.sh > /var/log/crontab.kog 2>%1
변경 후 반드시 cron 다시 실행시키기
crontab(크론탭) 명령을 등록 후 반드시 cron을 다시 실행해야 변경 내용에 제대로 반영됩니다.
실행 파일 권한 문제 – Permission denied
가끔 실행 파일에 제대로 권한이 성정되지 않았다면 아래와 같은 메세지가 메일로 보내집니다.
/bin/sh: 1: dbit.sh: Permission denied
chmod +x dbbackup.sh
crontab 작동 확인 – service cron status
그리고 crontab이 제대로 작동하는지 확인하기 위해서 service cron status 명령을 사용합니다.
service cron status # checks if cron is running
crontab 초 단위 지연 (sleep)
- 매 1분에 수행 -> 10초 중단 -> 10 ~ 11초때 foo.sh 수행
*/1 * * * * sleep 10; /sample/foo.shcrontab 명령어의 옵션
-l : 현재 설정된 크론설정보기
-e : 크론설정 편집하기
-r : 크론파일 삭제하기
$ crontab -e $ crontab -l $ crontab -r
시간 설정
* * * * * 분(0-59) 시간(0-23) 일(1-31) 월(1-12) 요일(0-7)
예제 1)
$ crontab -e # 1분마다 ls의 결과를 result.txt에 append를 하겠습니다. (crontab -e를 켜고 하셔야합니다) */1 * * * * ls >> result.txt 2> error.txt # 이 명령어를 추가해보았습니다. $ crontab -l # 이를 실행하면 수정한 crontab 파일을 확인할 수 있습니다.
예제 2)
# 매분 test.sh 실행 * * * * * /home/script/test.sh
예제 3)
# 매주 금요일 오전 5시 45분에 test.sh 를 실행 45 5 * * 5 /home/script/test.sh
예제 4)
# 매일 매시간 0분, 20분, 40분에 test.sh 를 실행 0,20,40 * * * * /home/script/test.sh
예제 5)
# 매일 1시 0분부터 30분까지 매분 tesh.sh 를 실행 0-30 1 * * * /home/script/test.sh
예제 6)
# 매 10분마다 test.sh 를 실행 */10 * * * * /home/script/test.sh
예제 7)
# 5일에서 6일까지 2시,3시,4시에 매 10분마다 test.sh 를 실행 */10 2,3,4 5-6 * * /home/script/test.sh
예제 8)
* * * * * /home/script/test.sh > /home/script/test.sh.log 2>&1 # 매분마다 test.sh.log 파일이 갱신 되어 작업 내용이 어떻게 처리 되었는지 알 수 있습니다. # 만약 2>&1 을 제거하면 쉘스크립트에서 표준 출력 내용만 나옵니다.
* * * * * /home/script/test.sh >> /home/script/test.sh.log 2>&1 # 이게 너무 자주 실행 되고 또한 지속적으로 로깅이 되야 해서 로그를 계속 남겨둬야 한다면 다음처럼 입력 # 그러면 계속 로그가 누적이 되는 것을 확인 할 수 있을겁니다. # 대신 로그가 과도하게 쌓이면 리눅스 퍼포먼스에 영향을 주므로 가끔씩 비워주거나 파일을 새로 만들어주는 센스가 필요
* * * * * /home/script/test.sh > /dev/null 2>&1 # 반대로 로그는 필요 없는 크론을 위해선 다음처럼 입력
https://ponyozzang.tistory.com/401?category=792394
https://jhnyang.tistory.com/68
https://ggodong.tistory.com/187
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=dudwo567890&logNo=130157925046
#include <sys/types.h> #include <sys/stat.h> #include <sys/wait.h> #include <errno.h> #include <limits.h> #include <signal.h> #include <stdarg.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <syslog.h> #include <time.h> #include <unistd.h> #include <fcntl.h> int main(void) { unsigned int pid; time_t t; struct tm *tm; int fd; char *argv[3]; char buf[512]; int fd0, fd1, fd2; fd = open("./crontab", O_RDWR); // crontab 읽고 쓰기 모드로 열기 pid = fork(); /* 새로운 프로세스 생성 */ if(pid == -1) return -1; if(pid != 0) exit(0); /* 새로운 세션과 프로세스 그룹을 생성, 세션 리더로 만듬 */ if(setsid() < 0) exit(0); /* 작업 디렉토리를 루트 디렉토리로 변경 */ if(chdir("/") < 0) exit(0); umask(0); close(0); /* 모든 파일 디스크립터(fd)를 닫음 */ close(1); close(2); fd0 = open("/dev/null", O_RDWR); /* 표준 입력, 출력, 에러 파일을 /dev/null 로 리다이렉션 */ fd1 = open("/dev/null", O_RDWR); fd2 = open("/dev/null", O_RDWR); t = time(NULL); tm = localtime(&t); /* 데몬에서 수행할 작업... */ while (1) { buf[0] = '\0'; // insert your code // ## hints ## // strtok_r(); // pid = fork(); // execl("/bin/sh", "/bin/sh", "-c", argv[2], (char*) NULL); t = time(NULL); tm = localtime(&t); sleep(60 - tm->tm_sec % 60); } return 0; }
728x90'System Programming > Ubuntu Linux' 카테고리의 다른 글
시스템 프로그래밍 실습 8주차 : IPC (0) 2021.10.17 시스템 프로그래밍 실습 7주차 : Signals (0) 2021.10.10 시스템 프로그래밍 실습 5주차 : Processes (0) 2021.09.27 시스템 프로그래밍 실습 4주차 : File I/O (0) 2021.09.20 시스템 프로그래밍 실습 3주차 : Shell & Makefile & Git (0) 2021.09.16 - 세션 검색 : getsid( )