How do I create a daemon?

Posted by universelittle on Fri, 14 Jan 2022 20:13:19 +0100

1. Daemon creation steps

Daemons are processes without terminals. They run in the background and are often started when the system boots So how do I create a daemon?
Refer to APUE 13.3 to create a daemon:

  1. Call umask to set the permission mask word (umask) of the file created by the process to facilitate the daemon to create the file
    umask is usually set to 0. If you call the library function to create a file, it can be set to 007
  2. Call fork, and the parent process exit s
    To call setsid to create a session, you need to ensure that the calling process (sub process) is not the process group leader. fork sub process can ensure this
    PS: it is uncertain how to start the program. It may or may not be the process group leader.
  3. Call setsid to create a new session
    The subprocess calls setsid to become the new session header process and the leader of the new process group, and disconnects the terminal.
    PS: calling setsid to the process group leader will fail, return - 1, and errno will be set. Therefore, you must first pass fork + parent process exit, so that the child process will become an orphan process and be adopted by init, so as to ensure that the child process is not the process leader.
  4. Call fork again and the parent process exit s (optional)
    It is not necessary, mainly to ensure that the process cannot obtain the terminal again through open /dev/tty, because when open is called, the system will create a control terminal for the first process of the session by default.
  5. Call chdir to change the current working directory to the root directory
    Daemons generally exist for a long time. When daemons exist, they cannot uninstall the working directory To avoid this, change the current working directory to the root directory ("/")
  6. Call close to close all unnecessary file descriptors
    open_ Max, getrlimit and sysconf (_sc_open_max) can obtain the highest value of the file descriptor
  7. Open the / dev/null file so that the file descriptor 0,1,2 points to the file
    It can effectively prevent accidental effects

2. Create daemon code

The custom function daemon converts a process into a daemon

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

#define MAXLINE 200

static void err_doit(int errnoflag, int error, const char *fmt, va_list ap) {
    char buf[MAXLINE];

    // Convert the format string fmt (parameter ap) into a string and store it in buf
    vsnprintf(buf, MAXLINE - 1, fmt, ap);
    if (errnoflag) {
        // The last parameter of snprintf is const char *, and the last parameter of vsnprintf is va_list, same function
        snprintf(buf + strlen(buf), MAXLINE - strlen(buf) - 1, ": %s", strerror(error));
    }

    strcat(buf, "\n"); // Paste "\ n" at the end of buf and end with '\ 0'
    fflush(stdout); // In case stdout and stderr are the same equipment, flush stdout first
    fputs(buf, stderr);
}

/**
 * Fatal error related to system call
 * Print messages and terminate programs
 */
static void err_quit (const char *fmt, ...) {
    va_list ap;

    // va_start, va_end pairing to get variable parameters, Store to va_list ap
    va_start(ap, fmt);
    err_doit(0, 0, fmt, ap);
    va_end(ap);

    exit(1);
}

/**
* Convert a process to a daemon
* Steps:
* 1. Call umask to set umask for creating file permissions
* 2. Call fork to let the parent process exit and the child process become an orphan process
* 3. Call setsid to create a new session. The sub process becomes the leader of the new session header process and incoming process group, and disconnect the control terminal
* 4. Call fork again and let the parent process exit. The child process is not the first process of the session (to prevent the control terminal from being obtained again)
* 5. Change the current working directory to the root directory to prevent the directory from being uninstalled
* 6. Close unneeded file descriptors
* 7. Open / dev/null with file descriptor 0,1,2
*/
void daemonize(const char *pname) {
    int i, fd0, fd1, fd2;
    pid_t pid;
    struct rlimit rl;
    struct sigaction sa;

    // 1. Call umask to modify the umask of creating file permissions
    umask(0);

    if (getrlimit(RLIMIT_NOFILE, &rl) < 0) {
        // <=> sysconf(_SC_OPEN_MAX);
        err_quit("%s: getrlimit error", pname);
    }

    // 2. Call fork, the parent process exits, and the child process is called an orphan and adopted by the init process
    if ((pid = fork()) < 0)
        err_quit("%s: fork error", pname);
    else if (pid > 0) { // Parent process
        exit(0);
    }

    // 3. Call setsit to create a new session, and the child process becomes the first process
    setsid();
   /* Situation of sending SIGHUP signal:
    1)When the terminal is closed, the signal is sent to the first process of the session and the process submitted as a job (the process run by the shell in & mode)
    2)session When the first process exits, the signal is sent to all foreground processes in the same session
    3)If the parent process exits, the process is formed into an orphan process group, and some processes in the process group are stopped (SIGSTOP signal or sigstp signal is received), the signal will be sent to each member of the process group
    */
    sa.sa_handler = SIG_IGN;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    if (sigaction(SIGHUP, &sa, NULL) < 0)
        err_quit("%s: can't ignore SIGHUP", pname);

    // 4. Call fork again
    if ((pid = fork()) < 0)
        err_quit("%s: fork error", pname);
    else if (pid > 0)
        exit(0);

    // 5. Change the working directory to "/ (root directory)
    if (chdir("/") < 0)
        err_quit("%s: can't change directory to /", pname);

    // 6. Close unnecessary file descriptors
    if (rl.rlim_max == RLIM_INFINITY)
        rl.rlim_max = 1024;
    for (i = 0; i < rl.rlim_max; ++i)
        close(i);

#if 1
    // 7. Attach file descriptor 0,1,2 to / dev/null
    // Since all file descriptors have been closed previously, reopen and the file descriptors obtained by DUP are incremented
    fd0 = open("/dev/null", O_RDWR);
    fd1 = dup(0);
    fd2 = dup(0);
#else
    open("/dev/null", O_RDONLY);
    open("/dev/null", O_RDWR);
    open("/dev/null", O_RDWR);
#endif

    /* Establish a connection to syslog
       LOG_CONS: If it cannot be sent to the syslogd daemon, it is registered to the console
       LOG_DAEMON: Identifies that the type of message sending process is system daemon */
    openlog(pname, LOG_CONS, LOG_DAEMON);

    /* File descriptor exception check: under normal conditions, fd0, FD1 and fd2 should be 0, 1 and 2 respectively */
    if (fd0 != 0 || fd1 != 1 || fd2 != 2) {
        syslog(LOG_ERR, "unexpected file descriptors %d %d %d", fd0, fd1, fd2);
        exit(1);
    }
}

main function

#include <stdio.h>
#include <unistd.h>

#include "daemonize.h"

int main() {
    char *s = "mydaemonize";

    printf("ready to convert a normal process into a daemonize process\n");
    daemonize(s);

    while(1)
        sleep(1);
    return 0;
}

Check Daemons

Compile and run the new daemon a.out

$ ./a.out
$ ps -efj
UID        PID  PPID  PGID   SID  C STIME TTY          TIME CMD
martin   12596  1614 12595 12595  0 23:27 ?        00:00:00 ./a.out
$ ps -efj | grep 12595
martin   12596  1614 12595 12595  0 23:27 ?        00:00:00 ./a.out

Termination daemon

Using the kill -9 command

$ kill -9 12596

Topics: Linux