Nginx的奇幻冒险:从编译开始

前言

之前的文章简单地分析了nginx中的内存池分配方法,于是就想着写一系列关于nginx的文章,最好能讲清楚nginx中最核心的内容——nginx是怎么处理用户连接的,为什么nginx能处理上万的并发量,web服务器的原理等等。。。这个系列的名字就叫nginx的奇幻冒险吧٩(•̤̀ᵕ•̤́๑)

下载nginx

我用的系统是Ubuntu 20.04.6 LTS,下载nginx官方提供的包进行解压(下载地址),我安装的版本是1.16.1。执行目录下的configure脚本,默认情况下需要安装两个依赖,zlib和pcre。前者已经安装完毕,后者需要安装dev版本,直接用apt找会定位不到。

sudo apt install libpcre3-dev

执行configure

./configure --with-debug --with-cc-opt=”-g -O0”

我为了调试添加了debug和额外的编译器参数-g-O0,这两个参数主要为了让gdb调试起来更为直观,避免编译器优化导致无法追溯源码。以下是configure脚本执行后的结果,都是默认的路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Configuration summary
+ using system PCRE library
+ OpenSSL library is not used
+ using system zlib library

nginx path prefix: "/usr/local/nginx"
nginx binary file: "/usr/local/nginx/sbin/nginx"
nginx modules path: "/usr/local/nginx/modules"
nginx configuration prefix: "/usr/local/nginx/conf"
nginx configuration file: "/usr/local/nginx/conf/nginx.conf"
nginx pid file: "/usr/local/nginx/logs/nginx.pid"
nginx error log file: "/usr/local/nginx/logs/error.log"
nginx http access log file: "/usr/local/nginx/logs/access.log"
nginx http client request body temporary files: "client_body_temp"
nginx http proxy temporary files: "proxy_temp"
nginx http fastcgi temporary files: "fastcgi_temp"
nginx http uwsgi temporary files: "uwsgi_temp"
nginx http scgi temporary files: "scgi_temp"

生成makefile

执行完毕之后会生成一个makefile文件和一个objs文件夹,根目录下的Makefile文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
default: build

clean:
rm -rf Makefile objs

build:
$(MAKE) -f objs/Makefile

install:
$(MAKE) -f objs/Makefile install

modules:
$(MAKE) -f objs/Makefile modules

upgrade:
/usr/local/nginx/sbin/nginx -t

kill -USR2 `cat /usr/local/nginx/logs/nginx.pid`
sleep 1
test -f /usr/local/nginx/logs/nginx.pid.oldbin

kill -QUIT `cat /usr/local/nginx/logs/nginx.pid.oldbin`

基本的Makefile文件格式为

名称: 依赖
指令

执行 make && make install就可以安装自己编译的nginx了。makefile会依次执行objs里makefile中的make和install。
objs里的Makefile会执行具体的编译器指令,最后生成二进制的可执行文件。install指令依赖默认的build,会生成默认安装的文件夹,最后把二进制文件拷贝到/usr/local/nginx/sbin下。由于linux系统完善的安全机制,普通用户使用make install/usr下创建文件会出现 Permission denied的报错,加上sudo即可。

生成二进制文件

虽然nginx源码比较多,但是没想到gcc的编译速度还挺快的,可能是因为只编译了部分的模块,那我们就来看下nginx编译的结果是啥吧。

tree -d ./objs/src/

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
./objs/src/
├── core
│   ├── nginx.o
│   ├── ngx_array.o
│   ├── ngx_buf.o
│   ├── ngx_conf_file.o
│   ├── ngx_connection.o
│   ├── ngx_cpuinfo.o
│   ├── ngx_crc32.o
....
├── event
│   ├── modules
│   ├── ngx_event.o
│   ├── ngx_event_accept.o
│   ├── ngx_event_connect.o
│   ├── ngx_event_pipe.o
│   ├── ngx_event_posted.o
│   ├── ngx_event_timer.o
│   └── ngx_event_udp.o
├── http
│   ├── modules
│   ├── ngx_http.o
│   ├── ngx_http_copy_filter_module.o
│   ├── ngx_http_core_module.o
│   ├── ngx_http_file_cache.o
│   ├── ngx_http_header_filter_module.o
│   ├── ngx_http_parse.o
....
├── os
│   ├── unix

