c - 데몬 - 리눅스 어플리케이션 만들기




리눅스에서 데몬 만들기 (6)

리눅스에서는 멈출 수없고 파일 시스템 변경을 감시하는 데몬을 추가하고 싶습니다. 변경 사항이 감지되면 콘솔이 시작된 곳의 경로와 줄 바꿈을 써야합니다.

대몬은 배경에서 작동하며 (보통 ...) TTY에 속하지 않으므로 아마 원하는 방식으로 stdout / stderr를 사용할 수 없습니다. 대개 syslog 데몬 ( syslogd )은 파일에 메시지를 기록하는 데 사용됩니다 (debug, error, ...).

게다가, 프로세스를 대몬 케하는 몇 가지 필수 단계 가 있습니다.

올바르게 기억한다면 다음 단계를 따르십시오.

  • 부모 프로세스를 포크 하고 포킹이 성공하면 종료되도록합니다. -> 상위 프로세스가 종료되었으므로 이제 하위 프로세스가 백그라운드에서 실행됩니다.
  • setsid - 새 세션을 만듭니다. 호출 프로세스는 새 세션의 리더가되고 새 프로세스 그룹의 프로세스 그룹 리더가됩니다. 이제 프로세스가 제어 터미널 (CTTY)에서 분리됩니다.
  • 신호 잡기 - 신호를 무시하거나 처리합니다.
  • 포크를 다시 시작 하고 부모 프로세스가 세션 주도 프로세스를 제거하도록 종료합니다. (세션 리더 만 TTY를 다시받을 수 있습니다.)
  • chdir - 데몬의 작업 디렉토리를 변경합니다.
  • umask - 데몬의 필요에 따라 파일 모드 마스크를 변경하십시오.
  • close - 부모 프로세스에서 상속 될 수있는 열려있는 모든 파일 설명자를 닫 습니다 .

시작 지점을 제공하려면 다음과 같은 기본 단계를 보여주는이 뼈대 코드를보십시오.

/*
 * daemonize.c
 * This example daemonizes a process, writes a few log messages,
 * sleeps 20 seconds and terminates afterwards.
 */

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

static void skeleton_daemon()
{
    pid_t pid;

    /* Fork off the parent process */
    pid = fork();

    /* An error occurred */
    if (pid < 0)
        exit(EXIT_FAILURE);

    /* Success: Let the parent terminate */
    if (pid > 0)
        exit(EXIT_SUCCESS);

    /* On success: The child process becomes session leader */
    if (setsid() < 0)
        exit(EXIT_FAILURE);

    /* Catch, ignore and handle signals */
    //TODO: Implement a working signal handler */
    signal(SIGCHLD, SIG_IGN);
    signal(SIGHUP, SIG_IGN);

    /* Fork off for the second time*/
    pid = fork();

    /* An error occurred */
    if (pid < 0)
        exit(EXIT_FAILURE);

    /* Success: Let the parent terminate */
    if (pid > 0)
        exit(EXIT_SUCCESS);

    /* Set new file permissions */
    umask(0);

    /* Change the working directory to the root directory */
    /* or another appropriated directory */
    chdir("/");

    /* Close all open file descriptors */
    int x;
    for (x = sysconf(_SC_OPEN_MAX); x>=0; x--)
    {
        close (x);
    }

    /* Open the log file */
    openlog ("firstdaemon", LOG_PID, LOG_DAEMON);
}
int main()
{
    skeleton_daemon();

    while (1)
    {
        //TODO: Insert daemon code here.
        syslog (LOG_NOTICE, "First daemon started.");
        sleep (20);
        break;
    }

    syslog (LOG_NOTICE, "First daemon terminated.");
    closelog();

    return EXIT_SUCCESS;
}


  • 코드 컴파일 : gcc -o firstdaemon daemonize.c
  • 데몬을 시작합니다 : ./firstdaemon
  • 모든 것이 제대로 작동하는지 확인하십시오 : ps -xj | grep firstdaemon ps -xj | grep firstdaemon

  • 출력은 다음과 유사해야합니다.

