Today I publish the initialization process of nginx.

Nginx (四)  启动过程

从main()函数开始,进行了一系列的初始化处理工作。

1.  从main()函数开始,进行了一系列的初始化处理工作。下面将分别介绍,对于不是很重要或是很好理解力的部分可能不作详细说明。

a)       首先是从命令行获取参数,打印参数的用法说明。

b)       ngx_time_init()函数,获取当前系统的日期和时间。Nginx中定义了三个全局ngx_str_t变量,ngx_cached_err_log_time、ngx_cached_http_time、ngx_cached_http_log_time分别用来表示error.log中的时间格式,http协议中的时间格式,和accesse.log中的时间格式。另外,nginx中还分别定义了ngx_str_t 类型cached_err_log_time[NGX_TIME_SLOTS],cached_http_time[NGX_TIME_SLOTS],cached_http_log_time[NGX_TIME_SLOTS]用来缓存系统的时间。还有static ngx_time_t cached_time[NGX_TIME_SLOTS]。

这些数组的长度为64。这此缓存数极组有什么用处,还不知道。

在函数最后,调用ngx_time_update()。

c)       ngx_time_update()函数,通过ngx_gettimeofday()取得当前的毫秒数,其后逻辑比较简单,只是根据获取的毫秒数通过一些系统调用获取年,月,日,时,分,秒以及按不同时区进行的转换。

d)       注意,在ngx_time_update()函数后部使用了一个ngx_memory_barrier()。这是个宏。对不同的编译器不同的环境有不同的定义。关于x86和gcc的定义如下:

/*

* on x86 the write operations go in a program order, so we need only

* to disable the gcc reorder optimizations

*/

#define ngx_memory_barrier()    __asm__ volatile ("" ::: "memory")

2.  用ngx_getpid()获取nginx的pid。

3.  如果宏NGX_OPENSSL定义了,在ngx_ssl_init()函数中载入open ssl的库。

4.  随后在全局的ngx_cycle变量中,创建内存池,大小为1024。

5.  ngx_log_init()函数,先确定NGX_ERROR_LOG_PATH宏是否定义了errlog文件的路径,如果没有定义,nginx会以stderr作为log输出文件,然后函数返回。

如果定义了errlog文件的路径,接着检查这个路径是不是绝对路径,Window系统中查看盘符,其他系统则盾根目录’/’;如果是相对路径,则会加上设定的或是NGX_PREFIX宏定义的前辍路径;最后以append模式打开路径中指定的log文件。如果打开log文件失败,则会再次将stderr设为log输出。(ngx_log全局变量中保存着nginx中log的相关信息,包括打开的log文件指针,log级别,处理函数等。)

struct ngx_log_s {

ngx_uint_t           log_level;

ngx_open_file_t     *file;

ngx_atomic_uint_t    connection;

ngx_log_handler_pt   handler;

void                *data;

/*

* we declare "action" as "char *" because the actions are usually

* the static strings and in the "u_char *" case we have to override

* their types all the time

*/

char                *action;

};

6.  下面代码比较重要:

ngx_memzero(&init_cycle, sizeof(ngx_cycle_t));

init_cycle.log = log;

ngx_cycle = &init_cycle;

后面对init_cycle的操作,实际上都是对ngx_cycle起作用。

7.  ngx_save_argv()函数则是将从main()函数传入的参数表保存到nginx的全局变量ngx_argc,ngx_argv[],ngx_os_argv,ngx_os_environ中。如果是freebsd系统,直接保存即可,否则nginx会创建新的参数数组保存命令行参数;

8.  ngx_process_options()函数,做下面工作:

a)           确定前缀路径,如果当前nginx系统已经取得了前缀路径,则将其保存在cycle变量(即init_cycle)中,同时也将该前缀作为conf文件的前缀路径;

b)           若还没有取得前缀路径,就立即设置前缀路径。这时如果NGX_PREFIX宏没有定义,就以当前工作路径作为前缀路径,同时也检查NGX_CONF_PREFIX宏,有定义则conf文件的前缀采用NGX_CONF_PREFIX宏中的定义。

