守护进程,一份nginx实现,一份我的实现,看着拿呗

Posted 看,未来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了守护进程,一份nginx实现,一份我的实现,看着拿呗相关的知识,希望对你有一定的参考价值。

示例出处

这个守护进程的示例是我从nginx的源码当中剥离出来的。

nginx的源码是比muduo要复杂些哈,muduo跟我以前写过的服务端项目有很多共通之处,就相当于是剥离了业务代码的网络层框架,所以看起来也比较亲切。这个nginx就感觉稍微有点陌生哈。

所以我决定一块一块能用的我先剥出来。


守护进程概念

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

守护进程没有控制终端,因此当某些情况发生时,不管是一般的报告性信息,还是需由管理员处理的紧急信息,都需要以某种方式输出。

创建步骤

1、创建“孤儿进程”,形式上与终端脱离;
2、让这个“孤儿进程”成为新会话的组长,防止进程被原会话中其他进程干扰;
3、改变工作目录并重设文件创建掩码;
4、关闭文件描述符,因为没必要开着了。

以上2/3/4都是在消除父进程的印记。

还看到有些人说,应该先屏蔽些信号,省的出师未捷身先死哈。想屏蔽就屏蔽呗,也不差写那四五行代码。

存在即合理

1)终端要干别的事儿了,后边凉快的地方呆着去。
2)避免进程被任何终端所产生的信息所打断,其在执行过程中的信息也不在任何终端上显示。逍遥吧!


接下来我们看看nginx里面的守护进程实现哈,当然,我们要带着辩证的角度来看,要是看到它省略了几句啥,咱可以自己补上嘛,有试无害嘛。

nginx中的daemon

ngx_int_t ngx_daemon(ngx_log_t *log)	
{
    int  fd;

		//要成为守护进程,首先要成为孤儿,进孤儿院
		/*
    	 调用fork函数创建子进程后,使父进程立即退出。产生的子进程将被init进程接管,
    	 同时,所产生的新进程将变为在后台运行。
		*/
    switch (fork()) {
    case -1:
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "fork() failed");
        return NGX_ERROR;

    case 0:
        break;

    default:
        exit(0);
    }

		/*
			    孤儿调用setsid()函数脱离控制终端和进程组,使该进程成为会话组长,并与原来的登录会话和进程组脱离。
    			此时孤儿进程已经成为无终端的会话组长,但它可以重新申请打开一个控制终端。
    			为了避免这种情况,可以通过使进程不再成为会话组长来禁止进程重新打开控制终端,
    			父进程(会话组长)退出,子进程继续执行,并不再拥有打开控制终端的能力。
    			在正在执行的进程中调用INIT_DAEMON后,进程将成为守护进程,脱离控制终端进入后台执行。
		*/
    if (setsid() == -1) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "setsid() failed");
        return NGX_ERROR;
    }

		/*
			    很多情况下,守护进程会创建一些临时文件。出于安全性的考虑,往往不希望这些文件被别的用户查看。
    			这时,可以使用umask函数修改文件权限,创建掩码的取值,以满足守护进程的要求。
		*/
    umask(0);


		/*
    			新产生的进程从父进程继承了某些打开的文件描述符,如果不使用这些文件描述符,则需要关闭它们。
    			守护进程是运行在系统后台的,不应该在终端有任何的输出信息。
    			可以使用dup函数将标准输入、输出和错误输出重定向到/dev/null设备上
    			(/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
    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;
        }
    }

		/*
			    改变当前工作目录(nginx没有做)
    			使用fork函数产生的子进程将继承父进程的当前工作目录。当进程没有结束时,其工作目录是不能被卸载的。
    			为了防止这种问题发生,守护进程一般会将其工作目录更改到根目录下(/目录)。
		*/
    return NGX_OK;
}

是吧,人家的实现里面有些细节我们前面还是没有考虑到的,不过我们前面考虑到的一些细节人家也是没有采用的,不知道是不是没有必要还是咋滴,我还是将两者结合一下补一份哈,有需要的看情况自取。


