nginx架构及源码阅读

nginx安装

从官方网站下载源码包(http://nginx.org/en/download.html)。

解压

1
tar -zxvf nginx-1.*.tar.gz

编译安装

1
2
3
4
cd nginx
./config
make
make install

可以使用

1
./config --help

查看config包含的参数。

这里主要介绍几个参数:

—prefix=PATH

nginx安装部署后的根目录。默认在/usr/local/nginx目录。该目录影响其他目录的相对目录。

—sbin-path=PATH

该目录是可执行文件放置的目录。默认为\/sbin/nginx。

—conf-path=PATH

配置文件目录。默认为\/conf/nginx.conf。执行时读取的默认配置。

—error-log-path=PATH

error日志的放置路径。默认为\/log/error.log。可以打印不同等级的log日志,在nginx.conf中控制。

—pid-path=PATH

pid文件存放路径。默认为\/log/nginx.pid。该文件以ASCII码存放着nginx master的进程ID。

nginx的命令行控制

直接执行Nginx二进制文件

1
/usr/local/nginx/sbin/nginx

读取默认配置文件

指定配置文件

1
/usr/local/nginx/sbin/nginx -c 配置文件路径

另行指定全局配置项

-g

1
/usr/local/nginx/sbin/nginx -g "pid /tem/test.pid;"

这时,pid文件将写到/tem/test.pid。-g的配置不能与nginx.conf不一致。

对于以-g启动的nginx来说,要执行其他操作,也应该包含-g参数,如

1
/usr/local/nginx/sbin/nginx -g "pid /tem/test.pid;" -s stop

测试配置信息是否错误

1
/usr/local/nginx/sbin/nginx -t

显示版本

1
/usr/local/nginx/sbin/nginx -v

显示编译阶段参数

1
/usr/local/nginx/sbin/nginx -V

快速停止服务

-s

1
/usr/local/nginx/sbin/nginx -s stop

-s参数是告诉Nginx向正在运行的服务发送信号。直接使用kill也是可以的。

优雅的停止服务

关闭监听端口,处理完当前所有请求。

1
/usr/local/nginx/sbin/nginx -s quit

等价于

1
kill -s SIGQIT masterpid

停止某个worker进程

1
kill -s SIGQIT workerpid

Nginx的配置

部署Nginx是,都是使用一个master进程来管理多个worker进程,一般worker进程数量与服务器中的CPU核心数一致。

配置demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
user nobody;

worker_process 8;
error_log /var/log/nginx/error.log error;

#pid logs/nginx.pid

events {
use epoll;
worker_connections 500000;
}

http {
include mime.types;
default_type application/octet-stream;

log_format main '$remote_addr [$time_local] "$request" '
'$status $bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log logs/access.log main buffer=32k;

....
}

块配置

块配置项由一个块配置名和一对大括号组成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
events {
...
}

http {
upstream backend {
server 127.0.0.1:8080;
}

gzip on;

server {
...;
location /webstatic {
gzip off;
}
}

上面的events、http、server、location、upstream都是块配置项,块配置项可以嵌套。

配置项语法

基本的配置项语法为

1
配置项名 配置项值1 配置项值2 ... ;

配置项值可以是数字,字符串,正则表达式,这取决于该配置项名实际需求。(这个有点像是函数名和参数的关系)。

配置项单位

指定大小时,可以使用的单位包括:

  1. K或者k字节(KiB)
  2. M或者m字节(MiB)

指定时间时,可以使用:

  1. ms(毫秒)
  2. s(秒)
  3. m(分钟)
  4. h(小时)
  5. d(天)
  6. w(周)
  7. M(月,30天)
  8. y(年,365天)

配置中使用变量

部分模块可以使用变量,如

1
2
3
log_format main '$remote_addr [$time_local] "$request" '
'$status $bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

变量前加上$

nginx提供的基础服务

用语调试和定位问题的配置

是否以守护进程来执行

1
2
3
4
daemon on|off

#default
daemon on

是否以master/worker方式进行工作

1
2
3
4
master_process on|off

#deault
master_process on

error日志设置

1
2
3
4
error_log /path/file level

#default
error_log logs/error.log error

当指定/path/file/dev/null时即关闭日志。

level为日志级别。取值为debug info notice warn error crit alert emerg。从左到右等级依次升高。指定的等级时,大于等于该等级的日志都会输出。

设置特殊的调试点

1
debug_points [stop|abort]

nginx在关键逻辑中设置了调试点。如果设置为stop,则nginx代码运行到这些调试点就会发出SIGSTOP信号以用于调试。如果设置为abort,则会产生一个coredump文件,可以使用gdb来查看。

对指定客户端输出debug级别日志

该配置属于事件类型配置,要放在events中才有效,起值可以是IP地址或者CIDR地址,如

1
2
3
4
events {
debug_connection 10.224.66.14;
debug_connection 10.224.57.0/24;
}

此时,只有配置的IP才会打印debug级别日志,其他请求仍然沿用error_log中配置的日志级别。

限制coredump核心转储文件大小

1
worker_rlimit_core size;

指定coredump文件生成目录

1
working_directory path;

正常运行配置

定义环境变量

1
env VAR|VAR=VALUE

该配置可以让用户直接设置操作系统上的环境变量。仅是变更运行时使用的环境变量。

嵌入其他配置

1
include /path/file

将其他配置文件嵌入到当前配置中,参数可以是绝对路径,也可以是相对路径(相对于当前配置文件的目录)。路径可以是字符串和正则表达式。

pid文件路径

1
2
3
4
pid path/file

#default
pid logs/nginx.pid

master进程的pid

nginx进程运行的用户及用户组

1
2
3
4
user username [groupname]

#default
user nobody nobody

worker进程最大句柄描述符个数

1
worker_rlimit_nofile limit;

限制信号队列

1
worker_rlimit_sigpending limit;

设置每个用户发往nginx的信号队列大小,当超过该值时,该用户发送的信号将被丢弃。

性能优化配置

nginx的worker进程数

1
2
3
4
worker_processes number;

#default
worker_processes 1;

绑定nginx的worker进程到指定的CPU内核

1
worker_cpu_affinity cpumask [cpumask, ...]

避免多个worker进程抢占同一个cpu,设置每个进程绑定的cpu内核来加速。如

1
2
worker_processes  8;
worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000;

事件类配置(都在event块中)

是否打开负载均衡锁

1
2
3
4
accept_mutex [on|off]

#default
accept_mutex on;

Accept_mutex是nginx的负载均衡锁。该锁来让各个worker进程直接负载均衡。关闭该锁可以加快TCP连接速度,但会导致负载不均衡。

lock文件路径

1
2
3
4
lock_file path/file;

#default
lock_file logs/nginx.lock;

存储负载均衡锁所需文件路径。

使用accept锁后未建立连接后等待时间

1
2
3
4
accept_mutex_delay Nms;

#default
accept_mutex_delay 500ms;

使用accept锁后,每个时刻,只能有一个worker进程能够获取accept锁。该accept不是阻塞锁,没有获取就会立即返回。如果一个worker进程尝试获取accept锁没有获得时,至少要等到accept_mutex_delay时间才能再次获取。

批量建立新连接

1
2
3
4
multi_accept [on|off];

#default
multi_accept off;

当事件模型通知有新连接时,尽可能地对本次调度中客户端发起的所有TCP请求都建立连接。

选择事件模型

1
2
3
4
use [kqueue|rtsig|epoll|/dev/poll|select|poll|eventport]

#default
use 最适合的(nginx自己寻找)。

每个master的最大连接数

1
worker_connections number;

虚拟主机与请求的分发

由于IP地址限制,存在多个主机域名对应同一IP地址的情况,这时在nginx.conf中按照server_name(对应用户请求中的主机域名)并通过server块来定义虚拟机,每个server块就是一个虚拟机,它只处理与之相对应的主机域名请求。这样,一台服务器上的nginx就能够以不同方式处理访问不同主机域名的HTTP的请求了。

监听端口

1
2
3
4
5
6
7
listen address:port [default(deprecated in 0.8.21) | default_server | [backlog=num | rcvbuf=size | sndbuf=size | accept_filter=filter | deferred | bind | ipv6only=[on|off] ssl]];

#default
listion 80;

#conf block
server

listen决定nginx服务监听端口。listen后可以加IP地址、端口号或者主机名。如

1
2
3
listen 127.0.0.1:8002;
listen 127.0.0.1; //default port 80
listen *:8000;

地址后可以加其他参数。

参数 含义
default 将所在server块作为整个web服务的默认server块。当一个请求无法匹配配置文件中的所有主机域名时,就会选择默认虚拟主机。如果所有server都未指定,则默认选第一个。
backlog=num TCP中backlog大小。(默认-1,无限制)在TCP建立三次连接时,进程还未监听句柄,此时backlog队列将会放置这些连接。如果backlog已满,还有客户端企图建立连接,则会失败。
rcvbuf=size 设置监听句柄的SO_RCVBUF参数。
sndbuf=size 设置监听句柄的SO_SNDBUF参数。
Accept_filter 设置accept过滤,只对FreeBSD系统有用。
deferred 设置该参数时,若用户建立了TCP连接(三次握手),内核也不会对该连接调度worker进程来处理,只会在用户真正发送请求时才会分配worker进程。使用于大并发情况下。
bind 绑定当前端口/地址对,如127.0.0.1:8000。
ssl 在当前监听的端口上建立的连接必须基于SSL。

主机名称

1
2
3
4
5
6
7
server_name name [...];

#default
server_name "";

#conf block
server

Server_name后面可以有多个主机名称,如

1
server_name www.testweb.com download.testweb.com

在开始处理一个HTTP请求时,nginx会取出header头中的Host,与每个server中的server_name进行比对,以决定由哪个server块来处理该请求。server_name与Host匹配优先级为:

  1. 最先选择完全匹配的
  2. 其次选择通配符在前面的,如*.testweb.com
  3. 再次选择通配符在后面的,如www.testweb.*
  4. 最后选择使用正则表达式的,如~^\.testweb.com$

Server_name后面是空字符串时,表示匹配没有这个host这个http头的请求。

server_name_hash_bucket_size

1
2
3
4
5
6
7
server_name_hash_bucket_size size;

#default
server_name_hash_bucket_size 32|64|128;

#conf block
http server location

为快速寻找对应server_name能力,nginx使用散列来存储server name。server_name_hash_bucket_size设置每个散列桶占用内存大小。

重定向主机名称处理

1
2
3
4
5
6
7
server_name_in_redirect on|off;

#default
server_name_in_redirect on|off;

# conf block
http server location

该配置配合server_name使用。在打开on时,表示重定向请求时会使用server_name的配置的第一个主机名代替原先请求的Host,使用off时,表示重定向时使用请求本身的Host。

location

1
2
3
4
location [=|~|~*|^~|@] /uri/ {...}

#conf block
server

location尝试根据用户请求中的URI来匹配上面的/uri表达式,如果可以匹配,就选择location块中的请求来处理。

参数 含义 举例
= 把URI作为字符串,与参数的uri做完全匹配 Location = / {}
~ 匹配URI时是大小写敏感的
~* 忽略大小写
^~ 匹配前半部分uri即可。
@ 表示仅用于nginx服务内部请求之间的重定向,带有@的请求不直接处理用户请求。

最常用的是uri为正则表达式。

文件路径的定义

以root方式设置资源路径

1
2
3
4
5
6
7
root path

# default
root html

#conf block
http, server, location, if

例如:

1
2
3
location /download/ {
root /opt/web/html/;
}

对于上面的配置,如果有一个请求的URI是/download/index/test.html,那么web服务器返回服务器上/opt/web/html/download/index/test.html文件的内容。

alias方式设置资源路径

语法

1
2
3
4
alias path;

#conf block
location

alias也是用来设置文件资源路径,与root不同点主要在于如何解读跟在location后面的uri参数,这使得alias与root以不同的方式将用户请求映射到真正的磁盘文件上。例如,如果一个请求的URI是/conf/nginx.conf,而用户实际希望访问的文件在/usr/location/nginx/conf/nginx.conf。中,如果想要使用alias来进行设置的话,可以采用如下形式:

1
2
3
location /conf {
alias /usr/local/nginx/conf/;
}

如果使用root,则语句如下:

1
2
3
location /conf {
root /usr/location/nginx/;
}

使用alias时,在URI向实际路径的映射过程中,已经将location后配置的/conf部分去除了,因此/conf/nginx.conf请求将根据alias path映射为path/nginx.conf。root则会根据完整的URI请求来映射,因此root会根据root path映射为path/conf/nginx.conf。

alias后面还可以添加正则表达式,例如:

1
2
3
location ~^/test/(\w+)\.(w+)$ {
alias /usr/location/nginx/$2/$1.$2;
}

访问首页

语法:

1
2
3
4
5
6
7
index file

#default
index index.html

#conf block
http, server, location

有时,访问站点的URI为/,这一般是网页站点,这与root和alias都不同,使用nginx_http_index_module模块提供的index配置实现。index后面可以更多个文件参数,nginx会按顺序访问这些文件,例如:

1
2
3
4
location / {
root path;
index index.html, /html/index.html;
}

根据http返回码重定向页面

语法:

1
2
3
4
error_page code[code...][=|=answer-code] uri | @named_location

#conf block
http,server,location,if

对于某个请求返回错误码时,如果匹配上了error_page中设置的code,则重定向到新的URI中,例如

1
2
3
4
error_page 404  /404.html
error_page 502 504 504 /50x.html
error_page 403 http://example.com/forbidden.html
error_page 404 = @fetch

注意,虽然重定向了URI,但返回的HTTP错误码还是原来的错误码。可以通过’=’来更改返回的错误码,例如

1
2
error_page 404 = 200  /empty.html;
error_page 404 = 403 /forbidden.gif;

也可以不指定确切的错误码,而是由重定向后时间处理的真实结果来决定,这时只要把=后面的错误码去掉即可,例如

1
error_page 404  /empty.html;

如果不想修改URI,只是想让请求重定向到另一个location中进行处理,那么可以设置为:

1
2
3
4
5
6
7
location / {
error_page 404 @fallback;
}

location @fallback {
proxy_pass http://backend;
}

此时,出现404错误时,请求转发到反向代理http://backend服务上。

是否允许递归使用error_page

语法:

1
2
3
4
5
6
7
recursive_error_pages [on|off];

#default
recursive_error_pages off;

#conf block
http,server,location

try_file

语法:

1
2
3
4
try_file path1[path2] uri;

#conf bloak
server,location

try_file后跟若果路径,如path1,path2…,最后必须要有uri参数,意义为:尝试顺序访问每一个path,如果可以有效获取,就直接向用户返回对于的文件请求,并结束,否则接着向下访问,如果所有path都找不到,则重定向到最后的uri上。例如

1
2
3
4
try_file /sys/main.html $uri $uri/index/html $uri.html @other;
location @other {
proxy_pass http://backend;
}

示例

一个网页,要访问服务器上3个页面,为路径$path下的index.html,master.html和slave.html,其中index.html是首页,其请求的uri为/。后面两个文件是index.html返回后,请求的两个页面,请求的uri分别为/showmaster/和/showslave/。此时,nginx配置可设置为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
error_log /home/work/error.log debug;
events {
worker_connections 1024;
}
http {
log_format main '"$request"';
access_log /home/work/debug.log main;

server {
listen 8894;
location ~* ^/show([a-z]+)\/$ {
root path/$1.html;
}
location / {
root path;
index index.html;
}
}
}

内存及磁盘资源的分配

HTTP包体只存储在磁盘文件中

语法

1
2
3
4
5
6
7
client_body_in_file_only on | clean | off

#default
client_body_in_file_only off

#conf block
http,server,location

当设置为非off时,用户请求的HTTP包体一律存储到磁盘文件中,即使只有0字节也会存储为文件。当配置结束时,如果设置为on,则该文件不会被删除(常用于调试),如果配置为clean,则会在请求结束,清除文件。

HTTP包体尽量写到一个内存buffer中

语法:

1
2
3
4
5
6
7
client_body_in_single_buffer on | off

#default
client_body_in_single_buffer off

#conf block
http,server,location

配置on时,HTTP包体一律存储到内存buffer。如果大小超过了client_body_buffer_size值,包体依然会写入磁盘。

存储HTTP请求头部的内存大小

语法:

1
2
3
4
5
6
7
client_header_buffer_size size;

#default
client_header_buffer_size 1k

#conf block
http,server

该配置定义了正常情况下Nginx接收用户请求中HTTP header部分(包括HTTP行和HTTP头部)分配的内存buffer大小。当HTTP header部分超过该值时,定义的buffer大小将失效。

存储超大HTTP头部的内存buffer大小

语法:

1
2
3
4
5
6
7
lager_client_header_buffer number size;

#defult
lager_client_header_buffer 4 8k;

#conf block
http,server

订阅了Nginx接收一个超大HTTP头部请求的buffer个数和每个buffer大小。如果请求行(如GET /index HTTP/1.1)大小超过上面单个大小限制时,返回Request URI too large(414)。请求中有很多header,每个header的大小也不能超过单个buffer大小,否则会返回Bad request(400)。请求行和请求头总数也不能超过buffer个数*buffer大小。

存储HTTP包体的内存buffer大小

语法:

1
2
3
4
5
6
7
client_body_buffer_size size;

#default
client_body_buffer_size 8k/16k;

#conf block
http,server,location

定义了Nginx接收http请求包体的内存缓冲区大小。即HTTP包体会先接收到指定的缓存中,再决定是否写入磁盘。

HTTP包体临时存放目录

语法

1
2
3
4
5
6
7
client_body_temp_path dir-path [level1 [level2 [level3]]]

#default
client_body_temp_path client_body_temp

#conf block
http,server,location

配置包体存放的临时目录。包体大小超过client_body_buffer_size指定大小时,会以递增的整数命名并存放到client_body_temp_path指定目录。后面的level1,level2,level3是防止一个目录文件过多,影响性能,使用level参数,可以按文件临时名再多加3层目录。

connection_pool_size

语法

1
2
3
4
5
6
7
connection_pool_size size;

#default
connection_pool_size 256;

#conf block
http,server

Nginx对每一个建立的成功的TCP连接会预先分配一个内存池,这里size指定内存池初始大小,用于减少对小块内存的分配次数。过大的size导致内存资源浪费,过小的size,导致重复分配次数增加,影响性能,谨慎设置。

request_pool_size

语法

1
2
3
4
5
6
7
request_pool_size size;

#default
request_pool_size 4k

#conf block
http,server

Nginx开始处理HTTP请求时,会为每个请求都分配一个内存池。这里size指定内存池初始大小,用于减少对小块内存的分配次数。TCP连接关闭时会销毁connection_pool_size指定的内存池,HTTP请求结束会销毁request_pool_size指定的内存池,但其创建和销毁时间并不一致,因为一个TCP连接可能被复用于多个HTTP请求。

网络连接设置

读取HTTP头部超时时间

语法

1
2
3
4
5
6
7
client_header_timeout time(s)

#default
client_header_timeout 60;

#conf block
http,server,location

客户端与服务器建立TCP连接后将开始接收HTTP头部,在这个过程中,如果在一个时间间隔内没有读取到客户端发来的字节,则认为超时,向客户端返回408(Request timed out)响应。

读取HTTP包体超时时间

语法:

1
2
3
4
5
6
7
client_body_timeout time(s);

#default
client_body_timeout 60;

#conf block
http,server,location

与client_header_timeout类似,不过使用在读取http包体。

发送响应超时时间

语法:

1
2
3
4
5
6
7
send_timeout time;

#default
send_timeout 60;

#conf block
http,server,location

Nginx向客户端发送数据包,客户端一直没有接收数据包,超过超时响应时间后,Nginx关闭连接。

还有很多网络连接配置,看书。

client_body_in_file_only

此指令禁用NGINX缓冲区并将请求体存储在临时文件中。 文件包含纯文本数据。 该指令在NGINX配置的http,server和location区块使用。

1
2
3
4
client_body_in_file_only off|on|clean

default
off

不同值含义如下:

  1. off 该值将禁用文件写入
  2. clean:请求body将被写入文件。 该文件将在处理请求后删除。
  3. on:请求正文将被写入文件。 处理请求后,将不会删除该文件。

client_body_in_single_buffer

该指令设置NGINX将完整的请求主体存储在单个缓冲区中。 默认情况下,指令值为off。如果启用,它将优化读取$request_body变量时涉及的I/O操作。

长连接

http为了避免每次都需要进行连接的三次握手和四次挥手,支持连接的复用,报错连接为长连接。nginx也支持该功能。对应的配置包括。

keepalive_requests

长连接最多接收的请求数量。语法为:

1
2
3
4
Syntax:	keepalive_requests number;
Default:
keepalive_requests 1000;
Context: http, server, location

keepalive_timeout

对于保持长连接的请求来说,如果指定事件未收到请求,则关闭连接。配置如下:

1
2
3
4
Syntax:	keepalive_timeout timeout [header_timeout];
Default:
keepalive_timeout 75s;
Context: http, server, location

如果配置的事件为0,则表示不支持长连接。可选的第二个参数在响应的header域中设置一个值“Keep-Alive: timeout=time”。这两个参数可以不一样。

除了长连接以外,还有一个特性为pipeline。

在http1.1中,引入了一种新的特性,即pipeline。那么什么是pipeline呢?pipeline其实就是流水线作业,它可以看作为keepalive的一种升华,因为pipeline也是基于长连接的,目的就是利用一个连接做多次请求。如果客户端要提交多个请求,对于keepalive来说,那么第二个请求,必须要等到第一个请求的响应接收完全后,才能发起,这和TCP的停止等待协议是一样的,得到两个响应的时间至少为2RTT。而对pipeline来说,客户端不必等到第一个请求处理完后,就可以马上发起第二个请求。得到两个响应的时间可能能够达到1RTT。nginx是直接支持pipeline的,但是,nginx对pipeline中的多个请求的处理却不是并行的,依然是一个请求接一个请求的处理,只是在处理第一个请求的时候,客户端就可以发起第二个请求。这样,nginx利用pipeline减少了处理完一个请求后,等待第二个请求的请求头数据的时间。其实nginx的做法很简单,前面说到,nginx在读取数据时,会将读取的数据放到一个buffer里面,所以,如果nginx在处理完前一个请求后,如果发现buffer里面还有数据,就认为剩下的数据是下一个请求的开始,然后就接下来处理下一个请求,否则就设置keepalive。

tcp_nopush/tcp_nodelay

对于tcp连接来说,缓存中数据发送方式会对传输存在一定的影响。

如果是缓存中有数据就立即发出,将导致负载过高的问题。例如可能传输的数据只有一个字节,但tcp头本身就有40字节长度,效率过低。因此当前tcp默认传输方式是使用Nagle算法,TCP堆栈实现了等待数据 0.2秒钟,因此操作后它不会发送一个数据包,而是将这段时间内的数据打成一个大的包。

但等待0.2秒不一定适合所有场景,因此有了tcp_nopush和tcp_nodelay方式,这些都是套接字属性,通过setsockopt系统调用设置套接字属性即可。

在 nginx 中,tcp_nopush 配置和 tcp_nodelay “互斥”。它可以配置一次发送数据的包大小。也就是说,它不是按时间累计 0.2 秒后发送包,而是当包累计到一定大小后就发送。在 nginx 中,tcp_nopush 必须和 sendfile 搭配使用。

tcp_nodelay表示不使用等待,立即发送。

默认配置为:

1
tcp_nopush : on;

配置块在http、server、location中。

延迟关闭(lingering_close)

lingering_close,字面意思就是延迟关闭,也就是说,当nginx要关闭连接时,并非立即关闭连接,而是先关闭tcp连接的写,再等待一段时间后再关掉连接的读。为什么要这样呢?我们先来看看这样一个场景。nginx在接收客户端的请求时,可能由于客户端或服务端出错了,要立即响应错误信息给客户端,而nginx在响应错误信息后,大分部情况下是需要关闭当前连接。nginx执行完write()系统调用把错误信息发送给客户端,write()系统调用返回成功并不表示数据已经发送到客户端,有可能还在tcp连接的write buffer里。接着如果直接执行close()系统调用关闭tcp连接,内核会首先检查tcp的read buffer里有没有客户端发送过来的数据留在内核态没有被用户态进程读取,如果有则发送给客户端RST报文来关闭tcp连接丢弃write buffer里的数据,如果没有则等待write buffer里的数据发送完毕,然后再经过正常的4次分手报文断开连接。所以,当在某些场景下出现tcp write buffer里的数据在write()系统调用之后到close()系统调用执行之前没有发送完毕,且tcp read buffer里面还有数据没有读,close()系统调用会导致客户端收到RST报文且不会拿到服务端发送过来的错误信息数据。那客户端肯定会想,这服务器好霸道,动不动就reset我的连接,连个错误信息都没有。

在上面这个场景中,我们可以看到,关键点是服务端给客户端发送了RST包,导致自己发送的数据在客户端忽略掉了。所以,解决问题的重点是,让服务端别发RST包。再想想,我们发送RST是因为我们关掉了连接,关掉连接是因为我们不想再处理此连接了,也不会有任何数据产生了。对于全双工的TCP连接来说,我们只需要关掉写就行了,读可以继续进行,我们只需要丢掉读到的任何数据就行了,这样的话,当我们关掉连接后,客户端再发过来的数据,就不会再收到RST了。当然最终我们还是需要关掉这个读端的,所以我们会设置一个超时时间,在这个时间过后,就关掉读,客户端再发送数据来就不管了,作为服务端我会认为,都这么长时间了,发给你的错误信息也应该读到了,再慢就不关我事了,要怪就怪你RP不好了。当然,正常的客户端,在读取到数据后,会关掉连接,此时服务端就会在超时时间内关掉读端。这些正是lingering_close所做的事情。协议栈提供 SO_LINGER 这个选项,它的一种配置情况就是来处理lingering_close的情况的,不过nginx是自己实现的lingering_close。lingering_close存在的意义就是来读取剩下的客户端发来的数据,所以nginx会有一个读超时时间,通过lingering_timeout选项来设置,如果在lingering_timeout时间内还没有收到数据,则直接关掉连接。nginx还支持设置一个总的读取时间,通过lingering_time来设置,这个时间也就是nginx在关闭写之后,保留socket的时间,客户端需要在这个时间内发送完所有的数据,否则nginx在这个时间过后,会直接关掉连接。

lingering_close

设置是否需要延迟关闭。语法如下:

1
2
3
4
Syntax:	lingering_close off | on | always;
Default:
lingering_close on;
Context: http, server, location

默认值“on”指示 nginx 在完全关闭连接之前等待并处理来自客户端的额外数据,但前提是启发式表明客户端可能正在发送更多数据。

值“always”将导致 nginx 无条件等待和处理额外的客户端数据。

值“off”告诉nginx永远不要等待更多数据并立即关闭连接。 这种行为违反了协议,不应在正常情况下使用。

lingering_time

设置总的延迟时间。当 lingering_close 生效时,该指令指定 nginx 处理(读取和忽略)来自客户端的附加数据的最长时间。 之后,即使会有更多数据,连接也会关闭。语法如下:

1
2
3
4
Syntax:	lingering_time time;
Default:
lingering_time 30s;
Context: http, server, location

lingering_timeout

当 lingering_close 生效时,该指令指定更多客户端数据到达的最大等待时间。 如果在此期间未收到数据,则关闭连接。 否则,数据被读取并忽略,nginx再次开始等待更多数据。 重复“等待-读取-忽略”循环,但不会超过 lingering_time 指令指定的时间。

语法如下:

1
2
3
4
Syntax:	lingering_timeout time;
Default:
lingering_timeout 5s;
Context: http, server, location

加速close

在某个请求超时时,nginx会主动关闭该请求,这时会主动调用close函数来关闭套接字,但主动关闭套接字会导致套接字处于TIME_WAIT状态,这将导致大量的超时连接无法被立即释放,造成大量浪费。

tcp通过了SO_LINGER选项来设置关闭方式。其中参数为linger结构:

1
2
3
4
5
struct linger
{
int l_onoff; /* Nonzero to linger on close. */
int l_linger; /* Time to linger. */
};

l_onoff为正数,l_linger为0时,会立即在执行close时,立即向客户端发送RST报文,立即关闭连接,并释放此套接字占用的所有内存。

nginx中使用reset_timedout_connection配置来支持该功能:

1
2
3
4
Syntax:	reset_timedout_connection on | off;
Default:
reset_timedout_connection off;
Context: http, server, location

当配置为on时,会快速关闭。

Nginx常用数据结构

ngx_buf_t

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
typedef struct ngx_buf_s  ngx_buf_t;

struct ngx_buf_s {
// pos通常表示从该位置处理内存中的数据
u_char *pos;
// last表示有效内容的结尾。ps到last的内存是nginx需要处理的内容
u_char *last;
// 处理文件时,file_post与file_last和处理内存时的pos与last相同
off_t file_pos;
off_t file_last;

// 如果ngx_buf_t缓冲区用于内存,那么start指向这段内存的起始地址
u_char *start; /* start of buffer */
// 如果ngx_buf_t缓冲区用于内存,那么start指向这段内存的结束地址
u_char *end; /* end of buffer */

// 表示当前缓冲区类型,例如由哪个模块使用,就执行该模块的ngx_module_t变量的地址
ngx_buf_tag_t tag;
// 引用的文件
ngx_file_t *file;
// 当前缓冲区的影子缓冲区,使用较少。
ngx_buf_t *shadow;


/* the buf's content could be changed */
unsigned temporary:1;

/*
* the buf's content is in a memory cache or in a read only memory
* and must not be changed
*/
unsigned memory:1;

/* the buf's content is mmap()ed and must not be changed */
unsigned mmap:1;
// 是否可回收
unsigned recycled:1;
// 处理的是否为文件
unsigned in_file:1;
// 是否需要flush
unsigned flush:1;
// 是否使用同步方式,需谨慎考虑,可能会阻塞nginx。sync为1时可能会以阻塞的方式进行I/O
unsigned sync:1;
// 是否为最后一个缓冲区。因为ngx_buf_t可以由ngx_chain_t链表串联起来。
unsigned last_buf:1;
// 是否为ngx_chain_t中最后一个缓冲区
unsigned last_in_chain:1;

// 省份为最后一个影子缓冲区,与shadow配合使用
unsigned last_shadow:1;
// 当前缓冲区是否为临时文件
unsigned temp_file:1;

/* STUB */ int num;
};

ngx_buf_t为缓存区结构。

ngx_chain_s

该结构为配合ngx_buf_t使用的链表,定义如下:

1
2
3
4
struct ngx_chain_s {
ngx_buf_t *buf;
ngx_chain_t *next;
};

ngx_list_t

ngx_list_t为nginx封装的链表容器,其定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct ngx_list_part_s  ngx_list_part_t;

struct ngx_list_part_s {
void *elts; // 数组元素的起始地址
ngx_uint_t nelts; // 当前已经使用的元素数量
ngx_list_part_t *next; // 下一个链表元素地址
};

typedef struct {
ngx_list_part_t *last; // 指向链表中最后一个元素的地址
ngx_list_part_t part; // 链表的首个数组元素
size_t size; // 每个ngx_list_part_s中的元素大小
ngx_uint_t nalloc; // 每个ngx_list_part_s可以容纳的元素数量
ngx_pool_t *pool; // 链表中管理内存分配的内存池
} ngx_list_t;

ngx_list_t描述整个链表,ngx_list_part_s,描述每个链表的元素。每个链表的元素ngx_list_part_s是一个数组,拥有连续的内存,依赖ngx_list_t的size和nalloc来表示数组容量,又依赖每个ngx_list_part_s成员的nelts来表示已经使用的容量。

IPik9I.png

初始化链表ngx_list_init

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static ngx_inline ngx_int_t
ngx_list_init(ngx_list_t *list, ngx_pool_t *pool, ngx_uint_t n, size_t size)
{
// 为第一个元素分配指定的空间
list->part.elts = ngx_palloc(pool, n * size);
if (list->part.elts == NULL) {
return NGX_ERROR;
}

list->part.nelts = 0;
list->part.next = NULL;
list->last = &list->part;
list->size = size;
list->nalloc = n;
list->pool = pool;

return NGX_OK;
}

创建list ngx_list_create

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ngx_list_t *
ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size)
{
ngx_list_t *list;

list = ngx_palloc(pool, sizeof(ngx_list_t));
if (list == NULL) {
return NULL;
}

if (ngx_list_init(list, pool, n, size) != NGX_OK) {
return NULL;
}

return list;
}

添加数据ngx_list_push

该方法返回添加元素对应的地址,返回地址后由调用方对地址赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void *
ngx_list_push(ngx_list_t *l)
{
void *elt;
ngx_list_part_t *last;

last = l->last;
// 如果最后的地址已经使用完了,新建一个元素
if (last->nelts == l->nalloc) {

/* the last part is full, allocate a new list part */

last = ngx_palloc(l->pool, sizeof(ngx_list_part_t));
if (last == NULL) {
return NULL;
}

last->elts = ngx_palloc(l->pool, l->nalloc * l->size);
if (last->elts == NULL) {
return NULL;
}

last->nelts = 0;
last->next = NULL;

l->last->next = last;
l->last = last;
}
// 分配一个地址
elt = (char *) last->elts + l->size * last->nelts;
// 已使用地址+1
last->nelts++;

return elt;
}

ngx_table_elt_t

1
2
3
4
5
6
typedef struct {
ngx_uint_t hash; // 某个散列表中执行对应的hash算法计算的hash值
ngx_str_t key;
ngx_str_t value;
u_char *lowcase_key; // 全小写的key值
} ngx_table_elt_t;

ngx_table_elt_t是一个Key/Value对。主要使用于解析http头部。

散列表(ngx_hash_t)

存储散列表的类有如下三个相关结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// hash基础数据类
typedef struct {
ngx_str_t key; // key值
ngx_uint_t key_hash; // key经过ngx_hash_key_pt转换的类
void *value; // key对应的值
} ngx_hash_key_t;

// 散列表中存储的每个数据
typedef struct {
void *value; // 对应存储的数据
u_short len; // name长度
u_char name[1]; // key值,name
} ngx_hash_elt_t;

// 实际存储散列表的结构
typedef struct {
ngx_hash_elt_t **buckets; // 存储数据内容。为了解决冲突,因此是一个二维数组。
ngx_uint_t size; // 桶大小,多少个桶
} ngx_hash_t;

// 用来构建散列表的结构
typedef struct {
ngx_hash_t *hash;
ngx_hash_key_pt key; // 将数据的key进行转换的函数

ngx_uint_t max_size; // hash表中的桶的个数。该字段越大,元素存储时冲突的可能性越小,每个桶中存储的元素会更少,则查询起来的速度更快。当然,这个值越大,越造成内存的浪费也越大,(实际上也浪费不了多少)。
ngx_uint_t bucket_size; // 每个桶的最大限制大小,单位是字节。如果在初始化一个hash表的时候,发现某个桶里面无法存的下所有属于该桶的元素,则hash表初始化失败。

char *name; // 散列表的名称
ngx_pool_t *pool; // 内存池
ngx_pool_t *temp_pool; // 临时内存池
} ngx_hash_init_t;

typedef ngx_uint_t (*ngx_hash_key_pt) (u_char *data, size_t len);

散列表构建

构建hash表使用函数ngx_hash_init:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
#define NGX_HASH_ELT_SIZE(name)                                               \
(sizeof(void *) + ngx_align((name)->key.len + 2, sizeof(void *)))
/* 该宏返回每个key生成ngx_hash_elt_t大小(字节)的sizeof(void *)的整数倍,这即使用其占用内存大小(虽然可能实际需要内存小于该值)。这里sizeof(void *)对应于ngx_hash_elt_t的value指针,(name)->key.len对应与name占用空间,2对应于len字节。这里使用ngx_align方法不是为了内存对其,而是为了将每个ngx_hash_elt_t进行划分,为了方便查询时更方快速获取。由于ngx_hash_t中的buckets申请的连续内存,name字段是要存储在该连续内存中,因此每个ngx_hash_elt_t所占用空间大小不一致,不能直接使用下标进行获取,使用该方法是为了内存对其,加速获取速度,具体查找时的方法参加下一小*/

ngx_int_t
ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts)
{
// 这里参数ngx_hash_key_t一般是存储在ngx_array_t中第一个元素,nelts是元素数量
u_char *elts;
size_t len;
u_short *test;
ngx_uint_t i, n, key, size, start, bucket_size;
ngx_hash_elt_t *elt, **buckets;

// max_size为0表示不能分配一个桶
if (hinit->max_size == 0) {
ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,
"could not build %s, you should "
"increase %s_max_size: %i",
hinit->name, hinit->name, hinit->max_size);
return NGX_ERROR;
}