这里把每个c文件编译成.o的对象文件,最后将这些对象文件链接成一个可执行的二进制文件,其中不生成任何的库文件。文件较多这里只列出来一部分且没有文件的文件夹未列出,如os/win32nginx会依照不同操作系统编译不同的源文件,linux中的系统调用并不适用于window环境,反过来也一样。最后查看安装目录里的内容:

ls /usr/local/nginx/sbin/

结果为conf html logs sbin这四个文件夹,sbin中存放的是二进制可执行文件。如果要执行的话就要到对应目录下,挺麻烦的。在环境变量路径里面设置个软连接可以避免输入路径,软连接可以简单理解为window里的快捷方式。

ln -s /usr/local/nginx/sbin/nginx /usr/sbin/nginx

测试下是否能够正常运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
nginx -h

nginx version: nginx/1.16.1
Usage: nginx [-?hvVtTq] [-s signal] [-c filename] [-p prefix] [-g directives]

Options:
-?,-h : this help
-v : show version and exit
-V : show version and configure options then exit
-t : test configuration and exit
-T : test configuration, dump it and exit
-q : suppress non-error messages during configuration testing
-s signal : send signal to a master process: stop, quit, reopen, reload
-p prefix : set prefix path (default: /usr/local/nginx/)
-c filename : set configuration file (default: conf/nginx.conf)
-g directives : set global directives out of configuration file
1
2
3
4
sudo nginx -t

nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
1
2
3
4
5
6
sudo nginx  
ps -aux | grep nginx

root 13762 0.0 0.0 4664 392 ? Ss 11:01 0:00 nginx: master process nginx
nobody 13763 0.0 0.0 5356 2504 ? S 11:01 0:00 nginx: worker process
ssj 13765 0.0 0.0 8168 724 pts/0 S+ 11:01 0:00 grep --color=auto nginx

上面两个进程就是nginx运行后开启的,nginx.conf中worker_processes 1设置worker子进程的数量,所以这边只有一个worker子进程。

GDB

使用gdb调试看看

sudo gdb ./obj/nginx
b nginx.c:204
run

1
2
3
4
5
6
Starting program: /home/ssj/project/nginx-1.16.1/objs/nginx-deb
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, main (argc=1, argv=0x7fffffffe648) at src/core/nginx.c:206
206 if (ngx_strerror_init() != NGX_OK) {

咦怎么跳过204行了?但是它依旧停下来了,看下代码对不对。

1
2
3
4
5
6
7
8
9
10
11
12
list

201 ngx_conf_dump_t *cd;
202 ngx_core_conf_t *ccf;
203
204 ngx_debug_init();
205
206 if (ngx_strerror_init() != NGX_OK) {
207 return 1;
208 }
209
210 if (ngx_get_options(argc, argv) != NGX_OK) {

代码是对的,我本来想看看ngx_debug_init做了什么,查一下发现是个空的宏定义。

1
2
// ngx_linux_config.h
#define ngx_debug_init()

好吧,总而言之调试环境也算搭建好了。

总结

  1. 本文主要介绍了源码编译的方式安装nginx,展示了Makefile的编译指令以及编译后的结果文件,对编译过程进行了简单的介绍。
  2. 使用了几个较为常见的shell指令,如ps查询nginx的进程信息、ls列出对应文件夹下的内容等。
  3. 编译可使用gdb调试的二进制执行文件,并使用简单的gdb指令run,list,break等追踪代码的执行流程。