Linux 守护进程启动过程

/*****************************************************************************

  • fork daemon:
  • (https://github.com/pasce/daemon-skeleton-linux-c/)
  • (https://nullraum.net/how-to-create-a-daemon-in-c/)
  • forking a daemon requires the following steps:
  • o - fork off the parent process, so now the child runs in background.
  • o - setsid to create a new session. the process is now the session leader and the process group leader of the new process group.
  • o - catch signals
  • o - fork again. let the parent terminate so the child gets rid of session leader.
  • o - umask
  • o - chdir
  • o - close all open fds.
    *****************************************************************************/
/*****************************************************************************
 * NAME: UpdateSignalHandling
 *
 * DESCRIPTION:
 *      this function sets up the signal handling and masking for the daemon
 *      process. the following is done here:
 *
 *      o - change some default signal disposition with sigaction().
 *      o - block all signals. we will use sigwait() in a dedicated thread
 *          to process the signals we are interested in.
 *
 *      the failures in this function are non-fatal. so we keep going after
 *      taking a note of the error code.
 *
 * INPUTS:
 *      NONE.
 *
 * RETURN:
 *      int.
 */
static
int
UpdateSignalHandling(
    void
    )
{

    sigset_t sigset;
    struct sigaction sa = { .sa_handler = SIG_IGN };
    int status = 0;

    if (sigfillset(&sa.sa_mask) == 0)
    {
        static int IGNORE_SIG[] = { SIGINT, SIGTERM, SIGCHLD, SIGHUP, SIGPIPE, SIGIO };
        unsigned int i = 0;

        for (i = 0; i < sizeof(IGNORE_SIG)/sizeof(IGNORE_SIG[0]); i++)
        {
            if (sigaction(IGNORE_SIG[i], &sa, NULL) != 0)
            {
                status = -errno;
            }
        }
    }
    else
    {
        status = -errno;
    }

    if (sigfillset(&sigset) == 0)
    {
        static int UNMASK_SIG[] = { SIGBUS, SIGFPE, SIGILL, SIGSEGV };
        unsigned int i = 0;
        int r = 0;

        for (i = 0; i < sizeof(UNMASK_SIG)/sizeof(UNMASK_SIG[0]); i++)
        {
            (void)sigdelset(&sigset, UNMASK_SIG[i]);
        }

        /*
         * per man page, the use of sigprocmask() is unspecified in MT process.
         * use pthread_sigmask() instead.
         */
        r = pthread_sigmask(SIG_BLOCK, &sigset, NULL);

        if (r != 0)
        {
            status = -r;
        }
    }
    else
    {
        status = -errno;
    }

    return status;
}


/*****************************************************************************
 * NAME: MakeDir
 *
 * DESCRIPTION:
 *      this function makes the directory recursively.
 *
 * INPUTS:
 *      DirPath         - [in] the dir path.
 *      Mode            - [in] the mode/permission of the dirs.
 *
 * RETURN:
 *      int. errno is also set just for convenience of ForkDaemon().
 */
static
int
MakeDir(
    char const* DirPath,
    mode_t Mode
    )
{
    int status = 0;

    if (mkdir(DirPath, Mode) != 0)
    {
        status = -errno;

        if (status == -ENOENT)
        {
            size_t len = strlen(DirPath);
            char* end = strchr(DirPath, '/');

            if (len > 0 && end != NULL && DirPath + len > end + 1)
            {
                char* path = strdup(DirPath);

                if (path != NULL)
                {
                    char* p = path;
                    status = 0;

                    for ( ; ; )
                    {
                        if ((p = strchr(p + 1, '/')) != NULL)
                        {
                            *p = 0;
                        }

                        if (mkdir(path, Mode) != 0 && errno != EEXIST)
                        {
                            status = -errno;
                            break;
                        }

                        if (p == NULL)
                        {
                            break;
                        }

                        *p = '/';
                    }

                    free(path);
                }
                else
                {
                    status = -ENOMEM;
                }
            }
        }
        else if (status == -EEXIST)
        {
            status = 0;
        }
    }

    errno = -status;
    return status;
}
/*****************************************************************************
 * NAME: ForkDaemon
 *
 * DESCRIPTION:
 *      this function forks the process into a daemon and exits the parent.
 *      by doing this the orphaned child will be monitored by init.
 *
 * INPUTS:
 *      WorkingDir      - [in] the working directory of the daemon.
 *
 * RETURN:
 *      STATUS. if the return value is STATUS_PARENT, the caller is
 *      forked as the parent and must clean up if needed and exit ASAP.
 */
#define STATUS_PARENT 1

static
int
ForkDaemon(
    char const* WorkingDir
    )
{
    int status = 0;
    pid_t pid = fork();

    if (pid == 0)
    {
        if (setsid() != -1)
        {
            int r = UpdateSignalHandling();

            if (r != 0)
            {
                /* signal handler update failures are non-fatal. */
                printf("failed to update signal handlers (%d). still ok to proceed.", r);
            }

            pid = fork();

            if (pid == 0)
            {
                (void)umask(S_IWGRP | S_IWOTH);

                if (chdir(WorkingDir) == 0 ||
                    (errno == ENOENT &&
                     MakeDir(WorkingDir, S_IRWXU) == LW_STATUS_OK &&
                     chdir(WorkingDir) == 0))
                {
                    /* now close all FDs. in case trace/log uses FDs, we uninit then reinit it. */
                    int fd;
                    (void)fprintf(stderr, "CommServer running as service...\n");
					/*all fds opened are being closed here*/
                    for (fd = sysconf(_SC_OPEN_MAX); fd >= 0; fd--)
                    {
                        (void)close(fd);
                    }
                }
                else
                {
                    status = -errno;
                    (void)fprintf(stderr, "failed to enter/create working dir: %s (%d)\n",
                        WorkingDir, status);
                }
            }
            else if (pid != -1)
            {
                status = STATUS_PARENT;
            }
            else
            {
                status = -errno;
            }
        }
        else
        {
            status = -errno;
        }
    }
    else if (pid != -1)
    {
        status = STATUS_PARENT;
    }
    else
    {
        status = -errno;
    }

    return status;
}
  1. 第一次fork的作用是让shell 认为本条命令 已经终止,不用挂在终端输入上。还有一个作用是为后面setsid服务。setsid的调用者不能是进程组组长(group leader). 此时父进程是进程组组长。

  2. setsid() 是本函数最重要的一个调用。它完成了daemon函数想要做的大部分事情。调用完整个函数。子进程是会话组长(sid==pid),也是进程组组长(pgid == pid),并且脱离了原来控制终端。到了这一步,基本上不管控制终端如何怎么样。新的进程都不会收到那些信号。

  3. 经过前面2个步骤,基本想要做的都做了。第2次fork不是必须的。也看到很多开源服务没有fork第二次。fork第二次主要目的是。防止进程再次打开一个控制终端。因为打开一个控制终端的前提条件是该进程必须是会话组长。再fork一次,子进程ID != sid(sid是进程父进程的sid)。所以也无法打开新的控制终端。

daemon目的就是防止终端产生的一些信号让进程退出。上面函数并没有直接调用signal函数去处理它。而是间接通过fork和setsid函数使用更少代码优雅处理。而被有些人误以为是僵死进程的原因需要这样处理。

猜你喜欢

转载自blog.csdn.net/vegeta852/article/details/107730282
今日推荐