原创

守护进程

首先,什么是守护进程?

守护进程是一个在后台长期运行并且不受任何终端控制的进程。

其次,为什么需要守护进程?

我们知道linux有许多自带的守护进程,比如syslogd、crond、sendmail等。那用户或开发者自己编写的程序为什么也需要成为守护进程呢?

这主要是因为守护进程的特性。守护进程不受任何终端控制是为了避免进程在的执行的过程中在终端上输出信息,同时避免进程被终端所产生的信息打断(比如在终端输入ctrl+c或直接退出ssh连接导致的进程退出)。一般的服务软件都需要这样的性质,比如nginx,就要长期运行并且不受终端输入的影响。

然后,如何创建守护进程?

一、Call fork and have the parent exit.--创建子进程,父进程退出。

这一步的作用有两个:

1.非系统守护进程可能是由用户手动执行的,比如在终端shell上执行./nginx。父进程的终止会让shell认为这条命令已经执行完毕。

2.子进程会继承父进程的进程组ID,但子进程肯定不是组长,这个特性是下一步调用setsid的提前。

二、Call setsid to create a new session.--创建新会话。

这一步的作用有三个:

1.使进程成为新会话的首进程。

2.使进程成为新进程组的组长。

3.使进程脱离控制终端。(用户登陆终端就会产生一个新的会话,比如ssh telnet等登陆。这些登陆产生的会话都是可见的,而setsid产生的新会话是不可见的,所以就达到了脱离终端控制的目的。。个人理解,不知道对不对。)

三、Call umask to set the file mode creation mask to a known value, usally 0.--设置文件屏蔽字。

这一步的作用有三个:

1.修改从父进程继承来的文件屏蔽字,避免父进程原先设置的文件屏蔽字对守护进程来说不合理。

2.守护进程应该会需要调用一些库函数,而这些库函数可能并不允许调用者通过一个显式的参数来指定模式,所以最好能在进程中就设置好自己期望的默认文件屏蔽字。

四、Changing the current working directory to be the root directory.--切换工作目录。

这一步可有可无,主要是让守护进程自己设置自己的工作目录,不是死板的继承父进程的。

五、Unneeded file descriptors should be closed.--关闭不必要的文件描述符。

这一步同样可有可无,如果守护进程觉得有必要,可以这样做。如果确信没有从父进程继承什么文件描述符,或者需要这些继承的文件描述符,就不需要关闭。

六、Dup file descriptors 0 1 2 to /dev/null.--屏蔽掉标准输入,标准输出,标准错误输出。

目的很明显,我们不希望在该终端上见到守护进程的输出,用户也不期望他们在终端上的输入被守护进程所读取。

最后,举个例子来说明一下。

我们以nginx实现守护进程的方式来具体说明一下上面的几个步骤:

ngx_int_t
ngx_daemon(ngx_log_t *log)
{
    int  fd;

	// 1.创建子进程,父进程退出。
    switch (fork()) {
    case -1:
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "fork() failed");
        return NGX_ERROR;

    case 0:
        break;

    default:
        exit(0);
    }

    ngx_parent = ngx_pid;
    ngx_pid = ngx_getpid();

	// 2.创建新会话。
    if (setsid() == -1) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "setsid() failed");
        return NGX_ERROR;
    }

	// 3.设置文件屏蔽字。
    umask(0);

	// 6.将标准输出和标准输入重定向到/dev/null。
    fd = open("/dev/null", O_RDWR);
    if (fd == -1) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
                      "open(\"/dev/null\") failed");
        return NGX_ERROR;
    }

    if (dup2(fd, STDIN_FILENO) == -1) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDIN) failed");
        return NGX_ERROR;
    }

    if (dup2(fd, STDOUT_FILENO) == -1) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDOUT) failed");
        return NGX_ERROR;
    }

#if 0 //这里保留了标准错误输出,使得nginx在启动过程中有致命的错误导致不能启动时,输出这些错误。
    if (dup2(fd, STDERR_FILENO) == -1) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDERR) failed");
        return NGX_ERROR;
    }