// 每个桶的容量(字节数量)过大
if (hinit->bucket_size > 65536 - ngx_cacheline_size) {
ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,
"could not build %s, too large "
"%s_bucket_size: %i",
hinit->name, hinit->name, hinit->bucket_size);
return NGX_ERROR;
}

for (n = 0; n < nelts; n++) {
/* 如果某个key构建的ngx_hash_elt_t所需空间加上一个void*所用空间大于bucket_siz,则报错。这里增加void*指针,是因为在ngx_hash_t中的buckets中,对于每一个桶,并没有指名末尾是什么,因此需要在最后增加一个void*的空间,对应获取到ngx_hash_elt_t的value字段,该字段设置为空,表示桶的末尾。*/
if (hinit->bucket_size < NGX_HASH_ELT_SIZE(&names[n]) + sizeof(void *))
{
ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,
"could not build %s, you should "
"increase %s_bucket_size: %i",
hinit->name, hinit->name, hinit->bucket_size);
return NGX_ERROR;
}
}
// 分配 存储每个hash元素所需要的空间大小 数组
test = ngx_alloc(hinit->max_size * sizeof(u_short), hinit->pool->log);
if (test == NULL) {
return NGX_ERROR;
}
// bucket_size为允许桶存储的大小减去末尾记录为结尾的空间
bucket_size = hinit->bucket_size - sizeof(void *);

/* 初始分配需要多少个桶,使用元素数量/(每个桶允许存储数据的字节大小/(2 * sizeof(void *)),这里2 * sizeof(void *)是一个ngx_hash_elt_t预估占用内存大小,因此实际为元素数量除以每个桶允许存放的元素个数 */
start = nelts / (bucket_size / (2 * sizeof(void *)));
start = start ? start : 1;

if (hinit->max_size > 10000 && nelts && hinit->max_size / nelts < 100) {
start = hinit->max_size - 1000;
}

// 查找实际需要多少个桶才能满足每个桶中最多分配bucket_size内存,实际需要的桶数量不能超过限制的最大值max_size
for (size = start; size <= hinit->max_size; size++) {

ngx_memzero(test, size * sizeof(u_short));
// 遍历每一个元素
for (n = 0; n < nelts; n++) {
if (names[n].key.data == NULL) {
continue;
}
// 找到以当前分配桶的数量时,其应该存放到哪个桶中
key = names[n].key_hash % size;
// 计算当前分配到这个桶中所以元素所占用的内存大小
len = test[key] + NGX_HASH_ELT_SIZE(&names[n]);

#if 0
ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
"%ui: %ui %uz \"%V\"",
size, key, len, &names[n].key);
#endif
// 如果当前该桶占用的内存已经超出限制,则表示当前分配的桶数量不足,增加桶的数量,再查找验证
if (len > bucket_size) {
goto next;
}
// 如果循环结束,依然位发现有某个桶分配的元素所占用内存过大,则说明找到了桶大小
test[key] = (u_short) len;
}

goto found;

next:

continue;
}
// 未找到,则表示hash表初始化失败,报错
size = hinit->max_size;

ngx_log_error(NGX_LOG_WARN, hinit->pool->log, 0,
"could not build optimal %s, you should increase "
"either %s_max_size: %i or %s_bucket_size: %i; "
"ignoring %s_bucket_size",
hinit->name, hinit->name, hinit->max_size,
hinit->name, hinit->bucket_size, hinit->name);

found:
// 查找到需要建立多少个桶的处理
// 每一个桶中先分配一个作为末尾表示的空指针对应的存储空间大小
for (i = 0; i < size; i++) {
test[i] = sizeof(void *);
}
// 遍历每个元素
for (n = 0; n < nelts; n++) {
if (names[n].key.data == NULL) {
continue;
}
// 计算每个元素在该桶大小下分配到哪个桶中
key = names[n].key_hash % size;
// 记录该桶中当前已占用内存大小
len = test[key] + NGX_HASH_ELT_SIZE(&names[n]);
// 超过该值报错
if (len > 65536 - ngx_cacheline_size) {
ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,
"could not build %s, you should "
"increase %s_max_size: %i",
hinit->name, hinit->name, hinit->max_size);
ngx_free(test);
return NGX_ERROR;
}

test[key] = (u_short) len;
}

len = 0;
// 变量每一个桶,计算总共需要创建的内存大小
for (i = 0; i < size; i++) {
// 空的桶不需要创建内存
if (test[i] == sizeof(void *)) {
continue;
}
// 为了加速请求速度,每个桶分配内存都为cache line的整数倍
test[i] = (u_short) (ngx_align(test[i], ngx_cacheline_size));

len += test[i];
}
// 分配hash需要的内存大小,分配buckets空间
if (hinit->hash == NULL) {
hinit->hash = ngx_pcalloc(hinit->pool, sizeof(ngx_hash_wildcard_t)
+ size * sizeof(ngx_hash_elt_t *));
if (hinit->hash == NULL) {
ngx_free(test);
return NGX_ERROR;
}

buckets = (ngx_hash_elt_t **)
((u_char *) hinit->hash + sizeof(ngx_hash_wildcard_t));

} else {
buckets = ngx_pcalloc(hinit->pool, size * sizeof(ngx_hash_elt_t *));
if (buckets == NULL) {
ngx_free(test);
return NGX_ERROR;
}
}

// 分配存储所有元素需要的空间大小
elts = ngx_palloc(hinit->pool, len + ngx_cacheline_size);
if (elts == NULL) {
ngx_free(test);
return NGX_ERROR;
}

// 为加速,进行内存行对其
elts = ngx_align_ptr(elts, ngx_cacheline_size);

// 遍历每个桶
for (i = 0; i < size; i++) {
if (test[i] == sizeof(void *)) {
continue;
}
// 使用分配的内存,初始化每个桶的起始位置
buckets[i] = (ngx_hash_elt_t *) elts;
elts += test[i];
}

for (i = 0; i < size; i++) {
test[i] = 0;
}

// 对每一个元素赋值
for (n = 0; n < nelts; n++) {
if (names[n].key.data == NULL) {
continue;
}
// 找到该元素分配到每个哪个桶中
key = names[n].key_hash % size;
// 找到该元素应该分配到桶中的内存起始位置
elt = (ngx_hash_elt_t *) ((u_char *) buckets[key] + test[key]);
// 对元素赋值
elt->value = names[n].value;
elt->len = (u_short) names[n].key.len;

ngx_strlow(elt->name, names[n].key.data, names[n].key.len);
// 增加该桶中使用了的内存大小
test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));
}

// 对每一个桶中,增加结束标识
for (i = 0; i < size; i++) {
if (buckets[i] == NULL) {
continue;
}
// 获取到当前桶已经使用到的内存位置
elt = (ngx_hash_elt_t *) ((u_char *) buckets[i] + test[i]);
// 对该位置的value设置为null,查找时作为结束标识
elt->value = NULL;
}

ngx_free(test);
// 赋值桶和桶大小
hinit->hash->buckets = buckets;
hinit->hash->size = size;

#if 0

for (i = 0; i < size; i++) {
ngx_str_t val;
ngx_uint_t key;

elt = buckets[i];

if (elt == NULL) {
ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
"%ui: NULL", i);
continue;
}

while (elt->value) {
val.len = elt->len;
val.data = &elt->name[0];

key = hinit->key(val.data, val.len);

ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
"%ui: %p \"%V\" %ui", i, elt, &val, key);

elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,
sizeof(void *));
}
}

#endif

return NGX_OK;
}

散列表查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void *
ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len)
{
ngx_uint_t i;
ngx_hash_elt_t *elt;

#if 0
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "hf:\"%*s\"", len, name);
#endif
// 找到该元素属于哪个桶
elt = hash->buckets[key % hash->size];

if (elt == NULL) {
return NULL;
}

// 查找桶中元素,直到末尾
while (elt->value) {
if (len != (size_t) elt->len) {
goto next;
}

for (i = 0; i < len; i++) {
if (name[i] != elt->name[i]) {
goto next;
}
}
// 找到后返回
return elt->value;

next:

elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,
sizeof(void *));
continue;
}

return NULL;
}

管理散列表(ngx_hash_keys_arrays_t)

nginx为了让大家方便的构造hash表,提供给了此辅助类型,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
typedef struct {
// 将要构建的hash表的桶的个数。对于使用这个结构中包含的信息构建的三种类型的hash表都会使用此参数。
ngx_uint_t hsize;
// 构建这些hash表使用的pool。
ngx_pool_t *pool;
// 在构建这个类型以及最终的三个hash表过程中可能用到临时pool。该temp_pool可以在构建完成以后,被销毁掉。这里只是存放临时的一些内存消耗。
ngx_pool_t *temp_pool;

// 存放所有非通配符key的数组。
ngx_array_t keys;
/*这是个二维数组,第一个维度代表的是bucket的编号,那么keys_hash[i]中存放的是所有的key算出来的hash值对hsize取模以后的值为i的key。假设有3个key,分别是key1,key2和key3假设hash值算出来以后对hsize取模的值都是i,那么这三个key的值就顺序存放在keys_hash[i][0],keys_hash[i][1], keys_hash[i][2]。该值在调用的过程中用来保存和检测是否有冲突的key值,也就是是否有重复。用于存放非通配符数据*/
ngx_array_t *keys_hash;
// 放前向通配符key被处理完成以后的值。比如:“*.abc.com” 被处理完成以后,变成 “com.abc.” 被存放在此数组中。
ngx_array_t dns_wc_head;
// 该值在调用的过程中用来保存和检测是否有冲突的前向通配符的key值,也就是是否有重复。
ngx_array_t *dns_wc_head_hash;
// 存放后向通配符key被处理完成以后的值。比如:“mail.xxx.*” 被处理完成以后,变成 “mail.xxx.” 被存放在此数组中。
ngx_array_t dns_wc_tail;
// 该值在调用的过程中用来保存和检测是否有冲突的后向通配符的key值,也就是是否有重复。
ngx_array_t *dns_wc_tail_hash;
} ngx_hash_keys_arrays_t;
// 一维数组存储数据一般ngx_hash_key_t,二维数组存储数据一般ngx_str_t

构建ngx_hash_keys_arrays_t

使用ngx_hash_keys_array_init构建该结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
ngx_int_t
// type指名构建的大小。其中NGX_HASH_SMALL表示待初始化的元素较少,NGX_HASH_LARGE表示待初始化的元素较多
ngx_hash_keys_array_init(ngx_hash_keys_arrays_t *ha, ngx_uint_t type)
{
// 数组预分配空间大小
ngx_uint_t asize;
// 初始化较少时,预分配大小为4,桶数量为107
if (type == NGX_HASH_SMALL) {
asize = 4;
ha->hsize = 107;
// 初始化较多时,预分配大小为16384,桶数量为10007
} else {
asize = NGX_HASH_LARGE_ASIZE;
ha->hsize = NGX_HASH_LARGE_HSIZE;
}
// 初始化存储数据的三个数组
if (ngx_array_init(&ha->keys, ha->temp_pool, asize, sizeof(ngx_hash_key_t))
!= NGX_OK)
{
return NGX_ERROR;
}

if (ngx_array_init(&ha->dns_wc_head, ha->temp_pool, asize,
sizeof(ngx_hash_key_t))
!= NGX_OK)
{
return NGX_ERROR;
}

if (ngx_array_init(&ha->dns_wc_tail, ha->temp_pool, asize,
sizeof(ngx_hash_key_t))
!= NGX_OK)
{
return NGX_ERROR;
}

// 初始化用于查看是否有重复的简易hash表
ha->keys_hash = ngx_pcalloc(ha->temp_pool, sizeof(ngx_array_t) * ha->hsize);
if (ha->keys_hash == NULL) {
return NGX_ERROR;
}

ha->dns_wc_head_hash = ngx_pcalloc(ha->temp_pool,
sizeof(ngx_array_t) * ha->hsize);
if (ha->dns_wc_head_hash == NULL) {
return NGX_ERROR;
}

ha->dns_wc_tail_hash = ngx_pcalloc(ha->temp_pool,
sizeof(ngx_array_t) * ha->hsize);
if (ha->dns_wc_tail_hash == NULL) {
return NGX_ERROR;
}

return NGX_OK;
}

添加元素

使用如下函数增加元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
/* flag取值有三种:NGX_HASH_WILDCARD_KEY表示需要处理通配符。 NGX_HASH_READONLY_KEY表示不能对关键字做变更,即不能通过全小写关键字来获取散列码,其他值:即不处理通配符,又允许把关键字全小写获取散列码*/
ngx_int_t
ngx_hash_add_key(ngx_hash_keys_arrays_t *ha, ngx_str_t *key, void *value,
ngx_uint_t flags)
{
size_t len;
u_char *p;
ngx_str_t *name;
// skip表示起始位置要跳过字符数量,last表示到末尾的长度
ngx_uint_t i, k, n, skip, last;
ngx_array_t *keys, *hwc;
ngx_hash_key_t *hk;

last = key->len;
// 处理通配符
if (flags & NGX_HASH_WILDCARD_KEY) {

/*
* supported wildcards:
* "*.example.com", ".example.com", and "www.example.*"
*/

n = 0;
// 校验字符串正确性
for (i = 0; i < key->len; i++) {
// 如果出现多个*表示为nginx不正常的通配符格式(只支持首尾一个通配符)
if (key->data[i] == '*') {
if (++n > 1) {
return NGX_DECLINED;
}
}
// 错误通配符数据
if (key->data[i] == '.' && key->data[i + 1] == '.') {
return NGX_DECLINED;
}
// 字符串以\0结尾
if (key->data[i] == '\0') {
return NGX_DECLINED;
}
}
// ".example.com"格式数据,存储数据时去带.
if (key->len > 1 && key->data[0] == '.') {
skip = 1;
goto wildcard;
}

if (key->len > 2) {
// "*.example.com"格式数据,存储数据时去带.*
if (key->data[0] == '*' && key->data[1] == '.') {
skip = 2;
goto wildcard;
}
// "www.example.*"格式数据,存储数据时去带.*
if (key->data[i - 2] == '.' && key->data[i - 1] == '*') {
skip = 0;
last -= 2;
goto wildcard;
}
}
// 存在通配符*,但不在首尾
if (n) {
return NGX_DECLINED;
}
}

/* exact hash */
// 未找到通配符或不查看通配符
k = 0;
// 根据是否可以转换为小写计算哈希值
for (i = 0; i < last; i++) {
if (!(flags & NGX_HASH_READONLY_KEY)) {
key->data[i] = ngx_tolower(key->data[i]);
}
k = ngx_hash(k, key->data[i]);
}

k %= ha->hsize;

/* check conflicts in exact hash */
// 找到在keys_hash精准匹配的哪个桶中
name = ha->keys_hash[k].elts;
// 如果桶不为空,查看桶中是否有当前值
if (name) {
for (i = 0; i < ha->keys_hash[k].nelts; i++) {
if (last != name[i].len) {
continue;
}

if (ngx_strncmp(key->data, name[i].data, last) == 0) {
return NGX_BUSY;
}
}
// 初始化桶
} else {
if (ngx_array_init(&ha->keys_hash[k], ha->temp_pool, 4,
sizeof(ngx_str_t))
!= NGX_OK)
{
return NGX_ERROR;
}
}
// 添加元素到桶中
name = ngx_array_push(&ha->keys_hash[k]);
if (name == NULL) {
return NGX_ERROR;
}

*name = *key;

hk = ngx_array_push(&ha->keys);
if (hk == NULL) {
return NGX_ERROR;
}

hk->key = *key;
hk->key_hash = ngx_hash_key(key->data, last);
hk->value = value;

return NGX_OK;

// 通配符处理逻辑
wildcard:

/* wildcard hash */
// 去除通配符相关字段*.,.,转换小写计算hash值
k = ngx_hash_strlow(&key->data[skip], &key->data[skip], last - skip);

k %= ha->hsize;
// 对于是.example.com这种形式的key,其只能和精准的example.com两者直接存在一个。或者在精准中存在一个example.com,或者在前缀匹配中,存在一个.example.com
if (skip == 1) {

/* check conflicts in exact hash for ".example.com" */

name = ha->keys_hash[k].elts;

if (name) {
len = last - skip;

for (i = 0; i < ha->keys_hash[k].nelts; i++) {
if (len != name[i].len) {
continue;
}
// 如果精准匹配中已存在,则返回
if (ngx_strncmp(&key->data[1], name[i].data, len) == 0) {
return NGX_BUSY;
}
}

} else {
if (ngx_array_init(&ha->keys_hash[k], ha->temp_pool, 4,
sizeof(ngx_str_t))
!= NGX_OK)
{
return NGX_ERROR;
}
}
// 如果精准匹配中不存在,则添加一个example.com形式的key到精准匹配中,避免精准匹配中再增加
name = ngx_array_push(&ha->keys_hash[k]);
if (name == NULL) {
return NGX_ERROR;
}

name->len = last - 1;
name->data = ngx_pnalloc(ha->temp_pool, name->len);
if (name->data == NULL) {
return NGX_ERROR;
}

ngx_memcpy(name->data, &key->data[1], name->len);
}

// 对于前置通配符
if (skip) {

/*
* convert "*.example.com" to "com.example.\0"
* and ".example.com" to "com.example\0"
*/

p = ngx_pnalloc(ha->temp_pool, last);
if (p == NULL) {
return NGX_ERROR;
}

len = 0;
n = 0;
// 转换*.example.com到com.example.\0,.example.com到com.example\0,注意这里的i未到0
for (i = last - 1; i; i--) {
if (key->data[i] == '.') {
ngx_memcpy(&p[n], &key->data[i + 1], len);
n += len;
p[n++] = '.';
len = 0;
continue;
}

len++;
}
// 对于.example.com的来说,将example拼接上
if (len) {
ngx_memcpy(&p[n], &key->data[1], len);
n += len;
}

p[n] = '\0';

hwc = &ha->dns_wc_head;
keys = &ha->dns_wc_head_hash[k];

} else {

/* convert "www.example.*" to "www.example\0" */

last++;

p = ngx_pnalloc(ha->temp_pool, last);
if (p == NULL) {
return NGX_ERROR;
}

ngx_cpystrn(p, key->data, last);

hwc = &ha->dns_wc_tail;
keys = &ha->dns_wc_tail_hash[k];
}


/* check conflicts in wildcard hash */
// 在对应的通配符表中查找是否已经存在
name = keys->elts;

if (name) {
len = last - skip;

for (i = 0; i < keys->nelts; i++) {
if (len != name[i].len) {
continue;
}

if (ngx_strncmp(key->data + skip, name[i].data, len) == 0) {
return NGX_BUSY;
}
}

} else {
if (ngx_array_init(keys, ha->temp_pool, 4, sizeof(ngx_str_t)) != NGX_OK)
{
return NGX_ERROR;
}
}
// 向对应的简易hash表中增加数据,注意,这里增加的是去除通配符字段后原始数据,并未将首通配符样式字段倒排
name = ngx_array_push(keys);
if (name == NULL) {
return NGX_ERROR;
}

name->len = last - skip;
name->data = ngx_pnalloc(ha->temp_pool, name->len);
if (name->data == NULL) {
return NGX_ERROR;
}

ngx_memcpy(name->data, key->data + skip, name->len);


/* add to wildcard hash */
// 添加数据到对于的数组中,这里添加的是处理后的数据,即将首通配符样式字段倒排
hk = ngx_array_push(hwc);
if (hk == NULL) {
return NGX_ERROR;
}
// *.baidu.com被存储为com.baidu.;.baidu.com被存储为com.baidu; www.baidu.*被存储为www.baidu
hk->key.len = last - 1;
hk->key.data = p;
hk->key_hash = 0;
hk->value = value;

return NGX_OK;
}

对于构建好的通配符数组,不能直接调用ngx_hash_wildcard_init来构建包含通配符的散列表,而是要先对数组进行排序,这就是为什么要在字节后面添加\0的作用,用于判断字符串结尾。排序时,.被认为是最低顺序的字节,即:example.com小于(排在前面)example1.com

带有通配符的散列表(ngx_hash_wildcard_t)

1
2
3
4
5
6
typedef struct {
// 基本散列表
ngx_hash_t hash;
// 当使用ngx_hash_wildcard_t通配符散列表作为某容器的元素时,可以使用value指向用户数据
void *value;
} ngx_hash_wildcard_t;

nginx支持的带通配符的散列表,仅支持在起始或末尾带有散列表,即如下:

1
2
3
www.baidu.*
*.baidu.com
.baidu.com // 有可能被记录为不带通配符的baidu.com

构建带有通配符的散列表

ngx_hash_wildcard_init函数用来构建带有通配符的散列表。这里需要注意,不论是构建前置为通配符还是后置为通配符,其中的参数,即ngx_hash_key_t列表,均是z已被排序好的,即key均被排序完成,这时为了方便后续的构建。对于含有通配符的hash表来说,其是一个层级结构。例如对于如下两个key:

1
2
www.baidu.com
www.tencent.com

则构建的hash表为:

1
2
3
4
第一级   第二级    第三极
baidu com
www
tencent com

查找时也是同样的逻辑,先按照.划分块,再一层一层查找。这也是为啥要将前置的通配符反转,变更成类似于后置的结构。

具体逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
ngx_int_t
ngx_hash_wildcard_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names,
ngx_uint_t nelts)
{
size_t len, dot_len;
ngx_uint_t i, n, dot;
ngx_array_t curr_names, next_names;
ngx_hash_key_t *name, *next_name;
ngx_hash_init_t h;
ngx_hash_wildcard_t *wdc;
// 分配当前层级数据空间
if (ngx_array_init(&curr_names, hinit->temp_pool, nelts,
sizeof(ngx_hash_key_t))
!= NGX_OK)
{
return NGX_ERROR;
}
// 分配当下一层级数据空间
if (ngx_array_init(&next_names, hinit->temp_pool, nelts,
sizeof(ngx_hash_key_t))
!= NGX_OK)
{
return NGX_ERROR;
}
// 从第一个开始查看,并不是简单的遍历
for (n = 0; n < nelts; n = i) {

#if 0
ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
"wc0: \"%V\"", &names[n].key);
#endif
// 记录是否查找到逗号,并使用len记录逗号所在下标
dot = 0;

for (len = 0; len < names[n].key.len; len++) {
if (names[n].key.data[len] == '.') {
dot = 1;
break;
}
}
// 添加当前的数据到当前层级数据中,对于查找到逗号的情况,其key变更为逗号前的部分,value为当前key对应数据的value
name = ngx_array_push(&curr_names);
if (name == NULL) {
return NGX_ERROR;
}

name->key.len = len;
name->key.data = names[n].key.data;
name->key_hash = hinit->key(name->key.data, name->key.len);
name->value = names[n].value;

#if 0
ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
"wc1: \"%V\" %ui", &name->key, dot);
#endif
// 记录从开始到当前位置的长度,用于切割key
dot_len = len + 1;
// 如果发现了.,则len加一,对比时,.字符也会用来比较相同前缀的key
if (dot) {
len++;
}

next_names.nelts = 0;
// 表示并为遍历到结尾,后面还有待切割字段,则将后面部分数据添加到构建下一层hash表的列表中
if (names[n].key.len != len) {
next_name = ngx_array_push(&next_names);
if (next_name == NULL) {
return NGX_ERROR;
}

next_name->key.len = names[n].key.len - len;
next_name->key.data = names[n].key.data + len;
next_name->key_hash = 0;
// value存储对于的数据,后续可能会变更
next_name->value = names[n].value;

#if 0
ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
"wc2: \"%V\"", &next_name->key);
#endif
}
// 遍历后面的key,找到第一个和当前查找到的前缀不匹配的key,注意 . 也会被匹配,即,查看的应该是 example.或者com这两种形式的查找匹配。
for (i = n + 1; i < nelts; i++) {
if (ngx_strncmp(names[n].key.data, names[i].key.data, len) != 0) {
break;
}
// 如果没有查找到逗号,即当前的n是切割的末尾了,并且当前的i长度要大于现在的n的长度,且i的len不是.就结束查找。这时由于,com.ex也要是com的子一层hash表。但comm不是com子一层。
if (!dot
&& names[i].key.len > len
&& names[i].key.data[len] != '.')
{
break;
}
// 将查找到属于当前n的子一层hash元素的数据添加到用来构建子一层hash表的数组中。
next_name = ngx_array_push(&next_names);
if (next_name == NULL) {
return NGX_ERROR;
}
// 同样是要把当前一层的key去掉,子一层只用剩下的字符串
next_name->key.len = names[i].key.len - dot_len;
next_name->key.data = names[i].key.data + dot_len;
next_name->key_hash = 0;
next_name->value = names[i].value;

#if 0
ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
"wc3: \"%V\"", &next_name->key);
#endif
}

// 如果存在需要构建子一层hash表的key
if (next_names.nelts) {

h = *hinit;
h.hash = NULL;
// 对子数组递归调用ngx_hash_wildcard_init构建函数。这里会调用ngx_hash_init,对于hash等于null来说,构建的hash是ngx_hash_wildcard_t结构
if (ngx_hash_wildcard_init(&h, (ngx_hash_key_t *) next_names.elts,
next_names.nelts)
!= NGX_OK)
{
return NGX_ERROR;
}
// 转换为ngx_hash_wildcard_t
wdc = (ngx_hash_wildcard_t *) h.hash;
// 如果当前的n是查询的末尾,那么用ngx_hash_wildcard_t存储对应的value,如果不是查询末尾,则ngx_hash_wildcard_t存储对应的value对应为NULL
if (names[n].key.len == len) {
wdc->value = names[n].value;
}
// 当前ngx_hash_key_t的value指向下一层的hash表。同时利用指针最后两位来标识当前节点状况。10表示当前的value指向下一层hash表,11表示当前value指向下一层hash并且该字符串后面跟随着 .
name->value = (void *) ((uintptr_t) wdc | (dot ? 3 : 2));
// 如果不需要建下一层的hash表,并且发现了.,即com.example.这种情况,使用01记录。00则表示是最后的节点,其下层没有hash表,并且字段后面没有. 当前的value指向对应的数据
} else if (dot) {
name->value = (void *) ((uintptr_t) name->value | 1);
}
}
// 构建当前层级的hash表
if (ngx_hash_init(hinit, (ngx_hash_key_t *) curr_names.elts,
curr_names.nelts)
!= NGX_OK)
{
return NGX_ERROR;
}

return NGX_OK;
}

对于如下数据:

1
2
3
4
5
*.baidu.com               value1
*.baidu.cn value2
*.tencent.com value3
*.example.baidu.com value4
*.example.baidu-int.com value5

首先被转换并排序为:

1
2
3
4
5
cn.baidu.                value2
com.baidu. value1
com.baidu.example. value4
com.baidu-int.example. value5
com.tencent. value3

构建多层hash表如下:

WItYGR.png

查找后缀通配符

使用ngx_hash_find_wc_tail在后缀通配符中查找:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
void *
ngx_hash_find_wc_tail(ngx_hash_wildcard_t *hwc, u_char *name, size_t len)
{
void *value;
ngx_uint_t i, key;

#if 0
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "wct:\"%*s\"", len, name);
#endif

key = 0;
// 使用.进行切割。如果查找到末尾还未找到,则返回null。由于是后缀通配符,因此查找到了末尾还未找到对应数据,则应该在精准匹配里面查找,而不应该在后置表中查找。
for (i = 0; i < len; i++) {
if (name[i] == '.') {
break;
}
// 计算切割后的hash值
key = ngx_hash(key, name[i]);
}

if (i == len) {
return NULL;
}

#if 0
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "key:\"%ui\"", key);
#endif
// 查找当前的hash表
value = ngx_hash_find(&hwc->hash, key, name, i);

#if 0
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "value:\"%p\"", value);
#endif

if (value) {

/*
* the 2 low bits of value have the special meaning:
* 00 - value is data pointer;
* 11 - value is pointer to wildcard hash allowing "example.*".
*/
// 对于指针倒数第二位来说,如果是1,则表明value值是下一层的hash数据
if ((uintptr_t) value & 2) {
// 加1,去掉 . 继续查找
i++;
// 恢复指针为正常指针
hwc = (ngx_hash_wildcard_t *) ((uintptr_t) value & (uintptr_t) ~3);
// 查找下一层的hash数据
value = ngx_hash_find_wc_tail(hwc, &name[i], len - i);
// 如果找到,则返回
if (value) {
return value;
}
// 未找到,则查找到的value对应的hash表结构ngx_hash_wildcard_t的value值,有可能为null(取决于构建时是否构建该层级的数据)
return hwc->value;
}

return value;
}
// 如果hash表中没有对应切割的数据,那么返回当前层级hash表结构ngx_hash_wildcard_t的value值。有可能为空,因为查找时找最长匹配,如果后面没有对应的匹配,则使用当前层级的hash对应数据(有可能为null)。
return hwc->value;
}

查找前缀通配符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
void *
ngx_hash_find_wc_head(ngx_hash_wildcard_t *hwc, u_char *name, size_t len)
{
void *value;
ngx_uint_t i, n, key;

#if 0
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "wch:\"%*s\"", len, name);
#endif

n = len;
// 从后向前切词
while (n) {
if (name[n - 1] == '.') {
break;
}

n--;
}

key = 0;
// 计算切词的hash值
for (i = n; i < len; i++) {
key = ngx_hash(key, name[i]);
}

#if 0
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "key:\"%ui\"", key);
#endif
// 查找当前的前缀表
value = ngx_hash_find(&hwc->hash, key, &name[n], len - n);

#if 0
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "value:\"%p\"", value);
#endif
// 在当前hash表中查找到了对应数据
if (value) {

/*
* the 2 low bits of value have the special meaning:
* 00 - value is data pointer for both "example.com"
* and "*.example.com";
* 01 - value is data pointer for "*.example.com" only;
* 10 - value is pointer to wildcard hash allowing
* both "example.com" and "*.example.com";
* 11 - value is pointer to wildcard hash allowing
* "*.example.com" only.
*/
// 指针的倒数第二位是1,表示当前value指向的是下一层hash表
if ((uintptr_t) value & 2) {
// 查找到了key的末尾
if (n == 0) {

/* "example.com" */
// 11表示构建hash时,是*.example.com,并且其后还有下一层hash。而当前搜索的key是example.com,返回空
if ((uintptr_t) value & 1) {
return NULL;
}
// 10表示构建hash时,是.example.com,并且其后还有下一层hash。其允许搜索的是example.com,因此返回value对应的ngx_hash_wildcard_t中的value数据
hwc = (ngx_hash_wildcard_t *)
((uintptr_t) value & (uintptr_t) ~3);
return hwc->value;
}
// 恢复原指针,进行向下查找
hwc = (ngx_hash_wildcard_t *) ((uintptr_t) value & (uintptr_t) ~3);

value = ngx_hash_find_wc_head(hwc, name, n - 1);

if (value) {
return value;
}
// 如果没找到,则返回value对应的ngx_hash_wildcard_t中的value数据(有可能为空)
return hwc->value;
}
// 01表示构建表的时候,是*.example.com,且其后没有下一层hash表了
if ((uintptr_t) value & 1) {
// 而当前搜索的key是example.com,返回空
if (n == 0) {

/* "example.com" */

return NULL;
}
// 01表示构建表的时候,是.example.com,且其后没有下一层hash表了,其存储的是普通数据
return (void *) ((uintptr_t) value & (uintptr_t) ~3);
}
// 如果是00,表示就是普通数据
return value;
}
// 如果hash表中没有对应切割的数据,那么返回当前层级hash表结构ngx_hash_wildcard_t的value值。有可能为空,因为查找时找最长匹配,如果后面没有对应的匹配,则使用当前层级的hash对应数据(有可能为null)。
return hwc->value;
}

ngx_queue_t双向循环链表

数据结构

1
2
3
4
5
6
typedef struct ngx_queue_s  ngx_queue_t;

struct ngx_queue_s {
ngx_queue_t *prev; // 指向前一个元素的指针
ngx_queue_t *next; // 指向后一个元素的指针
};

ngx_queue_s维持了双向链表,忽略了其实际元素的内容,不负责链表元素的内存分配。要想使用双向链表,则要求我们定义的类能够通过指针转换为ngx_queue_s,因此其必须包含*prev*next。此时可以直接通过指针来进行转换。例如:

1
2
3
4
5
6
7
8
9
10
struct my_test {
ngx_queue_t *prev;
ngx_queue_t *next;
int num;
}

my_test a;
a.prev = &a;
a.next = &a;
ngx_queue_s *q = (ngx_queue_s *)&a

注意双向循环链表存在一个维护双向链表结构的节点,该节点不存储数据,只是作为维护结构的节点。后续参数中h,表示维护链表的节点,参数为q表示存储数据的节点。

初始化双向循环链表:ngx_queue_init(q)

1
2
3
#define ngx_queue_init(q)                                                     \
(q)->prev = q; \
(q)->next = q

q为链表容器结构ngx_queue_s。

判空:ngx_queue_empty(h)

1
2
#define ngx_queue_empty(h)                                                    \
(h == (h)->prev)

由于是循环链表,因此只需要判断任意一个链表元素的前驱是否为自身即可。这里使用的是维护结构的节点。

在头部插入元素:ngx_queue_insert_head(h,x)

在维护链表的节点后增加一个元素,即为在链表头插入元素。:

1
2
3
4
5
#define ngx_queue_insert_head(h, x)                                           \
(x)->next = (h)->next; \
(x)->next->prev = x; \
(x)->prev = h; \
(h)->next = x

在尾部插入节点:ngx_queue_insert_tail(h,x)

