nginx学习十三 初始fork和nginx守护进程ngx_daemon

Posted jzdwajue

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了nginx学习十三 初始fork和nginx守护进程ngx_daemon相关的知识,希望对你有一定的参考价值。

学习nginx已经有一个多月了。认为越来越吃力了。主要原因自己总结了一下:1平台是基于linux的,曾经差点儿没有接触过linux,而nginx使用了非常多linux的函数。2就是进程,这个东西接触的也非常少。linux的多进程更不用说,而如今正好看到这里,认为异常的吃力,这不看到nginx守护进程的建立,就找资料好好学习一下。所以本文已学习fork为主要内容。

好了,先看一下nginx的守护进程的建立,然后在学习fork。

http://blog.csdn.net/xiaoliangsky/article/details/39998373

1nginx的守护进程

直接看代码:

ngx_int_t ngx_daemon(ngx_log_t *log)
{
    int  fd;

    switch (fork()) {//用fork创建守护进程
    case -1://fork返回-1创建失败
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "fork() failed");
        return NGX_ERROR;

    case 0://子进程返回
        break;

    default://父进程返回
        exit(0);//父进程退出
    }

    ngx_pid = ngx_getpid();

    if (setsid() == -1) {//建立新的会话。然后子进程称为会话组长
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "setsid() failed");
        return NGX_ERROR;
    }

    umask(0);//重设文件创建掩模

    /*重定向标准输入、输出到/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) {//输入重定向到fd。即从/dev/null输入
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDIN) failed");
        return NGX_ERROR;
    }

    if (dup2(fd, STDOUT_FILENO) == -1) {//输出重定向到fd,即全部输出到/dev/null
        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;
        }
    }

    return NGX_OK;
}
这里不多解释,看了以下的内容。就知道上面的代码非常easy。

2fork函数

由fork创建的新进程被称为子进程(child process)。

该函数被调用一次。但返回两次。两次返回的差别是子进程的返回值是0,而父进程的返回值则是新进程(子进程)的进程 id。将子进程id返回给父进程的理由是:由于一个进程的子进程能够多于一个,没有一个函数使一个进程能够获得其全部子进程的进程id。对子进程来说,之所以fork返回0给它。是由于它随时能够调用getpid()来获取自己的pid;也能够调用getppid()来获取父进程的id。(进程id 0总是由交换进程使用,所以一个子进程的进程id不可能为0 )。

fork之后。操作系统会复制一个与父进程全然同样的子进程。虽说是父子关系。可是在操作系统看来。他们更像兄弟关系。这2个进程共享代码空间,可是数据空间是互相独立的,子进程数据空间中的内容是父进程的完整拷贝,指令指针也全然同样,子进程拥有父进程当前执行到的位置(两进程的程序计数器pc值同样,也就是说,子进程是从fork返回处開始执行的)。但有一点不同,假设fork成功,子进程中fork的返回值是0,父进程中fork的返回值是子进程的进程号,假设fork不成功,父进程会返回错误。

来个列子:

int main()
{
	int   count;
	int   flag;
    pid_t pid;

    pid = fork();

    if (pid > 0)
    {
        printf("parent process is run\n");
		flag = 1;
    }
    else if (pid < 0)
    {
        printf("fork is error\n");
        exit(-1);
    }
    else
    {
        printf("child is run \n");
		flag = 0;
    }

    count = 0;
	
	if (flag)
	{
		printf("count in parent is : %d\n", ++count);
	}
	else
	{
		printf("count in child is : %d\n", ++count);
	}
	
	
	return 0;
}
执行结果例如以下:

parent process is run
count in parent is : 1
child is run 
count in child is : 1
从这个样例。能够知道:

1父进程和子进程运行了同样的代码

2父进程和子进程不共享数据空间,否则count的值不可能一样。

3fork进程返回了两次,且父进程返回时pid大于0,子进程返回时pid=0,且子进程从返回处開始运行。


以下是《高级编程》具体介绍的父子进程之间的关系。

fork出来的子进程,基本上除了进程号之外父进程的所有东西都有一份拷贝,基本就意味着不是所有,以下我们要说的是子进程从父进程那里继承了什么东西,什么东西没有继承。另一点须要注意,子进程得到的仅仅是父进程的拷贝,而不是父进程资源的本身。



由子进程自父进程继承到:     
1.进程的资格(真实(real)/有效(effective)/已保存(saved)用户号(UIDs)和组号(GIDs))
2.环境(environment)
3.堆栈
4.内存
5.打开文件的描写叙述符(注意相应的文件的位置由父子进程共享,这会引起含糊情况)
6.运行时关闭(close-on-exec) 标志 (译者注:close-on-exec标志可通过fnctl()对文件描写叙述符设置,POSIX.1要求全部文件夹流都必须在exec函数调用时关闭。更具体说明,參见《UNIX环境高级编程》 W. R. Stevens, 1993, 尤晋元等译(下面简称《高级编程》), 3.13节和8.9节)
7.信号(signal)控制设定
8.nice值 (译者注:nice值由nice函数设定,该值表示进程的优先级,数值越小。优先级越高)
进程调度类别(scheduler class)(译者注:进程调度类别指进程在系统中被调度时所属的类别,不同类别有不同优先级,依据进程调度类别和nice值,进程调度程序可计算出每一个进程的全局优先级(Global process prority),优先级高的进程优先运行)
8.进程组号
9.对话期ID(Session ID) (译者注:译文取自《高级编程》,指:进程所属的对话期(session)ID。 一个对话期包含一个或多个进程组, 更具体说明參见《高级编程》9.5节)
10.当前工作文件夹
11.根文件夹 (译者注:根文件夹不一定是“/”,它可由chroot函数改变)
12.文件方式创建屏蔽字(file mode creation mask (umask))(译者注:译文取自《高级编程》,指:创建新文件的缺省屏蔽字)
13.资源限制
14.控制终端

子进程所独有:
进程号
1.不同的父进程号(译者注:即子进程的父进程号与父进程的父进程号不同, 父进程号可由getppid函数得到)
2.自己的文件描写叙述符和文件夹流的拷贝(译者注:文件夹流由opendir函数创建,因其为顺序读取,顾称“文件夹流”)
3.子进程不继承父进程的进程,正文(text)。 数据和其他锁定内存(memory locks)(译者注:锁定内存指被锁定的虚拟内存页。锁定后,4.不同意内核将其在必要时换出(page out),具体说明參见《The GNU C Library Reference Manual》 2.2版, 1999, 3.4.2节)
5.在tms结构中的系统时间(译者注:tms结构可由times函数获得,它保存四个数据用于记录进程使用中央处理器 (CPU:Central Processing Unit)的时间,包含:用户时间。系统时间, 用户各子进程合计时间,系统各子进程合计时间)
6.资源使用(resource utilizations)设定为0
8.堵塞信号集初始化为空集(译者注:原文此处不明白,译文依据fork函数手冊页稍做改动)
9.不继承由timer_create函数创建的计时器
10.不继承异步输入和输出

3守护进程的创建

守护进程(Daemon)是执行在后台的一种特殊进程。

它独立于控制终端而且周期性地执行某种任务或等待处理某些发生的事件。其次,守护进程必须与其执行前的环境隔离开来。

这些环 境包含未关闭的文件描写叙述符。控制终端,会话和进程组。工作文件夹以及文件创建掩模等。

这些环境一般是守护进程从执行它的父进程(特别是shell)中继承下 来的。

最后,守护进程的启动方式有其特殊之处。它能够在Linux系统启动时从启动脚本/etc/rc.d中启动,能够由作业规划进程crond启动,还 能够由用户终端(一般是 shell)运行。

守护进程创建的步骤:

1)在后台执行

为避免挂起控制终端将Daemon放入后台运行。方法是在进程中调用fork使父进程终止,让daemon在子进程中后台运行:

    pid = fork();
    if (pid > 0)
    {
        printf("parent is exit\n");
        exit(0);//父进程退出
    }
2)脱离控制终端,登录会话和进程组

Linux中的进程与控制终端,登录会话和进程组之间的关系:进程属于一个进程组,进程组号(GID)就是进程组长的进程(PID)。登录会话能够包括多个进程组。

这些进程组共享一个控制终端。

这个控制终端一般是创建进程的登录终端。 控制终端,登录会话和进程组一般是从父进程继承下来的。我们的目的就是要摆脱它们。使之不受它们的影响。方法是在第1点的基础上。调用setsid()使 进程成为会话组长:

if (setsid() == -1)
{
	printf("setsid is failed\n");
}
setsid()调用成功后,进程成为新的会话组长和新的进程组长。并与原来的登录会话和进程组脱离。因为会话过程对控制终端的独占性,进程同一时候与控制终端脱离。

3)禁止进程又一次打开控制终端(这个步骤可有可无,看情况)
如今,进程已经成为无终端的会话组长。但它能够又一次申请打开一个控制终端。能够通过使进程不再成为会话组长来禁止进程又一次打开控制终端:

if(pid=fork()) exit(0); //结束第一子进程。第二子进程继续(第二子进程不再是会话组长)
4)假设有打开的文件,就关闭打开的文件描写叙述符

进程从创建它的父进程那里继承了打开的文件描写叙述符。如不关闭。将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。

for(i=0;i<=getdtablesize();i++)
close(i)

5)改变当前工作文件夹(看情况)
进程活动时,其工作文件夹所在的文件系统不能卸下。一般须要将工作文件夹改变到根文件夹。

对于须要转储核心,写执行日志的进程将工作文件夹改变到特定文件夹。

chdir("/tmp")

6)重设文件创建掩模
进程从创建它的父进程那里继承了文件创建掩模。它可能改动守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:

umask(0);
ok,基本步骤已经完毕。

nginx的damon进程仅仅有步骤1,2,6,一般这3步即可了,只是复杂情况还是按步骤来写。

以下我们在看一个样例:

在子进程中打开一个文件,并向文件里写入数据。在满足一定条件时。守护进程退出。

void daemon_fork()
{
    pid_t pid;

    pid = fork();
    if (pid > 0)
    {
        printf("parent is exit\n");
        exit(0);//第1步
    }
    else if (pid < 0)
    {
        printf("fork is failed\n");
        exit(-1);
    }
    else
    {
		if (setsid() == -1)//第2步
		{
			printf("setsid is failed\n");
		}

		umask(000);//第6步
	
        printf("child is working\n");

		FILE *fp = fopen("test.txt", "a");
		if (fp == NULL)
		{
			kill(pid, SIGTERM);
		}
        //do something
        int i = 0;
        
        for (;;)
        {
			fprintf(fp, "%s", (u_char*)("I am the deamon two\n"));
            fprintf(fp, "i = %d\n", ++i);

            sleep(10);
			
			if (i > 10)
			{
				fclose(fp);
				kill(pid, SIGTERM);
			}			
        }
    }
}

http://blog.csdn.net/xiaoliangsky/article/details/39998373

參考:

http://blog.csdn.net/theone10211024/article/details/13774669

http://blog.chinaunix.net/uid-25365622-id-3055635.html


 














































以上是关于nginx学习十三 初始fork和nginx守护进程ngx_daemon的主要内容,如果未能解决你的问题,请参考以下文章

linux守护进程范例

Linux学习总结(四十三)nginx 负载均衡 https 配置

059.守护进程和初始化进程服务

Nginx是如何处理一个请求

#yyds干货盘点#nginx

分布式系列十三: nginx