c)           接着用ngx_conf_full_name()函数检查conf文件的路径是不是全路径(ngx_conf_full_name()函数会调用ngx_conf_test_full_name()函数测试文件名的全称,如果不是全称则会试着用cycle中的conf­_prefix来组合全称路径)。若不是全路径则函数返回错误;

d)           配置文件名是全路径时,从文件名字符串尾寻找第一个文件分隔符(‘/’或’/’)。找到后,用ngx_cycle中保存的配置文件名替换当前cycle(即init_cycle)中的conf_prefix,然后跳出循环;(由于init_cycle和ngx_cycle此时不过是别名,所以实质上是用ngx_cycle中的conf_file的名字放到conf_prefix中作为conf文件的前缀了。)

e)           ngx_conf_params存在时,将其保存到当前的cycle(即init_cycle)中;

f)            ngx_test_config有效时,当前cycle(即init_cycle)中log的log_level设为NGX_LOG_INFO。

9.  ngx_os_init()函数,在NGX_HAVE_OS_SPECIFIC_INIT宏有效时,调用ngx_os_specific_init()函数进行特定操作系统环境下的初进时化工作。在linux中,主要是获取当前操作系统发行版的全称,内核名称,类型,及操作最信号的限制等;

调用ngx_init_setproctitle()函数的逻辑如下:

a)       计算environ[]的大小,然后在内存池中分配足够大的空间;

b)       将ngx_os_argv_last定位到ngx_os_argv[]尾部(即)enivorn[]起始处);

c)       循环地将enivorn[]中环境变量复制到内存池已分配的空间中;

d)       ngx_os_argv_last最后定位到最后的环境变量。

调用getpagesize()函数取得pagesize大小;用NGX_CPU_CACHE_LINE常量设定ngx_cacheline_size。(NGX_CPU_CACHE_LINE未定义,可能不是宏,可能是通过编译前由configure脚本程序确定。)

下面这行代码的作用不明白:

for (n = ngx_pagesize; n >>= 1; ngx_pagesize_shift++) { /* void */ }

ngx_ncpu为0,则设为1;接着调用ngx_cpuinfo()函数,取得cpu类型,制造商,所支持的cacheline的大小。

调用getrlimit(RLIMIT_NOFILE, &rlmt)函数,取得进程所能打开最大文件数。相关代码如下:

if (getrlimit(RLIMIT_NOFILE, &rlmt) == -1) {

ngx_log_error(NGX_LOG_ALERT, log, errno,

"getrlimit(RLIMIT_NOFILE) failed)");

return NGX_ERROR;

}

ngx_max_sockets = (ngx_int_t) rlmt.rlim_cur;

接下来这段代码用来继承非阻塞属性:

#if (NGX_HAVE_INHERITED_NONBLOCK)

ngx_inherited_nonblocking = 1;

#else

ngx_inherited_nonblocking = 0;

#endif

初始化随机种子(srandom(ngx_time()))。

10.              ngx_crc32_table_init()函数中初始化用于crc32循环冗余校验的数据表。

11.              ngx_add_inherited_sockets()函数中,继承父进程中的socket。继承方法是通过取得”NGINX”这个环境变量的值,该值是个由”:”或”;”作为分隔符的列表,列表是表示socket的文件描述符。Nginx将继承的socket压入一个堆栈中,然后置变量ngx_inherited为1,表示已经取得要继承的socket。

最后调用ngx_set_inherited_sockets()函数。ngx_set_inherited_sockets()函数,由于是在ngx_add_inherited_sockets()函数最后调用,实际上对继承来的socket进行操作。那些socket,则保存在init_cycle的listening数组中。Listening结构如下:

struct ngx_listening_s {

ngx_socket_t    fd;                       /* socket所用的文件描述符 */

struct sockaddr  *sockaddr;        /* 指向sockaddr地址 */

socklen_t       socklen;        /* size of sockaddr */

size_t          addr_text_max_len;  /* 地址的文本表示的最大长度 */

ngx_str_t           addr_text;  /* 地址的文本表示 */

int                 type;            /*  */

int                 backlog;      /* listen()中使用的参数,默认511。 */

int                 rcvbuf;    /* 接受缓冲区的大小 */

int                 sndbuf;    /* 发送缓冲区的大小 */

/* handler of accepted connection */

ngx_connection_handler_pt   handler;

void               *servers;/* array of ngx_http_in_addr_t, for example */

ngx_log_t           log;

ngx_log_t          *logp;

size_t               pool_size;

/* should be here because of the AcceptEx() preread */

size_t               post_accept_buffer_size;

/* should be here because of the deferred accept */

ngx_msec_t          post_accept_timeout;

ngx_listening_t       *previous;

ngx_connection_t     *connection;

unsigned            open:1;    /* socket已经打开 */

unsigned            remain:1;  /*  */

unsigned            ignore:1;   /*  */

unsigned            bound:1;    /* already bound */

unsigned            inherited:1;  /* inherited from previous process */

unsigned            nonblocking_accept:1;

unsigned            listen:1;

unsigned            nonblocking:1;

unsigned            shared:1;  /* shared between threads or processes */

unsigned            addr_ntop:1;

#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)

unsigned            ipv6only:2;

#endif

#if (NGX_HAVE_DEFERRED_ACCEPT)

unsigned            deferred_accept:1;

unsigned            delete_deferred:1;

unsigned            add_deferred:1;

#ifdef SO_ACCEPTFILTER

char               *accept_filter;

#endif

#endif

};

ngx_set_inherited_sockets()函数中只有一个循环,在循环中对init_cycle->listening数组中继承而来的socket加工处理。

a)       在内存池中预分配保存地址结构的空间;

b)       调用getsockname()函数获取与socket相关连的地址,如果出错设定ignore为1;

c)       根据socket地址的类型,设置地址文本格式的最大长度;如果不是IPv4和IPv6类型设定ignore为1;

d)       地址文本格式的最大长度加上端口的文本格式的最大长度;

e)       使用ngx_sock_ntop()函数,将socket绑定的地址转换为文本格式(IPv4和IPv6的不相同);

f)        设置每个监听的socket的backlog(NGX_LISTEN_BACKLOG定义为511);

g)       获取SO_RCVBUF和SO_SNDBUF选项的大小,保存与当前socket对应的ngx_listening_t结构中;

h)       支持accept filter时,通过SO_ACCEPTFILTER选项取得socket的accept_filter表,保存在对应项的accept_filter中;

SO_ACCEPTFILTER places an accept_filter on the socket, which will filter incoming connections on a listening stream socket before being presented for accept. Once more, listen must be called on the socket before trying to install the filter on it, or else the setsockopt system call will fail.

struct  accept_filter_arg {
        char    af_name[16];
        char    af_arg[256-16];
};

i)             如果当前所在操作系统TCP层支持TCP DEFER ACCEPT功能,则试图获取TCP_DEFER_ACCEPT的timeout值。Timeout大于0时,则将socket对应deferred_accept标志设为1。