缝缝补补又一套

#include <unistd.h> 
#include <signal.h> 
#include <fcntl.h>
#include <sys/syslog.h>
#include <sys/param.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
 
int init_daemon(void)
{ 
	int i;
	
	// 1)屏蔽一些控制终端操作的信号
	//这些nginx在创建进程的时候设定了哈
	signal(SIGTTOU,SIG_IGN); 
	signal(SIGTTIN,SIG_IGN); 
	signal(SIGTSTP,SIG_IGN); 
	signal(SIGHUP ,SIG_IGN);
 
	// 2)创建孤儿进程
	switch (fork()) {
    case -1:
        return -1;
    case 0:
        break;
    default:
        exit(0);
  }
    
	// 3)脱离控制终端、登录会话和进程组
	setsid();  
	
	// 4)禁止进程重新打开控制终端
	/*
		现在,进程已经成为无终端的会话组长,但它可以重新申请打开一个控制终端。
		可以通过使进程不再成为会话组长来禁止进程重新打开控制终端,采用的方法是再次创建一个子进程。
		这个nginx里面没有实现,不知道是不是没有必要哈,反正个人看自己需要吧。
	*/
	switch (fork()) {
    case -1:
        return -1;
    case 0:
        break;
    default:	// 结束第一子进程,第二子进程继续(第二子进程不再是会话组长)
        exit(0);
  }
	
	// 5)关闭打开的文件描述符
	/* NOFILE 为 <sys/param.h> 的宏定义
	 NOFILE 为文件描述符最大个数,不同系统有不同限制
	 这个nginx也没有哈,这一步确实有点耗时哈,
	 还是那句话,看个人需求
	*/
	for(i=0; i< NOFILE; ++i){
		close(i);
	}
	
	// 6)改变当前工作目录
	//这个nginx也没有做
	chdir("/"); 
	
	// 7)重设文件创建掩模
	umask(0);  
	
	// 8)重定向标准流
	/*
    			新产生的进程从父进程继承了某些打开的文件描述符,如果不使用这些文件描述符,则需要关闭它们。
    			守护进程是运行在系统后台的,不应该在终端有任何的输出信息。
    			可以使用dup函数将标准输入、输出和错误输出重定向到/dev/null设备上
    			(/dev/null是一个空设备,向其写入数据不会有任何输出)。
		*/
    fd = open("/dev/null", O_RDWR);	//难怪在好多地方有看到这么个写法,当时就不知道是干嘛的
    if (fd == -1) {
        return -1;
    }

    if (dup2(fd, STDIN_FILENO) == -1) {
        return -1;
    }

    if (dup2(fd, STDOUT_FILENO) == -1) {
        return -1;
    }

#if 0
    if (dup2(fd, STDERR_FILENO) == -1) {
        return -1;
    }
#endif
		//把fd关了,nginx可真省,难怪前面那些都不要了
		//还是那句话,个人看个人情况自取
    if (fd > STDERR_FILENO) {
        if (close(fd) == -1) {
            return -1;
        }
    }

	// 9)处理 SIGCHLD 信号
	/*
		对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。(这个我有看到)
		如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。
		如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在
		Linux 下可以简单地将 SIGCHLD 信号的操作设为 SIG_IGN 。

		这个nginx在创建进程的时候设定了哈
	*/
	signal(SIGCHLD,SIG_IGN);
	
	return 0; 
} 
 
int main(int argc, char *argv[]) 
{
	init_daemon();
	
	while(1);
 
	return 0;
}

以上是关于守护进程,一份nginx实现,一份我的实现,看着拿呗的主要内容,如果未能解决你的问题,请参考以下文章

拿什么守护你-PHP程序级守护进程的实现与优化

Linux如何实现进程监控和守护

nginx通过Supervisor实现守护进程

一份针对nginx的内核优化参数

Nginx+Tomcat 动静分离实现负载均衡

Linux系统安装并配置nginx实现多服务同一端口