+------+------+------+------+-----+-------+------+------+------+-----+
| PPID | PID  | PGID | SID  | TTY | TPGID | STAT | UID  | TIME | CMD |
+------+------+------+------+-----+-------+------+------+------+-----+
|    1 | 3387 | 3386 | 3386 | ?   |    -1 | S    | 1000 | 0:00 | ./  |
+------+------+------+------+-----+-------+------+------+------+-----+

여기에서보아야 할 것은 :

  • 데몬에는 제어 터미널이 없습니다 ( TTY =? ).
  • 상위 프로세스 ID ( PPID )가 1 (초기화 프로세스)
  • PID! = SID 는 프로세스가 세션 리더가 아님을 의미합니다.
    (두 번째 fork () 때문에)
  • PID! = SID 프로세스 가 TTY를 다시 제어 할 수 없기 때문에

syslog 읽기 :

  • syslog 파일을 찾으십시오. 내 위치 : /var/log/syslog
  • 해야 할 일 : grep firstdaemon /var/log/syslog

  • 출력은 다음과 유사해야합니다.

  firstdaemon[3387]: First daemon started.
  firstdaemon[3387]: First daemon terminated.


참고 사항 : 실제로는 신호 처리기를 구현하고 올바르게 로깅을 설정해야합니다 (파일, 로그 수준 ...).

추가 읽기 :

리눅스에서는 멈출 수없고 파일 시스템 변경을 감시하는 데몬을 추가하고 싶습니다. 변경 사항이 감지되면 시작된 콘솔에 경로를 작성하고 개행을 작성해야합니다.

이미 파일 시스템 변경 코드가 거의 준비되어 있지만 데몬을 만드는 방법을 알 수는 없습니다.

내 코드는 여기에서 : http://www.yolinux.com/TUTORIALS/ForkExecProcesses.html

포크 후에 무엇을해야합니까?

int main (int argc, char **argv) {

  pid_t pID = fork();
  if (pID == 0)  {              // child
          // Code only executed by child process    
      sIdentifier = "Child Process: ";
    }
    else if (pID < 0) {
        cerr << "Failed to fork" << endl;
        exit(1);
       // Throw exception
    }
    else                                   // parent
    {
      // Code only executed by parent process

      sIdentifier = "Parent Process:";
    }       

    return 0;
}

fork ()를 호출하면 자식 프로세스가 생성됩니다. 포크가 성공하면 (포크는 0이 아닌 PID를 반환합니다) 실행은 자식 프로세스 내에서이 지점까지 계속됩니다. 이 경우에는 부모 프로세스를 정상적으로 종료 한 다음 하위 프로세스에서 작업을 계속 진행하고자합니다.

어쩌면이 도움이 될 것입니다 : http://www.netzmafia.de/skripten/unix/linux-daemon-howto.html


리눅스에서는 죽일 수없는 프로세스를 생성 할 수 없습니다. 루트 사용자 (uid = 0)는 프로세스에 신호를 보낼 수 있으며 잡히지 않는 두 개의 시그널 SIGKILL = 9, SIGSTOP = 19가 있습니다. 그리고 다른 신호 (잡히지 않은 경우)는 프로세스 종료를 초래할 수 있습니다.

프로그램 / 데몬의 이름을 지정할 수있는 좀 더 일반적인 데몬 기능과 프로그램을 실행할 경로 ( "/"또는 "/ tmp"등)가 필요할 수 있습니다. stderr 및 stdout에 대한 파일 (그리고 stdin을 사용하는 제어 경로)을 제공 할 수도 있습니다.

필요한 내용은 다음과 같습니다.

#include <stdio.h>    //printf(3)
#include <stdlib.h>   //exit(3)
#include <unistd.h>   //fork(3), chdir(3), sysconf(3)
#include <signal.h>   //signal(3)
#include <sys/stat.h> //umask(3)
#include <syslog.h>   //syslog(3), openlog(3), closelog(3)

그리고 더 일반적인 기능이 있습니다.