在维护链表的节点前插入一个元素,即为在链表尾部插入元素(双向的作用)

1
2
3
4
5
#define ngx_queue_insert_tail(h, x)                                           \
(x)->prev = (h)->prev; \
(x)->prev->next = x; \
(x)->next = h; \
(h)->prev = x

获取列表第一个元素:ngx_queue_head(h)

维护链表节点的后一个元素即为首尾元素:

1
2
#define ngx_queue_head(h)                                                     \
(h)->next

获取列表中最后一个元素:ngx_queue_last(h)

维护链表节点的前一个元素即为首尾元素:

1
2
#define ngx_queue_last(h)                                                     \
(h)->prev

返回链表容器结构体指针:ngx_queue_sentinel(h)

1
2
#define ngx_queue_sentinel(h)                                                 \
(h)

常在循环中判断终止。

获取当前元素的下一个元素:ngx_queue_next(q)

1
2
#define ngx_queue_next(q)                                                     \
(q)->next

获取当前元素的上一个元素:ngx_queue_prev(q)

1
2
#define ngx_queue_prev(q)                                                     \
(q)->prev

移出元素:ngx_queue_remove(x)

1
2
3
#define ngx_queue_remove(x)                                                   \
(x)->next->prev = (x)->prev; \
(x)->prev->next = (x)->next

拆分链表:ngx_queue_split(h, q, n)

h为维护链表节点,q为其中一个元素,该方法将链表切割成两部分,通过q进行切割成两个链表h和n,前半部分h由原链表的前半部分构成(不包含q),n链表由原链表的后半部分组成,q为其首元素。

1
2
3
4
5
6
7
#define ngx_queue_split(h, q, n)                                              \
(n)->prev = (h)->prev; \
(n)->prev->next = n; \
(n)->next = q; \
(h)->prev = (q)->prev; \
(h)->prev->next = h; \
(q)->prev = n;

合并链表:ngx_queue_add(h, n)

将n链表添加到h链表的末尾。

1
2
3
4
5
#define ngx_queue_add(h, n)                                                   \
(h)->prev->next = (n)->next; \
(n)->next->prev = (h)->prev; \
(h)->prev = (n)->prev; \
(h)->prev->next = h;

对应能够转换为ngx_queue_t的元素,使用该方法,通过元素内容,获取ngx_queue_t的起始地址:

1
2
#define ngx_queue_data(q, type, link)                                         \
(type *) ((u_char *) q - offsetof(type, link))

在指定元素后插入内容:ngx_queue_insert_after(h, x)

与ngx_queue_insert_head类似,不过一个是维护链表的节点,一个是存储数据的节点:

1
#define ngx_queue_insert_after   ngx_queue_insert_head

返回链表中心元素:ngx_queue_middle(ngx_queue_t *queue)

其处理方式为,分配两个指针middle和next,都从头开始遍历链表,让next一次走两步,middle一个走一步,当next走到终点时,milddle就是中间节点。具体逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
ngx_queue_t *
ngx_queue_middle(ngx_queue_t *queue)
{
ngx_queue_t *middle, *next;

middle = ngx_queue_head(queue);
// 判断是否只有一个元素
if (middle == ngx_queue_last(queue)) {
return middle;
}

next = ngx_queue_head(queue);

for ( ;; ) {
// middle先走一步
middle = ngx_queue_next(middle);
// next走第一步
next = ngx_queue_next(next);
// 判断next是否到末尾
if (next == ngx_queue_last(queue)) {
return middle;
}
// next再走一步,并判断是否到末尾
next = ngx_queue_next(next);

if (next == ngx_queue_last(queue)) {
return middle;
}
}
}

对链表排序:ngx_queue_sort

按照指定排序方式对链表排序:采用稳定的插入排序算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void
ngx_queue_sort(ngx_queue_t *queue,
ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *))
{
ngx_queue_t *q, *prev, *next;

q = ngx_queue_head(queue);

if (q == ngx_queue_last(queue)) {
return;
}
// 遍历链表
for (q = ngx_queue_next(q); q != ngx_queue_sentinel(queue); q = next) {

prev = ngx_queue_prev(q);
// 记录下一个要遍历的节点
next = ngx_queue_next(q);
// 先移出节点q
ngx_queue_remove(q);

do {
// 找到在q前面第一个无序的节点
if (cmp(prev, q) <= 0) {
break;
}

prev = ngx_queue_prev(prev);

} while (prev != ngx_queue_sentinel(queue));
// 在第一个无序节点后添加q
ngx_queue_insert_after(prev, q);
}
}

红黑树ngx_rbtree_node_t

关于红黑树具体介绍及代码实现可以参考如下文档:红黑树

数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
typedef ngx_uint_t  ngx_rbtree_key_t;
typedef ngx_int_t ngx_rbtree_key_int_t;


typedef struct ngx_rbtree_node_s ngx_rbtree_node_t;
// 节点类
struct ngx_rbtree_node_s {
// 无符合整型关键字,排序依据
ngx_rbtree_key_t key;
// 左子树
ngx_rbtree_node_t *left;
// 右子树
ngx_rbtree_node_t *right;
// 父节点
ngx_rbtree_node_t *parent;
// 节点颜色 0表示红色,1表示黑色
u_char color;
// 仅一个字节的节点数据。由于表示空间过小,很少使用
u_char data;
};


typedef struct ngx_rbtree_s ngx_rbtree_t;
// 为解决不同节点含有相同关键字的元素冲突问题,红黑树设置了ngx_rbtree_insert_pt指针来灵活地添加冲突元素
typedef void (*ngx_rbtree_insert_pt) (ngx_rbtree_node_t *root,
ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
// 红黑树容器
struct ngx_rbtree_s {
// 根节点
ngx_rbtree_node_t *root;
// 哨兵节点
ngx_rbtree_node_t *sentinel;
// 指示红黑树添加元素的指针函数,其决定在添加新节点时的行为是替换还是新增
ngx_rbtree_insert_pt insert;
};

对应节点来说,与之前的结构类似,存储数据要依托于ngx_rbtree_node_s结构,因此可以自定义节点元素,但是必须包含ngx_rbtree_node_s结构,以使得方便结构体强制转换为ngx_rbtree_node_s结构。例如:

1
2
3
4
typedef struct {
ngx_rbtree_node_t node;
ngx_uint_t num;
}

红黑树节点提供的方法

设置节点颜色

1
2
#define ngx_rbt_red(node)               ((node)->color = 1)
#define ngx_rbt_black(node) ((node)->color = 0)

判断节点颜色

1
2
#define ngx_rbt_is_red(node)            ((node)->color)
#define ngx_rbt_is_black(node) (!ngx_rbt_is_red(node))

拷贝节点颜色

1
#define ngx_rbt_copy_color(n1, n2)      (n1->color = n2->color)

将n1节点变更为与n2一样。

初始化哨兵节点

1
#define ngx_rbtree_sentinel_init(node)  ngx_rbt_black(node)

哨兵节点为黑色节点,因此设置节点为黑色即可。

查找子树中最小节点

1
2
3
4
5
6
7
8
9
static ngx_inline ngx_rbtree_node_t *
ngx_rbtree_min(ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
{
while (node->left != sentinel) {
node = node->left;
}

return node;
}

以key为关键字,查找最小的一个节点。

红黑树容器提供的方法

初始化红黑树ngx_rbtree_init

1
2
3
4
5
#define ngx_rbtree_init(tree, s, i)                                           \
ngx_rbtree_sentinel_init(s); \
(tree)->root = s; \
(tree)->sentinel = s; \
(tree)->insert = i

参数tree是红黑树容器指针,s是哨兵节点指针,i为ngx_rbtree_insert_pt类型的节点添加方法。

寻找下一个元素ngx_rbtree_next

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 在tree容器中,找到第一个比node大的元素
ngx_rbtree_node_t *
ngx_rbtree_next(ngx_rbtree_t *tree, ngx_rbtree_node_t *node)
{
ngx_rbtree_node_t *root, *sentinel, *parent;

sentinel = tree->sentinel;
// node节点如果存在又子树,则比node节点大的第一个元素为其右子树的最小值
if (node->right != sentinel) {
return ngx_rbtree_min(node->right, sentinel);
}
// 如果node节点不存在右子树,则向上查找其父辈节点,找到第一个使node节点处于其左子树的父节点。如果没有,则说明node自身为最大的节点,不存在下一个节点
root = tree->root;

for ( ;; ) {
parent = node->parent;

if (node == root) {
return NULL;
}

if (node == parent->left) {
return parent;
}

node = parent;
}
}

旋转节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 左旋
static ngx_inline void
ngx_rbtree_left_rotate(ngx_rbtree_node_t **root, ngx_rbtree_node_t *sentinel,
ngx_rbtree_node_t *node)
{
ngx_rbtree_node_t *temp;

temp = node->right;
node->right = temp->left;

if (temp->left != sentinel) {
temp->left->parent = node;
}

temp->parent = node->parent;

if (node == *root) {
*root = temp;

} else if (node == node->parent->left) {
node->parent->left = temp;

} else {
node->parent->right = temp;
}

temp->left = node;
node->parent = temp;
}

// 右旋
static ngx_inline void
ngx_rbtree_right_rotate(ngx_rbtree_node_t **root, ngx_rbtree_node_t *sentinel,
ngx_rbtree_node_t *node)
{
ngx_rbtree_node_t *temp;

temp = node->left;
node->left = temp->right;

if (temp->right != sentinel) {
temp->right->parent = node;
}

temp->parent = node->parent;

if (node == *root) {
*root = temp;

} else if (node == node->parent->right) {
node->parent->right = temp;

} else {
node->parent->left = temp;
}

temp->right = node;
node->parent = temp;
}

关于节点旋转,这里不详细介绍,具体参考文档:红黑树

添加节点ngx_rbtree_insert

向红黑树容器中增加节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
void
ngx_rbtree_insert(ngx_rbtree_t *tree, ngx_rbtree_node_t *node)
{
ngx_rbtree_node_t **root, *temp, *sentinel;

/* a binary tree insert */

root = &tree->root;
sentinel = tree->sentinel;
// 如果红黑树为空,则直接设置root节点即可
if (*root == sentinel) {
node->parent = NULL;
node->left = sentinel;
node->right = sentinel;
ngx_rbt_black(node);
*root = node;

return;
}
// 否则,调用容器的ngx_rbtree_insert_pt函数,向其中增加节点
tree->insert(*root, node, sentinel);

/* re-balance tree */
// 增加节点后导致红黑树无法满足原性质,进行调整。
while (node != *root && ngx_rbt_is_red(node->parent)) {

if (node->parent == node->parent->parent->left) {
temp = node->parent->parent->right;

if (ngx_rbt_is_red(temp)) {
ngx_rbt_black(node->parent);
ngx_rbt_black(temp);
ngx_rbt_red(node->parent->parent);
node = node->parent->parent;

} else {
if (node == node->parent->right) {
node = node->parent;
ngx_rbtree_left_rotate(root, sentinel, node);
}

ngx_rbt_black(node->parent);
ngx_rbt_red(node->parent->parent);
ngx_rbtree_right_rotate(root, sentinel, node->parent->parent);
}

} else {
temp = node->parent->parent->left;

if (ngx_rbt_is_red(temp)) {
ngx_rbt_black(node->parent);
ngx_rbt_black(temp);
ngx_rbt_red(node->parent->parent);
node = node->parent->parent;

} else {
if (node == node->parent->left) {
node = node->parent;
ngx_rbtree_right_rotate(root, sentinel, node);
}

ngx_rbt_black(node->parent);
ngx_rbt_red(node->parent->parent);
ngx_rbtree_left_rotate(root, sentinel, node->parent->parent);
}
}
}

ngx_rbt_black(*root);
}

nginx提供了三种ngx_rbtree_insert_pt方法来增加元素,后续会详细介绍,关于如果重新平衡二叉树,也参考文档即可:红黑树

删除元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
void
ngx_rbtree_delete(ngx_rbtree_t *tree, ngx_rbtree_node_t *node)
{
ngx_uint_t red;
ngx_rbtree_node_t **root, *sentinel, *subst, *temp, *w;

/* a binary tree delete */

root = &tree->root;
sentinel = tree->sentinel;

if (node->left == sentinel) {
temp = node->right;
subst = node;

} else if (node->right == sentinel) {
temp = node->left;
subst = node;

} else {
subst = ngx_rbtree_min(node->right, sentinel);
temp = subst->right;
}

if (subst == *root) {
*root = temp;
ngx_rbt_black(temp);

/* DEBUG stuff */
node->left = NULL;
node->right = NULL;
node->parent = NULL;
node->key = 0;

return;
}

red = ngx_rbt_is_red(subst);

if (subst == subst->parent->left) {
subst->parent->left = temp;

} else {
subst->parent->right = temp;
}

if (subst == node) {

temp->parent = subst->parent;

} else {

if (subst->parent == node) {
temp->parent = subst;

} else {
temp->parent = subst->parent;
}

subst->left = node->left;
subst->right = node->right;
subst->parent = node->parent;
ngx_rbt_copy_color(subst, node);

if (node == *root) {
*root = subst;

} else {
if (node == node->parent->left) {
node->parent->left = subst;
} else {
node->parent->right = subst;
}
}

if (subst->left != sentinel) {
subst->left->parent = subst;
}

if (subst->right != sentinel) {
subst->right->parent = subst;
}
}

/* DEBUG stuff */
node->left = NULL;
node->right = NULL;
node->parent = NULL;
node->key = 0;

if (red) {
return;
}

/* a delete fixup */

while (temp != *root && ngx_rbt_is_black(temp)) {

if (temp == temp->parent->left) {
w = temp->parent->right;

if (ngx_rbt_is_red(w)) {
ngx_rbt_black(w);
ngx_rbt_red(temp->parent);
ngx_rbtree_left_rotate(root, sentinel, temp->parent);
w = temp->parent->right;
}

if (ngx_rbt_is_black(w->left) && ngx_rbt_is_black(w->right)) {
ngx_rbt_red(w);
temp = temp->parent;

} else {
if (ngx_rbt_is_black(w->right)) {
ngx_rbt_black(w->left);
ngx_rbt_red(w);
ngx_rbtree_right_rotate(root, sentinel, w);
w = temp->parent->right;
}

ngx_rbt_copy_color(w, temp->parent);
ngx_rbt_black(temp->parent);
ngx_rbt_black(w->right);
ngx_rbtree_left_rotate(root, sentinel, temp->parent);
temp = *root;
}

} else {
w = temp->parent->left;

if (ngx_rbt_is_red(w)) {
ngx_rbt_black(w);
ngx_rbt_red(temp->parent);
ngx_rbtree_right_rotate(root, sentinel, temp->parent);
w = temp->parent->left;
}

if (ngx_rbt_is_black(w->left) && ngx_rbt_is_black(w->right)) {
ngx_rbt_red(w);
temp = temp->parent;

} else {
if (ngx_rbt_is_black(w->left)) {
ngx_rbt_black(w->right);
ngx_rbt_red(w);
ngx_rbtree_left_rotate(root, sentinel, w);
w = temp->parent->left;
}

ngx_rbt_copy_color(w, temp->parent);
ngx_rbt_black(temp->parent);
ngx_rbt_black(w->left);
ngx_rbtree_right_rotate(root, sentinel, temp->parent);
temp = *root;
}
}
}

ngx_rbt_black(temp);
}

删除元素这里也不过多介绍,阅读文档即可。

架构提供的三个ngx_rbtree_insert_pt增加节点函数。

ngx_rbtree_insert_value

使用场景:向红黑树中增加数据节点,每个数据节点的关键字都是唯一的,不存在同一个关键字有多个节点的情况。

逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void
ngx_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node,
ngx_rbtree_node_t *sentinel)
{
ngx_rbtree_node_t **p;
// 自订向下查找,遇到比自己的key大的节点就进入左子树继续查找,遇到比自己小的节点就到右子树继续查找,直到哨兵节点,决定添加位置。
for ( ;; ) {

p = (node->key < temp->key) ? &temp->left : &temp->right;

if (*p == sentinel) {
break;
}

temp = *p;
}

*p = node;
// 添加节点,设置节点为红色
node->parent = temp;
node->left = sentinel;
node->right = sentinel;
ngx_rbt_red(node);
}
ngx_rbtree_insert_timer_value

该函数向红黑树添加数据节点,每个节点的关键字表示时间或者时间差。因此其中的key可能为负数。这是并不关心是否有重复的key。其逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void
ngx_rbtree_insert_timer_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node,
ngx_rbtree_node_t *sentinel)
{
ngx_rbtree_node_t **p;

for ( ;; ) {

/*
* Timer values
* 1) are spread in small range, usually several minutes,
* 2) and overflow each 49 days, if milliseconds are stored in 32 bits.
* The comparison takes into account that overflow.
*/

/* node->key < temp->key */

p = ((ngx_rbtree_key_int_t) (node->key - temp->key) < 0)
? &temp->left : &temp->right;

if (*p == sentinel) {
break;
}

temp = *p;
}

*p = node;
node->parent = temp;
node->left = sentinel;
node->right = sentinel;
ngx_rbt_red(node);
}

与ngx_rbtree_insert_value逻辑一致,只是做了一个ngx_rbtree_key_int_t的强制类型转换,支持负数。

ngx_str_rbtree_insert_value

向红黑树中增加节点,每个数据节点的关键字可以不唯一,但是以字符串作为唯一标识,存放在ngx_str_node_t的str中。ngx_str_node_t定义如下:

1
2
3
4
typedef struct {
ngx_rbtree_node_t node;
ngx_str_t str;
} ngx_str_node_t;

对应的插入方法为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
void
ngx_str_rbtree_insert_value(ngx_rbtree_node_t *temp,
ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
{
ngx_str_node_t *n, *t;
ngx_rbtree_node_t **p;

for ( ;; ) {

n = (ngx_str_node_t *) node;
t = (ngx_str_node_t *) temp;

if (node->key != temp->key) {

p = (node->key < temp->key) ? &temp->left : &temp->right;

} else if (n->str.len != t->str.len) {

p = (n->str.len < t->str.len) ? &temp->left : &temp->right;

} else {
p = (ngx_memcmp(n->str.data, t->str.data, n->str.len) < 0)
? &temp->left : &temp->right;
}

if (*p == sentinel) {
break;
}

temp = *p;
}

*p = node;
node->parent = temp;
node->left = sentinel;
node->right = sentinel;
ngx_rbt_red(node);
}

与前两个类似,但是增加了对str的比较。

Nginx特殊技巧

ngx_align 值对齐宏

ngx_align 为nginx中的一个值对齐宏。主要在需要内存申请的地方使用,为了减少在不同的 cache line 中内存而生。

1
2
3
4
// d 为需要对齐的
// a 为对齐宽度,必须为 2 的幂
// 返回对齐值
#define ngx_align(d, a) (((d) + (a - 1)) & ~(a - 1))

原理简单,利用 ~(a - 1) 的低位全为 0。在与 ~(a - 1)& 操作时,低位的1被丢弃,就得到了a倍数的值(对齐)。

如果使用原始值直接与 ~(a - 1)& 操作,那么得到的对齐值是会小于等于原始值的,这样会造成内存重叠,而期望的对齐值是一个大于等于原始值的,所以需要加上一个数来补上至对齐值这中间的差,这个数为 (a - 1) ,选择这个数的原因是 (a - 1) & ~(a - 1) 的结果为0。

该操作含义为:取大于d且为a整数倍的第一个数。

ngx_align_ptr内存对其

1
2
#define ngx_align_ptr(p, a)                                                   \
(u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))

这里和上面都存储对其类似,a一般是cache line大小。对其后,指针指向cache line的整数倍的地方,加快读取速度。具体参考下面文章。

https://oopschen.github.io/posts/2013/cpu-cacheline/

指针最后两位一定是0

字节对齐和系统有关,也和编译器有关,具体到底是几字节对齐和本问题关系不大。主要是因为无论如何对齐,都是字节对齐,而不是bit对齐。也就是说指针的起始存放地址只可能是8的倍数,比如0x00,0x08,0x10,转换成二进制后三位永远是0。不可能出现0x02到0x22作为一个四字节的指针。

ps1:有些资料中将之描述为指针的后2位永远是0,猜想是汇编语言中好像有的语句可以把4bit看作一个基本的操作单位,所以只能保证指针的后2位永远是0。

ps2:是否可以申请一个数组,然后对其中的位进行操作,使之出现一个类似0x02到0x22作为一个四字节指针的情况,将这个畸形的指针传入指针操作API中会导致什么后果,这个问题还有待考证。

出处:https://www.zhihu.com/question/40636241/answer/311889614

内联汇编

内联汇编语言可以直接操作硬件。可以用来在nginx源码中实现对整数的原子操作。

使用GCC编译器在C语言中嵌入汇编语言的方式是使用__asm__关键字,语法如下:

1
2
3
4
5
__asm__ volatile ( 汇编语句部分
: 输出部分 /*可选*/
: 输入部分 /*可选*/
: 破坏描述部分 /*可选*/
);

加入volatile关键字用于限制GCC编译器对这段代码做优化。

内联汇编语言包含四部分:

  1. 汇编语句部分

    引号中所包含的汇编语句可以直接用占位符%来引用C语言中的变量(最多10个,%0-%9)。

  2. 输出部分

    将寄存器中的值设置到C语言的变量中

  3. 输入部分

    将C语言中的变量设置到寄存器中。

  4. 破坏描述部分

    通知编译器使用了哪些寄存器、内存。

以如下语句举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static ngx_inline ngx_atomic_uint_t
ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
ngx_atomic_uint_t set)
{
u_char res;

__asm__ volatile (

" lock "
" cmpxchgl %3, %1; "
" sete %0; "

: "=a" (res) : "m" (*lock), "a" (old), "r" (set) : "cc", "memory");

return res;
}

先看输入部分:”m” (*lock)表示lock变量是在内存中,操作\lock直接通过内存(不使用寄存器处理),而”a” (old)表示把old变量写入eax寄存器中,”r” (set)表示把set变量写入通用寄存器中。这些都是为cmpxchgl做准备。

再来看汇编语句部分:”lock”表示在多核架构上首先锁住总线。

cmpxchgl语句含义为如下代码表示:

1
2
3
4
5
6
7
8
9
10
11
12
/*
* "cmpxchgl r, [m]":
*
* if (eax == [m]) {
* zf = 1;
* [m] = r;
* } else {
* zf = 0;
* eax = [m];
* }
*
*/

即判断寄存器中的值是否等于[m],如果相等,则设置[m]为r,并且设置sf为1。否则,设置zf为0,并且设置寄存器中值为[m]。

在语句cmpxchgl %3, %1;中,寄存器变量为old。%3表示set,%1表示loct。先比较\lock是否等于old,如果相等设置*lock为set。并进行zf设置。

“sete %0;”表示设置zf值到寄存器变量中。

返回部分”=a” (res)将寄存器中值写入res变量中,返回。

Nginx架构及启动流程

Nginx的架构设计

优秀的模块化设计

高度模块化的设计是Nginx的架构基础。在nginx中,除了少量的核心代码,其他一切皆为模块。其具有如下特点:

  1. 高度抽象的模块接口

    所有模块都遵循同样的ngx_module_t接口设计规范,减少了系统变数。

  2. 模块接口非常简单,具有高度灵活性

    模块的基本接口nginx_module_t足够简单,只设计模块的初始化、退出以及对配置项的处理,同时带来了足够的灵活性。

    2pk4cn.md.png

图8-1

如图所示,nginx_module_t结构体作为所有模块的通用接口,其只定义了init_master init_module init_process init_thread exit_thread exit_process exit_master这七个回调方法,他们负责模块的初始化与退出,同时权限非常高,可以处理核心结构体nginx_cycle_t。

nginx_module_t类

nginx_module_t结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
struct ngx_module_s {
/* 下面的ctx_index、index、spare0、spare1、spare2、spare3、version不需要在定义时赋值,可以用Nginx准备好的宏NGINX_MODULE_V1来定义,其已经定义好了这7个值。 #define NGINX_MODULE_V1 0,0,0,0,0,0,1 */


/* 对于一类模块(同一type)而言,ctx_index表示当前模块在这类模块中的序号,改成员变量通常由管理这类模块的一个nginx核心模块设置的,对于http而言,由核心模块nginx_http_module设置。ctx_index十分重要,nginx模块化依赖各个模块顺序,其既表达优先级,也用于表面每个模块的位置*/
ngx_uint_t ctx_index;

/* index表示当前模块在ngx_modules数组中序号,即为当前模块在所有模块中的序号,nginx启动时,依据ngx_modules数组设置该值*/
ngx_uint_t index;

char *name;

ngx_uint_t spare0;
ngx_uint_t spare1;

ngx_uint_t version;
const char *signature;

/* 指向一类模块的上下文结构体,例如在http模块中,ctx指向ngx_http_module_t结构体*/
void *ctx;

/* commands将处理nginx.conf中的配置项 */
ngx_command_t *commands;
ngx_uint_t type;

/* master进程启动时回调init_master,但目前未使用,设置为NULL*/
ngx_int_t (*init_master)(ngx_log_t *log);

/* 初始化所有模块是调用init_module,在master/worker模式下,该阶段在启动worker子进程前完成,具体执行位置在ngx_init_cycle中,执行完成配置解析,并打开监听端口号后,执行每个模块的init_module */
ngx_int_t (*init_module)(ngx_cycle_t *cycle);

/*init_process在正常服务前被调用,在master/worker模式下,多个worker子进程已产生,在每个woker进程的初始化过程中会调用所有模块的该函数 */
ngx_int_t (*init_process)(ngx_cycle_t *cycle);
ngx_int_t (*init_thread)(ngx_cycle_t *cycle);
void (*exit_thread)(ngx_cycle_t *cycle);

/*exit_process在服务停止前被调用,在master/worker模式下,worker进程退出前调用 */
void (*exit_process)(ngx_cycle_t *cycle);

/*exit_master在master进程退出前调用*/
void (*exit_master)(ngx_cycle_t *cycle);

uintptr_t spare_hook0;
uintptr_t spare_hook1;
uintptr_t spare_hook2;
uintptr_t spare_hook3;
uintptr_t spare_hook4;
uintptr_t spare_hook5;
uintptr_t spare_hook6;
uintptr_t spare_hook7;
};

ctx是void指针,可以指向任何数据,这改模块提供了极大的灵活性。

  1. 配置模块的设计

    ngx_module_t接口中type类型指名了nginx在设计模块时定义模块类型,允许专注于不同领域的模块按照类型来区别。配置类型模块是唯一一个只有一个模块的模块类型。配置模块的类型叫做NGX_CONF_MODULE,其仅有的模块为ngx_conf_module,其为底层模块,指导所有模块以配置项为核心来提供功能。

  2. 核心模块的简单化

  3. 多层次,多类别的模块设计

    所以模块间是分层次、分类别的,官方Nginx共用5大类型模块:核心模块、配置模块、时间模块、HTTP模块、mail模块。虽然都具备相同的ngx_module_t接口,但在请求处理中的层级并不相同。

    2ELokD.png

图8-2

如上图,配置模块和核心模块由Nginx的框架代码定义,配置模块是所有模块的基础,其实现了最基础的配置项解析功能(解析nginx.conf)。Nginx框架还会调用核心模块,但其他3种模块都不会与框架产生直接关系。事件模块、HTTP模块、mail模块的共性为:它们在核心模块中各有一个模块,作为其代言人,并在同类模块中有一个作为核心业务与管理功能的模块。

事件驱动框架

事件驱动指:由一些事件发送源来产生事件,由一个或多个时间收集器来收集、分发时间,然后许多事件处理器会注册自己感兴趣的,同时会消费这些事件。

对于nginx来说,一般会由网卡、磁盘产生事件,事件模块负责收集、分发操作,所有模块都可能是消费者,其首选向事件模块注册感兴趣的事件类型,这样,有事件产生时,事件模块会把事件分发到响应模块中进行处理。

传统Web服务器,采用的事件驱动往往局限于在TCP连接、关闭事件上,一个连接建立后,在其关闭前所有操作都不再是事件驱动,此时会退化为按需执行每个操作的批处理模式,这样,每个请求在连接后都将始终占有系统资源,直到连接关闭才会释放。

Nginx则不然,他不会使用进程或线程作为事件消费者,所谓事件消费者只能是某个模块。只有事件收集器、分发器才有资格占用进程资源,它们会在分发某个事件时调用事件消费模块使用当前占用进程资源。

2EXtrq.png

图8-4

如上图,在事件收集、分发者进程的一次处理过程中,5个事件按序被收集后,将开始使用当前进程分发事件,从而调用响应的事件消费者模块来处理事件。事件消费者只是被事件分发者进程短期调用而已。

请求的多阶段异步处理

请求的多阶段异步处理是指:把一个请求过程按照事件的触发方式划分为多个阶段,每个阶段都可以由事件收集、分发来触发。

请求的多阶段异步处理优势:这种设计配合事件驱动架构,将极地提高网络性能,同时使得每个进程都能全力运转,不会或者尽量少的出现进程休眠状况。

划分请求阶段原则为:找到请求处理流程中阻塞方法,在阻塞代码段上按照下面四个方法来划分阶段:

  1. 将阻塞进程的方法按照相关的触发事件分解为两个阶段:

    一个本身可能导致进程休眠的方法或系统调用,一般可以分解为多个更小的方法或者系统调用,这些调用间可以通过事件触发关联起来。大部分情况,一个阻塞的方法调用可以划分为两个阶段:第一阶段为,将阻塞方法改为非阻塞方法,并将进程归还给事件分发器;第二阶段,用于处理非阻塞方法最终返回结果,这里的返回结果就是第二阶段触发事件。

    例如使用send调用时,如果使用阻塞socket句柄,send向内核发送数据后将使当前进程休眠,直到成功发出数据。可以将send调用划分为两个阶段:使用非阻塞socket句柄,发送后进程不休眠,再将socket句柄加入事件收集器中就可以等待相应事件触发下一阶段,send发送数据被对方接收后会触发send结果返回阶段。

  2. 将阻塞方法调用按照时间分解为多个阶段的方法调用

    系统中事件收集器、分发器并非可以处理任何事件。例如读取文件调用(非异步I/O),如果我们读取10MB文件,这些文件在磁盘块未必是连续的,此时可能需要多次驱动硬盘寻址,寻址时,进程多半会休眠或等待。如果内核不支持异步I/O时(或未打开),就不能采用第一个方案。此时可以分解读取文件调用:把10MB的文件划分为1000份,每次读取10KB。每次读取10KB的时间是可控的,意味着该事件不会占用进程太久,整个系统可以及时处理其他请求。

    在读取0KB-10KB后如何进入10KB-20KB呢,可以有多种方式:如读取完10KB要使用网络进行发送,可以由网络事件进行触发。或者没有网络事件,可以设置一个简单的定时器。

  3. 在”无所事事”且必须等待系统响应时,使用定时器划分阶段

    有时阻塞代码可以是这样的:进行某个无阻塞的系统调用后,必须通过持续检查标志位来确定是否继续向下执行,当标志位没有获得满足时就循环地检查。此时,应该使用定时器来代替循环检查标志,这样定时器事件发送时就会先检查标志,如果标志不满足,就立即归还进程控制权,同时继续加入期望的下一个定时器事件。

  4. 如果阻塞方法完全无法划分,则必须使用独立的进程执行这个阻塞方法

    如果某个方法的调用时可能导致进程休眠,或者占用进程时间过长,开始又无法将该方法分级为非阻塞的方法,那么,这与事件驱动框架是相违背的。通常是由于方法实现者未开放非阻塞接口所导致,这时必须通过产生新的进程或者指定某个非事件分发者进程来执行阻塞方法,并在阻塞方法执行完毕时向事件收集、分发者进程发送事件通知继续执行。因此,至少要拆分为两个阶段:阻塞方法执行前阶段、阻塞方法执行后阶段,阻塞方法由单独的进程取调度,并在方法返回后发送事件通知。一旦出现这种情况,应该考虑这样的事件消费者是否合理,有没有必要使用这种违反事件驱动的方式来解决阻塞问题。

管理进程、多工作进程设计

Nginx采用一个master管理进程,多个worker工作进程的设计方式,如下图:

2npYee.png

图8-5

该设计的优点为:

  1. 利用多核系统的并发处理能力

  2. 负载均衡

    每个worker工作进程通过进程间通信来实现负载均衡,即一个请求到达时会更容易地被分配到负载较轻的进程中。

  3. 管理进程负责监控工作进程的状态,并负责其行为

    管理进程不会占用太多系统资源,其只用来启动、停止、建库或使用其他行为来控制工作进程。首选,这提高了系统的可靠性,当工作进程出现问题时,管理进程可以启动新的工作进程来避免系统性能下降。其次,管理进程支持nginx服务运行中的程序升级、配置项的修改等操作。这种设计使得动态可扩展性、动态定制性、动态可进化性较容易实现。

内存池的设计

为了避免出现内存碎片、减少向操作系统申请内存的次数、降低各个模块的开发复杂度,Nginx设计了简单的内存池。内存池没有很复杂的功能:其通常不负责回收内存池中已经分配的内存。内存池最大的优点在于:把多次向系统申请内存的操作整合到一次,这大大减少了CPU资源消耗,同时减少了内存碎片。

Nginx框架中的核心结构体ngx_cycle_t

Nginx核心的框架代码围绕ngx_cycle_t结构体展开。

ngx_listening_t结构体

作为web服务器,nginx首先需要监听端口并处理其中的网络事件。ngx_cycle_t对象有一个动态数组成员listening,其每个元素都是ngx_listening_t结构体,每个ngx_listening_t结构体代表nginx服务器监听的一个端口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
typedef struct ngx_listening_s  ngx_listening_t;