#endif

    if (fd > STDERR_FILENO) {
        if (close(fd) == -1) {
            ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "close() failed");
            return NGX_ERROR;
        }
    }

    return NGX_OK;
	// 4和5可做可不做。
}

注意

有很多守护进程的实现是两次调用fork,这样做主要是为了避免僵尸进程的产生。

何为僵尸进程呢?

linux里的进程都属于一棵树,树的根结点是init(pid为1)。除了init进程,其它进程都会有一个父进程,父进程负责分配(fork)和回收(wait4)它的子进程资源。

父进程先于子进程退出:其子进程就被init接管,父进程变为init,这种子进程被称为孤儿进程。

子进程先于父进程退出:子进程结束调用exit清理资源后,此时它并没有结束,而是进入僵尸状态,等待它的父进程调用wait4来清理它的task_struct。也就是在其调用exit后和父进程调用wait4前这段时间,子进程被称为僵尸进程。

如果一切正常,子进程僵尸状态只会存在很短的一段时间。但在一些异常情况下,如果父进程长期阻塞在其它业务上而不能调用wait4,则会导致僵尸状态长期存在。只有父进程可以回收子进程的资源,所以父进程不死,没有其它进程能解决这个僵尸进程;父进程死了,则可以由init来接管,僵尸进程就不存在了。僵尸进程是服务器的大忌,大量的僵尸进程会导致服务器宕机。

两次fork

守护进程两次调用fork就是出于僵尸进程的考虑:父进程生成守护进程后,还有其它事情要做,其『人生意义』不止是创建守护进程。这个时候如果守护进程因为一些意外退出了,而父进程又阻塞在其它业务中无法调用wait4,就会产生僵尸进程。而如果父进程先fork子进程,子进程再立刻fork孙子进程,这样孙子进程成为守护进程,立刻被init接管,无论父进程怎么阻塞,都与守护进程无关了。

是不是需要两次fork主要是看自己设计,上面nginx就没有两次fork,因为设计上很明确,父进程创建守护进程后就立刻退出了,不会存在僵尸进程的问题。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 深入理解select的行为

    select的第一个参数为输入参数,其它4个参数既是输入也是输出。3个事件集合:读事件集合、写事件集合、异常事件集合。输出为触发了该事件的集合。最后一个参数为还...

    wytheZ
  • 安全放心的使用rm

    如果你用过linux,那你肯定听说过rm的故事,由这个恐怖的命令引发的灾难比比皆是。比如,rm -rf /*,感兴趣的可以尝试一下,后果自负。

    wytheZ
  • 图文并茂VLAN详解,让你看一遍就理解VLAN

    VLAN(Virtual LAN),翻译成中文是“虚拟局域网”。LAN可以是由少数几台家用计算机构成的网络,也可以是数以百计的计算机构成的企业网络。VLAN所指...

    wytheZ
  • Linux并发(进程裂变)

    进程的分裂跟细胞的分裂几乎一致,一个进程通过fork函数来自我复制,新出现的子进程拥有跟父进程几乎一样的外表和内在。

    用户2617681
  • 操作系统知识点整理

    书《计算机操作系统》第四版(汤小丹编著) 课程操作系统 操作系统启动流程略了 md和pdf下载:Chasssser 完整版包括收集的题目 以下仅为知识...

    Enterprise_
  • Android多进程3

    Android 系统将尽量长时间地保持应用进程,但为了新建进程或运行更重要的进程,最终需要移除旧进程来回收内存。为了确定保留或终止哪些进程,系统会根据进程中正在...

    随心助手
  • Python--进程

    进程:正则进行的一个过程或者说一个任务,而负责执行任务的则是CPU。进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操...

    py3study
  • Android多进程的数据库访问问题

    在Android开发中,我们可能会使用单独的进程来做一些事情,比如推送服务,心跳服务等,这些不需要主应用启动,只需要一个独立的进程即可。这时候我们一般都会采用启...

    飞雪无情
  • 2.并发编程多编程

    ​ python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程。P...

    changxin7
  • linux进程管理:进程,程序,线程 & 9个进程管理工具 & 作业控制

    程序 ------》系统调用-------》缓存(内存) -------》cpu处理 执行任务

    用户5807183

扫码关注云+社区

领取腾讯云代金券