int
daemonize(char* name, char* path, char* outfile, char* errfile, char* infile )
{
    if(!path) { path="/"; }
    if(!name) { name="medaemon"; }
    if(!infile) { infile="/dev/null"; }
    if(!outfile) { outfile="/dev/null"; }
    if(!errfile) { errfile="/dev/null"; }
    //printf("%s %s %s %s\n",name,path,outfile,infile);
    pid_t child;
    //fork, detach from process group leader
    if( (child=fork())<0 ) { //failed fork
        fprintf(stderr,"error: failed fork\n");
        exit(EXIT_FAILURE);
    }
    if (child>0) { //parent
        exit(EXIT_SUCCESS);
    }
    if( setsid()<0 ) { //failed to become session leader
        fprintf(stderr,"error: failed setsid\n");
        exit(EXIT_FAILURE);
    }

    //catch/ignore signals
    signal(SIGCHLD,SIG_IGN);
    signal(SIGHUP,SIG_IGN);

    //fork second time
    if ( (child=fork())<0) { //failed fork
        fprintf(stderr,"error: failed fork\n");
        exit(EXIT_FAILURE);
    }
    if( child>0 ) { //parent
        exit(EXIT_SUCCESS);
    }

    //new file permissions
    umask(0);
    //change to path directory
    chdir(path);

    //Close all open file descriptors
    int fd;
    for( fd=sysconf(_SC_OPEN_MAX); fd>0; --fd )
    {
        close(fd);
    }

    //reopen stdin, stdout, stderr
    stdin=fopen(infile,"r");   //fd=0
    stdout=fopen(outfile,"w+");  //fd=1
    stderr=fopen(errfile,"w+");  //fd=2

    //open syslog
    openlog(name,LOG_PID,LOG_DAEMON);
    return(0);
}

다음은 데몬이되고, 돌아 다니며, 떠나는 샘플 프로그램입니다.

int
main()
{
    int res;
    int ttl=120;
    int delay=5;
    if( (res=daemonize("mydaemon","/tmp",NULL,NULL,NULL)) != 0 ) {
        fprintf(stderr,"error: daemonize failed\n");
        exit(EXIT_FAILURE);
    }
    while( ttl>0 ) {
        //daemon code here
        syslog(LOG_NOTICE,"daemon ttl %d",ttl);
        sleep(delay);
        ttl-=delay;
    }
    syslog(LOG_NOTICE,"daemon ttl expired");
    closelog();
    return(EXIT_SUCCESS);
}

SIG_IGN은 신호를 잡아 무시한다는 것을 나타냅니다. 신호 수신을 기록하고 플래그를 설정할 수있는 신호 처리기를 만들 수 있습니다 (예 : 정상 종료를 나타내는 플래그).


앱이 다음 중 하나 인 경우 :

{
  ".sh": "bash",
  ".py": "python",
  ".rb": "ruby",
  ".coffee" : "coffee",
  ".php": "php",
  ".pl" : "perl",
  ".js" : "node"
}

NodeJS 종속성에 신경 쓰지 않고 NodeJS를 설치 한 다음 다음을 수행하십시오.

npm install -g pm2

pm2 start yourapp.yourext --name "fred" # where .yourext is one of the above

pm2 start yourapp.yourext -i 0 --name "fred" # run your app on all cores

pm2 list

재부팅 할 때 모든 응용 프로그램을 계속 실행하려면 (및 pm2를 실행하십시오.)

pm2 startup

pm2 save

이제 다음을 수행 할 수 있습니다.

service pm2 stop|restart|start|status

(또한 코드 변경이 발생하면 앱 디렉토리에서 코드 변경 사항을보고 앱 프로세스를 자동으로 다시 시작할 수 있음)


man 7 daemon 은 데몬을 아주 자세하게 만드는 방법을 설명합니다. 내 대답은이 설명서에서 발췌 한 것입니다.

적어도 두 가지 유형의 데몬이 있습니다.

  1. 전통적인 SysV 데몬 ( old-style ),
  2. systemd 데몬 ( new-style ).

SysV 데몬