struct ngx_listening_s {
ngx_socket_t fd;

struct sockaddr *sockaddr;
socklen_t socklen; /* size of sockaddr */
size_t addr_text_max_len;
ngx_str_t addr_text;

int type;

/* TCP实现监听时的backlog队列,它表示允许正在通过三次握手建立TCP连接但还没有任何进程开始处理的连接的最大个数 */
int backlog;
/* 内核中对该套接字的接收缓冲区大小 */
int rcvbuf;
/* 内核中对该套接字的发送缓冲区大小 */
int sndbuf;
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
int keepidle;
int keepintvl;
int keepcnt;
#endif

/* handler of accepted connection */
/* 当新的TCP连接成功建立后的处理方法 */
ngx_connection_handler_pt handler;

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

/* log和kogp都是可用的日志对象的指针 */
ngx_log_t log;
ngx_log_t *logp;

/* 如果为新的TCP连接创建内存池,则内存池从初始大小 */
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 */
/* TCP_DEFER_ACCEPT选项将在建立TCP连接成功并且接受到用户请求数据后,才向对监听套接字感兴趣的进程发送事件通知,而连接成功后,如果post_accept_timeout秒后仍未收到用户数据,则内核直接丢弃连接 */
ngx_msec_t post_accept_timeout;

/* 前一个ngx_listening_t的指针,多个ngx_listening_t结构体之间由previous指针组成单链表 */
ngx_listening_t *previous;

/* 当前监听句柄对应着的ngx_connection_t结构体,为一个单链表 */
ngx_connection_t *connection;

ngx_rbtree_t rbtree;
ngx_rbtree_node_t sentinel;

ngx_uint_t worker;

/* 标志位,为1表示当前监听句柄有效,且执行ngx_init_cycle时不关闭监听端口,为0正常关闭。该标志由架构代码自动设置 */
unsigned open:1;
/* 为1表示已有ngx_cycle_t来初始化新的ngx_cycle_t结构体时,不关闭原先打开的监听端口,对运行中升级程序很有效。为0时,表示正常关闭曾经打开的监听端口。该标志由架构代码自动设置 */
unsigned remain:1;
/* 1表示跳过设置当前ngx_listening_t结构体中套接字,0正常初始化套接字。 该标志由架构代码自动设置*/
unsigned ignore:1;

unsigned bound:1; /* already bound */
/* 当前监听句柄是否来着于前一个进程 */
unsigned inherited:1; /* inherited from previous process */
unsigned nonblocking_accept:1;
/* (书中说是当前套接字是否已监听),看代码似乎是说要重新执行listing函数来设置监听的第二个参数 */
unsigned listen:1;
/* 套接字是否阻塞,目前无意义 */
unsigned nonblocking:1;
unsigned shared:1; /* shared between threads or processes */

/* 1时nginx将网络地址转变为字符串形式地址 */
unsigned addr_ntop:1;
unsigned wildcard:1;

#if (NGX_HAVE_INET6)
unsigned ipv6only:1;
#endif
unsigned reuseport:1;
unsigned add_reuseport:1;
unsigned keepalive:2;

unsigned deferred_accept:1;
unsigned delete_deferred:1;
unsigned add_deferred:1;
#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
char *accept_filter;
#endif
#if (NGX_HAVE_SETFIB)
int setfib;
#endif

#if (NGX_HAVE_TCP_FASTOPEN)
int fastopen;
#endif

};

ngx_connection_handler_pt类型的handler成员表示在这个监听端口上成功建立新的tcp连接后,就会回调handler方法,其定义为:

1
typedef void (*ngx_connection_handler_pt)(ngx_connect_t *c);

ngx_cycle_t结构体

首先来介绍一下ngx_cycle_t中的成员(其中connectins、read_events、write_events、files、free_connection成员与事件模块强相关,在事件模块中详细介绍)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
struct ngx_cycle_s {
/* 保存着所有模块(modules)存储配置项的结构体的指针。其首先是一个数组,每个数组成员又是一个指针,这个指针指向另一个存储着指针的数组 */
void ****conf_ctx;

/* 内存池 */
ngx_pool_t *pool;
/* 在未执行ngx_init_cycle方法前,未确定日志输出位置时,暂时使用log,将内容输出到屏幕上,在执行了ngx_init_cycle方法后,确定日志输出位置时,会替换该值 */
ngx_log_t *log;
/* 由nginx.conf配置文件读取到日志后,将开始初始化error_log日志文件,但log依然在向屏幕输出日志。这时会使用new_log对象暂时替代log日志,待初始化成功,使用new_log的地址替换log指针 */
ngx_log_t new_log;

ngx_uint_t log_use_stderr; /* unsigned log_use_stderr:1; */

/* 对于poll、rtsig的事件模块,会以有效文件句柄来预先建立这些ngx_connection_t结构体,以加速事件的收集分发,此时file保存了ngx_connection_t的指针数组 */
ngx_connection_t **files;

/* 可用连接池,与free_connection_n配合使用 */
ngx_connection_t *free_connections;
/* 可用连接池中连接总数 */
ngx_uint_t free_connection_n;

ngx_module_t **modules;
ngx_uint_t modules_n;
ngx_uint_t modules_used; /* unsigned modules_used:1; */

/* 双向链表容器,元素类型为ngx_connection_t,表示可重复使用连接队列 */
ngx_queue_t reusable_connections_queue;
ngx_uint_t reusable_connections_n;

/* 动态数组,每个数组元素中存储着ngx_listening_t成员,表示监听端口及相关参数 */
ngx_array_t listening;

/* 动态数组容器,其存储着Nginx所有要操作的目录。如果目录不存在则会试图创建,创建失败将会导致nginx启动失败.比如配置了client_body_temp_path,此时就需要创建一个路径临时存放请求用户http包体*/
ngx_array_t paths;

ngx_array_t config_dump;
ngx_rbtree_t config_dump_rbtree;
ngx_rbtree_node_t config_dump_sentinel;

/* 单链表容器,元素类型为ngx_open_file_t结构体,其表示nginx已经打开的所有文件 */
ngx_list_t open_files;

/* 单链表容器,元素类型为ngx_shm_zone_y,每一个元素表示一块共享内存 */
ngx_list_t shared_memory;

/* 当前进程中连接总数 */
ngx_uint_t connection_n;
ngx_uint_t files_n;

/* 指向当前进程中所有链接对象,与connection_n配合使用 */
ngx_connection_t *connections;
/* 指向当前进程中所有读事件 connection_n同时表示读事件总数*/
ngx_event_t *read_events;
/* 指向当前进程中所有写事件 connection_n同时表示写事件总数*/
ngx_event_t *write_events;

/* 旧的ngx_cycle_t对象引用上一个ngx_cycle_t对象的成员。在调用ngx_init_cycle方法前会建立一个临时ngx_cycle_t对象保存一些变量,再调用ngx_init_cycle方法时将旧的ngx_cycle_t传递进去,此时old_cycle将保存旧的ngx_cycle_t */
ngx_cycle_t *old_cycle;

/* 配置文件相对按照目录的路径名 */
ngx_str_t conf_file;
/* nginx处理配置文件时需要特殊处理的在命令行携带的参数,一般是-g选项携带的参数 */
ngx_str_t conf_param;
/* nginx配置文件所在目录的路径 */
ngx_str_t conf_prefix;
/* nginx安装目录 */
ngx_str_t prefix;
/* 用于进程间同步的锁的名称 */
ngx_str_t lock_file;
/* gethostname获取到的主机名 */
ngx_str_t hostname;
};

ngx_cycle_t支持的方法

每个模块都可以通过init_module、init_process、exit_process、exit_master等方法操作进程的单独的ngx_cycle_t结构体。nginx框架关于ngx_cycle_t结构体方法如下:

方法名 参数含义 执行含义
ngx_cycle_t ngx_init_cycle(ngx_cycle_t old_cycle) old_cycle表示临时的ngx_cycle_t指针,一般用来传递配置文件路径等参数。 返回初始化完成的结构体,该函数将会负责初始化ngx_cycle_t中的数据结构、解析配置文件、加载所有模块、打开监听端口、初始化进程间通讯方式等工作。失败返回null。
ngx_init_t ngx_process_options(ngx_cycle_t cycle) 与上一个参数一致 用运行Nginx时可能携带的目录参数来初始化cycle,包括初始化运行目录、配置目录,并生成完整的nginx.conf配置文件路径
ngx_init_t ngx_add_inherited_sockets(ngx_cycle_t cycle) cycle是当前进程的ngx_cycle_t结构体指针 在不重启服务器升级时,老的nginx进程会通过环境变量NGINX来传递需要打开的监听端口,新的nginx进程会通过ngx_add_ingerited_sockets方法来使用已经打开的TCP监听端口
ngx_int_t ngx_open_listening_sockets(ngx_cycle_t *cycle) cycle是当前进程的ngx_cycle_t结构体指针 监听、绑定cycle中listening动态数组指定的相应端口
void ngx_configure_listening_sockets(ngx_cycle_t cycle) cycle是当前进程的ngx_cycle_t结构体指针 根据nginx.conf中的配置项设置已经监听的句柄
void ngx_close_listening_sockets(ngx_cycle_t *cycle) cycle是当前进程的ngx_cycle_t结构体指针 关闭cycle中listening动态数组中已经监听的句柄
void ngx_master_process_cycle(ngx_cycle_t cycle) cycle是当前进程的ngx_cycle_t结构体指针 进入master进程主循环
void ngx_master_single_cycle(ngx_cycle_t *cycle) cycle是当前进程的ngx_cycle_t结构体指针 进入单进程模式的工作循环
void ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type) cycle是当前进程的ngx_cycle_t结构体指针,n是启动进程数量,type是启动方式,其取值为如下5个;1)NGX_PROCESS_RESPAWN;2)NGX_PROCESS_NORESPAWN;3)NGX_PROCESS_JUST_SPAWN;4)NGX_PROCESS_JUST_RESPAWN;5)NGX_PROCESS_DEFACHED。type值影响ngx_process_t中respawn,detached,just_spawn标志位值 启动n个work子进程,并设置好每个子进程与父进程之间使用socketpair系统调用建立起来的socket句柄通信机制
void ngx_start_cache_manger_processes(ngx_cycle_t *cycle, ngx_nint_t respawn) cycle是当前进程的ngx_cycle_t结构体指针,respawn与ngx_start_worker_processes的type一致 根据是否使用文件缓存模块,即cycle中存储路径的动态数组中是否有路径的manage标志打开,来决定是否启动cache manage子进程,根据loader标志位来决定是否启动cache loader子进程
void ngx_pass_open_channel(ngx_cycle_t cycle, ngx_channel_t ch) cycle是当前进程的ngx_cycle_t结构体指针,ch是将要发送的信息 向所有已经打开的channel(通过socketpair生成的句柄进行通信)发送ch信息
void ngx_single_worker_processes(ngx_cycle_t *cycle, int signo) cycle是当前进程的ngx_cycle_t结构体指针,signo是信号 处理worker进程接受到的信号
ngx_uint_t ngx_reap_children(ngx_cycle_t *cycle) cycle是当前进程的ngx_cycle_t结构体指针 检查master进程的所有子进程,根据每个子进程的状态(ngx_process_t结构体中标志位)判断是否要启动子进程、更改pid文件等
void ngx_master_process_exit(ngx_cycle_t *cycle) cycle是当前进程的ngx_cycle_t结构体指针 退出master进程主循环
void ngx_work_process_cycle(ngx_cycle_t cycle, void data) cycle是当前进程的ngx_cycle_t结构体指针,data目前还未使用,null 进入worker进程主循环
void ngx_work_process_init(ngx_cycle_t *cycle, ngx_uint_t priority) cycle是当前进程的ngx_cycle_t结构体指针,priority是当前worker进程的优先级 进入worker进程主循环之前的初始化工作
void ngx_work_process_exit(ngx_cycle_t *cycle) cycle是当前进程的ngx_cycle_t结构体指针 退出worker进程主循环
void ngx_cache_manager_process_cycle(ngx_cycle_t cycle, void data) cycle是当前进程的ngx_cycle_t结构体指针,data是传入的ngx_cache_manager_ctx_t结构体指针 执行缓存管理工作的循环方法。
void ngx_process_events_and_timers(ngx_cycle_t *cycle) cycle是当前进程的ngx_cycle_t结构体指针 使用事件管理模块处理截止到现在已经收集到的事件

nginx启动时框架处理流程

21Zga9.png

图8-6

nginx启动过程详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
int ngx_cdecl
main(int argc, char *const *argv)
{
ngx_buf_t *b;
ngx_log_t *log;
ngx_uint_t i;
ngx_cycle_t *cycle, init_cycle;
ngx_conf_dump_t *cd;
ngx_core_conf_t *ccf;

ngx_debug_init();

if (ngx_strerror_init() != NGX_OK) {
return 1;
}

if (ngx_get_options(argc, argv) != NGX_OK) {
return 1;
}

if (ngx_show_version) {
ngx_show_version_info();

if (!ngx_test_config) {
return 0;
}
}

/* TODO */ ngx_max_sockets = -1;

ngx_time_init();

#if (NGX_PCRE)
ngx_regex_init();
#endif
// 获取进程id和父进程id
ngx_pid = ngx_getpid();
ngx_parent = ngx_getppid();

log = ngx_log_init(ngx_prefix);
if (log == NULL) {
return 1;
}

/* STUB */
#if (NGX_OPENSSL)
ngx_ssl_init(log);
#endif

/*
* init_cycle->log is required for signal handlers and
* ngx_process_options()
*/

ngx_memzero(&init_cycle, sizeof(ngx_cycle_t));
init_cycle.log = log;
ngx_cycle = &init_cycle;

init_cycle.pool = ngx_create_pool(1024, log);
if (init_cycle.pool == NULL) {
return 1;
}

if (ngx_save_argv(&init_cycle, argc, argv) != NGX_OK) {
return 1;
}

if (ngx_process_options(&init_cycle) != NGX_OK) {
return 1;
}

if (ngx_os_init(log) != NGX_OK) {
return 1;
}

/*
* ngx_crc32_table_init() requires ngx_cacheline_size set in ngx_os_init()
*/

if (ngx_crc32_table_init() != NGX_OK) {
return 1;
}

/*
* ngx_slab_sizes_init() requires ngx_pagesize set in ngx_os_init()
*/

ngx_slab_sizes_init();

if (ngx_add_inherited_sockets(&init_cycle) != NGX_OK) {
return 1;
}

if (ngx_preinit_modules() != NGX_OK) {
return 1;
}

cycle = ngx_init_cycle(&init_cycle);
if (cycle == NULL) {
if (ngx_test_config) {
ngx_log_stderr(0, "configuration file %s test failed",
init_cycle.conf_file.data);
}

return 1;
}

if (ngx_test_config) {
if (!ngx_quiet_mode) {
ngx_log_stderr(0, "configuration file %s test is successful",
cycle->conf_file.data);
}

if (ngx_dump_config) {
cd = cycle->config_dump.elts;

for (i = 0; i < cycle->config_dump.nelts; i++) {

ngx_write_stdout("# configuration file ");
(void) ngx_write_fd(ngx_stdout, cd[i].name.data,
cd[i].name.len);
ngx_write_stdout(":" NGX_LINEFEED);

b = cd[i].buffer;

(void) ngx_write_fd(ngx_stdout, b->pos, b->last - b->pos);
ngx_write_stdout(NGX_LINEFEED);
}
}

return 0;
}

if (ngx_signal) {
return ngx_signal_process(cycle, ngx_signal);
}

ngx_os_status(cycle->log);

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;
}

#if !(NGX_WIN32)

if (ngx_init_signals(cycle->log) != NGX_OK) {
return 1;
}

if (!ngx_inherited && ccf->daemon) {
if (ngx_daemon(cycle->log) != NGX_OK) {
return 1;
}

ngx_daemonized = 1;
}

if (ngx_inherited) {
ngx_daemonized = 1;
}

#endif

if (ngx_create_pidfile(&ccf->pid, cycle->log) != NGX_OK) {
return 1;
}

if (ngx_log_redirect_stderr(cycle) != NGX_OK) {
return 1;
}

if (log->file->fd != ngx_stderr) {
if (ngx_close_file(log->file->fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
ngx_close_file_n " built-in log failed");
}
}

ngx_use_stderr = 0;

if (ngx_process == NGX_PROCESS_SINGLE) {
ngx_single_process_cycle(cycle);

} else {
ngx_master_process_cycle(cycle);
}

return 0;
}

解析启动参数

1
2
3
if (ngx_get_options(argc, argv) != NGX_OK) {
return 1;
}

逐字符解析启动请求参数,根据解析参数设置全局变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
static ngx_int_t
ngx_get_options(int argc, char *const *argv)
{
u_char *p;
ngx_int_t i;

for (i = 1; i < argc; i++) {

p = (u_char *) argv[i];
// 启动参数,必须使用-开始
if (*p++ != '-') {
ngx_log_stderr(0, "invalid option: \"%s\"", argv[i]);
return NGX_ERROR;
}

while (*p) {
// 检查参数
switch (*p++) {

case '?':
case 'h':
// 打印help信息标识
ngx_show_version = 1;
ngx_show_help = 1;
break;

case 'v':
// 打印版本标识
ngx_show_version = 1;
break;

case 'V':
// 打印版本标识和显示配置编译阶段信息
ngx_show_version = 1;
ngx_show_configure = 1;
break;

case 't':
// 测试配置正确性标识
ngx_test_config = 1;
break;

case 'T':
// 测速配置正确性标识,dump配置并输出标识
ngx_test_config = 1;
ngx_dump_config = 1;
break;

case 'q':
// 测试配置选项时,使用-q使得error级别以下的信息不输出
ngx_quiet_mode = 1;
break;

case 'p':
// 另行指定nginx安装目录。决定了,error日志,conf配置和pid文件存储的默认位置
if (*p) {
// 使用ngx_prefix存储值,-p直接接目录,如 -p/home/work/nginx
ngx_prefix = p;
goto next;
}
// -p /home/work/nginx
if (argv[++i]) {
ngx_prefix = (u_char *) argv[i];
goto next;
}

ngx_log_stderr(0, "option \"-p\" requires directory name");
return NGX_ERROR;

case 'c':
// 配置文件目录,使用ngx_conf_file存储
if (*p) {
ngx_conf_file = p;
goto next;
}

if (argv[++i]) {
ngx_conf_file = (u_char *) argv[i];
goto next;
}

ngx_log_stderr(0, "option \"-c\" requires file name");
return NGX_ERROR;

case 'g':
// 指定一些全局变量,使用ngx_conf_params存储,后续会被与解析配置方式一样进行解析,使用ngx_conf_params存储
if (*p) {
ngx_conf_params = p;
goto next;
}

if (argv[++i]) {
ngx_conf_params = (u_char *) argv[i];
goto next;
}

ngx_log_stderr(0, "option \"-g\" requires parameter");
return NGX_ERROR;

case 's':
// 发送信号,使用ngx_signal存储
if (*p) {
ngx_signal = (char *) p;

} else if (argv[++i]) {
ngx_signal = argv[i];

} else {
ngx_log_stderr(0, "option \"-s\" requires parameter");
return NGX_ERROR;
}

if (ngx_strcmp(ngx_signal, "stop") == 0
|| ngx_strcmp(ngx_signal, "quit") == 0
|| ngx_strcmp(ngx_signal, "reopen") == 0
|| ngx_strcmp(ngx_signal, "reload") == 0)
{
// 如果是上述四种之一,设置ngx_process为NGX_PROCESS_SIGNALLER
ngx_process = NGX_PROCESS_SIGNALLER;
goto next;
}

ngx_log_stderr(0, "invalid option: \"-s %s\"", ngx_signal);
return NGX_ERROR;

default:
ngx_log_stderr(0, "invalid option: \"%c\"", *(p - 1));
return NGX_ERROR;
}
}

next:

continue;
}

return NGX_OK;
}

初始化信息

1
ngx_time_init();

详见事件处理部分。

初始化log打印描述符

1
log = ngx_log_init(ngx_prefix);

更加启动参数或默认log路径,初始化log信息,包括描述符和等级等信息。

申请内存池空间Pool

1
2
3
4
5
ngx_memzero(&init_cycle, sizeof(ngx_cycle_t));
init_cycle.log = log;
ngx_cycle = &init_cycle;

init_cycle.pool = ngx_create_pool(1024, log);

存储命令行参数

1
2
3
if (ngx_save_argv(&init_cycle, argc, argv) != NGX_OK) {
return 1;
}

将命令行存储到全国变量中:

1
2
ngx_argv
ngx_argc

设置相关路径

1
2
3
if (ngx_process_options(&init_cycle) != NGX_OK) {
return 1;
}

通过启动命令行参数或默认值设置cycle中的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
static ngx_int_t
ngx_process_options(ngx_cycle_t *cycle)
{
u_char *p;
size_t len;
// 如果存在-p参数,即设置了nginx环境目录
if (ngx_prefix) {
len = ngx_strlen(ngx_prefix);
p = ngx_prefix;
// 如果设置的目录最后不是/,则添加一个/
if (len && !ngx_path_separator(p[len - 1])) {
p = ngx_pnalloc(cycle->pool, len + 1);
if (p == NULL) {
return NGX_ERROR;
}

ngx_memcpy(p, ngx_prefix, len);
p[len++] = '/';
}
// 设置conf_prefix配置文件所在目录路径和prefix安装路径
cycle->conf_prefix.len = len;
cycle->conf_prefix.data = p;
cycle->prefix.len = len;
cycle->prefix.data = p;

} else {
// 如果未设置,则使用默认路径

#ifndef NGX_PREFIX

p = ngx_pnalloc(cycle->pool, NGX_MAX_PATH);
if (p == NULL) {
return NGX_ERROR;
}

if (ngx_getcwd(p, NGX_MAX_PATH) == 0) {
ngx_log_stderr(ngx_errno, "[emerg]: " ngx_getcwd_n " failed");
return NGX_ERROR;
}

len = ngx_strlen(p);

p[len++] = '/';

cycle->conf_prefix.len = len;
cycle->conf_prefix.data = p;
cycle->prefix.len = len;
cycle->prefix.data = p;

#else

#ifdef NGX_CONF_PREFIX
// 设置conf_prefix为conf/,为相对于prefix路径
ngx_str_set(&cycle->conf_prefix, NGX_CONF_PREFIX);
#else
ngx_str_set(&cycle->conf_prefix, NGX_PREFIX);
#endif
// /usr/local/nginx/
ngx_str_set(&cycle->prefix, NGX_PREFIX);

#endif
}
// 如果-c指定了配置文件,则设置为对应文件
if (ngx_conf_file) {
cycle->conf_file.len = ngx_strlen(ngx_conf_file);
cycle->conf_file.data = ngx_conf_file;

} else {
// 否则设置为相对与prefix的路径conf/nginx.conf
ngx_str_set(&cycle->conf_file, NGX_CONF_PATH);
}
// 根据prefix、和conf_file二者确认实际配置文件路径
if (ngx_conf_full_name(cycle, &cycle->conf_file, 0) != NGX_OK) {
return NGX_ERROR;
}
// 根据conf_file设置conf_prefix
for (p = cycle->conf_file.data + cycle->conf_file.len - 1;
p > cycle->conf_file.data;
p--)
{
if (ngx_path_separator(*p)) {
cycle->conf_prefix.len = p - cycle->conf_file.data + 1;
cycle->conf_prefix.data = cycle->conf_file.data;
break;
}
}
// 如果使用-g指定了额外参数,使用cycle->conf_param存储
if (ngx_conf_params) {
cycle->conf_param.len = ngx_strlen(ngx_conf_params);
cycle->conf_param.data = ngx_conf_params;
}

if (ngx_test_config) {
cycle->log->log_level = NGX_LOG_INFO;
}

return NGX_OK;
}

初始化系统相关全局变量

1
2
3
if (ngx_os_init(log) != NGX_OK) {
return 1;
}

待详细查看。目前看包括如下数据:

1
2
3
4
5
6
ngx_pagesize = getpagesize(); // 内存分页大小
ngx_cacheline_size = NGX_CPU_CACHE_LINE; // 缓存行的大小
ngx_ncpu = sysconf(_SC_NPROCESSORS_ONLN); // cpu数据

getrlimit(RLIMIT_NOFILE, &rlmt);
ngx_max_sockets = (ngx_int_t) rlmt.rlim_cur; // 最大sockets链接数量

初始化差错校验

1
2
3
if (ngx_crc32_table_init() != NGX_OK) {
return 1;
}

nginx使用CRC:循环冗余检测(Cycle Redundancy Check)来进行差错校验。

初始化slab共享内存

1
ngx_slab_sizes_init();

具体详见slab共享内存。

监听环境变量中的端口

1
2
3
if (ngx_add_inherited_sockets(&init_cycle) != NGX_OK) {
return 1;
}

对于平滑升级来说,需要保证用户无感知,因此要将原本开发监听的套接字存放于环境变量,而后,由新启动的进程读取,重新进行监听。

首选从环境变量NGINX中读取到套接字:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
static ngx_int_t
ngx_add_inherited_sockets(ngx_cycle_t *cycle)
{
u_char *p, *v, *inherited;
ngx_int_t s;
ngx_listening_t *ls;
// 首选从环境变量NGINX中读取到套接字:
inherited = (u_char *) getenv(NGINX_VAR);

if (inherited == NULL) {
return NGX_OK;
}

ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
"using inherited sockets from \"%s\"", inherited);

if (ngx_array_init(&cycle->listening, cycle->pool, 10,
sizeof(ngx_listening_t))
!= NGX_OK)
{
return NGX_ERROR;
}

for (p = inherited, v = p; *p; p++) {
if (*p == ':' || *p == ';') {
s = ngx_atoi(v, p - v);
if (s == NGX_ERROR) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
"invalid socket number \"%s\" in " NGINX_VAR
" environment variable, ignoring the rest"
" of the variable", v);
break;
}

v = p + 1;
// 将环境变量里的套接字添加到cyyle的listening中
ls = ngx_array_push(&cycle->listening);
if (ls == NULL) {
return NGX_ERROR;
}

ngx_memzero(ls, sizeof(ngx_listening_t));

ls->fd = (ngx_socket_t) s;
}
}

if (v != p) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
"invalid socket number \"%s\" in " NGINX_VAR
" environment variable, ignoring", v);
}

ngx_inherited = 1;

return ngx_set_inherited_sockets(cycle);
}

获取套接字对应的信息ngx_set_inherited_sockets:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
getsockname(ls[i].fd, ls[i].sockaddr, &ls[i].socklen); // 获取套接字的sockaddr
getsockopt; // 获取套接字上设置的额外信息

ngx_int_t
ngx_set_inherited_sockets(ngx_cycle_t *cycle)
{
size_t len;
ngx_uint_t i;
ngx_listening_t *ls;
socklen_t olen;
#if (NGX_HAVE_DEFERRED_ACCEPT || NGX_HAVE_TCP_FASTOPEN)
ngx_err_t err;
#endif
#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
struct accept_filter_arg af;
#endif
#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
int timeout;
#endif
#if (NGX_HAVE_REUSEPORT)
int reuseport;
#endif
// 当前listening中的套接字均为从环境变量中继承而来,遍历每一个套接字,获取其对应的ngx_listening_t结构中字段信息
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
// 分配套接字地址
ls[i].sockaddr = ngx_palloc(cycle->pool, sizeof(ngx_sockaddr_t));
if (ls[i].sockaddr == NULL) {
return NGX_ERROR;
}
// 使用getsockname函数获取套接字地址,如果获取失败,则ignore置1,表示忽略该套接字。
ls[i].socklen = sizeof(ngx_sockaddr_t);
if (getsockname(ls[i].fd, ls[i].sockaddr, &ls[i].socklen) == -1) {
ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_socket_errno,
"getsockname() of the inherited "
"socket #%d failed", ls[i].fd);
ls[i].ignore = 1;
continue;
}

if (ls[i].socklen > (socklen_t) sizeof(ngx_sockaddr_t)) {
ls[i].socklen = sizeof(ngx_sockaddr_t);
}
// 根据套接字族类型,设置addr_text_max_len大小,即用于存储addr_text字段的空间大小
switch (ls[i].sockaddr->sa_family) {

#if (NGX_HAVE_INET6)
case AF_INET6:
ls[i].addr_text_max_len = NGX_INET6_ADDRSTRLEN;
len = NGX_INET6_ADDRSTRLEN + sizeof("[]:65535") - 1;
break;
#endif

#if (NGX_HAVE_UNIX_DOMAIN)
case AF_UNIX:
ls[i].addr_text_max_len = NGX_UNIX_ADDRSTRLEN;
len = NGX_UNIX_ADDRSTRLEN;
break;
#endif

case AF_INET:
ls[i].addr_text_max_len = NGX_INET_ADDRSTRLEN;
len = NGX_INET_ADDRSTRLEN + sizeof(":65535") - 1;
break;

default:
ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_socket_errno,
"the inherited socket #%d has "
"an unsupported protocol family", ls[i].fd);
ls[i].ignore = 1;
continue;
}

ls[i].addr_text.data = ngx_pnalloc(cycle->pool, len);
if (ls[i].addr_text.data == NULL) {
return NGX_ERROR;
}
// 使用地址回填addr_text内容,IP:PORT
len = ngx_sock_ntop(ls[i].sockaddr, ls[i].socklen,
ls[i].addr_text.data, len, 1);
if (len == 0) {
return NGX_ERROR;
}

ls[i].addr_text.len = len;
// 设置默认的backlog
ls[i].backlog = NGX_LISTEN_BACKLOG;

olen = sizeof(int);
// 使用getsockopt获取套接字类型吗,如果失败,则ignore置1
if (getsockopt(ls[i].fd, SOL_SOCKET, SO_TYPE, (void *) &ls[i].type,
&olen)
== -1)
{
ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_socket_errno,
"getsockopt(SO_TYPE) %V failed", &ls[i].addr_text);
ls[i].ignore = 1;
continue;
}

olen = sizeof(int);
// 使用getsockopt获取套接字rcvbuf,如果没有设置,则默认值-1,后续内容一样,都是通过getsockopt获取套接字的其他特性
if (getsockopt(ls[i].fd, SOL_SOCKET, SO_RCVBUF, (void *) &ls[i].rcvbuf,
&olen)
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"getsockopt(SO_RCVBUF) %V failed, ignored",
&ls[i].addr_text);

ls[i].rcvbuf = -1;
}

olen = sizeof(int);

if (getsockopt(ls[i].fd, SOL_SOCKET, SO_SNDBUF, (void *) &ls[i].sndbuf,
&olen)
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"getsockopt(SO_SNDBUF) %V failed, ignored",
&ls[i].addr_text);

ls[i].sndbuf = -1;
}

#if 0
/* SO_SETFIB is currently a set only option */

#if (NGX_HAVE_SETFIB)

olen = sizeof(int);

if (getsockopt(ls[i].fd, SOL_SOCKET, SO_SETFIB,
(void *) &ls[i].setfib, &olen)
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"getsockopt(SO_SETFIB) %V failed, ignored",
&ls[i].addr_text);

ls[i].setfib = -1;
}

#endif
#endif

#if (NGX_HAVE_REUSEPORT)

reuseport = 0;
olen = sizeof(int);

#ifdef SO_REUSEPORT_LB

if (getsockopt(ls[i].fd, SOL_SOCKET, SO_REUSEPORT_LB,
(void *) &reuseport, &olen)
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"getsockopt(SO_REUSEPORT_LB) %V failed, ignored",
&ls[i].addr_text);

} else {
ls[i].reuseport = reuseport ? 1 : 0;
}

#else

if (getsockopt(ls[i].fd, SOL_SOCKET, SO_REUSEPORT,
(void *) &reuseport, &olen)
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"getsockopt(SO_REUSEPORT) %V failed, ignored",
&ls[i].addr_text);

} else {
ls[i].reuseport = reuseport ? 1 : 0;
}
#endif

#endif

if (ls[i].type != SOCK_STREAM) {
continue;
}

#if (NGX_HAVE_TCP_FASTOPEN)

olen = sizeof(int);

if (getsockopt(ls[i].fd, IPPROTO_TCP, TCP_FASTOPEN,
(void *) &ls[i].fastopen, &olen)
== -1)
{
err = ngx_socket_errno;

if (err != NGX_EOPNOTSUPP && err != NGX_ENOPROTOOPT
&& err != NGX_EINVAL)
{
ngx_log_error(NGX_LOG_NOTICE, cycle->log, err,
"getsockopt(TCP_FASTOPEN) %V failed, ignored",
&ls[i].addr_text);
}

ls[i].fastopen = -1;
}

#endif

#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)

ngx_memzero(&af, sizeof(struct accept_filter_arg));
olen = sizeof(struct accept_filter_arg);

if (getsockopt(ls[i].fd, SOL_SOCKET, SO_ACCEPTFILTER, &af, &olen)
== -1)
{
err = ngx_socket_errno;

if (err == NGX_EINVAL) {
continue;
}

ngx_log_error(NGX_LOG_NOTICE, cycle->log, err,
"getsockopt(SO_ACCEPTFILTER) for %V failed, ignored",
&ls[i].addr_text);
continue;
}

if (olen < sizeof(struct accept_filter_arg) || af.af_name[0] == '\0') {
continue;
}

ls[i].accept_filter = ngx_palloc(cycle->pool, 16);
if (ls[i].accept_filter == NULL) {
return NGX_ERROR;
}

(void) ngx_cpystrn((u_char *) ls[i].accept_filter,
(u_char *) af.af_name, 16);
#endif

#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)

timeout = 0;
olen = sizeof(int);

if (getsockopt(ls[i].fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &timeout, &olen)
== -1)
{
err = ngx_socket_errno;

if (err == NGX_EOPNOTSUPP) {
continue;
}

ngx_log_error(NGX_LOG_NOTICE, cycle->log, err,
"getsockopt(TCP_DEFER_ACCEPT) for %V failed, ignored",
&ls[i].addr_text);
continue;
}

if (olen < sizeof(int) || timeout == 0) {
continue;
}

ls[i].deferred_accept = 1;
#endif
}

return NGX_OK;
}

预初始化模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if (ngx_preinit_modules() != NGX_OK) {
return 1;
}

ngx_int_t
ngx_preinit_modules(void)
{
ngx_uint_t i;

for (i = 0; ngx_modules[i]; i++) {
// 设置每个模块在所有模块中的index
ngx_modules[i]->index = i;
ngx_modules[i]->name = ngx_module_names[i];
}

ngx_modules_n = i;
ngx_max_module = ngx_modules_n + NGX_MAX_DYNAMIC_MODULES;

return NGX_OK;
}

初始化cycle