TCP_DEFER_ACCEPT
我 们首先考虑的第1个选项是TCP_DEFER_ACCEPT(这是Linux系统上的叫法,其他一些操作系统上也有同样的选项但使用不同的名字)。为了理 解TCP_DEFER_ACCEPT选项的具体思想,我们有必要大致阐述一下典型的HTTP客户/服务器交互过程。请回想下TCP是如何与传输数据的目标建立连接的。在网络上,在分离的单元之间传输的信息称为IP包(或IP 数据报)。一个包总有一个携带服务信息的包头,包头用于内部协议的处理,并且它也可以携带数据负载。服务信息的典型例子就是一套所谓的标志,它把包标记代表TCP/IP协议栈内的特殊含义,例如收到包的成功确认等等。通常,在经过“标记”的包里携带负载是完全可能的,但有时,内部逻辑迫使TCP/IP协议 栈发出只有包头的IP包。这些包经常会引发讨厌的网络延迟而且还增加了系统的负载,结果导致网络性能在整体上降低。
现在服务器创建了一个套接字同时等待连接。TCP/IP式的连接过程就是所谓“3次握手”。首先,客户程序发送一个设置SYN标志而且不带数据负载的TCP包(一个SYN包)。服务器则以发出带SYN/ACK标志的数据包(一个SYN/ACK包)作为刚才收到包的确认响应。客户随后发送一个ACK包确认收到了第2个包从而结束连接 过程。在收到客户发来的这个SYN/ACK包之后,服务器会唤醒一个接收进程等待数据到达。当3次握手完成后,客户程序即开始把“有用的”的数据发送给服务器。通常,一个HTTP请求的量是很小的而且完全可以装到一个包里。但是,在以上的情况下,至少有4个包将用来进行双向传输,这样就增加了可观的延迟时间。此外,你还得注意到,在“有用的”数据被发送之前,接收方已经开始在等待信息了。
为了减轻这些问题所带来的影响,Linux(以及其他的 一些操作系统)在其TCP实现中包括了TCP_DEFER_ACCEPT选项。它们设置在侦听套接字的服务器方,该选项命令内核不等待最后的ACK包而且在第1个真正有数据的包到达才初始化侦听进程。在发送SYN/ACK包之后,服务器就会等待客户程序发送含数据的IP包。现在,只需要在网络上传送3个包 了,而且还显著降低了连接建立的延迟,对HTTP通信而言尤其如此。

对于那些支持deffered accept的操作系统,nginx会设置这个参数来增强功能,设置了这个参数,在accept的时候,只有当实际收到了数据,才唤醒在accept等待的进程,可以减少一些无聊的上下文切换,如下:
val = 5;
setsockopt(socket_fd, SOL_TCP, TCP_DEFER_ACCEPT, &val, sizeof(val));
kernel 在 val 秒之内还没有收到数据,不会继续唤醒进程,而是直接丢弃连接,如果connect之后立刻收到数据,kernel才创建数据套接口并唤醒在accept上等待的进程。

注意如果打开这个功能,kernel 在 val 秒之内还没有收到数据,不会继续唤醒进程,而是直接丢弃连接。

12. ngx_init_cycle()函数,这是个比较重要的函数。

13.  ngx_os_specific_status()函数中,仅仅是在特殊操作系统情况下记录操作系统名称以及相关信息。

14.  计算nginx的模块数,同时设置每个模块的index值。

15.  如果执行nginx的命令行参数中设置了signal值,则进行ngx_signal_process()处理。

ngx_signal_process()函数先取得自己的pid。(可能是从/proc目录下nginx的pid文件中读取的pid值。仅是猜测,目前还不清楚是不是这样。)

最后调用ngx_os_signal_process() 函数,来向pid代表的进程发送信号。

发送的信号都定义在一个全局信号表中:

ngx_signal_t  signals[] = {

{ ngx_signal_value(NGX_RECONFIGURE_SIGNAL),

"SIG" ngx_value(NGX_RECONFIGURE_SIGNAL),

"reload",

ngx_signal_handler },

{ ngx_signal_value(NGX_REOPEN_SIGNAL),

"SIG" ngx_value(NGX_REOPEN_SIGNAL),

"reopen",

ngx_signal_handler },

{ ngx_signal_value(NGX_NOACCEPT_SIGNAL),

"SIG" ngx_value(NGX_NOACCEPT_SIGNAL),

"",

ngx_signal_handler },

{ ngx_signal_value(NGX_TERMINATE_SIGNAL),

"SIG" ngx_value(NGX_TERMINATE_SIGNAL),

"stop",

ngx_signal_handler },

{ ngx_signal_value(NGX_SHUTDOWN_SIGNAL),

"SIG" ngx_value(NGX_SHUTDOWN_SIGNAL),

"quit",

ngx_signal_handler },

{ ngx_signal_value(NGX_CHANGEBIN_SIGNAL),

"SIG" ngx_value(NGX_CHANGEBIN_SIGNAL),

"",

ngx_signal_handler },

{ SIGALRM, "SIGALRM", "", ngx_signal_handler },

{ SIGINT, "SIGINT", "", ngx_signal_handler },

{ SIGIO, "SIGIO", "", ngx_signal_handler },

{ SIGCHLD, "SIGCHLD", "", ngx_signal_handler },

{ SIGSYS, "SIGSYS, SIG_IGN", "", SIG_IGN },

{ SIGPIPE, "SIGPIPE, SIG_IGN", "", SIG_IGN },

{ 0, NULL, "", NULL }

};