전통적인 SysV 데몬에 관심이 있다면 다음 단계를 구현해야 합니다 .

  1. 표준 입력 , 출력오류 (즉, 처음 세 파일 설명자 0, 1, 2)를 제외한 열려있는 파일 설명자를 모두 닫습니다. 이렇게하면 우연히 전달 된 파일 디스크립터가 데몬 프로세스에 남아 있지 않게됩니다. 리눅스에서는 /proc/self/fd 를 반복하여 파일 디스크립터 3에서 RLIMIT_NOFILE 대한 getrlimit() 에 의해 반환되는 값으로 반복하는 것이 가장 좋습니다.
  2. 모든 신호 핸들러를 기본값으로 Reset 하십시오. _NSIG 한도까지 사용 가능한 신호를 반복하고 SIG_DFL 다시 설정하는 것이 가장 좋습니다.
  3. sigprocmask() 사용하여 신호 마스크를 재설정하십시오.
  4. 데몬 런타임에 부정적인 영향을 줄 수있는 환경 변수를 제거하거나 재설정하여 환경 블록을 살균하십시오.
  5. fork() 호출하여 백그라운드 프로세스를 만듭니다.
  6. 자식에서 setsid() 를 호출하여 터미널에서 분리하고 독립 session 만듭니다.
  7. 자식에서 fork() 다시 호출하여 데몬이 터미널을 다시 획득 할 수 없도록하십시오.
  8. 첫 번째 자식에서 exit() 를 호출하면 두 번째 자식 (실제 데몬 프로세스) 만 유지됩니다. 이렇게하면 모든 데몬이 있어야하므로 데몬 프로세스가 init / PID 1에 다시 적용됩니다.
  9. 데몬 프로세스에서 표준 입력 , 출력오류에 /dev/null 을 연결하십시오.
  10. 데몬 프로세스에서 umask 를 0으로 재설정하면 open() , mkdir() 등과 같은 파일 모드가 생성 된 파일 및 디렉토리의 액세스 모드를 직접 제어합니다.
  11. 디먼 프로세스에서 마운트 지점이 마운트 해제되는 것을 의도하지 않게 방지하기 위해 현재 디렉토리를 루트 디렉토리 ( / )로 change 하십시오.
  12. 데몬 프로세스에서 PID 파일 (예 : 가상의 데몬 "foobar"의 경우)에 /run/foobar.pid 와 같은 PID 파일에 데몬 PID ( getpid() 반환 한 값)를 작성하여 데몬을 두 번 이상 시작할 수 없도록하십시오 . 이것은 PID 파일에 이전에 저장된 PID가 더 이상 존재하지 않거나 외부 프로세스에 속하는 것과 동시에 검증 될 때만 PID 파일이 갱신되도록 경쟁없는 방식으로 구현되어야합니다.
  13. 데몬 프로세스에서 가능하고 적용 가능한 경우 권한을 삭제하십시오.
  14. 데몬 프로세스에서 초기화 프로세스가 완료되었음을 알리는 원래 프로세스에 알립니다. 이것은 첫 번째 fork() 이전에 작성된 무명 파이프 또는 유사한 통신 채널을 통해 구현 될 수 있으므로 원래 프로세스와 데몬 프로세스에서 모두 사용할 수 있습니다.
  15. 원래 프로세스에서 exit() 를 호출하십시오. 데몬을 호출 한 프로세스는 초기화가 완료되고 모든 외부 통신 채널이 설정되고 액세스 가능한 exit() 가 발생한다는 점에 의존 할 수 있어야합니다.

이 경고에 유의하십시오.

BSD daemon() 함수 이 단계의 하위 집합 만 구현하므로 사용 해서는 안됩니다 .

SysV 시스템과의 호환성 을 제공해야하는 데몬은 위에서 지적한 체계를 구현해야합니다. 그러나 디버깅을 쉽게하고 systemd를 사용하는 시스템으로의 통합을 단순화하기 위해 명령 줄 인수를 통해이 동작을 선택적으로 구성하는 것이 좋습니다.

daemon()POSIX 호환되지 않습니다.

새로운 스타일 데몬