1
2
3
4
5
6
7
8
9
cycle = ngx_init_cycle(&init_cycle);
if (cycle == NULL) {
if (ngx_test_config) {
ngx_log_stderr(0, "configuration file %s test failed",
init_cycle.conf_file.data);
}

return 1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
/* 这里参数ngx_cycle_t被视为old cycle,一方面在不终止服务重新加载配置时会执行该操作,此时会传递一个old的cycle,另一方面,在一个新启动的服务来说,根据之前的操作,已经加载了相应的cycle参数,这里会继承上述获取到的部分参数,并进行升级合并 */
ngx_cycle_t *
ngx_init_cycle(ngx_cycle_t *old_cycle)
{
void *rv;
char **senv;
ngx_uint_t i, n;
ngx_log_t *log;
ngx_time_t *tp;
ngx_conf_t conf;
ngx_pool_t *pool;
ngx_cycle_t *cycle, **old;
ngx_shm_zone_t *shm_zone, *oshm_zone;
ngx_list_part_t *part, *opart;
ngx_open_file_t *file;
ngx_listening_t *ls, *nls;
ngx_core_conf_t *ccf, *old_ccf;
ngx_core_module_t *module;
char hostname[NGX_MAXHOSTNAMELEN];
// 读取环境变量的TZ,获取时区
ngx_timezone_update();

/* force localtime update with a new timezone */

tp = ngx_timeofday();
tp->sec = 0;

ngx_time_update();


log = old_cycle->log;

pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log);
if (pool == NULL) {
return NULL;
}
pool->log = log;
// 创建一个新的ngx_cycle_t
cycle = ngx_pcalloc(pool, sizeof(ngx_cycle_t));
if (cycle == NULL) {
ngx_destroy_pool(pool);
return NULL;
}

cycle->pool = pool;
cycle->log = log;
// 存储老的cycle
cycle->old_cycle = old_cycle;
// 继承老的cycle中conf_prefix、prefix、conf_file、conf_param信息
cycle->conf_prefix.len = old_cycle->conf_prefix.len;
cycle->conf_prefix.data = ngx_pstrdup(pool, &old_cycle->conf_prefix);
if (cycle->conf_prefix.data == NULL) {
ngx_destroy_pool(pool);
return NULL;
}

cycle->prefix.len = old_cycle->prefix.len;
cycle->prefix.data = ngx_pstrdup(pool, &old_cycle->prefix);
if (cycle->prefix.data == NULL) {
ngx_destroy_pool(pool);
return NULL;
}

cycle->conf_file.len = old_cycle->conf_file.len;
cycle->conf_file.data = ngx_pnalloc(pool, old_cycle->conf_file.len + 1);
if (cycle->conf_file.data == NULL) {
ngx_destroy_pool(pool);
return NULL;
}
ngx_cpystrn(cycle->conf_file.data, old_cycle->conf_file.data,
old_cycle->conf_file.len + 1);

cycle->conf_param.len = old_cycle->conf_param.len;
cycle->conf_param.data = ngx_pstrdup(pool, &old_cycle->conf_param);
if (cycle->conf_param.data == NULL) {
ngx_destroy_pool(pool);
return NULL;
}

// 根据旧的path(管理路径列表)分配相应的新的cycle中paths的内存
n = old_cycle->paths.nelts ? old_cycle->paths.nelts : 10;

if (ngx_array_init(&cycle->paths, pool, n, sizeof(ngx_path_t *))
!= NGX_OK)
{
ngx_destroy_pool(pool);
return NULL;
}

ngx_memzero(cycle->paths.elts, n * sizeof(ngx_path_t *));

// 初始化config_dump数组
if (ngx_array_init(&cycle->config_dump, pool, 1, sizeof(ngx_conf_dump_t))
!= NGX_OK)
{
ngx_destroy_pool(pool);
return NULL;
}
// 初始化config_dump_rbtree红黑树,具体参看红黑树部分解析
ngx_rbtree_init(&cycle->config_dump_rbtree, &cycle->config_dump_sentinel,
ngx_str_rbtree_insert_value);
// 根据旧的open_files(打开文件列表)分配相应的新的cycle中open_files的内存
if (old_cycle->open_files.part.nelts) {
n = old_cycle->open_files.part.nelts;
for (part = old_cycle->open_files.part.next; part; part = part->next) {
n += part->nelts;
}

} else {
n = 20;
}

if (ngx_list_init(&cycle->open_files, pool, n, sizeof(ngx_open_file_t))
!= NGX_OK)
{
ngx_destroy_pool(pool);
return NULL;
}


// 根据旧的shared_memory(共享内存列表)分配相应的新的cycle中shared_memory的内存
if (old_cycle->shared_memory.part.nelts) {
n = old_cycle->shared_memory.part.nelts;
for (part = old_cycle->shared_memory.part.next; part; part = part->next)
{
n += part->nelts;
}

} else {
n = 1;
}

if (ngx_list_init(&cycle->shared_memory, pool, n, sizeof(ngx_shm_zone_t))
!= NGX_OK)
{
ngx_destroy_pool(pool);
return NULL;
}

// 根据旧的listening(监听地址列表)分配相应的新的cycle中listening的内存
n = old_cycle->listening.nelts ? old_cycle->listening.nelts : 10;

if (ngx_array_init(&cycle->listening, pool, n, sizeof(ngx_listening_t))
!= NGX_OK)
{
ngx_destroy_pool(pool);
return NULL;
}

ngx_memzero(cycle->listening.elts, n * sizeof(ngx_listening_t));

// 初始化用于事件模块的reusable_connections_queue,其值为双向队列。具体参考事件模块的处理
ngx_queue_init(&cycle->reusable_connections_queue);

// 分配配置文件解析内存
cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *));
if (cycle->conf_ctx == NULL) {
ngx_destroy_pool(pool);
return NULL;
}

// 根据gethostname获取机器的hostname
if (gethostname(hostname, NGX_MAXHOSTNAMELEN) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "gethostname() failed");
ngx_destroy_pool(pool);
return NULL;
}

/* on Linux gethostname() silently truncates name that does not fit */

hostname[NGX_MAXHOSTNAMELEN - 1] = '\0';
cycle->hostname.len = ngx_strlen(hostname);

cycle->hostname.data = ngx_pnalloc(pool, cycle->hostname.len);
if (cycle->hostname.data == NULL) {
ngx_destroy_pool(pool);
return NULL;
}

ngx_strlow(cycle->hostname.data, (u_char *) hostname, cycle->hostname.len);

// 复制全局变量ngx_modules(模块列表)到cycle的modules(深拷贝)
if (ngx_cycle_modules(cycle) != NGX_OK) {
ngx_destroy_pool(pool);
return NULL;
}

/* 执行每个核心模块的create_conf方法。由于核心模块管理者其所属下的各个模块。如ngx_http_module模块管理所有http模块。创建一个结构体,用于管理其下所有模块解析后的结果。具体可以参考配置解析*/
for (i = 0; cycle->modules[i]; i++) {
if (cycle->modules[i]->type != NGX_CORE_MODULE) {
continue;
}

module = cycle->modules[i]->ctx;

if (module->create_conf) {
rv = module->create_conf(cycle);
if (rv == NULL) {
ngx_destroy_pool(pool);
return NULL;
}
cycle->conf_ctx[cycle->modules[i]->index] = rv;
}
}


senv = environ;

// 配置解析,解析包括-g传递的参数,和配置文件
ngx_memzero(&conf, sizeof(ngx_conf_t));
/* STUB: init array ? */
conf.args = ngx_array_create(pool, 10, sizeof(ngx_str_t));
if (conf.args == NULL) {
ngx_destroy_pool(pool);
return NULL;
}

conf.temp_pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log);
if (conf.temp_pool == NULL) {
ngx_destroy_pool(pool);
return NULL;
}


conf.ctx = cycle->conf_ctx;
conf.cycle = cycle;
conf.pool = pool;
conf.log = log;
conf.module_type = NGX_CORE_MODULE;
conf.cmd_type = NGX_MAIN_CONF;

#if 0
log->log_level = NGX_LOG_DEBUG_ALL;
#endif
// 解析-g传递的参数
if (ngx_conf_param(&conf) != NGX_CONF_OK) {
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
// 解析配置文件
if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}

// 如果是测试配置文件正确性,正常打印error级别下错误
if (ngx_test_config && !ngx_quiet_mode) {
ngx_log_stderr(0, "the configuration file %s syntax is ok",
cycle->conf_file.data);
}

// 执行每个核心模块的init方法。用于将create_conf时创建的管理配置项的类中,未设置的成员,设置为默认值
for (i = 0; cycle->modules[i]; i++) {
if (cycle->modules[i]->type != NGX_CORE_MODULE) {
continue;
}

module = cycle->modules[i]->ctx;

if (module->init_conf) {
if (module->init_conf(cycle,
cycle->conf_ctx[cycle->modules[i]->index])
== NGX_CONF_ERROR)
{
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
}
}

// 如果是要给已存在服务发送信号,则结束处理。
if (ngx_process == NGX_PROCESS_SIGNALLER) {
return cycle;
}
// 获取核心模块ngx_core_module解析配置后生成的ngx_core_conf_t结构体
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
// 如果是测试配置文件
if (ngx_test_config) {
// 创建pid文件,如果是以单进程模式运行,即非master-worker方式,就不创建了
if (ngx_create_pidfile(&ccf->pid, log) != NGX_OK) {
goto failed;
}

} else if (!ngx_is_init_cycle(old_cycle)) {
// 如果不是新开始的一个服务(即已存在的服务调用init函数时),通过查看old_cycle是否存在ctx_conf来判断

/*
* we do not create the pid file in the first ngx_init_cycle() call
* because we need to write the demonized process pid
*/

old_ccf = (ngx_core_conf_t *) ngx_get_conf(old_cycle->conf_ctx,
ngx_core_module);
// 如果新的pid目录和当前的不一致,则创建一个最新的,并删除当前的
if (ccf->pid.len != old_ccf->pid.len
|| ngx_strcmp(ccf->pid.data, old_ccf->pid.data) != 0)
{
/* new pid file name */

if (ngx_create_pidfile(&ccf->pid, log) != NGX_OK) {
goto failed;
}

ngx_delete_pidfile(old_cycle);
}
}

/* 测试锁文件,即为了解决惊群现象,多个子进程见使用锁来实现负载均衡。如果系统不支持原子的锁机制,则使用文件锁的形式,这时需要创建一个文件。*/
if (ngx_test_lockfile(cycle->lock_file.data, log) != NGX_OK) {
goto failed;
}


// 创建管理路径,具体下面介绍
if (ngx_create_paths(cycle, ccf->user) != NGX_OK) {
goto failed;
}

// 打开日志文件,具体下面介绍
if (ngx_log_open_default(cycle) != NGX_OK) {
goto failed;
}

/* open the new files */
// 对于需要打开的文件,执行创建
part = &cycle->open_files.part;
file = part->elts;

for (i = 0; /* void */ ; i++) {

if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
file = part->elts;
i = 0;
}

if (file[i].name.len == 0) {
continue;
}

file[i].fd = ngx_open_file(file[i].name.data,
NGX_FILE_APPEND,
NGX_FILE_CREATE_OR_OPEN,
NGX_FILE_DEFAULT_ACCESS);

ngx_log_debug3(NGX_LOG_DEBUG_CORE, log, 0,
"log: %p %d \"%s\"",
&file[i], file[i].fd, file[i].name.data);

if (file[i].fd == NGX_INVALID_FILE) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
ngx_open_file_n " \"%s\" failed",
file[i].name.data);
goto failed;
}

#if !(NGX_WIN32)
// 设置文件为执行时关闭,即子进程执行exec后不继承该文件描述符,适用于平滑升级
if (fcntl(file[i].fd, F_SETFD, FD_CLOEXEC) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
"fcntl(FD_CLOEXEC) \"%s\" failed",
file[i].name.data);
goto failed;
}
#endif
}

cycle->log = &cycle->new_log;
pool->log = &cycle->new_log;


/* create shared memory */
// 创见共享内存,具体在共享内存介绍
part = &cycle->shared_memory.part;
shm_zone = part->elts;

for (i = 0; /* void */ ; i++) {

if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
shm_zone = part->elts;
i = 0;
}

if (shm_zone[i].shm.size == 0) {
ngx_log_error(NGX_LOG_EMERG, log, 0,
"zero size shared memory zone \"%V\"",
&shm_zone[i].shm.name);
goto failed;
}

shm_zone[i].shm.log = cycle->log;

opart = &old_cycle->shared_memory.part;
oshm_zone = opart->elts;

for (n = 0; /* void */ ; n++) {

if (n >= opart->nelts) {
if (opart->next == NULL) {
break;
}
opart = opart->next;
oshm_zone = opart->elts;
n = 0;
}

if (shm_zone[i].shm.name.len != oshm_zone[n].shm.name.len) {
continue;
}

if (ngx_strncmp(shm_zone[i].shm.name.data,
oshm_zone[n].shm.name.data,
shm_zone[i].shm.name.len)
!= 0)
{
continue;
}

if (shm_zone[i].tag == oshm_zone[n].tag
&& shm_zone[i].shm.size == oshm_zone[n].shm.size
&& !shm_zone[i].noreuse)
{
shm_zone[i].shm.addr = oshm_zone[n].shm.addr;
#if (NGX_WIN32)
shm_zone[i].shm.handle = oshm_zone[n].shm.handle;
#endif

if (shm_zone[i].init(&shm_zone[i], oshm_zone[n].data)
!= NGX_OK)
{
goto failed;
}

goto shm_zone_found;
}

break;
}

if (ngx_shm_alloc(&shm_zone[i].shm) != NGX_OK) {
goto failed;
}

if (ngx_init_zone_pool(cycle, &shm_zone[i]) != NGX_OK) {
goto failed;
}

if (shm_zone[i].init(&shm_zone[i], NULL) != NGX_OK) {
goto failed;
}

shm_zone_found:

continue;
}


/* handle the listening sockets */
// 处理监听套接字。

if (old_cycle->listening.nelts) {
ls = old_cycle->listening.elts;
// 遍历每一个旧版cycle监听的套接字,设置remain为0,即默认关闭所有旧套接字。
for (i = 0; i < old_cycle->listening.nelts; i++) {
ls[i].remain = 0;
}

nls = cycle->listening.elts;
/* 遍历每个新cycle的监听套接字,和旧套接字进行比对,如果新监听的套接字中存在与旧套接字一样的,则不关闭对应的旧套接字。*/
for (n = 0; n < cycle->listening.nelts; n++) {

for (i = 0; i < old_cycle->listening.nelts; i++) {
// 如果旧套接字设置了ignore,表示该套接字发生错误,不需要进行比对。
if (ls[i].ignore) {
continue;
}
// 如果旧套接字的remain已经为1,表示已经有一个新套接字和当前套接字地址一致,不能关闭当前套接字,因此不需要再比对
if (ls[i].remain) {
continue;
}
// 类型不一致,不用比对
if (ls[i].type != nls[n].type) {
continue;
}
// 对比新旧套接字地址是否一致
if (ngx_cmp_sockaddr(nls[n].sockaddr, nls[n].socklen,
ls[i].sockaddr, ls[i].socklen, 1)
== NGX_OK)
{
// 新套接字设置为旧套接字的描述符
nls[n].fd = ls[i].fd;
// previous设置为对应的旧套接字
nls[n].previous = &ls[i];
// 记录需要继续维护旧套接字(不能close)
ls[i].remain = 1;
// 如果新的套接字的backlog和旧版的不一致,则将listen置1,告知后续需要再次执行listen函数,来变更backlog
if (ls[i].backlog != nls[n].backlog) {
nls[n].listen = 1;
}

#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)

/*
* FreeBSD, except the most recent versions,
* could not remove accept filter
*/
nls[n].deferred_accept = ls[i].deferred_accept;

if (ls[i].accept_filter && nls[n].accept_filter) {
if (ngx_strcmp(ls[i].accept_filter,
nls[n].accept_filter)
!= 0)
{
nls[n].delete_deferred = 1;
nls[n].add_deferred = 1;
}

} else if (ls[i].accept_filter) {
nls[n].delete_deferred = 1;

} else if (nls[n].accept_filter) {
nls[n].add_deferred = 1;
}
#endif

#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)

if (ls[i].deferred_accept && !nls[n].deferred_accept) {
nls[n].delete_deferred = 1;

} else if (ls[i].deferred_accept != nls[n].deferred_accept)
{
nls[n].add_deferred = 1;
}
#endif

#if (NGX_HAVE_REUSEPORT)
// 如果新的套接字设置了复用端口,但老的未设置,则将add_reuseport置1,告知后续需要设置套接字复用端口。
if (nls[n].reuseport && !ls[i].reuseport) {
nls[n].add_reuseport = 1;
}
#endif

break;
}
}
// 如果套接字不为-1,表示当前套接字有效,后续不需要关闭套接字
if (nls[n].fd == (ngx_socket_t) -1) {
nls[n].open = 1;
#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
if (nls[n].accept_filter) {
nls[n].add_deferred = 1;
}
#endif
#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
if (nls[n].deferred_accept) {
nls[n].add_deferred = 1;
}
#endif
}
}

} else {
// 如果不存在旧的监听套接字,则设置每个待初始化的新套接字有效
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
ls[i].open = 1;
#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
if (ls[i].accept_filter) {
ls[i].add_deferred = 1;
}
#endif
#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
if (ls[i].deferred_accept) {
ls[i].add_deferred = 1;
}
#endif
}
}

// 处理监听套接字,打开并绑定,详见配置项解析中,监听端口的管理章节
if (ngx_open_listening_sockets(cycle) != NGX_OK) {
goto failed;
}
// 设置套接字属性
if (!ngx_test_config) {
ngx_configure_listening_sockets(cycle);
}


/* commit the new cycle configuration */
// 设置日志的错误输出到标注错误输出上
if (!ngx_use_stderr) {
(void) ngx_log_redirect_stderr(cycle);
}

pool->log = cycle->log;
// 执行每个module的init_module函数
if (ngx_init_modules(cycle) != NGX_OK) {
/* fatal */
exit(1);
}


/* close and delete stuff that lefts from an old cycle */

/* free the unnecessary shared memory */
// 释放不再使用的共享存储
opart = &old_cycle->shared_memory.part;
oshm_zone = opart->elts;

for (i = 0; /* void */ ; i++) {

if (i >= opart->nelts) {
if (opart->next == NULL) {
goto old_shm_zone_done;
}
opart = opart->next;
oshm_zone = opart->elts;
i = 0;
}

part = &cycle->shared_memory.part;
shm_zone = part->elts;

for (n = 0; /* void */ ; n++) {

if (n >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
shm_zone = part->elts;
n = 0;
}

if (oshm_zone[i].shm.name.len != shm_zone[n].shm.name.len) {
continue;
}

if (ngx_strncmp(oshm_zone[i].shm.name.data,
shm_zone[n].shm.name.data,
oshm_zone[i].shm.name.len)
!= 0)
{
continue;
}

if (oshm_zone[i].tag == shm_zone[n].tag
&& oshm_zone[i].shm.size == shm_zone[n].shm.size
&& !oshm_zone[i].noreuse)
{
goto live_shm_zone;
}

break;
}

ngx_shm_free(&oshm_zone[i].shm);

live_shm_zone:

continue;
}

old_shm_zone_done:


/* close the unnecessary listening sockets */
// 关闭不再监听的旧的监听套接字
ls = old_cycle->listening.elts;
for (i = 0; i < old_cycle->listening.nelts; i++) {
// 对于需要维护的,或者是-1的,不进行处理
if (ls[i].remain || ls[i].fd == (ngx_socket_t) -1) {
continue;
}

if (ngx_close_socket(ls[i].fd) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
ngx_close_socket_n " listening socket on %V failed",
&ls[i].addr_text);
}

#if (NGX_HAVE_UNIX_DOMAIN)
// 对应unix域的套接字,删除文件
if (ls[i].sockaddr->sa_family == AF_UNIX) {
u_char *name;

name = ls[i].addr_text.data + sizeof("unix:") - 1;

ngx_log_error(NGX_LOG_WARN, cycle->log, 0,
"deleting socket %s", name);

if (ngx_delete_file(name) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
ngx_delete_file_n " %s failed", name);
}
}

#endif
}


/* close the unnecessary open files */
// 关闭不再需要的文件
part = &old_cycle->open_files.part;
file = part->elts;

for (i = 0; /* void */ ; i++) {

if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
file = part->elts;
i = 0;
}

if (file[i].fd == NGX_INVALID_FILE || file[i].fd == ngx_stderr) {
continue;
}

if (ngx_close_file(file[i].fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
ngx_close_file_n " \"%s\" failed",
file[i].name.data);
}
}

ngx_destroy_pool(conf.temp_pool);

if (ngx_process == NGX_PROCESS_MASTER || ngx_is_init_cycle(old_cycle)) {

ngx_destroy_pool(old_cycle->pool);
cycle->old_cycle = NULL;

return cycle;
}

// 申请临时的内存池
if (ngx_temp_pool == NULL) {
ngx_temp_pool = ngx_create_pool(128, cycle->log);
if (ngx_temp_pool == NULL) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
"could not create ngx_temp_pool");
exit(1);
}

n = 10;

if (ngx_array_init(&ngx_old_cycles, ngx_temp_pool, n,
sizeof(ngx_cycle_t *))
!= NGX_OK)
{
exit(1);
}

ngx_memzero(ngx_old_cycles.elts, n * sizeof(ngx_cycle_t *));
// 设置定时清理事件
ngx_cleaner_event.handler = ngx_clean_old_cycles;
ngx_cleaner_event.log = cycle->log;
ngx_cleaner_event.data = &dumb;
dumb.fd = (ngx_socket_t) -1;
}

ngx_temp_pool->log = cycle->log;
// 将旧的cycle添加到全局变量ngx_old_cycles中
old = ngx_array_push(&ngx_old_cycles);
if (old == NULL) {
exit(1);
}
*old = old_cycle;
// 设置定时清理事件的时间
if (!ngx_cleaner_event.timer_set) {
ngx_add_timer(&ngx_cleaner_event, 30000);
ngx_cleaner_event.timer_set = 1;
}
// 返回新创建的cycle
return cycle;


failed:

if (!ngx_is_init_cycle(old_cycle)) {
old_ccf = (ngx_core_conf_t *) ngx_get_conf(old_cycle->conf_ctx,
ngx_core_module);
if (old_ccf->environment) {
environ = old_ccf->environment;
}
}

/* rollback the new cycle configuration */

part = &cycle->open_files.part;
file = part->elts;

for (i = 0; /* void */ ; i++) {

if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
file = part->elts;
i = 0;
}

if (file[i].fd == NGX_INVALID_FILE || file[i].fd == ngx_stderr) {
continue;
}

if (ngx_close_file(file[i].fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
ngx_close_file_n " \"%s\" failed",
file[i].name.data);
}
}

/* free the newly created shared memory */

part = &cycle->shared_memory.part;
shm_zone = part->elts;

for (i = 0; /* void */ ; i++) {

if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
shm_zone = part->elts;
i = 0;
}

if (shm_zone[i].shm.addr == NULL) {
continue;
}

opart = &old_cycle->shared_memory.part;
oshm_zone = opart->elts;

for (n = 0; /* void */ ; n++) {

if (n >= opart->nelts) {
if (opart->next == NULL) {
break;
}
opart = opart->next;
oshm_zone = opart->elts;
n = 0;
}

if (shm_zone[i].shm.name.len != oshm_zone[n].shm.name.len) {
continue;
}

if (ngx_strncmp(shm_zone[i].shm.name.data,
oshm_zone[n].shm.name.data,
shm_zone[i].shm.name.len)
!= 0)
{
continue;
}

if (shm_zone[i].tag == oshm_zone[n].tag
&& shm_zone[i].shm.size == oshm_zone[n].shm.size
&& !shm_zone[i].noreuse)
{
goto old_shm_zone_found;
}

break;
}

ngx_shm_free(&shm_zone[i].shm);

old_shm_zone_found:

continue;
}

if (ngx_test_config) {
ngx_destroy_cycle_pools(&conf);
return NULL;
}

ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
if (ls[i].fd == (ngx_socket_t) -1 || !ls[i].open) {
continue;
}

if (ngx_close_socket(ls[i].fd) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
ngx_close_socket_n " %V failed",
&ls[i].addr_text);
}
}

ngx_destroy_cycle_pools(&conf);

return NULL;
}

创建管理目录

ngx_create_paths。对于需要管理目录的配置,生成相应的目录,例如:http请求包体零时存放路径。

1
client_body_temp_path dir-path [level1 [level2 [level3]]]

创建管理文件

ngx_http_log_set_log

1
2
3
4
5
6
7
{ ngx_string("access_log"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
|NGX_HTTP_LMT_CONF|NGX_CONF_1MORE,
ngx_http_log_set_log,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL },

打开日志文件

1
2
3
4
5
6
7
8
9
10
11
static ngx_command_t  ngx_errlog_commands[] = {

{ ngx_string("error_log"),
NGX_MAIN_CONF|NGX_CONF_1MORE,
ngx_error_log,
0,
0,
NULL },

ngx_null_command
};

打开并设置监听地址

在看这部分之前,应该先查看配置项解析章节,至少需要查看其中的管理监听端口号部分。

ngx_open_listening_sockets打开监听地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
ngx_int_t
ngx_open_listening_sockets(ngx_cycle_t *cycle)
{
int reuseaddr;
ngx_uint_t i, tries, failed;
ngx_err_t err;
ngx_log_t *log;
ngx_socket_t s;
ngx_listening_t *ls;

reuseaddr = 1;
#if (NGX_SUPPRESS_WARN)
failed = 0;
#endif

log = cycle->log;

/* TODO: configurable try number */
// 尝试重复执行5次
for (tries = 5; tries; tries--) {
failed = 0;

/* for each listening socket */

ls = cycle->listening.elts;
// 遍历每一个监听的listening
for (i = 0; i < cycle->listening.nelts; i++) {
// 如果是ignore,则忽略
if (ls[i].ignore) {
continue;
}

#if (NGX_HAVE_REUSEPORT)
// 如果是add_reuseport则表示是一个旧的监听地址,需要增加reuseport属性
if (ls[i].add_reuseport) {

/*
* to allow transition from a socket without SO_REUSEPORT
* to multiple sockets with SO_REUSEPORT, we have to set
* SO_REUSEPORT on the old socket before opening new ones
*/

int reuseport = 1;

#ifdef SO_REUSEPORT_LB

if (setsockopt(ls[i].fd, SOL_SOCKET, SO_REUSEPORT_LB,
(const void *) &reuseport, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(SO_REUSEPORT_LB) %V failed, "
"ignored",
&ls[i].addr_text);
}

#else
// 设置地址的reuseport属性
if (setsockopt(ls[i].fd, SOL_SOCKET, SO_REUSEPORT,
(const void *) &reuseport, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(SO_REUSEPORT) %V failed, ignored",
&ls[i].addr_text);
}
#endif
// 恢复为0,避免下次重复执行(由于整个大循环可能会执行多次)
ls[i].add_reuseport = 0;
}
#endif
// 如果套接字不为-1,则说明已经是已经打开的套接字(大循环可能执行多次)
if (ls[i].fd != (ngx_socket_t) -1) {
continue;
}
// 如果是继承而来的,则跳过
if (ls[i].inherited) {

/* TODO: close on exit */
/* TODO: nonblocking */
/* TODO: deferred accept */

continue;
}
// 创建套接字
s = ngx_socket(ls[i].sockaddr->sa_family, ls[i].type, 0);

if (s == (ngx_socket_t) -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
ngx_socket_n " %V failed", &ls[i].addr_text);
return NGX_ERROR;
}
// 设置套接字复用地址属性
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
(const void *) &reuseaddr, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
"setsockopt(SO_REUSEADDR) %V failed",
&ls[i].addr_text);

if (ngx_close_socket(s) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
ngx_close_socket_n " %V failed",
&ls[i].addr_text);
}

return NGX_ERROR;
}

#if (NGX_HAVE_REUSEPORT)
// 如果用户设置了套接字的reuseport属性,则设置套接字的reuseport属性
if (ls[i].reuseport && !ngx_test_config) {
int reuseport;

reuseport = 1;

#ifdef SO_REUSEPORT_LB

if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT_LB,
(const void *) &reuseport, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
"setsockopt(SO_REUSEPORT_LB) %V failed",
&ls[i].addr_text);

if (ngx_close_socket(s) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
ngx_close_socket_n " %V failed",
&ls[i].addr_text);
}

return NGX_ERROR;
}

#else

if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT,
(const void *) &reuseport, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
"setsockopt(SO_REUSEPORT) %V failed",
&ls[i].addr_text);

if (ngx_close_socket(s) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
ngx_close_socket_n " %V failed",
&ls[i].addr_text);
}

return NGX_ERROR;
}
#endif
}
#endif

#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
// 设置套接字属性
if (ls[i].sockaddr->sa_family == AF_INET6) {
int ipv6only;

ipv6only = ls[i].ipv6only;

if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
(const void *) &ipv6only, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
"setsockopt(IPV6_V6ONLY) %V failed, ignored",
&ls[i].addr_text);
}
}
#endif
/* TODO: close on exit */
// 如果选择的事件模块不是NGX_USE_IOCP_EVENT,则设置套接字为非阻塞的ioctl(s, FIONBIO, &nb)
if (!(ngx_event_flags & NGX_USE_IOCP_EVENT)) {
if (ngx_nonblocking(s) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
ngx_nonblocking_n " %V failed",
&ls[i].addr_text);

if (ngx_close_socket(s) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
ngx_close_socket_n " %V failed",
&ls[i].addr_text);
}

return NGX_ERROR;
}
}

ngx_log_debug2(NGX_LOG_DEBUG_CORE, log, 0,
"bind() %V #%d ", &ls[i].addr_text, s);
// 绑定套接字及其地址
if (bind(s, ls[i].sockaddr, ls[i].socklen) == -1) {
err = ngx_socket_errno;

if (err != NGX_EADDRINUSE || !ngx_test_config) {
ngx_log_error(NGX_LOG_EMERG, log, err,
"bind() to %V failed", &ls[i].addr_text);
}

if (ngx_close_socket(s) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
ngx_close_socket_n " %V failed",
&ls[i].addr_text);
}

if (err != NGX_EADDRINUSE) {
return NGX_ERROR;
}

if (!ngx_test_config) {
failed = 1;
}

continue;
}

#if (NGX_HAVE_UNIX_DOMAIN)
// 如果是unix域套接字,则变更对应的文件属性
if (ls[i].sockaddr->sa_family == AF_UNIX) {
mode_t mode;
u_char *name;

name = ls[i].addr_text.data + sizeof("unix:") - 1;
mode = (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);

if (chmod((char *) name, mode) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"chmod() \"%s\" failed", name);
}

if (ngx_test_config) {
if (ngx_delete_file(name) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
ngx_delete_file_n " %s failed", name);
}
}
}
#endif

if (ls[i].type != SOCK_STREAM) {
ls[i].fd = s;
continue;
}
// 开启监听
if (listen(s, ls[i].backlog) == -1) {
err = ngx_socket_errno;

/*
* on OpenVZ after suspend/resume EADDRINUSE
* may be returned by listen() instead of bind(), see
* https://bugzilla.openvz.org/show_bug.cgi?id=2470
*/

if (err != NGX_EADDRINUSE || !ngx_test_config) {
ngx_log_error(NGX_LOG_EMERG, log, err,
"listen() to %V, backlog %d failed",
&ls[i].addr_text, ls[i].backlog);
}

if (ngx_close_socket(s) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
ngx_close_socket_n " %V failed",
&ls[i].addr_text);
}

if (err != NGX_EADDRINUSE) {
return NGX_ERROR;
}

if (!ngx_test_config) {
failed = 1;
}

continue;
}
// 标记已监听
ls[i].listen = 1;
// 设置描述符
ls[i].fd = s;
}
// 如果没有错误,则跳出循环
if (!failed) {
break;
}

/* TODO: delay configurable */

ngx_log_error(NGX_LOG_NOTICE, log, 0,
"try again to bind() after 500ms");
// 如果有错,则500ms重试
ngx_msleep(500);
}

if (failed) {
ngx_log_error(NGX_LOG_EMERG, log, 0, "still could not bind()");
return NGX_ERROR;
}

return NGX_OK;
}

这里需要着重关注一下reuseport属性。对于监听多个地址:不同ip+同一个port,如果未开启该属性时,会失败。例如如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
http {
server {
listen 127.0.0.1:8884 bind;
location /L1 {
root /home/work/Nginx/study/;
}
}
server {
listen 8884;
server_name chst.bcc-bdbl.baidu.com;
location /L2 {
root /home/work/Nginx/study/;
}
}
}

这里,对于127.0.0.1:8884这个地址,我们希望进行单独的监听(设置了bind),这时会先单独对该地址进行bind。而后,对于0.0.0.0:8884这个通配符地址来说,我们还要再执行一次bind。但由于未设置reuseport属性,监听0.0.0.0:8884将会出错。改成如下配置则可以正常监听:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
http {
server {
listen 127.0.0.1:8884 bind reuseport;
location /L1 {
root /home/work/Nginx/study/;
}
}
server {
listen 8884 reuseport;
server_name chst.bcc-bdbl.baidu.com;
location /L2 {
root /home/work/Nginx/study/;
}
}
}

这里,两个地址都设置了reuseport属性,此时可以实现正常监听。由于对每个ip+port形式的地址,nginx维护一个ngx_http_listen_opt_t。因此两个listen都需要加上reuseport才行。注意如下配置也不会有问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
http {
server {
listen 127.0.0.1:8884;
location /L1 {
root /home/work/Nginx/study/;
}
}
server {
listen 8884;
server_name chst.bcc-bdbl.baidu.com;
location /L2 {
root /home/work/Nginx/study/;
}
}
}

这时由于第一个地址并未设置bind。此时不会单独对127.0.0.1:8884创建一个套接字。而是直接在0.0.0.0:8884这个通配符地址上进行监听。对于建立的连接,通过getsockname函数来发现绑定到套接字上的地址,以此来区分使用哪个虚拟服务。

ngx_configure_listening_sockets设置套接字属性

通过setsockopt函数对套接字进行设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
void
ngx_configure_listening_sockets(ngx_cycle_t *cycle)
{
int value;
ngx_uint_t i;
ngx_listening_t *ls;

#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
struct accept_filter_arg af;
#endif

ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {

ls[i].log = *ls[i].logp;

if (ls[i].rcvbuf != -1) {
if (setsockopt(ls[i].fd, SOL_SOCKET, SO_RCVBUF,
(const void *) &ls[i].rcvbuf, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(SO_RCVBUF, %d) %V failed, ignored",
ls[i].rcvbuf, &ls[i].addr_text);
}
}

if (ls[i].sndbuf != -1) {
if (setsockopt(ls[i].fd, SOL_SOCKET, SO_SNDBUF,
(const void *) &ls[i].sndbuf, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(SO_SNDBUF, %d) %V failed, ignored",
ls[i].sndbuf, &ls[i].addr_text);
}
}

if (ls[i].keepalive) {
value = (ls[i].keepalive == 1) ? 1 : 0;

if (setsockopt(ls[i].fd, SOL_SOCKET, SO_KEEPALIVE,
(const void *) &value, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(SO_KEEPALIVE, %d) %V failed, ignored",
value, &ls[i].addr_text);
}
}

#if (NGX_HAVE_KEEPALIVE_TUNABLE)

if (ls[i].keepidle) {
value = ls[i].keepidle;

#if (NGX_KEEPALIVE_FACTOR)
value *= NGX_KEEPALIVE_FACTOR;
#endif

if (setsockopt(ls[i].fd, IPPROTO_TCP, TCP_KEEPIDLE,
(const void *) &value, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(TCP_KEEPIDLE, %d) %V failed, ignored",
value, &ls[i].addr_text);
}
}

if (ls[i].keepintvl) {
value = ls[i].keepintvl;

#if (NGX_KEEPALIVE_FACTOR)
value *= NGX_KEEPALIVE_FACTOR;
#endif

if (setsockopt(ls[i].fd, IPPROTO_TCP, TCP_KEEPINTVL,
(const void *) &value, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(TCP_KEEPINTVL, %d) %V failed, ignored",
value, &ls[i].addr_text);
}
}

if (ls[i].keepcnt) {
if (setsockopt(ls[i].fd, IPPROTO_TCP, TCP_KEEPCNT,
(const void *) &ls[i].keepcnt, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(TCP_KEEPCNT, %d) %V failed, ignored",
ls[i].keepcnt, &ls[i].addr_text);
}
}

#endif

#if (NGX_HAVE_SETFIB)
if (ls[i].setfib != -1) {
if (setsockopt(ls[i].fd, SOL_SOCKET, SO_SETFIB,
(const void *) &ls[i].setfib, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(SO_SETFIB, %d) %V failed, ignored",
ls[i].setfib, &ls[i].addr_text);
}
}
#endif

#if (NGX_HAVE_TCP_FASTOPEN)
if (ls[i].fastopen != -1) {
if (setsockopt(ls[i].fd, IPPROTO_TCP, TCP_FASTOPEN,
(const void *) &ls[i].fastopen, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(TCP_FASTOPEN, %d) %V failed, ignored",
ls[i].fastopen, &ls[i].addr_text);
}
}
#endif

#if 0
if (1) {
int tcp_nodelay = 1;

if (setsockopt(ls[i].fd, IPPROTO_TCP, TCP_NODELAY,
(const void *) &tcp_nodelay, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(TCP_NODELAY) %V failed, ignored",
&ls[i].addr_text);
}
}
#endif

if (ls[i].listen) {

/* change backlog via listen() */
// 通过listen函数来重新设置backlog大小
if (listen(ls[i].fd, ls[i].backlog) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"listen() to %V, backlog %d failed, ignored",
&ls[i].addr_text, ls[i].backlog);
}
}

/*
* setting deferred mode should be last operation on socket,
* because code may prematurely continue cycle on failure
*/

#if (NGX_HAVE_DEFERRED_ACCEPT)

#ifdef SO_ACCEPTFILTER

if (ls[i].delete_deferred) {
if (setsockopt(ls[i].fd, SOL_SOCKET, SO_ACCEPTFILTER, NULL, 0)
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(SO_ACCEPTFILTER, NULL) "
"for %V failed, ignored",
&ls[i].addr_text);

if (ls[i].accept_filter) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
"could not change the accept filter "
"to \"%s\" for %V, ignored",
ls[i].accept_filter, &ls[i].addr_text);
}