其中元素的结构如下:

typedef struct {

int     signo;

char   *signame;

char   *name;

void  (*handler)(int signo);

} ngx_signal_t;

16.  ngx_os_status()函数则是对应不同的操作系统,将不操作系统的内核版本,发布版本等写出到log文件中。不同系统输出内容不一样。

17.  设置全局的ngx_cycle。ngx_cycle = cycle;

ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);

if (ccf->master && ngx_process == NGX_PROCESS_SINGLE) {

ngx_process = NGX_PROCESS_MASTER;

}

18.  随后在非Win32情况下,调用ngx_init_signals()函数中,针对singals表中的信号安装信号处理函数。

Nginx中的信号处理函数是ngx_signal_handler()。

在ngx_signal_handler()函数中,会检查收到的信号是不是nginx有效的信号,取得当前系统的时间。

接着根据不同的进程模式——NGX_PROCESS_MASTER,NGX_PROCESS_SINGLE,NGX_PROCESS_WORKER,对应不同信号进行相应处理。

NGX_PROCESS_MASTER与NGX_PROCESS_SINGLE模式下信号处理方式一致。其中NGX_CHANGEBIN_SIGNAL信号需要特别在意。

case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):

if (getppid() > 1 || ngx_new_binary > 0) {

/*

* Ignore the signal in the new binary if its parent is

* not the init process, i.e. the old binary's process

* is still running. Or ignore the signal in the old binary's

* process if the new binary's process is already running.

*/

action = ", ignoring";

ignore = 1;

break;

}

ngx_change_binary = 1;

action = ", changing binary";

break;

收到这个信号表示要进程切换,ngx_change_binary置为1。如果收到该信号的进程是下列两种情况,就忽略信号。

a)       新启动的进程,其父进程不是init;

b)       老的进程(即将终了的进程),其派生的子进程已经在运行了。

NGX_CHANGEBIN_SIGNAL信号用来restart起动nginx。Nginx是采用一种平滑进程切换的方式来重启。这种方式保证了不间断地处理请求。上面两种情况其实是指由将要退出的老进程生成新的进程。新生的进程对NGX_CHANGEBIN_SIGNAL信号不作处理。

19.  如果配置文件中设置了daemon运行模式且ngx_inherited为0;ngx_inherited设为1。另外,ngx_inherited在ngx_add_inherited_sockets()函数的最后也设为1。

调用ngx_daemon()将进程置为daemon运行模式。ngx_daemon()函数使用fork()和setsid()系统调用将进程变成后台进程。

20.  ngx_create_pidfile()函数将pid写入文件。

21.  如果cycle->log->file->fd不是标准错误,将标准错误输出重定向到当前打开的log文件。

22. ngx_use_stderr = 0;   根据进程模式是NGX_PROCESS_SINGLE还是NGX_PROCESS_MASTER分别调用ngx_single_process_cycle(),ngx_master_process_cycle()。

