Tuesday 5th January 2010

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_timengx_cached_http_timengx_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()。这是个宏。对不同的编译器不同的环境有不同的定义。关于x86gcc的定义如下:

/*

 * 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()获取nginxpid

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全局变量中保存着nginxlog的相关信息,包括打开的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_argcngx_argv[]ngx_os_argvngx_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_cyclengx_cycle此时不过是别名,所以实质上是用ngx_cycle中的conf_file的名字放到conf_prefix中作为conf文件的前缀了。)

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

f)            ngx_test_config有效时,当前cycle(init_cycle)loglog_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_ncpu0,则设为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_inherited1,表示已经取得要继承的socket

 

最后调用ngx_set_inherited_sockets()函数。ngx_set_inherited_sockets()函数,由于是在ngx_add_inherited_sockets()函数最后调用,实际上对继承来的socket进行操作。那些socket,则保存在init_cyclelistening数组中。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相关连的地址,如果出错设定ignore1

c)       根据socket地址的类型,设置地址文本格式的最大长度;如果不是IPv4IPv6类型设定ignore1

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

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

f)        设置每个监听的socketbacklog(NGX_LISTEN_BACKLOG定义为511)

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

h)       支持accept filter时,通过SO_ACCEPTFILTER选项取得socketaccept_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_ACCEPTtimeout值。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目录下nginxpid文件中读取的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_cyclengx_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_MASTERNGX_PROCESS_SINGLENGX_PROCESS_WORKER,对应不同信号进行相应处理。

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

19.  如果配置文件中设置了daemon运行模式且ngx_inherited0ngx_inherited设为1。另外,ngx_inheritedngx_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()

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值