continue;
}

ls[i].deferred_accept = 0;
}

if (ls[i].add_deferred) {
ngx_memzero(&af, sizeof(struct accept_filter_arg));
(void) ngx_cpystrn((u_char *) af.af_name,
(u_char *) ls[i].accept_filter, 16);

if (setsockopt(ls[i].fd, SOL_SOCKET, SO_ACCEPTFILTER,
&af, sizeof(struct accept_filter_arg))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(SO_ACCEPTFILTER, \"%s\") "
"for %V failed, ignored",
ls[i].accept_filter, &ls[i].addr_text);
continue;
}

ls[i].deferred_accept = 1;
}

#endif

#ifdef TCP_DEFER_ACCEPT

if (ls[i].add_deferred || ls[i].delete_deferred) {

if (ls[i].add_deferred) {
/*
* There is no way to find out how long a connection was
* in queue (and a connection may bypass deferred queue at all
* if syncookies were used), hence we use 1 second timeout
* here.
*/
value = 1;

} else {
value = 0;
}

if (setsockopt(ls[i].fd, IPPROTO_TCP, TCP_DEFER_ACCEPT,
&value, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(TCP_DEFER_ACCEPT, %d) for %V failed, "
"ignored",
value, &ls[i].addr_text);

continue;
}
}

if (ls[i].add_deferred) {
ls[i].deferred_accept = 1;
}

#endif

#endif /* NGX_HAVE_DEFERRED_ACCEPT */

#if (NGX_HAVE_IP_RECVDSTADDR)

if (ls[i].wildcard
&& ls[i].type == SOCK_DGRAM
&& ls[i].sockaddr->sa_family == AF_INET)
{
value = 1;

if (setsockopt(ls[i].fd, IPPROTO_IP, IP_RECVDSTADDR,
(const void *) &value, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(IP_RECVDSTADDR) "
"for %V failed, ignored",
&ls[i].addr_text);
}
}

#elif (NGX_HAVE_IP_PKTINFO)

if (ls[i].wildcard
&& ls[i].type == SOCK_DGRAM
&& ls[i].sockaddr->sa_family == AF_INET)
{
value = 1;

if (setsockopt(ls[i].fd, IPPROTO_IP, IP_PKTINFO,
(const void *) &value, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(IP_PKTINFO) "
"for %V failed, ignored",
&ls[i].addr_text);
}
}

#endif

#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO)

if (ls[i].wildcard
&& ls[i].type == SOCK_DGRAM
&& ls[i].sockaddr->sa_family == AF_INET6)
{
value = 1;

if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_RECVPKTINFO,
(const void *) &value, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(IPV6_RECVPKTINFO) "
"for %V failed, ignored",
&ls[i].addr_text);
}
}

#endif
}

return;
}

测试配置的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
if (ngx_test_config) {
// 测试配置文件处理
// 如果需要打印非error的日志
if (!ngx_quiet_mode) {
ngx_log_stderr(0, "configuration file %s test is successful",
cycle->conf_file.data);
}
// 如果需要将配置dump打印
if (ngx_dump_config) {
cd = cycle->config_dump.elts;

for (i = 0; i < cycle->config_dump.nelts; i++) {

ngx_write_stdout("# configuration file ");
(void) ngx_write_fd(ngx_stdout, cd[i].name.data,
cd[i].name.len);
ngx_write_stdout(":" NGX_LINEFEED);

b = cd[i].buffer;

(void) ngx_write_fd(ngx_stdout, b->pos, b->last - b->pos);
ngx_write_stdout(NGX_LINEFEED);
}
}

return 0;
}

发送信号处理

1
2
3
4
if (ngx_signal) {
// 如果是向正在执行的nginx服务发送信号
return ngx_signal_process(cycle, ngx_signal);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
ngx_int_t
ngx_signal_process(ngx_cycle_t *cycle, char *sig)
{
ssize_t n;
ngx_pid_t pid;
ngx_file_t file;
ngx_core_conf_t *ccf;
u_char buf[NGX_INT64_LEN + 2];

ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "signal process started");
// 获取解析到的pid文件
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);

ngx_memzero(&file, sizeof(ngx_file_t));

file.name = ccf->pid;
file.log = cycle->log;

file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY,
NGX_FILE_OPEN, NGX_FILE_DEFAULT_ACCESS);

if (file.fd == NGX_INVALID_FILE) {
ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno,
ngx_open_file_n " \"%s\" failed", file.name.data);
return 1;
}
// 读取内容
n = ngx_read_file(&file, buf, NGX_INT64_LEN + 2, 0);

if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
ngx_close_file_n " \"%s\" failed", file.name.data);
}

if (n == NGX_ERROR) {
return 1;
}
// 去除末尾的\r,\n
while (n-- && (buf[n] == CR || buf[n] == LF)) { /* void */ }
// 获取进程id
pid = ngx_atoi(buf, ++n);

if (pid == (ngx_pid_t) NGX_ERROR) {
ngx_log_error(NGX_LOG_ERR, cycle->log, 0,
"invalid PID number \"%*s\" in \"%s\"",
n, buf, file.name.data);
return 1;
}
// 执行发送信号
return ngx_os_signal_process(cycle, sig, pid);

}


typedef struct {
int signo;
char *signame;
char *name;
void (*handler)(int signo, siginfo_t *siginfo, void *ucontext);
} ngx_signal_t;


ngx_int_t
ngx_os_signal_process(ngx_cycle_t *cycle, char *name, ngx_pid_t pid)
{
ngx_signal_t *sig;
// 遍历所有系统支持信号,找到发送的信号,向对应的进程发送信号。
for (sig = signals; sig->signo != 0; sig++) {
if (ngx_strcmp(name, sig->name) == 0) {
if (kill(pid, sig->signo) != -1) {
return 0;
}

ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"kill(%P, %d) failed", pid, sig->signo);
}
}

return 1;
}

具体对接收到信号的处理,后续介绍。

判断运行方式

1
2
3
4
5
6
7
// 获取配置解析的ngx_core_module解析项
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
// 如果设置了master,并且在此之前未设置其他运行方式(默认NGX_PROCESS_SINGLE 0),ngx_process为全局变量,初值为0
if (ccf->master && ngx_process == NGX_PROCESS_SINGLE) {
// 设置运行方式为mater-worker方式运行
ngx_process = NGX_PROCESS_MASTER;
}

信号处理

1
2
3
if (ngx_init_signals(cycle->log) != NGX_OK) {
return 1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
typedef struct {
int signo; // 信号
char *signame; // 信号名
char *name;
void (*handler)(int signo, siginfo_t *siginfo, void *ucontext); // 处理函数
} ngx_signal_t;

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", "", NULL },

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

{ 0, NULL, "", NULL }
};

ngx_int_t
ngx_init_signals(ngx_log_t *log)
{
ngx_signal_t *sig;
struct sigaction sa;
// 其中signals为ngx_signal_t数组
for (sig = signals; sig->signo != 0; sig++) {
ngx_memzero(&sa, sizeof(struct sigaction));

if (sig->handler) {
sa.sa_sigaction = sig->handler;
sa.sa_flags = SA_SIGINFO;

} else {
sa.sa_handler = SIG_IGN;
}
// 注册信号处理函数
sigemptyset(&sa.sa_mask);
if (sigaction(sig->signo, &sa, NULL) == -1) {
#if (NGX_VALGRIND)
ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
"sigaction(%s) failed, ignored", sig->signame);
#else
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
"sigaction(%s) failed", sig->signame);
return NGX_ERROR;
#endif
}
}

return NGX_OK;
}

信号处理相关内容可以查看如下文档:信号处理。其中处理函数为空的函数,即表示不对信号做任何处理,即忽略。其他信号处理函数均为ngx_signal_handler。处理如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
static void
ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext)
{
char *action;
ngx_int_t ignore;
ngx_err_t err;
ngx_signal_t *sig;

ignore = 0;

err = ngx_errno;
// 首先找到对应的信号,用于获取信号名记录log
for (sig = signals; sig->signo != 0; sig++) {
if (sig->signo == signo) {
break;
}
}
// 以线程安全的方式更新时间,具体参考时间章节介绍
ngx_time_sigsafe_update();

action = "";
// 在每个子进程中,都会先设置进行类型ngx_process
switch (ngx_process) {
// master进程,或者单进程运行模式下处理
case NGX_PROCESS_MASTER:
case NGX_PROCESS_SINGLE:
switch (signo) {
// 退出信号,设置ngx_quit为1
case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
ngx_quit = 1;
action = ", shutting down";
break;

// 终止信息。ngx_terminate设置为1
case ngx_signal_value(NGX_TERMINATE_SIGNAL):
case SIGINT:
ngx_terminate = 1;
action = ", exiting";
break;
// 停止接收连接信号,如果是守护进程方式运行,则将ngx_noaccept置1
case ngx_signal_value(NGX_NOACCEPT_SIGNAL):
if (ngx_daemonized) {
ngx_noaccept = 1;
action = ", stop accepting connections";
}
break;
// 重新加载配置
case ngx_signal_value(NGX_RECONFIGURE_SIGNAL):
ngx_reconfigure = 1;
action = ", reconfiguring";
break;
// 重新打开日志文件
case ngx_signal_value(NGX_REOPEN_SIGNAL):
ngx_reopen = 1;
action = ", reopening logs";
break;
// 变更二进制文件,即不关机重启
case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):
/* 对应不关机升级来说,master进行会fork子进程,运行新的二进制文件。而后应该由用户向老的master进程发送终止解析,使旧版服务退出。原master退出来,新的mater进程将会被init进程接管,即此时新master进程的父进程id为init进程id:1。对于新进程来说,如果其父进程依旧为原来mater进程的子进程id时,说明原来master进程还未退出,此时会忽略该信号。对于老的master进程来说,如果ngx_new_binary大于0,表明新的二进制文件已经在运行了,此时也会忽略该信号。*/
if (ngx_getppid() == ngx_parent || ngx_new_binary > 0) {

/*
* Ignore the signal in the new binary if its parent is
* not changed, 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
ngx_change_binary = 1;
action = ", changing binary";
break;

case SIGALRM:
// 时钟信号
ngx_sigalrm = 1;
break;
// 异步io
case SIGIO:
ngx_sigio = 1;
break;
// 子进程退出或终止
case SIGCHLD:
ngx_reap = 1;
break;
}

break;
// worker和helper进程处理逻辑
case NGX_PROCESS_WORKER:
case NGX_PROCESS_HELPER:
switch (signo) {
// 退出debug
case ngx_signal_value(NGX_NOACCEPT_SIGNAL):
if (!ngx_daemonized) {
break;
}
ngx_debug_quit = 1;
/* fall through */
// 关闭
case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
ngx_quit = 1;
action = ", shutting down";
break;
// 退出
case ngx_signal_value(NGX_TERMINATE_SIGNAL):
case SIGINT:
ngx_terminate = 1;
action = ", exiting";
break;
// 重新打开日志文件
case ngx_signal_value(NGX_REOPEN_SIGNAL):
ngx_reopen = 1;
action = ", reopening logs";
break;
// 忽略重新读取配置、变更二进制文件,异步io
case ngx_signal_value(NGX_RECONFIGURE_SIGNAL):
case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):
case SIGIO:
action = ", ignoring";
break;
}

break;
}

// 记录日志,根据是否能够明确信号来源进行划分。
if (siginfo && siginfo->si_pid) {
ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0,
"signal %d (%s) received from %P%s",
signo, sig->signame, siginfo->si_pid, action);

} else {
ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0,
"signal %d (%s) received%s",
signo, sig->signame, action);
}

if (ignore) {
ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, 0,
"the changing binary signal is ignored: "
"you should shutdown or terminate "
"before either old or new binary's process");
}
// 如果子进程终止,则执行如下处理
if (signo == SIGCHLD) {
ngx_process_get_status();
}

ngx_set_errno(err);
}

对应子进程退出时的处理如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
static void
ngx_process_get_status(void)
{
int status;
char *process;
ngx_pid_t pid;
ngx_err_t err;
ngx_int_t i;
ngx_uint_t one;

one = 0;

for ( ;; ) {
// 不阻塞的查看其子进程中哪个进程结束
pid = waitpid(-1, &status, WNOHANG);
// 如果没有终止进程,则返回
if (pid == 0) {
return;
}
// 如果获取终止子进程失败
if (pid == -1) {
err = ngx_errno;

if (err == NGX_EINTR) {
continue;
}

if (err == NGX_ECHILD && one) {
return;
}

/*
* Solaris always calls the signal handler for each exited process
* despite waitpid() may be already called for this process.
*
* When several processes exit at the same time FreeBSD may
* erroneously call the signal handler for exited process
* despite waitpid() may be already called for this process.
*/

if (err == NGX_ECHILD) {
ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, err,
"waitpid() failed");
return;
}

ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, err,
"waitpid() failed");
return;
}

// 标注找到一个退出进程
one = 1;
process = "unknown process";
// 遍历保存进程信息的ngx_processes数组,找到对应的元素,设置其状态,并设置其exited为1
for (i = 0; i < ngx_last_process; i++) {
if (ngx_processes[i].pid == pid) {
ngx_processes[i].status = status;
ngx_processes[i].exited = 1;
process = ngx_processes[i].name;
break;
}
}
// 进程异常终止,获取子进程终止的信号编号。
if (WTERMSIG(status)) {
#ifdef WCOREDUMP
// 可以通过WCOREDUMP获取否生成了终止进程的core文件
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
"%s %P exited on signal %d%s",
process, pid, WTERMSIG(status),
WCOREDUMP(status) ? " (core dumped)" : "");
#else
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
"%s %P exited on signal %d",
process, pid, WTERMSIG(status));
#endif

} else {
ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0,
"%s %P exited with code %d",
process, pid, WEXITSTATUS(status));
}
// 获取子进程传递给exit或_exit参数的低八位,如果值为2,并且进程需要重启
if (WEXITSTATUS(status) == 2 && ngx_processes[i].respawn) {
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
"%s %P exited with fatal code %d "
"and cannot be respawned",
process, pid, WEXITSTATUS(status));
ngx_processes[i].respawn = 0;
}
// 释放终止进程所持有的锁
ngx_unlock_mutexes(pid);
}
}

对应进程终止相关处理,可参考如下文档:进程控制

释放终止进程锁的函数逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
static void
ngx_unlock_mutexes(ngx_pid_t pid)
{
ngx_uint_t i;
ngx_shm_zone_t *shm_zone;
ngx_list_part_t *part;
ngx_slab_pool_t *sp;

/*
* unlock the accept mutex if the abnormally exited process
* held it
*/
/* 如果设置了进程间的负载均衡锁,则根据该进程是否拥有该锁,进行释放操作。注意ngx_accept_mutex也是使用mmap内存映射技术来生成的,因此所有进程共享。*/
if (ngx_accept_mutex_ptr) {
(void) ngx_shmtx_force_unlock(&ngx_accept_mutex, pid);
}

/*
* unlock shared memory mutexes if held by the abnormally exited
* process
*/
// 释放共享内存的锁
part = (ngx_list_part_t *) &ngx_cycle->shared_memory.part;
shm_zone = part->elts;

for (i = 0; /* void */ ; i++) {

if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
shm_zone = part->elts;
i = 0;
}

sp = (ngx_slab_pool_t *) shm_zone[i].shm.addr;

if (ngx_shmtx_force_unlock(&sp->mutex, pid)) {
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
"shared memory zone \"%V\" was locked by %P",
&shm_zone[i].shm.name, pid);
}
}
}

变更运行状态为守护进程

1
2
3
4
5
6
7
8
9
10
11
12
13
// 非继承而来,即正常启动,并且设置为守护进程运行模式(默认)
if (!ngx_inherited && ccf->daemon) {
// 进程变更为守护进程模式
if (ngx_daemon(cycle->log) != NGX_OK) {
return 1;
}

ngx_daemonized = 1;
}

if (ngx_inherited) {
ngx_daemonized = 1;
}

nginx默认为以守护进程的模式运行。关于守护进程,详细信息可以参考如下文档,其实际方法也与其大致相同。守护进程

创建pid文件

1
2
3
4
// 对于非继承而来的进程,会创建pid文件,继承而来的进程,已经在init cycle中创建了,详情参考上文。
if (ngx_create_pidfile(&ccf->pid, cycle->log) != NGX_OK) {
return 1;
}

设置运行方式

1
2
3
4
5
6
if (ngx_process == NGX_PROCESS_SINGLE) {
ngx_single_process_cycle(cycle);

} else {
ngx_master_process_cycle(cycle);
}

根据是单进程模式运行还是master-workers方式执行对应的方法。这里我们只看master-worker方式运行。

执行主体循环

根据配置,选择运行方式,分别为单进程方式运行和master-workers方式运行。

1
2
3
4
5
6
if (ngx_process == NGX_PROCESS_SINGLE) {
ngx_single_process_cycle(cycle);

} else {
ngx_master_process_cycle(cycle);
}

具体细节参考master进程和worker进程逻辑。

master进程逻辑

整体处理函数

这里只介绍以master-worker形式运行的情况。其执行入口为如下函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
void
ngx_master_process_cycle(ngx_cycle_t *cycle)
{
char *title;
u_char *p;
size_t size;
ngx_int_t i;
ngx_uint_t sigio;
sigset_t set;
struct itimerval itv;
ngx_uint_t live;
ngx_msec_t delay;
ngx_core_conf_t *ccf;

sigemptyset(&set);
sigaddset(&set, SIGCHLD);
sigaddset(&set, SIGALRM);
sigaddset(&set, SIGIO);
sigaddset(&set, SIGINT);
sigaddset(&set, ngx_signal_value(NGX_RECONFIGURE_SIGNAL));
sigaddset(&set, ngx_signal_value(NGX_REOPEN_SIGNAL));
sigaddset(&set, ngx_signal_value(NGX_NOACCEPT_SIGNAL));
sigaddset(&set, ngx_signal_value(NGX_TERMINATE_SIGNAL));
sigaddset(&set, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
sigaddset(&set, ngx_signal_value(NGX_CHANGEBIN_SIGNAL));

// 暂时屏蔽上述信号,使其处于未决状态
if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"sigprocmask() failed");
}
// 设置set信号集为空
sigemptyset(&set);


size = sizeof(master_process);

for (i = 0; i < ngx_argc; i++) {
size += ngx_strlen(ngx_argv[i]) + 1;
}

title = ngx_pnalloc(cycle->pool, size);
if (title == NULL) {
/* fatal */
exit(2);
}

p = ngx_cpymem(title, master_process, sizeof(master_process) - 1);
for (i = 0; i < ngx_argc; i++) {
*p++ = ' ';
p = ngx_cpystrn(p, (u_char *) ngx_argv[i], size);
}
// 通过设置argv[0]的值来变更进程名,影响ps命令打印的进程名
ngx_setproctitle(title);

// 获取配置解析后的ngx_core_conf_t结构体
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);

// 根据设置的workers子进程数量,生成子进程。详见子进程处理逻辑部分
ngx_start_worker_processes(cycle, ccf->worker_processes,
NGX_PROCESS_RESPAWN);
// 运行cache管理进程(复制管理)
ngx_start_cache_manager_processes(cycle, 0);

// 标记是否已经运行了一个新的二进制文件,如果是,则该值为新的二进制文件的pid
ngx_new_binary = 0;
// 收到强制关闭时延迟关闭时间
delay = 0;
// 在执行退出时,记录子进程关闭数量
sigio = 0;
// 是否还有存活的子进程
live = 1;

// master主进程循环
for ( ;; ) {
// 如果delay不为0,表示接收到强制退出的指令
if (delay) {
// 如果ngx_sigalrm不为0,表示之前的定时器已超时。则将delay乘以2,将sigio置为0(为了能够再次向子进程下发终止信号)
if (ngx_sigalrm) {
sigio = 0;
delay *= 2;
ngx_sigalrm = 0;
}
// 打印日志
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"termination cycle: %M", delay);
/* 使用setitime设置一个delay秒后的一个时钟信号,用来检测超时时间。(这里有个问题是,当设置了时钟信号,在时钟信号下发之前又接收到了其他信号,则会重新设置时钟信号,一般向子进程下发了退出信号后,再接收到的信号应该是SIGCHLD信号,即子进程退出信号)*/
itv.it_interval.tv_sec = 0;
itv.it_interval.tv_usec = 0;
itv.it_value.tv_sec = delay / 1000;
itv.it_value.tv_usec = (delay % 1000 ) * 1000;

if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"setitimer() failed");
}
}

ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "sigsuspend");
// 使用sigsuspend接收到之前被屏蔽的信号组中任意一个信号。
sigsuspend(&set);
// 更新缓存的时间
ngx_time_update();

ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"wake up, sigio %i", sigio);

// 接收到子进程退出信号的处理
if (ngx_reap) {
// 恢复标识
ngx_reap = 0;
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "reap children");
// 根据退出原因进行相应的处理,具体下面介绍,返回的live为当前是否依然有子进程存活
live = ngx_reap_children(cycle);
}
// 如果没有子进程存活,且收到了强制退出或者优雅的方式退出,则执行master进程退出。
if (!live && (ngx_terminate || ngx_quit)) {
ngx_master_process_exit(cycle);
}
// 强制退出信号,注意,这里处理时并未恢复ngx_terminate为0,并且只处理SIGCHLD信号
if (ngx_terminate) {
// 如果delay为0,表示刚接收到强制退出
if (delay == 0) {
// 设置延迟时间为50ms
delay = 50;
}

/* 如果sigio不为0,认为子进程数量减少了1,并继续等待其他信号。当收到不是子进程终止的信号是,sigio会变成0,或者超过了设置的等待时间时,sigio也会变成0.这是是为了方便执行后续的校验delay时间。*/
if (sigio) {
sigio--;
continue;
}

// 设置sigio为子进程总数量+1.这样正常情况下,不需要重复下发关闭信号
sigio = ccf->worker_processes + 2 /* cache processes */;

// 如果延迟关闭时间超过1000ms时,则执行强制kill命令,关闭子进程,具体逻辑下面会详细介绍
if (delay > 1000) {
ngx_signal_worker_processes(cycle, SIGKILL);
} else {
// 否则向子进程通过unix域套接字传递终止信息
ngx_signal_worker_processes(cycle,
ngx_signal_value(NGX_TERMINATE_SIGNAL));
}
// 不再执行其他信号的处理逻辑
continue;
}

// 如果执行优雅的退出服务,则向子进程下发对应的SHUTDOWN信息。关闭监听端口。忽略后续处理
if (ngx_quit) {
ngx_signal_worker_processes(cycle,
ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
ngx_close_listening_sockets(cycle);

continue;
}

// 收到重新读取配置信号
if (ngx_reconfigure) {
// 恢复表示
ngx_reconfigure = 0;

/* 已经执行了新的二进制文件的处理逻辑(由于在启动新的二进制文件前,会将旧的master进程的pid文件转移,因此正常来说,使用nginx命令是不会被旧进程接收到信号的,这应该是直接使用kill向旧进程下发的指令)。逻辑没太理解,只是又新增一批woker子进程和cache管理子进程,并设置ngx_noaccepting(表明当前依然在进行监听),忽略其余信号的处理*/
if (ngx_new_binary) {
ngx_start_worker_processes(cycle, ccf->worker_processes,
NGX_PROCESS_RESPAWN);
ngx_start_cache_manager_processes(cycle, 0);
ngx_noaccepting = 0;

continue;
}

ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reconfiguring");
// 重新执行初始化cycle,这里会重新读取配置。
cycle = ngx_init_cycle(cycle);
if (cycle == NULL) {
cycle = (ngx_cycle_t *) ngx_cycle;
continue;
}

ngx_cycle = cycle;
// 获取新配置的核心配置信息
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,
ngx_core_module);
// 使用NGX_PROCESS_JUST_RESPAWN方式重新运行一批worker进程,用来区分旧进程,具体后续讲解
ngx_start_worker_processes(cycle, ccf->worker_processes,
NGX_PROCESS_JUST_RESPAWN);
// 运行新的cache管理程序
ngx_start_cache_manager_processes(cycle, 1);

/* allow new processes to start */
// 休眠一段时间,让子进程都启动起来
ngx_msleep(100);
// 标识存在子进程存活
live = 1;
// 关闭旧进程
ngx_signal_worker_processes(cycle,
ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
}
/* restart并非一个指令触发的操作,而是执行新的二进制文件出现意外时的止损方案。在下发了执行新的二进制文件时,会创建一个子进程来运行新的二进制文件。但是如果新的二进制没办法正常运行,且我们下发了关闭当前matser的accept时,将导致没有worker进程能够正常运行。只是,在ngx_reap_children中,如果我们收到了新启动的运行新的二进制文件的进程意外退出,并且当前没有进程在接受请求,则会将ngx_restart设为1。此时执行的操作是,在当前版本的二进制程序中再次打开worker子进程和cache管理子进程。*/
if (ngx_restart) {
ngx_restart = 0;
ngx_start_worker_processes(cycle, ccf->worker_processes,
NGX_PROCESS_RESPAWN);
ngx_start_cache_manager_processes(cycle, 0);
live = 1;
}

// 重新打开日志文件处理
if (ngx_reopen) {
ngx_reopen = 0;
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
// 重新打开文件
ngx_reopen_files(cycle, ccf->user);
// 向子进程传递重新打开文件信息
ngx_signal_worker_processes(cycle,
ngx_signal_value(NGX_REOPEN_SIGNAL));
}
// 平滑升级,重新执行新的二进制文件,信号中获取的指令
if (ngx_change_binary) {
ngx_change_binary = 0;
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "changing binary");
// 生成子进程,执行新的二进制文件
ngx_new_binary = ngx_exec_new_binary(cycle, ngx_argv);
}
// 停止接收链接,信号中获取的指令
if (ngx_noaccept) {
ngx_noaccept = 0;
// 标识状态,当前不接收请求
ngx_noaccepting = 1;
// 向子进程下发指令
ngx_signal_worker_processes(cycle,
ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
}
}
}

对于循环中使用的信号相关变量,参考上文中的信号处理部分。

启动worker子进程

相关数据结构

进程信息ngx_process_t

ngx_process_t结构存储了进程的相关信息。其定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 进程执行的处理函数
typedef void (*ngx_spawn_proc_pt) (ngx_cycle_t *cycle, void *data);

typedef struct {
// 进程id,为-1表示该结构未绑定一个进程
ngx_pid_t pid;
// 进程状态
int status;
// 用于数据传参的两个unix域套接字
ngx_socket_t channel[2];
// 进程绑定的处理函数,即fork创建进程后执行的重新
ngx_spawn_proc_pt proc;
// 对应的数据信息,基本与proc的内容一致
void *data;
// 进程名称。操作系统中显示的进程名称与name相同
char *name;
// 生成子进程
unsigned respawn:1;
// 刚生成的子进程,与respawn区分,表示新旧子进程
unsigned just_spawn:1;
// 父子进程分离,用于旧版本的master进程生成新的进程运行新的二进制文件。此时该标志位来标注是运行新的二进制文件的子进程
unsigned detached:1;
// 进程正在退出
unsigned exiting:1;
// 进程已退出
unsigned exited:1;
} ngx_process_t;

进程间传递信息ngx_channel_t

ngx_channel_t结构用于进程间传递信息,包括直接传递unix域套接字。

1
2
3
4
5
6
7
8
9
10
typedef struct {
// 需要传递的指令,接收方根据该内容来决定对应的处理
ngx_uint_t command;
// 标识数据来源。进程id为pid的进程传递的指令
ngx_pid_t pid;
// 对应于全局变量ngx_processes数组中的下标,在传递描述符时有用,接收方需要通过该值设置对应数组元素
ngx_int_t slot;
// 传递unix域描述符时为要传递的fd,否则为-1,仅仅只传递上述信息
ngx_fd_t fd;
} ngx_channel_t;

传递unix域套接字详见;传递文件描述符

全局变量ngx_processes

全局变量ngx_processes存储了每一个子进程当前状态。该数据会在master进程和各个子进程间进行维护(目前子进程只需要关注自己对应的一个元素)。

1
ngx_process_t    ngx_processes[NGX_MAX_PROCESSES];

启动函数

使用ngx_start_worker_processes函数来启动子进程。其逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// n为启动子进程数量。type为启动方式
static void
ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type)
{
ngx_int_t i;
// 初始化进程间传递信息的ngx_channel_t结构
ngx_channel_t ch;

ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start worker processes");

ngx_memzero(&ch, sizeof(ngx_channel_t));
// 对应指令为打开通道,即在子进程中增加该描述符。
ch.command = NGX_CMD_OPEN_CHANNEL;
// 创建n个子进程
for (i = 0; i < n; i++) {

ngx_spawn_process(cycle, ngx_worker_process_cycle,
(void *) (intptr_t) i, "worker process", type);
/* 对ch赋值。其中ngx_process_slot为全局变量,表示上一步ngx_spawn_process创建子进程对应的进程信息存储在全局变量ngx_processes中的下标 */
ch.pid = ngx_processes[ngx_process_slot].pid;
ch.slot = ngx_process_slot;
ch.fd = ngx_processes[ngx_process_slot].channel[0];
// 向之前已经启动的进程传递用于与该循环创建的子进程进行信息传递的域套接字
ngx_pass_open_channel(cycle, &ch);
}
}

ngx_spawn_process

这里ngx_spawn_process函数为创建子进程的统一处理函数。其逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
/* proc为子进程运行的函数。data为向子进程传递的额外参数信息,name为子进程名,即操作系统中显示的进程名,当respawn为负数时,表示启动的进程类型,用于控制创建何种进程 当是正数时,表示重启对应ngx_processes下标的进程*/
ngx_pid_t
ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
char *name, ngx_int_t respawn)
{
u_long on;
ngx_pid_t pid;
// 要操作的进程在ngx_processes中的下标
ngx_int_t s;
// 如果respawn大于0,则要操作的进程即为respawn
if (respawn >= 0) {
s = respawn;

} else {
// 否则在ngx_processes数组中找到第一个未被使用的元素
for (s = 0; s < ngx_last_process; s++) {
if (ngx_processes[s].pid == -1) {
break;
}
}
// 如果超过了能够创建的最多的进程数量1024,则报错
if (s == NGX_MAX_PROCESSES) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
"no more than %d processes can be spawned",
NGX_MAX_PROCESSES);
return NGX_INVALID_PID;
}
}

// 创建的子进程不是父子进程分离新式的(父子进程分离式进程即创建的子进程运行新的二进制文件)
if (respawn != NGX_PROCESS_DETACHED) {

/* Solaris 9 still has no AF_LOCAL */
// 生成用于进程间通讯的unix域套接字
if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"socketpair() failed while spawning \"%s\"", name);
return NGX_INVALID_PID;
}

ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
"channel %d:%d",
ngx_processes[s].channel[0],
ngx_processes[s].channel[1]);
// 变更两个套接字属性都为非阻塞式,使用ioctl方法
if (ngx_nonblocking(ngx_processes[s].channel[0]) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
ngx_nonblocking_n " failed while spawning \"%s\"",
name);
ngx_close_channel(ngx_processes[s].channel, cycle->log);
return NGX_INVALID_PID;
}

if (ngx_nonblocking(ngx_processes[s].channel[1]) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
ngx_nonblocking_n " failed while spawning \"%s\"",
name);
ngx_close_channel(ngx_processes[s].channel, cycle->log);
return NGX_INVALID_PID;
}

/* 设置第一个unix域套接字为异步io。可参考如下内容:http://www.yinkuiwang.cn/2019/12/18/unix%E9%AB%98%E7%BA%A7%E7%BC%96%E7%A8%8B/#%E9%9D%9E%E9%98%BB%E5%A1%9E%E5%92%8C%E5%BC%82%E6%AD%A5I-O */
on = 1;
if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"ioctl(FIOASYNC) failed while spawning \"%s\"", name);
ngx_close_channel(ngx_processes[s].channel, cycle->log);
return NGX_INVALID_PID;
}

if (fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"fcntl(F_SETOWN) failed while spawning \"%s\"", name);
ngx_close_channel(ngx_processes[s].channel, cycle->log);
return NGX_INVALID_PID;
}
// 设置套接字是执行时关闭。默认情况下,子进程中如果执行exec函数时,依然会继承父进程的套接字。的那个设置该值时,会在子进程执行exec时在子进程中关闭套接字。注意:如果只是fork创建子进程,但没有执行exec时,不会关闭。该方法主要作用在执行新的二进制文件时,避免继承原master进程的套接字。*/
if (fcntl(ngx_processes[s].channel[0], F_SETFD, FD_CLOEXEC) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
name);
ngx_close_channel(ngx_processes[s].channel, cycle->log);
return NGX_INVALID_PID;
}

if (fcntl(ngx_processes[s].channel[1], F_SETFD, FD_CLOEXEC) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
name);
ngx_close_channel(ngx_processes[s].channel, cycle->log);
return NGX_INVALID_PID;
}
// 记录当前将要创建的子进程使用的unix域套接字,后续使用
ngx_channel = ngx_processes[s].channel[1];

} else {
// 对应执行新的二进制文件的子进程来说,不需要与旧的master进程进行通讯,因此并未使用
ngx_processes[s].channel[0] = -1;
ngx_processes[s].channel[1] = -1;
}
// 记录当前操作子进程对应存储在ngx_processes的下标
ngx_process_slot = s;

// 创建子进程
pid = fork();