새 스타일 데몬의 경우 다음 단계를 수행 하는 것이 좋습니다.

  1. SIGTERM 이 수신되면, 데몬을 종료하고 완전히 종료하십시오.
  2. SIGHUP 이 수신되면 구성 파일을 다시로드하십시오 (적용되는 경우).
  3. init 시스템이 서비스 오류 및 문제점을 감지하는 데 사용되므로 주 데몬 프로세스에서 올바른 종료 코드를 제공하십시오. SysV init 스크립트LSB 권장 사항에 정의 된대로 종료 코드 체계를 따르는 것이 좋습니다 .
  4. 가능한 경우 적용 가능한 경우 D-Bus IPC 시스템을 통해 데몬의 제어 인터페이스를 노출하고 초기화의 마지막 단계로 버스 이름을 가져옵니다.
  5. systemd에 통합하려면 데몬 시작, 중지 및 유지 보수에 대한 정보가 들어있는 .service unit 파일을 제공하십시오. 자세한 내용은 systemd.service(5) 를 참조하십시오.
  6. 가능한 한 많이 initd 시스템의 기능을 사용하여 데몬의 파일, 서비스 및 기타 자원에 대한 액세스를 제한하십시오. 예를 들어 systemd의 경우에는 systemd의 자원 제한 제어 에 의존하고 systemd의 권한은 떨어집니다 코드를 데몬에서 구현하는 대신 유사하게 만들 수 있습니다. 사용 가능한 컨트롤에 대해서는 systemd.exec(5) 를 참조하십시오.
  7. D-Bus 를 사용하는 경우, D-Bus 서비스 활성화 구성 파일 을 제공하여 버스를 활성화 할 수있게하십시오 . 이것은 여러 가지 장점이 있습니다 : 여러분의 데몬은 온 디맨드로 느리게 시작될 수 있습니다; 그것은 병렬 처리와 부팅 속도 를 최대화하는 다른 데몬과 병렬로 시작될 수 있습니다. 버스가 활성화 가능한 서비스에 대한 요청을 대기열에 넣기 때문에 버스 요청을 잃지 않고 장애가 발생하면 데몬을 다시 시작할 수 있습니다. 자세한 내용은 below 를 참조하십시오.
  8. 데몬이 소켓을 통해 다른 로컬 프로세스 나 원격 클라이언트에 서비스를 제공하는 경우 below 구성에 따라 socket-activatable 가능으로 만들어야 below . D-Bus 활성화와 마찬가지로 이것은 주문형 서비스 시작을 가능하게 할뿐 아니라 서비스 시작의 병렬화를 향상시킵니다. 또한 syslog, DNS와 같은 상태없는 프로토콜의 경우 단일 요청을 잃지 않고 소켓 기반 활성화를 구현하는 데몬을 다시 시작할 수 있습니다. 자세한 내용은 below 를 참조하십시오.
  9. 적용 가능한 경우 데몬은 sd_notify(3) 인터페이스를 통해 시작 완료 또는 상태 업데이트에 대해 init 시스템에 알려야합니다.
  10. syslog() 호출을 사용하여 시스템 syslog 서비스에 직접 로그하는 대신 새로운 스타일의 데몬은 fprintf() 를 통해 표준 오류에 단순히 기록하도록 선택하면 init 시스템에서 syslog로 전달됩니다. 로그 수준이 필요한 경우에는 개별 로그 행 앞에 "<4>"(syslog 우선 순위 구성표의 로그 수준 4 "WARNING") 문자열을 접두어로 사용하여 인코딩 할 수 있습니다.이 형식은 Linux 커널의 printk() 체계. 자세한 내용은 sd-daemon(3)systemd.exec(5) .

자세한 내용은 전체 man 7 daemon 읽으십시오.


daemon 기능을 사용해보십시오 :

#include <unistd.h>

int daemon(int nochdir, int noclose);

daemon() :

daemon () 함수는 제어 터미널에서 스스로 분리하고 시스템 데몬으로 백그라운드에서 실행하려는 프로그램을위한 것입니다.

nochdir이 0이면 daemon ()은 호출 프로세스의 현재 작업 디렉토리를 루트 디렉토리 ( "/")로 변경합니다. 그렇지 않으면 현재 작업 디렉토리가 변경되지 않습니다.

noclose가 0이면 daemon ()은 표준 입력, 표준 출력 및 표준 오류를 / dev / null로 리디렉션합니다. 그렇지 않으면이 파일 설명자를 변경하지 않습니다.





daemon