前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Nginx(4):守护进程,一份nginx实现,一份我的实现,看着拿呗

Nginx(4):守护进程,一份nginx实现,一份我的实现,看着拿呗

作者头像
看、未来
发布2021-10-09 11:59:52
9970
发布2021-10-09 11:59:52
举报
文章被收录于专栏:CSDN搜“看,未来”

愿打开此篇对你有所帮助。

文章目录

示例出处

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

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

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


守护进程概念

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

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

创建步骤

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

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

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

存在即合理

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


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

nginx中的daemon

代码语言:javascript
复制
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;
}

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


缝缝补补又一套

代码语言:javascript
复制
#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;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/10/02 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 示例出处
  • 守护进程概念
    • 创建步骤
      • 存在即合理
      • nginx中的daemon
      • 缝缝补补又一套
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档