switch (pid) {
// 创建失败的处理
case -1:
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"fork() failed while spawning \"%s\"", name);
ngx_close_channel(ngx_processes[s].channel, cycle->log);
return NGX_INVALID_PID;
// 子进程的处理。
case 0:
// 注意,这里ngx_parent使用的是旧master进程的pid。这时用于后续判断是否旧的master进程已退出
ngx_parent = ngx_pid;
// 获取当前子进程的pid
ngx_pid = ngx_getpid();
// 执行进程的处理函数,这里子进程一般就不会返回了。
proc(cycle, data);
break;

default:
break;
}
// 父进程的处理
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start %s %P", name, pid);
// 记录存储子进程的信息
ngx_processes[s].pid = pid;
// 恢复子进程退出状态(如果是重启子进程的时候,原本的exited为1,这里恢复为0)
ngx_processes[s].exited = 0;
// 如果是用来重启子进程,则到这里就结束了
if (respawn >= 0) {
return pid;
}
// 设置对应的ngx_processes内容
ngx_processes[s].proc = proc;
ngx_processes[s].data = data;
ngx_processes[s].name = name;
ngx_processes[s].exiting = 0;
// 对应每一种启动的进程模式,设置对应的ngx_processes成员变量
switch (respawn) {
// 子进程终止时,不需要重新生成,cache管理子进程使用
case NGX_PROCESS_NORESPAWN:
ngx_processes[s].respawn = 0;
ngx_processes[s].just_spawn = 0;
ngx_processes[s].detached = 0;
break;
// 刚启动的进程,且进程退出不需要重启,区分新建进程。cache管理进程使用
case NGX_PROCESS_JUST_SPAWN:
ngx_processes[s].respawn = 0;
ngx_processes[s].just_spawn = 1;
ngx_processes[s].detached = 0;
break;
// 子进程意外终止时,需要重新生成标识
case NGX_PROCESS_RESPAWN:
ngx_processes[s].respawn = 1;
ngx_processes[s].just_spawn = 0;
ngx_processes[s].detached = 0;
break;
// 刚启动的子进程,通过RESPAWN与旧的进行进行区分,重读配置项时使用
case NGX_PROCESS_JUST_RESPAWN:
ngx_processes[s].respawn = 1;
ngx_processes[s].just_spawn = 1;
ngx_processes[s].detached = 0;
break;
// 父子进程分离。运行新的二进制的子进程
case NGX_PROCESS_DETACHED:
ngx_processes[s].respawn = 0;
ngx_processes[s].just_spawn = 0;
ngx_processes[s].detached = 1;
break;
}
// 变更记录当前ngx_processes使用的数量
if (s == ngx_last_process) {
ngx_last_process++;
}
// 返回创建的子进程的id
return pid;
}

进程间通讯

进程之间通过unix域套接字来传递信息。但在启动函数中有一个问题,即for循环中,在前面创建的子进程将无法拥有在后面创建的子进程的套接字。例如第一个进程(即在ngx_processes中下标为0的进程),将不会有第二个进程(即在ngx_processes中下标为0的进程)中的channel(两个unix域套接字,其中第一个用于其他进程向该进程发送信息,第二个用于进程本身接收信息)信息。这样将导致子进程之间无法直接进行通讯。

虽然目前nginx架构并未使用子进程之间进行通讯(都是matser与子进程进行通讯)。但为了后续的升级,nginx已经支持了子进程之间的通讯,其原理是通过unix域套接字传递文件描述符。具体原理可参考:传递文件描述符

向之前创建的进程传递unix描述符函数逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static void
ngx_pass_open_channel(ngx_cycle_t *cycle, ngx_channel_t *ch)
{
ngx_int_t i;
// 遍历已经创建的进程
for (i = 0; i < ngx_last_process; i++) {
// 不需要给自己传递,不需要给未使用的ngx_processes传递,不需要给未创建channel的传递(运行新的二进制文件的子进程)
if (i == ngx_process_slot
|| ngx_processes[i].pid == -1
|| ngx_processes[i].channel[0] == -1)
{
continue;
}

ngx_log_debug6(NGX_LOG_DEBUG_CORE, cycle->log, 0,
"pass channel s:%i pid:%P fd:%d to s:%i pid:%P fd:%d",
ch->slot, ch->pid, ch->fd,
i, ngx_processes[i].pid,
ngx_processes[i].channel[0]);

/* TODO: NGX_AGAIN */
// 向指定的描述符ngx_processes[i].channel[0]中传递ch信息
ngx_write_channel(ngx_processes[i].channel[0],
ch, sizeof(ngx_channel_t), cycle->log);
}
}
ngx_write_channel

其中详细介绍一下向域套接字写数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
ngx_int_t
ngx_write_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size,
ngx_log_t *log)
{
ssize_t n;
ngx_err_t err;
struct iovec iov[1];
struct msghdr msg;

#if (NGX_HAVE_MSGHDR_MSG_CONTROL)

union {
struct cmsghdr cm;
char space[CMSG_SPACE(sizeof(int))];
} cmsg;
// ch->fd为-1,表示只是简单的数据传输,并不用传递文件描述符
if (ch->fd == -1) {
msg.msg_control = NULL;
msg.msg_controllen = 0;

} else {
// 添加文件描述符到外代数据中
msg.msg_control = (caddr_t) &cmsg;
msg.msg_controllen = sizeof(cmsg);

ngx_memzero(&cmsg, sizeof(cmsg));

cmsg.cm.cmsg_len = CMSG_LEN(sizeof(int));
cmsg.cm.cmsg_level = SOL_SOCKET;
cmsg.cm.cmsg_type = SCM_RIGHTS;

/*
* We have to use ngx_memcpy() instead of simple
* *(int *) CMSG_DATA(&cmsg.cm) = ch->fd;
* because some gcc 4.4 with -O2/3/s optimization issues the warning:
* dereferencing type-punned pointer will break strict-aliasing rules
*
* Fortunately, gcc with -O1 compiles this ngx_memcpy()
* in the same simple assignment as in the code above
*/

ngx_memcpy(CMSG_DATA(&cmsg.cm), &ch->fd, sizeof(int));
}

msg.msg_flags = 0;

#else

if (ch->fd == -1) {
msg.msg_accrights = NULL;
msg.msg_accrightslen = 0;

} else {
msg.msg_accrights = (caddr_t) &ch->fd;
msg.msg_accrightslen = sizeof(int);
}

#endif
// 添加数据内容
iov[0].iov_base = (char *) ch;
iov[0].iov_len = size;

msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
// 向套接字发送数据
n = sendmsg(s, &msg, 0);

if (n == -1) {
err = ngx_errno;
if (err == NGX_EAGAIN) {
return NGX_AGAIN;
}

ngx_log_error(NGX_LOG_ALERT, log, err, "sendmsg() failed");
return NGX_ERROR;
}

return NGX_OK;
}

具体发送域套接字参考:传递文件描述符

启动cache管理子进程

暂时还未详细阅读,后续补充。

设置时钟信号

对于设置时钟信号,参考https://blog.csdn.net/lixianlin/article/details/25604779

子进程退出时处理

当子进程退出时,将执行ngx_reap_children函数来进行检查。具体逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
static ngx_uint_t
ngx_reap_children(ngx_cycle_t *cycle)
{
ngx_int_t i, n;
// 记录是否还有子进程存活
ngx_uint_t live;
ngx_channel_t ch;
ngx_core_conf_t *ccf;

ngx_memzero(&ch, sizeof(ngx_channel_t));
// 向其余子进程传递信息指令:关闭通讯管道
ch.command = NGX_CMD_CLOSE_CHANNEL;
// 不需要传递文件描述符
ch.fd = -1;

live = 0;
// 遍历每个ngx_processes
for (i = 0; i < ngx_last_process; i++) {

ngx_log_debug7(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"child: %i %P e:%d t:%d d:%d r:%d j:%d",
i,
ngx_processes[i].pid,
ngx_processes[i].exiting,
ngx_processes[i].exited,
ngx_processes[i].detached,
ngx_processes[i].respawn,
ngx_processes[i].just_spawn);
// 跳过未使用的ngx_processes
if (ngx_processes[i].pid == -1) {
continue;
}
// 对于退出的进程的处理
if (ngx_processes[i].exited) {
// 进程不是运行新的二进制文件的处理
if (!ngx_processes[i].detached) {
// 关闭unix域套接字
ngx_close_channel(ngx_processes[i].channel, cycle->log);

ngx_processes[i].channel[0] = -1;
ngx_processes[i].channel[1] = -1;

ch.pid = ngx_processes[i].pid;
ch.slot = i;
// 向剩余存活的子进程传递关闭通讯通道的指令
for (n = 0; n < ngx_last_process; n++) {
// 不需要关注退出的进程,未使用的ngx_processes和未打开channel的进程
if (ngx_processes[n].exited
|| ngx_processes[n].pid == -1
|| ngx_processes[n].channel[0] == -1)
{
continue;
}

ngx_log_debug3(NGX_LOG_DEBUG_CORE, cycle->log, 0,
"pass close channel s:%i pid:%P to:%P",
ch.slot, ch.pid, ngx_processes[n].pid);

/* TODO: NGX_AGAIN */

ngx_write_channel(ngx_processes[n].channel[0],
&ch, sizeof(ngx_channel_t), cycle->log);
}
}
// 需要在终止后重启的子进程,并且当前未收到关闭服务指令,并且进程不是正在退出时的处理
if (ngx_processes[i].respawn
&& !ngx_processes[i].exiting
&& !ngx_terminate
&& !ngx_quit)
{
// 重启子进程
if (ngx_spawn_process(cycle, ngx_processes[i].proc,
ngx_processes[i].data,
ngx_processes[i].name, i)
== NGX_INVALID_PID)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
"could not respawn %s",
ngx_processes[i].name);
continue;
}

// 向其余子进程传递用于进程间通讯的域套接字
ch.command = NGX_CMD_OPEN_CHANNEL;
ch.pid = ngx_processes[ngx_process_slot].pid;
ch.slot = ngx_process_slot;
ch.fd = ngx_processes[ngx_process_slot].channel[0];

ngx_pass_open_channel(cycle, &ch);
// 记录有进程存活
live = 1;
// 跳过后续处理
continue;
}
// 如果退出的进程是执行新的二进制文件的进程
if (ngx_processes[i].pid == ngx_new_binary) {

ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,
ngx_core_module);
// 恢复pid文件,具体参考下文的执行新的二进制文件
if (ngx_rename_file((char *) ccf->oldpid.data,
(char *) ccf->pid.data)
== NGX_FILE_ERROR)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
ngx_rename_file_n " %s back to %s failed "
"after the new binary process \"%s\" exited",
ccf->oldpid.data, ccf->pid.data, ngx_argv[0]);
}
// 设置运行新的二进制文件为0
ngx_new_binary = 0;
// 止损操作。如果当前已经设置了关闭监听,则设置ngx_restart为1,重新开启worker子进程和cache管理子进程。
if (ngx_noaccepting) {
ngx_restart = 1;
ngx_noaccepting = 0;
}
}
// 如果是最后一个进程,维护ngx_last_process数值
if (i == ngx_last_process - 1) {
ngx_last_process--;

} else {
// 否则标记对应的ngx_processes未使用
ngx_processes[i].pid = -1;
}

} else if (ngx_processes[i].exiting || !ngx_processes[i].detached) {
// 正在退出算是存活状态,并且不需要考虑运行新的二进制文件的子进程
live = 1;
}
}

return live;
}

退出master进程

如果是接收到退出信号,并且所有子进程已完成退出,则会执行master进程的退出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
static void
ngx_master_process_exit(ngx_cycle_t *cycle)
{
ngx_uint_t i;
// 删除pid文件
ngx_delete_pidfile(cycle);

ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exit");
// 执行每个modules的exit_master函数
for (i = 0; cycle->modules[i]; i++) {
if (cycle->modules[i]->exit_master) {
cycle->modules[i]->exit_master(cycle);
}
}
// 关闭监听端口
ngx_close_listening_sockets(cycle);

/*
* Copy ngx_cycle->log related data to the special static exit cycle,
* log, and log file structures enough to allow a signal handler to log.
* The handler may be called when standard ngx_cycle->log allocated from
* ngx_cycle->pool is already destroyed.
*/


ngx_exit_log = *ngx_log_get_file_log(ngx_cycle->log);

ngx_exit_log_file.fd = ngx_exit_log.file->fd;
ngx_exit_log.file = &ngx_exit_log_file;
ngx_exit_log.next = NULL;
ngx_exit_log.writer = NULL;

ngx_exit_cycle.log = &ngx_exit_log;
ngx_exit_cycle.files = ngx_cycle->files;
ngx_exit_cycle.files_n = ngx_cycle->files_n;
ngx_cycle = &ngx_exit_cycle;
// 销毁内存池
ngx_destroy_pool(cycle->pool);

exit(0);
}

删除pid文件

会根据是否运行新的二进制文件来删除对应的pid文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void
ngx_delete_pidfile(ngx_cycle_t *cycle)
{
u_char *name;
ngx_core_conf_t *ccf;

ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
// 如果已经运行了新的二进制文件,则删除旧的pid文件
name = ngx_new_binary ? ccf->oldpid.data : ccf->pid.data;

if (ngx_delete_file(name) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
ngx_delete_file_n " \"%s\" failed", name);
}
}

关闭监听套接字

程序退出会关闭对套接字的监听。其处理逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
void
ngx_close_listening_sockets(ngx_cycle_t *cycle)
{
ngx_uint_t i;
ngx_listening_t *ls;
ngx_connection_t *c;
// 如果使用的事件模块为NGX_USE_IOCP_EVENT,则直接结束
if (ngx_event_flags & NGX_USE_IOCP_EVENT) {
return;
}

ngx_accept_mutex_held = 0;
ngx_use_accept_mutex = 0;

ls = cycle->listening.elts;
// 遍历每一个监听套接字
for (i = 0; i < cycle->listening.nelts; i++) {

c = ls[i].connection;
// 存在连接时处理
if (c) {
// 存在读事件时(监听依然存活)
if (c->read->active) {
// 使用epoll,删除读事件
if (ngx_event_flags & NGX_USE_EPOLL_EVENT) {

/*
* it seems that Linux-2.6.x OpenVZ sends events
* for closed shared listening sockets unless
* the events was explicitly deleted
*/

ngx_del_event(c->read, NGX_READ_EVENT, 0);

} else {
// 关闭读时间
ngx_del_event(c->read, NGX_READ_EVENT, NGX_CLOSE_EVENT);
}
}
// 释放connection
ngx_free_connection(c);

c->fd = (ngx_socket_t) -1;
}

ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
"close listening %V #%d ", &ls[i].addr_text, ls[i].fd);
// 关闭套接字
if (ngx_close_socket(ls[i].fd) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
ngx_close_socket_n " %V failed", &ls[i].addr_text);
}

#if (NGX_HAVE_UNIX_DOMAIN)
/* 删除域套接字创建的文件,只能单进程模式或者多进程模式的master进程来删除,并且当前没有新的二进制文件运行并且该域套接字不是继承而来或者不是新执行的二进制文件 */
if (ls[i].sockaddr->sa_family == AF_UNIX
&& ngx_process <= NGX_PROCESS_MASTER
&& ngx_new_binary == 0
&& (!ls[i].inherited || ngx_getppid() != ngx_parent))
{
u_char *name = ls[i].addr_text.data + sizeof("unix:") - 1;

if (ngx_delete_file(name) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
ngx_delete_file_n " %s failed", name);
}
}

#endif

ls[i].fd = (ngx_socket_t) -1;
}

cycle->listening.nelts = 0;
}

该函数用到了很多事件相关的处理,详细参考后面关于事件模块的介绍。

向子进程下发指令

master进程通过unix域套接字或者信号下发指令,函数为ngx_signal_worker_processes,其处理逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
static void
ngx_signal_worker_processes(ngx_cycle_t *cycle, int signo)
{
ngx_int_t i;
ngx_err_t err;
ngx_channel_t ch;
// 初始化传递信息的ch
ngx_memzero(&ch, sizeof(ngx_channel_t));

#if (NGX_BROKEN_SCM_RIGHTS)

ch.command = 0;

#else
// 根据信号类型,决定传递的信号
switch (signo) {

case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
ch.command = NGX_CMD_QUIT;
break;

case ngx_signal_value(NGX_TERMINATE_SIGNAL):
ch.command = NGX_CMD_TERMINATE;
break;

case ngx_signal_value(NGX_REOPEN_SIGNAL):
ch.command = NGX_CMD_REOPEN;
break;

default:
ch.command = 0;
}

#endif
// fd为-1,表示单纯传递数据
ch.fd = -1;

// 遍历每一个ngx_processes
for (i = 0; i < ngx_last_process; i++) {

ngx_log_debug7(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"child: %i %P e:%d t:%d d:%d r:%d j:%d",
i,
ngx_processes[i].pid,
ngx_processes[i].exiting,
ngx_processes[i].exited,
ngx_processes[i].detached,
ngx_processes[i].respawn,
ngx_processes[i].just_spawn);
// 如果是运行新二进制重新的子进程,或者未使用的ngx_processes,则忽略
if (ngx_processes[i].detached || ngx_processes[i].pid == -1) {
continue;
}
/* 如果是刚启动的进程,则将标识just_spawn置为0,跳过处理。这里是为了处理重读配置,先关闭旧的子进程,再将新的子进程设置为旧的子进程 */
if (ngx_processes[i].just_spawn) {
ngx_processes[i].just_spawn = 0;
continue;
}
/* 如果进程正在退出,并且收到的信号是NGX_SHUTDOWN_SIGNAL,则跳过处理。对应优雅的关闭进程,可能会下发多次NGX_SHUTDOWN_SIGNAL信号。*/
if (ngx_processes[i].exiting
&& signo == ngx_signal_value(NGX_SHUTDOWN_SIGNAL))
{
continue;
}
// 如果设置了下发信息,则通过unix域套接字下发指令
if (ch.command) {
if (ngx_write_channel(ngx_processes[i].channel[0],
&ch, sizeof(ngx_channel_t), cycle->log)
== NGX_OK)
{
// 使用unix域套接字下发的指令,除了NGX_REOPEN_SIGNAL外,其余均是关闭子进程的,因此这里设置子进程正在关闭
if (signo != ngx_signal_value(NGX_REOPEN_SIGNAL)) {
ngx_processes[i].exiting = 1;
}

continue;
}
}

ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
"kill (%P, %d)", ngx_processes[i].pid, signo);
// 如果不是通过unix域套接字传递的信号,则使用kill下子进程下发信息
if (kill(ngx_processes[i].pid, signo) == -1) {
err = ngx_errno;
ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
"kill(%P, %d) failed", ngx_processes[i].pid, signo);
// 如果是向不存在的进程下发信号,则设置对应的ngx_processes已经退出
if (err == NGX_ESRCH) {
ngx_processes[i].exited = 1;
ngx_processes[i].exiting = 0;
ngx_reap = 1;
}

continue;
}

if (signo != ngx_signal_value(NGX_REOPEN_SIGNAL)) {
ngx_processes[i].exiting = 1;
}
}
}

对应kill函数,可参考文档:kill和raise.

执行新的二进制文件

执行新的二进制文件函数ngx_exec_new_binary。其处理逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
ngx_pid_t
ngx_exec_new_binary(ngx_cycle_t *cycle, char *const *argv)
{
char **env, *var;
u_char *p;
ngx_uint_t i, n;
ngx_pid_t pid;
ngx_exec_ctx_t ctx;
ngx_core_conf_t *ccf;
ngx_listening_t *ls;
// ctx为执行新的二进制的传参
ngx_memzero(&ctx, sizeof(ngx_exec_ctx_t));
// 执行新的二进制文件的path
ctx.path = argv[0];
// 新的子进程的名字
ctx.name = "new binary process";
// 命令行参数,与旧版的请求参数一致,这里传参使用的是ngx_argv,即之前存储的启动时参数
ctx.argv = argv;
// n表示要额外申请的数组长度(除了目前已经在使用的长度外)
n = 2;
// 获取运行新的二进制文件时的环境变量表
env = ngx_set_environment(cycle, &n);
if (env == NULL) {
return NGX_INVALID_PID;
}
// 将监听端口写入要执行的二进制文件的环境变量中,这里进行分配空间
var = ngx_alloc(sizeof(NGINX_VAR)
+ cycle->listening.nelts * (NGX_INT32_LEN + 1) + 2,
cycle->log);
if (var == NULL) {
ngx_free(env);
return NGX_INVALID_PID;
}

p = ngx_cpymem(var, NGINX_VAR "=", sizeof(NGINX_VAR));
// 遍历每一个监听端口,将端口写入环境变量
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
p = ngx_sprintf(p, "%ud;", ls[i].fd);
}
// 设置字符串末尾
*p = '\0';
// 添加环境变量到环境变量表,这里的n已经在ngx_set_environment进行了变更,具体参考ngx_set_environment处理
env[n++] = var;

#if (NGX_SETPROCTITLE_USES_ENV)

/* allocate the spare 300 bytes for the new binary process title */
// 分配空间为新执行的二进制文件的title,这里不是很明白。监听端口变量和改变量是n为2的原因
env[n++] = "SPARE=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";

#endif
// 设置环境的变量的末尾
env[n] = NULL;
// config中增加--with-debug,增加运行时debug信息,打印环境变量
#if (NGX_DEBUG)
{
char **e;
for (e = env; *e; e++) {
ngx_log_debug1(NGX_LOG_DEBUG_CORE, cycle->log, 0, "env: %s", *e);
}
}
#endif
// 添加环境变量
ctx.envp = (char *const *) env;
// 通过ngx_core_module模块获取pid文件
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
// 变更pid文件,在pid文件后面增加.oldbin后缀,来保证新二进制文件的正常启动
if (ngx_rename_file(ccf->pid.data, ccf->oldpid.data) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
ngx_rename_file_n " %s to %s failed "
"before executing new binary process \"%s\"",
ccf->pid.data, ccf->oldpid.data, argv[0]);

ngx_free(env);
ngx_free(var);

return NGX_INVALID_PID;
}
// 创建新进程,运行新的二进制文件
pid = ngx_execute(cycle, &ctx);
// 运行失败,则恢复pid文件
if (pid == NGX_INVALID_PID) {
if (ngx_rename_file(ccf->oldpid.data, ccf->pid.data)
== NGX_FILE_ERROR)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
ngx_rename_file_n " %s back to %s failed after "
"an attempt to execute new binary process \"%s\"",
ccf->oldpid.data, ccf->pid.data, argv[0]);
}
}
// 释放环境变量
ngx_free(env);
ngx_free(var);
// 返回pid
return pid;
}

运行二进制文件

在ngx_execute函数中生成子进程并在子进程执行新的二进制文件。其逻辑如下:

1
2
3
4
5
6
7
ngx_pid_t
ngx_execute(ngx_cycle_t *cycle, ngx_exec_ctx_t *ctx)
{
// 这里运行的ngx_spawn_process为NGX_PROCESS_DETACHED,即父子进程分离
return ngx_spawn_process(cycle, ngx_execute_proc, ctx, ctx->name,
NGX_PROCESS_DETACHED);
}

子进程执行的函数为:

1
2
3
4
5
6
7
8
9
10
11
12
13
static void
ngx_execute_proc(ngx_cycle_t *cycle, void *data)
{
ngx_exec_ctx_t *ctx = data;
// 通过data获取执行文件的path,运行的argv和环境变量
if (execve(ctx->path, ctx->argv, ctx->envp) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"execve() failed while executing %s \"%s\"",
ctx->name, ctx->path);
}

exit(1);
}

具体execve执行函数可参考文档:exec函数。注意,这里第一个参数是path,即运行文件的路径,并不会如filename一样,在环境变量的PATH中进行查找。因此,如果运行的nginx是通过环境变量找到的时,例如将nginx可执行文件移动到/urs/bin目录下,在终端直接运行nginx生成的程序,如果要让其升级,一定会失败,即使第三个参数中存在PATH环境变量,这时由于第一个参数是path,并不会在环境变量中查找。因此运行nginx命令,一定要是完整的可执行文件路径。至于如何在第三个参数中增加PATH环境变量,下文详细介绍。

设置环境变量

默认情况下,如果运行的exec系列函数没有环境变量这一参数时,新生成的子进程是直接继承父进程的环境变量表的。具体可参考如下文档:环境表环境变量,exec函数

但由于在平滑升级时,我们要向子进程传递正在监听的文件描述符,而传输的方式是通过环境变量进行传递。注意,对于监听的文件描述符,之前并未设置为EXCECLOSE即执行时关闭,因此fork后的子进程运行exec时依然继承父进程的套接字,这时通过getsockname和getsockopt即可获取套接字上对应监听的属性,就可以在子进程中继续监听了。

这样就实现了监听描述符之间的传递,但是这也带来了一下额外的问题,由于要通过环境变量来传递套接字,这将导致子进程无法天然的基础父进程中使用的环境变量,因此我们需要将当前nginx使用的环境变量一起传递给子进程。这一步操作在ngx_set_environment完成,其处理逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/* 参数last用来区分使用创建,(当前)只有在创建子进程运行新的二进制文件时才会传递last为整数,其余均为NULL。last的大小表示除了当前进程需要使用的环境变量意外,需要额外申请的环境变量数组大小, 用于在返回后在环境变量中增加额外信息,如需要传递的套接字 */
char **
ngx_set_environment(ngx_cycle_t *cycle, ngx_uint_t *last)
{
char **p, **env;
ngx_str_t *var;
ngx_uint_t i, n;
ngx_core_conf_t *ccf;
ngx_pool_cleanup_t *cln;
// 获取ngx_core_module模块对应的配置
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
// 如果只是获取当前进程使用的环境变量,并且此前已经设置了environment变量,则直接返回即可
if (last == NULL && ccf->environment) {
return ccf->environment;
}
// 获取配置中使用到的环境变量
var = ccf->env.elts;
/* 遍历每一个配置文件中需要使用的,或者设置的环境变量,查找是否存在时区变量,即TZ,如果不存在,则在ccf中增加时区变量,这时要保证新旧进程使用同一个时间 */
for (i = 0; i < ccf->env.nelts; i++) {
if (ngx_strcmp(var[i].data, "TZ") == 0
|| ngx_strncmp(var[i].data, "TZ=", 3) == 0)
{
goto tz_found;
}
}

var = ngx_array_push(&ccf->env);
if (var == NULL) {
return NULL;
}

var->len = 2;
var->data = (u_char *) "TZ";

var = ccf->env.elts;

tz_found:
// 变量每一个配置中使用到的环境变量
n = 0;

for (i = 0; i < ccf->env.nelts; i++) {
// 如果配置中设置了环境变量的值,则直接使用配置文件中设置的即可,并记录使用的环境变量+1,跳过后续处理
if (var[i].data[var[i].len] == '=') {
n++;
continue;
}
/* 如果配置文件中未设置要使用的环境变量的值,则从环境变量表中查找,是否有对应的环境变量值,如果存在,则表示要使用,并且需要传递给子进程,这里ngx_os_environ=environ */
for (p = ngx_os_environ; *p; p++) {

if (ngx_strncmp(*p, var[i].data, var[i].len) == 0
&& (*p)[var[i].len] == '=')
{
n++;
break;
}
}
}
// 根据last创建对应的存储环境变量的字符串数组,+1为表示环境变量最后一个元素为"\0",表示终止
if (last) {
env = ngx_alloc((*last + n + 1) * sizeof(char *), cycle->log);
if (env == NULL) {
return NULL;
}
// 设置last到下一个该设置变量的数组下标,用于函数返回后的处理
*last = n;

} else {
/* 如果是单纯的获取当前使用到的环境变量,则有可能后续程序不会自动处理创建的env,因此在pool中注册销毁函数,保证在程序退出时,内存能够正常释放,具体可参考pool内存池设计 */
cln = ngx_pool_cleanup_add(cycle->pool, 0);
if (cln == NULL) {
return NULL;
}

env = ngx_alloc((n + 1) * sizeof(char *), cycle->log);
if (env == NULL) {
return NULL;
}
// 注册的销毁函数。先判断对应的data(即env),是否和environ一致,如果不是,则销毁env(因为environ程序本身会消除)
cln->handler = ngx_cleanup_environment;
cln->data = env;
}

n = 0;
// 遍历每个配置文件中使用到的环境变量和当前进程的环境表,来设置env
for (i = 0; i < ccf->env.nelts; i++) {

if (var[i].data[var[i].len] == '=') {
env[n++] = (char *) var[i].data;
continue;
}

for (p = ngx_os_environ; *p; p++) {

if (ngx_strncmp(*p, var[i].data, var[i].len) == 0
&& (*p)[var[i].len] == '=')
{
env[n++] = *p;
break;
}
}
}
// 标识当前的末尾
env[n] = NULL;
/* 如果仅仅是获取目前进程中使用的环境变量,则设置ccf->environment,避免后续重复计算。设置environ,避免后续的多余计算(只保留了当前需要使用的部分,这样后续计算会少很多)*/
if (last == NULL) {
ccf->environment = env;
environ = env;
}

return env;
}

配置中环境变量的解析

上述的程序中大量使用的ngx_core_module模块的ccf->env数据,这里有必要介绍一下环境变量配置的解析处理逻辑

环境变量的配置语法如下:

1
2
3
4
5
6
env name
env name=value

eg:
env = PATH
env = PATH=/usr/bin

对于只有name的情况,表示我们要使用name对应的系统环境变量,对应name和value组的情况,表示我们要设置的对应name的环境变量在运行时的值。env只是变更了环境变量,更改了运行时的环境变量,如果希望在处理时,将环境变量作为一个值使用,例如作为server_name使用,则还需要额外的模块进行处理(如perl和lua模块),这里不做详细介绍。

ngx_core_module模块如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
ngx_module_t  ngx_core_module = {
NGX_MODULE_V1,
&ngx_core_module_ctx, /* module context */
ngx_core_commands, /* module directives */
NGX_CORE_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};

static ngx_core_module_t ngx_core_module_ctx = {
ngx_string("core"),
ngx_core_module_create_conf,
ngx_core_module_init_conf
};

static ngx_command_t ngx_core_commands[] = {
...
{ ngx_string("env"),
NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
ngx_set_env,
0,
0,
NULL },
...
}

其中设置环境变量的函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static char *
ngx_set_env(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_core_conf_t *ccf = conf;

ngx_str_t *value, *var;
ngx_uint_t i;
// 向存储配置中使用的环境的变量的env中添加元素
var = ngx_array_push(&ccf->env);
if (var == NULL) {
return NGX_CONF_ERROR;
}
// 获取解析配置的参数
value = cf->args->elts;
// value内容
*var = value[1];
// 遍历值,找到等号的位置。如果存在,则记录等号的下标为长度,否则就是实际长度值
for (i = 0; i < value[1].len; i++) {

if (value[1].data[i] == '=') {

var->len = i;

return NGX_CONF_OK;
}
}

return NGX_CONF_OK;
}

这里有个问题是,记录的环境变量值并非一定是完整的配置文件中设置的值。这是由于配置文件中env有两个作用,一个是设置环境变量,另一个是使用环境变量。当我们只是写明了环境变量的key,如

1
env PATH

表示,我们要使用系统的PATH环境变量,其值为系统定义的值。

当我们写明的是环境变量的key和值时,表明我们要使用的环境变量,并且设置其值。如:

1
env PATH=/usr/bin

表明执行的二进制文件的PATH环境变量值为/usr/bin

如何区分二者呢,nginx就通过查看设置的值是否存在=进行区分。同时,为了方便后续使用,将len设置为等号的位置用于区分是使用环境变量还是设置环境变量。直接判断data[len]是否等于=即可。

worker进程逻辑

ngx_master_process_cycle函数会创建指定数量的的worker进程,每个进程执行的处理函数为ngx_worker_process_cycle,其执行逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// data为对应worker进程的id。从0开始连续整数
static void
ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
ngx_int_t worker = (intptr_t) data;
// 标识进程为worker进程,并记录id
ngx_process = NGX_PROCESS_WORKER;
ngx_worker = worker;
// 执行worker进程的初始化
ngx_worker_process_init(cycle, worker);
// 设置进程的title。ps时显示的进程名
ngx_setproctitle("worker process");
// 执行worker进程主循环
for ( ;; ) {
// 已经收到父进程优雅退出信号
if (ngx_exiting) {
// 判断是否所有事件均为可忽略事件,如果是则终止进程
if (ngx_event_no_timers_left() == NGX_OK) {
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
ngx_worker_process_exit(cycle);
}
}
// 记录日志
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");
// 执行事件函数
ngx_process_events_and_timers(cycle);
// 如果收到立即退出指令,则执行退出
if (ngx_terminate) {
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
ngx_worker_process_exit(cycle);
}
// 如果收到优雅退出指令,相应处理
if (ngx_quit) {
ngx_quit = 0;
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
"gracefully shutting down");
// 设置进程名
ngx_setproctitle("worker process is shutting down");
// 如果ngx_exiting不为1,则执行如下操作,不再接受请求
if (!ngx_exiting) {
ngx_exiting = 1;
ngx_set_shutdown_timer(cycle);
ngx_close_listening_sockets(cycle);
ngx_close_idle_connections(cycle);
}
}
// 如果收到重新打开日志文件的指令,执行相应操作
if (ngx_reopen) {
ngx_reopen = 0;
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
ngx_reopen_files(cycle, -1);
}
}
}

work进程初始化

执行worker循环之前,会先对worker进行初始化操作。其逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
static void
ngx_worker_process_init(ngx_cycle_t *cycle, ngx_int_t worker)
{
sigset_t set;
ngx_int_t n;
ngx_time_t *tp;
ngx_uint_t i;
ngx_cpuset_t *cpu_affinity;
struct rlimit rlmt;
ngx_core_conf_t *ccf;
ngx_listening_t *ls;
// 设置环境变量,参考master中的处理
if (ngx_set_environment(cycle, NULL) == NULL) {
/* fatal */
exit(2);
}
// 获取核心模块
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
/*
如果配置了进程优先级,则执行setpriority设置,具体可参考:http://www.yinkuiwang.cn/2019/12/18/unix%E9%AB%98%E7%BA%A7%E7%BC%96%E7%A8%8B/#%E8%BF%9B%E7%A8%8B%E8%B0%83%E5%BA%A6
*/
if (worker >= 0 && ccf->priority != 0) {
if (setpriority(PRIO_PROCESS, 0, ccf->priority) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"setpriority(%d) failed", ccf->priority);
}
}
/*
如果配置了最大打开描述符数量,则执行setrlimit设置,具体参考:
http://www.yinkuiwang.cn/2019/12/18/unix%E9%AB%98%E7%BA%A7%E7%BC%96%E7%A8%8B/#%E5%87%BD%E6%95%B0setjmp%E5%92%8Clongjmp
*/
if (ccf->rlimit_nofile != NGX_CONF_UNSET) {
rlmt.rlim_cur = (rlim_t) ccf->rlimit_nofile;
rlmt.rlim_max = (rlim_t) ccf->rlimit_nofile;

if (setrlimit(RLIMIT_NOFILE, &rlmt) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"setrlimit(RLIMIT_NOFILE, %i) failed",
ccf->rlimit_nofile);
}
}
// 如果配置了最大允许的core文件程度,则设置该值
if (ccf->rlimit_core != NGX_CONF_UNSET) {
rlmt.rlim_cur = (rlim_t) ccf->rlimit_core;
rlmt.rlim_max = (rlim_t) ccf->rlimit_core;

if (setrlimit(RLIMIT_CORE, &rlmt) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"setrlimit(RLIMIT_CORE, %O) failed",
ccf->rlimit_core);
}
}