Tuesday 5th January 2010相关推荐

  1. 软件工程中众包应用的综述

    A Survey of the Use of Crowdsourcing in Software Engineering 作者 摘要 1 介绍 2 背景 2.1 众包 2.2 众包软件工程 2.2.1 ...

  2. BigchainDB白皮书,中文翻译

    BigChainDB:可扩展区块链数据库 这篇白皮书介绍BigChainDB.BigChainDB填补了去中心生态系统中的一个空白:是一个可用的去中心数据库.它具有每秒百万次写操作,存储PB级别的数据 ...

  3. linux 计算星期对应的日期,如何计算给定日期的星期数?

    请注意,虽然您对一年中第n周的定义是可以接受的,但它也不是"标准"的定义. ISO 8601定义了用于表示日期,时间和时区的标准.它定义了从星期一开始的几周.它还说,一年的第1周是 ...

  4. DATEPART SQL函数

    This article explores the DATEPART SQL function and its use in writing t-SQL queries. In the previou ...

  5. 前沿速递:因果涌现在多种因果衡量标准下普遍存在

    来源: 集智俱乐部 作者:陈昊 编辑:邓一雪 导语 因果涌现理论指出,在宏观尺度下观察复杂系统可以减少因果关系中的噪声,从而得到具有更强因果关联的系统.目前该理论已经在有效信息和整合信息的因果度量标准 ...

  6. ASP.NET MVC扩展库

    很多同学都读过这篇文章吧 ASP.NET MVC中你必须知道的13个扩展点,今天给大家介绍一个ASP.NET MVC的扩展库,主要就是针对这些扩展点进行.这个项目的核心是IOC容器,包括Ninject ...

  7. 为了今年印象最深刻的唱片,转演唱会消息一个,虽然我去不了 55555

    My Chemical Romance Live in Hong Kong 香港演唱会2008年1月29日 发布: 2007-12-14 13:44 | 作者: Dreamson | 来源: musi ...

  8. 为什么安装了Microsoft .NET Framework 4之后我的电脑网卡启动会变得很慢很慢。。...

    为什么安装了Microsoft .NET Framework 4之后我的电脑网卡启动会变得很慢很慢.. FROM: http://social.microsoft.com/Forums/fi-FI/w ...

  9. Protocol Buffers proto语言语法说明

    原文地址:http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/proto.html proto语言有自己的 数据类型(Field T ...

最新文章

  1. java 加载dll后打包_让Jacob从当前路径读取dll文件及相关打包方法
  2. 学术-数学:哥德巴赫猜想
  3. weblogic学习笔记(1)
  4. 深度学习课程Deep Learning Courses
  5. 那些年一起学习的PHP(三)
  6. Flink 如何实时分析 Iceberg 数据湖的 CDC 数据
  7. oracle and和or的执行顺序,Oraclewhere语句中and,or,not的执行顺序
  8. java 8 jre_jre1.8 64位官方下载
  9. 如何设计出令人惊叹的关卡:来自策划、美术与程序的标准
  10. python自动修图软件_Ai修图软件|Ai修图神器SC-FEGAN下载(人脸照片涂鸦编辑) 免费版_数码资源网...
  11. html5比较热门的新标签,HTML5增加的几个新的标签
  12. 公司小程序,公众号申请支付流程
  13. 国务院:关于促进云计算创新发展 培育信息产业新业态的意见
  14. CBR的产生和Roger Schank
  15. js中获取元素对象的四种方式
  16. CCleaner科学使用方法
  17. MarkMind使用技巧
  18. 2022国赛中职网搭 windows组策略
  19. JDK1.8(jdk8)的下载与安装
  20. 一文讲透图像分割经典网络:FCN、Unet、DeepLabV3+、Vnet、Unet++

热门文章

  1. 外行人都能看懂的 NFS 知识,错过血亏
  2. windows编程之TextOut与DrawText
  3. C#界面库CSkin 使用简介
  4. Fzu软工第一次作业-准备篇
  5. 赛码习题:约德尔测试
  6. [Atcoder ARC085 F] NRE 线段树优化dp
  7. bios.h被淘汰了....
  8. 中秋佳节速成C语言_老九零基础学编程系列之C语言【章节1-章节5】
  9. 脱光衣服待着就能减肥,当真有这好事?
  10. python中的opener_Python爬虫中的Handler和Opener是什么