// 获取进程的有效用户ID失败
if (geteuid() == 0) {
// 设置进程的组ID
if (setgid(ccf->group) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"setgid(%d) failed", ccf->group);
/* fatal */
exit(2);
}
// 设置附属组ID
if (initgroups(ccf->username, ccf->group) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"initgroups(%s, %d) failed",
ccf->username, ccf->group);
}

#if (NGX_HAVE_PR_SET_KEEPCAPS && NGX_HAVE_CAPABILITIES)
if (ccf->transparent && ccf->user) {
if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"prctl(PR_SET_KEEPCAPS, 1) failed");
/* fatal */
exit(2);
}
}
#endif

if (setuid(ccf->user) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"setuid(%d) failed", ccf->user);
/* fatal */
exit(2);
}

#if (NGX_HAVE_CAPABILITIES)
if (ccf->transparent && ccf->user) {
struct __user_cap_data_struct data;
struct __user_cap_header_struct header;

ngx_memzero(&header, sizeof(struct __user_cap_header_struct));
ngx_memzero(&data, sizeof(struct __user_cap_data_struct));

header.version = _LINUX_CAPABILITY_VERSION_1;
data.effective = CAP_TO_MASK(CAP_NET_RAW);
data.permitted = data.effective;

if (syscall(SYS_capset, &header, &data) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"capset() failed");
/* fatal */
exit(2);
}
}
#endif
}
// 绑定进程到指定的CPU上
if (worker >= 0) {
cpu_affinity = ngx_get_cpu_affinity(worker);

if (cpu_affinity) {
ngx_setaffinity(cpu_affinity, cycle->log);
}
}

#if (NGX_HAVE_PR_SET_DUMPABLE)

/* allow coredump after setuid() in Linux 2.4.x */
// 设置进程可以进行核心转存(即生成core文件)在收到应该执行核心转存的信号时。正常情况所有进程都是可以的,但是在重新设置了uid和gid之后,该位被清除,这里进行重新设置,具体可参考prctl的man手册
if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"prctl(PR_SET_DUMPABLE) failed");
}

#endif
// 如果设置了工作目录(配置中working_directory),则变更工作目录
if (ccf->working_directory.len) {
if (chdir((char *) ccf->working_directory.data) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"chdir(\"%s\") failed", ccf->working_directory.data);
/* fatal */
exit(2);
}
}

// 设置接收所有信号(屏蔽信号集为空)
sigemptyset(&set);

if (sigprocmask(SIG_SETMASK, &set, NULL) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"sigprocmask() failed");
}
// 获取缓存中时间
tp = ngx_timeofday();
srandom(((unsigned) ngx_pid << 16) ^ tp->sec ^ tp->msec);

/*
* disable deleting previous events for the listening sockets because
* in the worker processes there are no events at all at this point
*/
// 将每一个监听连接的老版本设置为null(继承而来的,重新执行ngx_init_cycle会将之前的套接字设置为新的套接字中的previous)
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
ls[i].previous = NULL;
}
// 执行每个模块的init_process函数。ngx_core_event_modules的init_module在这里执行,初始化事件
for (i = 0; cycle->modules[i]; i++) {
if (cycle->modules[i]->init_process) {
if (cycle->modules[i]->init_process(cycle) == NGX_ERROR) {
/* fatal */
exit(2);
}
}
}

/* 关闭其他进程中与master进行通讯的unix域套接字(仅保留当前自己进程的,通过ngx_process_slot来判断是否为自己使用的数组,改字段从master进程继承而来)*/
for (n = 0; n < ngx_last_process; n++) {

if (ngx_processes[n].pid == -1) {
continue;
}

if (n == ngx_process_slot) {
continue;
}

if (ngx_processes[n].channel[1] == -1) {
continue;
}

if (close(ngx_processes[n].channel[1]) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"close() channel failed");
}
}
// 关闭master的发送端套接字
if (close(ngx_processes[ngx_process_slot].channel[0]) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"close() channel failed");
}

#if 0
ngx_last_process = 0;
#endif

// 添加监听与master通讯管道事件
if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT,
ngx_channel_handler)
== NGX_ERROR)
{
/* fatal */
exit(2);
}
}

绑定进程到指定CPU

进程绑定 CPU 的好处:在多核 CPU 结构中,每个核心有各自的L1、L2缓存,而L3缓存是共用的。如果一个进程在核心间来回切换,各个核心的缓存命中率就会受到影响。相反如果进程不管如何调度,都始终可以在一个核心上执行,那么其数据的L1、L2 缓存的命中率可以显著提高。

所以,将进程与 CPU 进行绑定可以提高 CPU 缓存的命中率,从而提高性能。而进程与 CPU 绑定被称为:CPU 亲和性

linux使用sched_setaffinity系统调用实现:

1
int sched_setaffinity(pid_t pid, size_t cpusetsize, const cpu_set_t *mask);

pid为要设置的进程id,如果是0,则是调用进程本身的进程id。

cpusetsize为mask参数的大小。

mask参数是一个位图,每个位对应一个CPU,当某个位置1时,指示进程绑定到对应CPU上运行,一个进程可以绑定多个CPU。

如下函数检查和设置mask:

1
2
3
4
5
6
7
8
9
10
11
typedef struct
{
__cpu_mask __bits[__CPU_SETSIZE / __NCPUBITS];
} cpu_set_t;

// 初始化一个空的cpu_set_t
CPU_ZERO(cpu_set_t cpusetp);
// 设置cpu的位置1
CPU_SET(int cpu, cpu_set_t cpusetp)
// 判断是否对应cpu位置1了
CPU_ISSET(int cpu, cpu_set_t cpusetp)

下面看一下nginx中设置进程绑定到cpu上。

配置解析

在ngx_core_module模块中进行解析。

通过配置worker_cpu_affinity来设置绑定关系。该值也可以是auto,其解析逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
{ ngx_string("worker_cpu_affinity"),
NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_1MORE,
ngx_set_cpu_affinity,
0,
0,
NULL },

static char *
ngx_set_cpu_affinity(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
#if (NGX_HAVE_CPU_AFFINITY)
ngx_core_conf_t *ccf = conf;

u_char ch, *p;
ngx_str_t *value;
ngx_uint_t i, n;
ngx_cpuset_t *mask;
// 重复设置
if (ccf->cpu_affinity) {
return "is duplicate";
}
// 分配mask数组,数量是参数数量减一(减去worker_cpu_affinity)
mask = ngx_palloc(cf->pool, (cf->args->nelts - 1) * sizeof(ngx_cpuset_t));
if (mask == NULL) {
return NGX_CONF_ERROR;
}
// 设置的cpu绑定数量
ccf->cpu_affinity_n = cf->args->nelts - 1;
// 每个cpu绑定的位图
ccf->cpu_affinity = mask;

value = cf->args->elts;
// 如果参数是auto
if (ngx_strcmp(value[1].data, "auto") == 0) {
// 参数大于三个,则错误
if (cf->args->nelts > 3) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid number of arguments in "
"\"worker_cpu_affinity\" directive");
return NGX_CONF_ERROR;
}
// 设置auto标识
ccf->cpu_affinity_auto = 1;
// 设置mask0,为所有位均置1
CPU_ZERO(&mask[0]);
for (i = 0; i < (ngx_uint_t) ngx_min(ngx_ncpu, CPU_SETSIZE); i++) {
CPU_SET(i, &mask[0]);
}
// n=2,通过下面的循环处理
n = 2;

} else {
n = 1;
}
// 处理绑定参数
for ( /* void */ ; n < cf->args->nelts; n++) {
// 如果参数长度大于位图字段长度,则是错误
if (value[n].len > CPU_SETSIZE) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"\"worker_cpu_affinity\" supports up to %d CPUs only",
CPU_SETSIZE);
return NGX_CONF_ERROR;
}
// 设置位图,反向遍历,进行绑定
i = 0;
CPU_ZERO(&mask[n - 1]);

for (p = value[n].data + value[n].len - 1;
p >= value[n].data;
p--)
{
ch = *p;

if (ch == ' ') {
continue;
}

i++;

if (ch == '0') {
continue;
}

if (ch == '1') {
CPU_SET(i - 1, &mask[n - 1]);
continue;
}

ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid character \"%c\" in \"worker_cpu_affinity\"",
ch);
return NGX_CONF_ERROR;
}
}

#else

ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
"\"worker_cpu_affinity\" is not supported "
"on this platform, ignored");
#endif

return NGX_CONF_OK;
}

获取进程绑定的cpu

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
ngx_cpuset_t *
ngx_get_cpu_affinity(ngx_uint_t n)
{
#if (NGX_HAVE_CPU_AFFINITY)
ngx_uint_t i, j;
ngx_cpuset_t *mask;
ngx_core_conf_t *ccf;

static ngx_cpuset_t result;

ccf = (ngx_core_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
ngx_core_module);
// 如果未设置cpu绑定,则不执行
if (ccf->cpu_affinity == NULL) {
return NULL;
}
// 如果是自动绑定
if (ccf->cpu_affinity_auto) {
// 获取被全部置为的mask
mask = &ccf->cpu_affinity[ccf->cpu_affinity_n - 1];
// 遍历找到n对应的cpu设置标志位
for (i = 0, j = n; /* void */ ; i++) {

if (CPU_ISSET(i % CPU_SETSIZE, mask) && j-- == 0) {
break;
}

if (i == CPU_SETSIZE && j == n) {
/* empty mask */
return NULL;
}

/* void */
}

CPU_ZERO(&result);
CPU_SET(i % CPU_SETSIZE, &result);

return &result;
}
// 如果设置的绑定关系小于当前worker的变换,则直接返回即可
if (ccf->cpu_affinity_n > n) {
return &ccf->cpu_affinity[n];
}
// 否则取最后一个设置的绑定关系
return &ccf->cpu_affinity[ccf->cpu_affinity_n - 1];

#else

return NULL;

#endif
}

设置绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void
ngx_setaffinity(ngx_cpuset_t *cpu_affinity, ngx_log_t *log)
{
ngx_uint_t i;
// 打印日志
for (i = 0; i < CPU_SETSIZE; i++) {
if (CPU_ISSET(i, cpu_affinity)) {
ngx_log_error(NGX_LOG_NOTICE, log, 0,
"sched_setaffinity(): using cpu #%ui", i);
}
}
// 执行绑定
if (sched_setaffinity(0, sizeof(cpu_set_t), cpu_affinity) == -1) {
ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
"sched_setaffinity() failed");
}
}

unix域套接字通信事件

在执行初始化的最后,会将与其他进程交互的套接字事件添加进入事件监控中。其逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
ngx_int_t
ngx_add_channel_event(ngx_cycle_t *cycle, ngx_fd_t fd, ngx_int_t event,
ngx_event_handler_pt handler)
{
ngx_event_t *ev, *rev, *wev;
ngx_connection_t *c;
// 获取一个空闲连接
c = ngx_get_connection(fd, cycle->log);

if (c == NULL) {
return NGX_ERROR;
}

c->pool = cycle->pool;

rev = c->read;
wev = c->write;

rev->log = cycle->log;
wev->log = cycle->log;
// 设置事件为unix域套接字
rev->channel = 1;
wev->channel = 1;
// 这里事件始终为读事件
ev = (event == NGX_READ_EVENT) ? rev : wev;
// 设置事件处理函数
ev->handler = handler;
// epoll事件驱动,将连接添加进入事件驱动中,其中ngx_add_conn就是epoll模块的ngx_epoll_add_connection
if (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0) {
if (ngx_add_conn(c) == NGX_ERROR) {
ngx_free_connection(c);
return NGX_ERROR;
}

} else {
if (ngx_add_event(ev, event, 0) == NGX_ERROR) {
ngx_free_connection(c);
return NGX_ERROR;
}
}

return NGX_OK;
}

ngx_channel_handler可读事件触发时处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
static void
ngx_channel_handler(ngx_event_t *ev)
{
ngx_int_t n;
ngx_channel_t ch;
ngx_connection_t *c;
// 如果事件已经超时,则跳过处理
if (ev->timedout) {
ev->timedout = 0;
return;
}
// 获取事件对应连接,这里是已经恢复末尾为0的指针
c = ev->data;

ngx_log_debug0(NGX_LOG_DEBUG_CORE, ev->log, 0, "channel handler");

for ( ;; ) {
// 从unix域套接字中读取内容,查看master处理对应函数
n = ngx_read_channel(c->fd, &ch, sizeof(ngx_channel_t), ev->log);

ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0, "channel: %i", n);
// 如果发送错误
if (n == NGX_ERROR) {
// 使用epoll,则从事件驱动中删除
if (ngx_event_flags & NGX_USE_EPOLL_EVENT) {
ngx_del_conn(c, 0);
}
/* 关闭连接,并释放。这样将导致无法与master进程通过套接字通讯。这时如果master进程要关闭该worker进程,只能使用kill指令 */
ngx_close_connection(c);
return;
}
// 非epoll处理
if (ngx_event_flags & NGX_USE_EVENTPORT_EVENT) {
if (ngx_add_event(ev, NGX_READ_EVENT, 0) == NGX_ERROR) {
return;
}
}
// 如果是NGX_AGAIN表示未读取完成,下一轮继续读取
if (n == NGX_AGAIN) {
return;
}

ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0,
"channel command: %ui", ch.command);
// 完成数据获取处理,判断传输指令
switch (ch.command) {
// 优雅退出指令,quit置1
case NGX_CMD_QUIT:
ngx_quit = 1;
break;
// 强制退出指令
case NGX_CMD_TERMINATE:
ngx_terminate = 1;
break;
// 重新打开打开的文件
case NGX_CMD_REOPEN:
ngx_reopen = 1;
break;
// 打开其他进程的unix域套接字,传递文件描述符使用。详情查看master中进程间通讯
case NGX_CMD_OPEN_CHANNEL:

ngx_log_debug3(NGX_LOG_DEBUG_CORE, ev->log, 0,
"get channel s:%i pid:%P fd:%d",
ch.slot, ch.pid, ch.fd);

ngx_processes[ch.slot].pid = ch.pid;
ngx_processes[ch.slot].channel[0] = ch.fd;
break;
// 关闭套接字
case NGX_CMD_CLOSE_CHANNEL:

ngx_log_debug4(NGX_LOG_DEBUG_CORE, ev->log, 0,
"close channel s:%i pid:%P our:%P fd:%d",
ch.slot, ch.pid, ngx_processes[ch.slot].pid,
ngx_processes[ch.slot].channel[0]);

if (close(ngx_processes[ch.slot].channel[0]) == -1) {
ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
"close() channel failed");
}

ngx_processes[ch.slot].channel[0] = -1;
break;
}
}
}

读取unix域数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
ngx_int_t
ngx_read_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size, ngx_log_t *log)
{
ssize_t n;
ngx_err_t err;
struct iovec iov[1];
struct msghdr msg;

#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
union {
struct cmsghdr cm;
char space[CMSG_SPACE(sizeof(int))];
} cmsg;
#else
int fd;
#endif

iov[0].iov_base = (char *) ch;
iov[0].iov_len = size;

msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = iov;
msg.msg_iovlen = 1;

#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
msg.msg_control = (caddr_t) &cmsg;
msg.msg_controllen = sizeof(cmsg);
#else
msg.msg_accrights = (caddr_t) &fd;
msg.msg_accrightslen = sizeof(int);
#endif

n = recvmsg(s, &msg, 0);

if (n == -1) {
err = ngx_errno;
if (err == NGX_EAGAIN) {
return NGX_AGAIN;
}

ngx_log_error(NGX_LOG_ALERT, log, err, "recvmsg() failed");
return NGX_ERROR;
}

if (n == 0) {
ngx_log_debug0(NGX_LOG_DEBUG_CORE, log, 0, "recvmsg() returned zero");
return NGX_ERROR;
}

if ((size_t) n < sizeof(ngx_channel_t)) {
ngx_log_error(NGX_LOG_ALERT, log, 0,
"recvmsg() returned not enough data: %z", n);
return NGX_ERROR;
}

#if (NGX_HAVE_MSGHDR_MSG_CONTROL)

if (ch->command == NGX_CMD_OPEN_CHANNEL) {

if (cmsg.cm.cmsg_len < (socklen_t) CMSG_LEN(sizeof(int))) {
ngx_log_error(NGX_LOG_ALERT, log, 0,
"recvmsg() returned too small ancillary data");
return NGX_ERROR;
}

if (cmsg.cm.cmsg_level != SOL_SOCKET || cmsg.cm.cmsg_type != SCM_RIGHTS)
{
ngx_log_error(NGX_LOG_ALERT, log, 0,
"recvmsg() returned invalid ancillary data "
"level %d or type %d",
cmsg.cm.cmsg_level, cmsg.cm.cmsg_type);
return NGX_ERROR;
}

/* ch->fd = *(int *) CMSG_DATA(&cmsg.cm); */

ngx_memcpy(&ch->fd, CMSG_DATA(&cmsg.cm), sizeof(int));
}

if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) {
ngx_log_error(NGX_LOG_ALERT, log, 0,
"recvmsg() truncated data");
}

#else

if (ch->command == NGX_CMD_OPEN_CHANNEL) {
if (msg.msg_accrightslen != sizeof(int)) {
ngx_log_error(NGX_LOG_ALERT, log, 0,
"recvmsg() returned no ancillary data");
return NGX_ERROR;
}

ch->fd = fd;
}

#endif

return n;
}

ngx_worker_process_exit进程退出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
static void
ngx_worker_process_exit(ngx_cycle_t *cycle)
{
ngx_uint_t i;
ngx_connection_t *c;
// 执行每个模块的exit_process方法
for (i = 0; cycle->modules[i]; i++) {
if (cycle->modules[i]->exit_process) {
cycle->modules[i]->exit_process(cycle);
}
}
// 如果是优雅的关闭连接
if (ngx_exiting) {
c = cycle->connections;
for (i = 0; i < cycle->connection_n; i++) {
if (c[i].fd != -1
&& c[i].read
&& !c[i].read->accept
&& !c[i].read->channel
&& !c[i].read->resolver)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
"*%uA open socket #%d left in connection %ui",
c[i].number, c[i].fd, i);
ngx_debug_quit = 1;
}
}

if (ngx_debug_quit) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "aborting");
ngx_debug_point();
}
}

/*
* Copy ngx_cycle->log related data to the special static exit cycle,
* log, and log file structures enough to allow a signal handler to log.
* The handler may be called when standard ngx_cycle->log allocated from
* ngx_cycle->pool is already destroyed.
*/

ngx_exit_log = *ngx_log_get_file_log(ngx_cycle->log);

ngx_exit_log_file.fd = ngx_exit_log.file->fd;
ngx_exit_log.file = &ngx_exit_log_file;
ngx_exit_log.next = NULL;
ngx_exit_log.writer = NULL;

ngx_exit_cycle.log = &ngx_exit_log;
ngx_exit_cycle.files = ngx_cycle->files;
ngx_exit_cycle.files_n = ngx_cycle->files_n;
ngx_cycle = &ngx_exit_cycle;

ngx_destroy_pool(cycle->pool);

ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0, "exit");

exit(0);
}

ngx_set_shutdown_timer设置关机时间

当配置的show_down字段时,会在优雅的关机时增加一个超时时间,用于加快关机,其逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void
ngx_set_shutdown_timer(ngx_cycle_t *cycle)
{
ngx_core_conf_t *ccf;

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

if (ccf->shutdown_timeout) {
ngx_shutdown_event.handler = ngx_shutdown_timer_handler;
ngx_shutdown_event.data = cycle;
ngx_shutdown_event.log = cycle->log;
ngx_shutdown_event.cancelable = 1;
// 将事件添加到时间驱动中
ngx_add_timer(&ngx_shutdown_event, ccf->shutdown_timeout);
}
}

其中handler处理函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static void
ngx_shutdown_timer_handler(ngx_event_t *ev)
{
ngx_uint_t i;
ngx_cycle_t *cycle;
ngx_connection_t *c;

cycle = ev->data;

c = cycle->connections;

for (i = 0; i < cycle->connection_n; i++) {
// 获取每一个普通的,非accept的和用于进程间通讯的连接
if (c[i].fd == (ngx_socket_t) -1
|| c[i].read == NULL
|| c[i].read->accept
|| c[i].read->channel
|| c[i].read->resolver)
{
continue;
}

ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0,
"*%uA shutdown timeout", c[i].number);
// 将连接关闭
c[i].close = 1;
c[i].error = 1;
// 执行对应的读事件的处理函数,以加快关机
c[i].read->handler(c[i].read);
}
}

ngx_close_listening_sockets关闭正在监听套接字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
void
ngx_close_listening_sockets(ngx_cycle_t *cycle)
{
ngx_uint_t i;
ngx_listening_t *ls;
ngx_connection_t *c;

if (ngx_event_flags & NGX_USE_IOCP_EVENT) {
return;
}
// 设置负载均衡锁相关全局变量
ngx_accept_mutex_held = 0;
ngx_use_accept_mutex = 0;

// 遍历每个连接
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {

c = ls[i].connection;

if (c) {
// 读事件为活跃状态
if (c->read->active) {
// 从事件驱动中删除
if (ngx_event_flags & NGX_USE_EPOLL_EVENT) {

/*
* it seems that Linux-2.6.x OpenVZ sends events
* for closed shared listening sockets unless
* the events was explicitly deleted
*/

ngx_del_event(c->read, NGX_READ_EVENT, 0);

} else {
ngx_del_event(c->read, NGX_READ_EVENT, NGX_CLOSE_EVENT);
}
}
// 释放连接
ngx_free_connection(c);
// 设置fd为-1
c->fd = (ngx_socket_t) -1;
}

ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
"close listening %V #%d ", &ls[i].addr_text, ls[i].fd);
// 关闭套接字
if (ngx_close_socket(ls[i].fd) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
ngx_close_socket_n " %V failed", &ls[i].addr_text);
}

#if (NGX_HAVE_UNIX_DOMAIN)
/* 如果是unix域套接字,并且是master进程或单进程模式运行,并且当前没有新的二进制文件被执行,并且不是继承而来的套接字或者是新运行的二进制程序,则删除对应的文件,这里复杂的判断是避免误删 */
if (ls[i].sockaddr->sa_family == AF_UNIX
&& ngx_process <= NGX_PROCESS_MASTER
&& ngx_new_binary == 0
&& (!ls[i].inherited || ngx_getppid() != ngx_parent))
{
u_char *name = ls[i].addr_text.data + sizeof("unix:") - 1;

if (ngx_delete_file(name) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
ngx_delete_file_n " %s failed", name);
}
}

#endif

ls[i].fd = (ngx_socket_t) -1;
}

cycle->listening.nelts = 0;
}

ngx_close_idle_connections关闭空闲连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void
ngx_close_idle_connections(ngx_cycle_t *cycle)
{
ngx_uint_t i;
ngx_connection_t *c;

c = cycle->connections;

for (i = 0; i < cycle->connection_n; i++) {

/* THREAD: lock */
// 连接正在使用,并且是空闲的,则关闭连接,执行对应的读事件
if (c[i].fd != (ngx_socket_t) -1 && c[i].idle) {
c[i].close = 1;
c[i].read->handler(c[i].read);
}
}
}

ngx_reopen_files重新打开日志文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
void
ngx_reopen_files(ngx_cycle_t *cycle, ngx_uid_t user)
{
ngx_fd_t fd;
ngx_uint_t i;
ngx_list_part_t *part;
ngx_open_file_t *file;
// 打开的文件都会在open_files中存储
part = &cycle->open_files.part;
file = part->elts;
// 变量所有文件
for (i = 0; /* void */ ; i++) {

if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
file = part->elts;
i = 0;
}

if (file[i].name.len == 0) {
continue;
}
// 如果含义flush刷新操作,则执行对应函数
if (file[i].flush) {
file[i].flush(&file[i], cycle->log);
}
// 重新打开文件
fd = ngx_open_file(file[i].name.data, NGX_FILE_APPEND,
NGX_FILE_CREATE_OR_OPEN, NGX_FILE_DEFAULT_ACCESS);

ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"reopen file \"%s\", old:%d new:%d",
file[i].name.data, file[i].fd, fd);

if (fd == NGX_INVALID_FILE) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
ngx_open_file_n " \"%s\" failed", file[i].name.data);
continue;
}

#if !(NGX_WIN32)
// 设置对应的文件属性
if (user != (ngx_uid_t) NGX_CONF_UNSET_UINT) {
ngx_file_info_t fi;

if (ngx_file_info(file[i].name.data, &fi) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
ngx_file_info_n " \"%s\" failed",
file[i].name.data);

if (ngx_close_file(fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
ngx_close_file_n " \"%s\" failed",
file[i].name.data);
}

continue;
}

if (fi.st_uid != user) {
if (chown((const char *) file[i].name.data, user, -1) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"chown(\"%s\", %d) failed",
file[i].name.data, user);

if (ngx_close_file(fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
ngx_close_file_n " \"%s\" failed",
file[i].name.data);
}

continue;
}
}

if ((fi.st_mode & (S_IRUSR|S_IWUSR)) != (S_IRUSR|S_IWUSR)) {

fi.st_mode |= (S_IRUSR|S_IWUSR);

if (chmod((const char *) file[i].name.data, fi.st_mode) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"chmod() \"%s\" failed", file[i].name.data);

if (ngx_close_file(fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
ngx_close_file_n " \"%s\" failed",
file[i].name.data);
}

continue;
}
}
}
// 设置执行时关闭
if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"fcntl(FD_CLOEXEC) \"%s\" failed",
file[i].name.data);

if (ngx_close_file(fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
ngx_close_file_n " \"%s\" failed",
file[i].name.data);
}

continue;
}
#endif
// 关闭原文件描述符
if (ngx_close_file(file[i].fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
ngx_close_file_n " \"%s\" failed",
file[i].name.data);
}
// 赋值新的文件描述符
file[i].fd = fd;
}
// 重定向标准输出
(void) ngx_log_redirect_stderr(cycle);
}

在使用open打开文件时,使用O_APPEND,保证多进程输入不会发送混乱。

在执行日志回滚时,应该先将旧文件移动到新的位置,在向master进程发送reopen信号。这时处理逻辑是,会先将缓冲区的内容写到旧的文件中。然后重新打开文件时发现文件不存在,新建文件,之后再关闭旧的文件描述符,之后使用新的文件描述符。

ngx_process_events_and_timers事件处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
void
ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
ngx_uint_t flags;
ngx_msec_t timer, delta;
// 如果设置了时钟触发,则设置timer为未定义
if (ngx_timer_resolution) {
timer = NGX_TIMER_INFINITE;
flags = 0;

} else {
// 设置更新时间,并设置epoll_wait超时时间为最近一个事件将要触发的时间,具体查看事件章节定时器事件
timer = ngx_event_find_timer();
flags = NGX_UPDATE_TIME;

#if (NGX_WIN32)

/* handle signals from master in case of network inactivity */

if (timer == NGX_TIMER_INFINITE || timer > 500) {
timer = 500;
}

#endif
}
// 如果使用负载均衡锁
if (ngx_use_accept_mutex) {
/* 如果ngx_accept_disabled大于0,则表明当前进程较为繁忙,不再接收连接,并将ngx_accept_disabled减一,直到到非正数才获取连接
初始化ngx_accept_disabled为0,每个连接事件发生时,处理函数中会根据当前连接数量重新对该值赋值。具体可以查看http处理
*/
if (ngx_accept_disabled > 0) {
ngx_accept_disabled--;

} else {
// 尝试获取锁,并将监听连接的套接字添加到事件驱动中
if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
return;
}
// 如果获取到了锁,则为了加快锁的释放,让其他进程能够获取到锁,所有事件触发函数放到post队列中,延后执行
if (ngx_accept_mutex_held) {
flags |= NGX_POST_EVENTS;

} else {
// 否则,如果时间是未定义的,则设置下一次epoll_wait的超时事件为获取负载均衡锁的最小间隔时间,保证下次尝试获取负载均衡锁与本次的时间间隔满足要求。
if (timer == NGX_TIMER_INFINITE
|| timer > ngx_accept_mutex_delay)
{
timer = ngx_accept_mutex_delay;
}
}
}
}

// 如果ngx_posted_next_events队列不为空,则将ngx_posted_next_events队列数据添加到ngx_posted_events队列中
if (!ngx_queue_empty(&ngx_posted_next_events)) {
ngx_event_move_posted_next(cycle);
timer = 0;
}
// 记录当前缓存事件
delta = ngx_current_msec;
/*
#define ngx_process_events ngx_event_actions.process_events
执行事件模块处理,对应epoll模块的ngx_epoll_process_events方法
*/
(void) ngx_process_events(cycle, timer, flags);
// 因为上一步可能会更新时间,判断时间差值
delta = ngx_current_msec - delta;

ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"timer delta: %M", delta);
// 处理ngx_posted_accept_events队列事件,其中执行待连接请求
ngx_event_process_posted(cycle, &ngx_posted_accept_events);
// 如果持有负载均衡锁,则释放,这里并未将ngx_accept_mutex_held置0,而是在下次尝试获取锁时置0
if (ngx_accept_mutex_held) {
ngx_shmtx_unlock(&ngx_accept_mutex);
}
// 如果时间发生变更,则执行事件红黑树中已经触发的所有事件,详情查看事件章节的定时器事件
if (delta) {
ngx_event_expire_timers();
}
// 执行ngx_posted_events队列中事件
ngx_event_process_posted(cycle, &ngx_posted_events);
}

负载均衡主要通过原子变量ngx_use_accept_mutex和ngx_accept_disabled控制。ngx_accept_disabled是一个整数,初始化为0,每次新连接建立时会根据当前连接数量对该值赋值:ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n即所有连接数的百分之一减去当前空闲连接。当负载过高时,空闲连接将会减少,当八分之七的连接已经使用时,该值将变成正值,这时循环中将不再获取负载均衡锁,即不再建立新的连接,而是将该值减一,直到到0才继续接收连接。

ngx_trylock_accept_mutex获取负载均衡锁

该方法会尝试获取负载均衡锁,并将监听套接字对应事件添加到事件驱动中。其逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
ngx_int_t
ngx_trylock_accept_mutex(ngx_cycle_t *cycle)
{
// 成功获取到负载均衡锁。具体逻辑查看锁机制
if (ngx_shmtx_trylock(&ngx_accept_mutex)) {

ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"accept mutex locked");
// ngx_accept_events非epoll模块使用的变量,不用考虑,如果当前已经持有负载均衡锁,则直接返回成功
if (ngx_accept_mutex_held && ngx_accept_events == 0) {
return NGX_OK;
}
// 如果之前没有获取到负载均衡锁,则将监听事件加入到epoll中,如果加入失败则释放锁
if (ngx_enable_accept_events(cycle) == NGX_ERROR) {
ngx_shmtx_unlock(&ngx_accept_mutex);
return NGX_ERROR;
}

ngx_accept_events = 0;
// 设置持有负载均衡锁
ngx_accept_mutex_held = 1;

return NGX_OK;
}

ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"accept mutex lock failed: %ui", ngx_accept_mutex_held);
// 如果没有获取到负载均衡锁,但是记录已经获取到,则将不应该监听的套接字事件从epoll中删除,并表示未持有
if (ngx_accept_mutex_held) {
if (ngx_disable_accept_events(cycle, 0) == NGX_ERROR) {
return NGX_ERROR;
}

ngx_accept_mutex_held = 0;
}

return NGX_OK;
}

注意在每次循环中,如果获取到负载均衡锁了,只会是否锁,并不会将监听事件从epoll中去除。只会在下一次循环中,未获取到负载均衡锁时才从中删除。这应该是处于效率考量的,如果其他进程负载都交高是,某一个负载较低的进程则很可能在多次循环中都能够获取到负载均衡锁,这时采用上述方法就不用每次都执行添加事件和删除事件了。

ngx_enable_accept_events添加监听套接字对应事件到事件驱动模块

注意不是所有监听套接字都需要使用该方法进行添加。对于不使用负载均衡锁来说,不用该方法。对应设置了端口可复用的套接字来说,会为每一个进程拷贝一份监听套接字,每个进程单独进行监听,操作系统提供负载均衡操作(具体查看事件模块的ngx_event_core_module模块和ngx_events_module模块的介绍)。

其处理逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
ngx_int_t
ngx_enable_accept_events(ngx_cycle_t *cycle)
{
ngx_uint_t i;
ngx_listening_t *ls;
ngx_connection_t *c;

ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {

c = ls[i].connection;
/*
对于支持端口复用的监听来说,如果不是为当前进程生成的ls,则对应的connection为空
对于为当前进程创建的ls结构来说,其读事件已经被加入到事件驱动中,其active为true
因此这里只是加入之前未被加入需要监听的套接字对应事件
*/
if (c == NULL || c->read->active) {
continue;
}

if (ngx_add_event(c->read, NGX_READ_EVENT, 0) == NGX_ERROR) {
return NGX_ERROR;
}
}

return NGX_OK;
}

ngx_disable_accept_events从事件驱动中删除监听套接字对应事件

与添加一样,也是只能删除该删除的,对应端口复用的不能删除:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// all如果为1,则是删除所有时间
static ngx_int_t
ngx_disable_accept_events(ngx_cycle_t *cycle, ngx_uint_t all)
{
ngx_uint_t i;
ngx_listening_t *ls;
ngx_connection_t *c;

ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {

c = ls[i].connection;

if (c == NULL || !c->read->active) {
continue;
}

#if (NGX_HAVE_REUSEPORT)

/*
* do not disable accept on worker's own sockets
* when disabling accept events due to accept mutex
*/
// 如果是支持端口复用的,并且不是要删除所有事件,则跳过
if (ls[i].reuseport && !all) {
continue;
}

#endif

if (ngx_del_event(c->read, NGX_READ_EVENT, NGX_DISABLE_EVENT)
== NGX_ERROR)
{
return NGX_ERROR;
}
}

return NGX_OK;
}

ngx_event_process_posted执行post队列中时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void
ngx_event_process_posted(ngx_cycle_t *cycle, ngx_queue_t *posted)
{
ngx_queue_t *q;
ngx_event_t *ev;
// 变量每个posted队列
while (!ngx_queue_empty(posted)) {
// 获取第一个元素
q = ngx_queue_head(posted);
// 获取对应的事件
ev = ngx_queue_data(q, ngx_event_t, queue);

ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"posted event %p", ev);
// 从队列中删除当前事件
ngx_delete_posted_event(ev);
// 执行事件对应的处理函数
ev->handler(ev);
}
}

HTTP请求处理流程

在worker进程启动时会调用事件核心模块,监听网络请求,在接收到请求和首先会执行ngx_event_accept监听事件回调方法。下面就按照该方法逐一讲解http处理流程。

主要结构

ngx_http_request_t请求结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263