How to efficiently learn Nginx source code and absorb nutrients?

Keywords: Linux Operation & Maintenance Nginx

There are many function points of Nginx, and the new concepts and design ideas involved are not particularly friendly to novices. I suggest learning the Nginx source code through debugging after understanding some basic knowledge of Nginx.

The following operations require some gdb debugging knowledge. If you are not familiar with gdb debugging, it is recommended to take a few minutes to learn.

gdb is not difficult to learn. You can master common commands. There are not many common commands. It is recommended< Debugging with GDB: link:   https://pan.baidu.com/s/1YPAx9CQgQoJmkd38HLZ35Q   Extraction code: d03u

1, Download Nginx source code

Download the latest nginx source code from the official website of nginx, and then compile and install it (when answering this question, the latest stable version of nginx is 1.18.0).

 ## Download nginx source code
 [root@iZbp14iz399acush5e8ok7Z zhangyl]# wget http://nginx.org/download/nginx-1.18.0.tar.gz
 --2020-07-05 17:22:10--  http://nginx.org/download/nginx-1.18.0.tar.gz
 Resolving nginx.org (nginx.org)... 95.211.80.227, 62.210.92.35, 2001:1af8:4060:a004:21::e3
 Connecting to nginx.org (nginx.org)|95.211.80.227|:80... connected.
 HTTP request sent, awaiting response... 200 OK
 Length: 1039530 (1015K) [application/octet-stream]
 Saving to: 'nginx-1.18.0.tar.gz'
 ​
 nginx-1.18.0.tar.gz                            100%[===================================================================================================>]   1015K   666KB/s    in 1.5s    
 ​
 2020-07-05 17:22:13 (666 KB/s) - 'nginx-1.18.0.tar.gz' saved [1039530/1039530]
 ​
 ## Unzip nginx
 [root@iZbp14iz399acush5e8ok7Z zhangyl]# tar zxvf nginx-1.18.0.tar.gz
 ​
 ## Compiling nginx
 [root@iZbp14iz399acush5e8ok7Z zhangyl]# cd nginx-1.18.0
 [root@iZbp14iz399acush5e8ok7Z nginx-1.18.0]# ./configure --prefix=/usr/local/nginx
 [root@iZbp14iz399acush5e8ok7Z nginx-1.18.0]make CFLAGS="-g -O0"
 ​
 ## Install so that nginx is installed in the / usr/local/nginx / directory
 [root@iZbp14iz399acush5e8ok7Z nginx-1.18.0]make install
Note: when compiling with the make command, we set the "- g -O0" option to make the generated Nginx with debugging symbol information and turn off compiler optimization.

2, Debug Nginx

There are two ways to debug Nginx:

Method 1

Start Nginx:

 [root@iZbp14iz399acush5e8ok7Z sbin]# cd /usr/local/nginx/sbin
 [root@iZbp14iz399acush5e8ok7Z sbin]# ./nginx -c /usr/local/nginx/conf/nginx.conf
 [root@iZbp14iz399acush5e8ok7Z sbin]# lsof -i -Pn | grep nginx
 nginx      5246            root    9u  IPv4 22252908      0t0  TCP *:80 (LISTEN)
 nginx      5247          nobody    9u  IPv4 22252908      0t0  TCP *:80 (LISTEN)

As shown above, Nginx will start two processes by default. The Nginx process running as root on my machine is the parent process, and the process number is   5246. The process running as nobody user is a child process, and the process number is   5247. In the current window, we use the gdb attach 5246 command to attach gdb to the Nginx main process.

 [root@iZbp14iz399acush5e8ok7Z sbin]# gdb attach 5246
 ...Omit some output information...
 0x00007fd42a103c5d in sigsuspend () from /lib64/libc.so.6
 Missing separate debuginfos, use: yum debuginfo-install glibc-2.28-72.el8_1.1.x86_64 libxcrypt-4.1.1-4.el8.x86_64 pcre-8.42-4.el8.x86_64 sssd-client-2.2.0-19.el8.x86_64 zlib-1.2.11-10.el8.x86_64
 (gdb)

At this point, we can debug the Nginx parent process, such as using   bt   Command to view the current call stack:

 (gdb) bt
 #0  0x00007fd42a103c5d in sigsuspend () from /lib64/libc.so.6
 #1  0x000000000044ae32 in ngx_master_process_cycle (cycle=0x1703720) at src/os/unix/ngx_process_cycle.c:164
 #2  0x000000000040bc05 in main (argc=3, argv=0x7ffe49109d68) at src/core/nginx.c:382
 (gdb) f 1
 #1  0x000000000044ae32 in ngx_master_process_cycle (cycle=0x1703720) at src/os/unix/ngx_process_cycle.c:164
 164             sigsuspend(&set);
 (gdb) l
 159                 }
 160             }
 161
 162             ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "sigsuspend");
 163
 164             sigsuspend(&set);
 165
 166             ngx_time_update();
 167
 168             ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
 (gdb)

use   f 1   When the command switches to the current call stack #1, we can find that the main thread of the Nginx parent process hangs at src/core/nginx.c:382.

You can use   c   Command to keep the program running. You can also add breakpoints or do other debugging operations.

Open another shell window and use gdb attach 5247 to attach gdb to the Nginx child process:

 [root@iZbp14iz399acush5e8ok7Z sbin]# gdb attach 5247
 ...Deployment output omitted...
 0x00007fd42a1c842b in epoll_wait () from /lib64/libc.so.6
 Missing separate debuginfos, use: yum debuginfo-install glibc-2.28-72.el8_1.1.x86_64 libblkid-2.32.1-17.el8.x86_64 libcap-2.26-1.el8.x86_64 libgcc-8.3.1-4.5.el8.x86_64 libmount-2.32.1-17.el8.x86_64 libselinux-2.9-2.1.el8.x86_64 libuuid-2.32.1-17.el8.x86_64 libxcrypt-4.1.1-4.el8.x86_64 pcre-8.42-4.el8.x86_64 pcre2-10.32-1.el8.x86_64 sssd-client-2.2.0-19.el8.x86_64 systemd-libs-239-18.el8_1.2.x86_64 zlib-1.2.11-10.el8.x86_64
 (gdb)

We use the bt command to view the current call stack of the main thread of a process.

 (gdb) bt
 #0  0x00007fd42a1c842b in epoll_wait () from /lib64/libc.so.6
 #1  0x000000000044e546 in ngx_epoll_process_events (cycle=0x1703720, timer=18446744073709551615, flags=1) at src/event/modules/ngx_epoll_module.c:800
 #2  0x000000000043f317 in ngx_process_events_and_timers (cycle=0x1703720) at src/event/ngx_event.c:247
 #3  0x000000000044c38f in ngx_worker_process_cycle (cycle=0x1703720, data=0x0) at src/os/unix/ngx_process_cycle.c:750
 #4  0x000000000044926f in ngx_spawn_process (cycle=0x1703720, proc=0x44c2e1 <ngx_worker_process_cycle>, data=0x0, name=0x4cfd70 "worker process", respawn=-3)
     at src/os/unix/ngx_process.c:199
 #5  0x000000000044b5a4 in ngx_start_worker_processes (cycle=0x1703720, n=1, type=-3) at src/os/unix/ngx_process_cycle.c:359
 #6  0x000000000044acf4 in ngx_master_process_cycle (cycle=0x1703720) at src/os/unix/ngx_process_cycle.c:131
 #7  0x000000000040bc05 in main (argc=3, argv=0x7ffe49109d68) at src/core/nginx.c:382
 (gdb) f 1
 #1  0x000000000044e546 in ngx_epoll_process_events (cycle=0x1703720, timer=18446744073709551615, flags=1) at src/event/modules/ngx_epoll_module.c:800
 800         events = epoll_wait(ep, event_list, (int) nevents, timer);
 (gdb)

It can be found that the child process is suspended in Src / event / modules / NGX_ epoll_ Module. C: epoll of 800_ At the wait function. We're in epoll_ After the wait function returns (src/event/modules/ngx_epoll_module.c:804), add a breakpoint, and then use   c   Command causes the Nginx child process to continue running.

 800         events = epoll_wait(ep, event_list, (int) nevents, timer);
 (gdb) list
 795         /* NGX_TIMER_INFINITE == INFTIM */
 796
 797         ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
 798                        "epoll timer: %M", timer);
 799
 800         events = epoll_wait(ep, event_list, (int) nevents, timer);
 801
 802         err = (events == -1) ? ngx_errno : 0;
 803
 804         if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
 (gdb) b 804
 Breakpoint 1 at 0x44e560: file src/event/modules/ngx_epoll_module.c, line 804.
 (gdb) c
 Continuing.

Then we visit the Nginx site in the browser. My IP address here is my virtual machine address. When the reader actually debugs, it is changed to the address of his own Nginx server. If it is local, it is 127.0.0.1. Since the default port is 80, there is no need to specify the port number.

 http://Your IP address: 80
 Equivalent to
 http://Your IP address

At this point, we return to the debugging interface of the Nginx sub process and find that the breakpoint is triggered:

 Breakpoint 1, ngx_epoll_process_events (cycle=0x1703720, timer=18446744073709551615, flags=1) at src/event/modules/ngx_epoll_module.c:804
 804         if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
 (gdb) 

Use the bt command to get the call stack at this time:

 (gdb) bt
 #0  ngx_epoll_process_events (cycle=0x1703720, timer=18446744073709551615, flags=1) at src/event/modules/ngx_epoll_module.c:804
 #1  0x000000000043f317 in ngx_process_events_and_timers (cycle=0x1703720) at src/event/ngx_event.c:247
 #2  0x000000000044c38f in ngx_worker_process_cycle (cycle=0x1703720, data=0x0) at src/os/unix/ngx_process_cycle.c:750
 #3  0x000000000044926f in ngx_spawn_process (cycle=0x1703720, proc=0x44c2e1 <ngx_worker_process_cycle>, data=0x0, name=0x4cfd70 "worker process", respawn=-3)
     at src/os/unix/ngx_process.c:199
 #4  0x000000000044b5a4 in ngx_start_worker_processes (cycle=0x1703720, n=1, type=-3) at src/os/unix/ngx_process_cycle.c:359
 #5  0x000000000044acf4 in ngx_master_process_cycle (cycle=0x1703720) at src/os/unix/ngx_process_cycle.c:131
 #6  0x000000000040bc05 in main (argc=3, argv=0x7ffe49109d68) at src/core/nginx.c:382
 (gdb) 

Use the info threads command to view all thread information of the child process. We find that the Nginx child process has only one main thread:

 (gdb) info threads
   Id   Target Id                                Frame 
 * 1    Thread 0x7fd42b17c740 (LWP 5247) "nginx" ngx_epoll_process_events (cycle=0x1703720, timer=18446744073709551615, flags=1) at src/event/modules/ngx_epoll_module.c:804
 (gdb) 

The Nginx parent process does not process client requests. The logic for processing client requests is in the child process. When the number of client requests of a single child process reaches a certain number, the parent process will fork a new child process to process new client requests, that is, there can be multiple child processes, and you can open multiple shell windows, Use gdb attach to debug each child process.

However, method 1 has a disadvantage, that is, the program has been started. We can only use gdb to observe the behavior of the program after that. If we want to debug the execution process of the program from start to run, method 1 may not be applicable. Some readers may say: after attaching to the process with gdb, add the breakpoint, and then restart the process with the run command, so that the execution process of the program can be debugged from start to run. The problem is that this method is not universal, because for the multi process service model, some parent-child processes have certain dependencies, which is inconvenient to restart during operation. At this time, method 2 is more appropriate.

Method 2

The gdb debugger provides an option called follow fork   set follow-fork mode   To set: when a process forks out a new child process, gdb continues to debug the parent process (the value is)   Parent) or child process (the value is   Child, the default is the parent process (the value is)   parent).

 # After fork, GDB attaches to the child process
 set follow-fork child
 # After fork, GDB attaches to the parent process, which is the default value
 set follow-fork parent

We can use   show follow-fork mode   View current value:

 (gdb) show follow-fork mode
 Debugger response to a program call of fork or vfork is "child".

Let's take debugging Nginx as an example, first enter the directory where the Nginx executable file is located, and stop the Nginx service in method 1:

 [root@iZbp14iz399acush5e8ok7Z sbin]# cd /usr/local/nginx/sbin/
 [root@iZbp14iz399acush5e8ok7Z sbin]# ./nginx -s stop

There is such logic in the Nginx source code, which will be called at the main function of the program:

 //src/os/unix/ngx_ Daemon. C: Line 13
 ngx_int_t
 ngx_daemon(ngx_log_t *log)
 {
     int  fd;
 ​
     switch (fork()) {
     case -1:
         ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "fork() failed");
         return NGX_ERROR;
     
     //The child process from fork takes this case
     case 0:
         break;
     
     //The return value of fork in the parent process is the PID of the child process, which is greater than 0, so go to this case
     //Therefore, the main process exits
     default:
         exit(0);
     }
 ​
     //... omit some code
 }

As shown in the comments in the above code, in order not to let the main process exit, we add a line in the configuration file of Nginx:

 daemon off;

In this way, Nginx will not call NGX_ The daemon function.

Next, we execute gdb nginx, and then pass the configuration file nginx.conf to the Nginx process to be debugged by setting parameters:

 Quit anyway? (y or n) y
 [root@iZbp14iz399acush5e8ok7Z sbin]# gdb nginx 
 ...Omit partial output...
 Reading symbols from nginx...done.
 (gdb) set args -c /usr/local/nginx/conf/nginx.conf
 (gdb) 

Then enter the run command to try to run Nginx:

 (gdb) run
 Starting program: /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
 [Thread debugging using libthread_db enabled]
 ...Omit some output information...
 [Detaching after fork from child process 7509]

As mentioned earlier, when gdb encounters the fork instruction, it will attach to the parent process by default. Therefore, there is a line prompt "Detaching after fork from child process 7509" in the above output "We interrupt the program according to Ctrl + c, then enter the bt command to view the current call stack, and the output stack information is the same as the calling stack we saw in the parent process, which means that gdb does attach after the program fork:

 ^C
 Program received signal SIGINT, Interrupt.
 0x00007ffff6f73c5d in sigsuspend () from /lib64/libc.so.6
 (gdb) bt
 #0  0x00007ffff6f73c5d in sigsuspend () from /lib64/libc.so.6
 #1  0x000000000044ae32 in ngx_master_process_cycle (cycle=0x71f720) at src/os/unix/ngx_process_cycle.c:164
 #2  0x000000000040bc05 in main (argc=3, argv=0x7fffffffe4e8) at src/core/nginx.c:382
 (gdb) 

If you want gdb to attach subprocesses after fork, you can set set follow fork child before the program runs, and then run the program again with the run command.

 (gdb) set follow-fork child 
 (gdb) run
 The program being debugged has been started already.
 Start it from the beginning? (y or n) y
 Starting program: /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
 [Thread debugging using libthread_db enabled]
 Using host libthread_db library "/lib64/libthread_db.so.1".
 [Attaching after Thread 0x7ffff7fe7740 (LWP 7664) fork to child process 7667]
 [New inferior 2 (process 7667)]
 [Detaching after fork from parent process 7664]
 [Inferior 1 (process 7664) detached]
 [Thread debugging using libthread_db enabled]
 Using host libthread_db library "/lib64/libthread_db.so.1".
 ^C
 Thread 2.1 "nginx" received signal SIGINT, Interrupt.
 [Switching to Thread 0x7ffff7fe7740 (LWP 7667)]
 0x00007ffff703842b in epoll_wait () from /lib64/libc.so.6
 (gdb) bt
 #0  0x00007ffff703842b in epoll_wait () from /lib64/libc.so.6
 #1  0x000000000044e546 in ngx_epoll_process_events (cycle=0x71f720, timer=18446744073709551615, flags=1) at src/event/modules/ngx_epoll_module.c:800
 #2  0x000000000043f317 in ngx_process_events_and_timers (cycle=0x71f720) at src/event/ngx_event.c:247
 #3  0x000000000044c38f in ngx_worker_process_cycle (cycle=0x71f720, data=0x0) at src/os/unix/ngx_process_cycle.c:750
 #4  0x000000000044926f in ngx_spawn_process (cycle=0x71f720, proc=0x44c2e1 <ngx_worker_process_cycle>, data=0x0, name=0x4cfd70 "worker process", respawn=-3)
     at src/os/unix/ngx_process.c:199
 #5  0x000000000044b5a4 in ngx_start_worker_processes (cycle=0x71f720, n=1, type=-3) at src/os/unix/ngx_process_cycle.c:359
 #6  0x000000000044acf4 in ngx_master_process_cycle (cycle=0x71f720) at src/os/unix/ngx_process_cycle.c:131
 #7  0x000000000040bc05 in main (argc=3, argv=0x7fffffffe4e8) at src/core/nginx.c:382
 (gdb) 

We then press Ctrl + C to interrupt the program, and then use the bt command to view the call stack of the current thread. The results show that it is indeed the call stack of the main thread of the sub process in method 1, which shows that gdb does attach to the sub process.

We can use method 2 to debug any logic before and after fork. It is a general multi process debugging method, which is recommended for readers to master.

To sum up, we can comprehensively use method 1 and method 2 to add various breakpoints to debug the function of Nginx, and gradually become familiar with the internal logic of Nginx.

3, Recommend some Nginx learning lists

  • In depth understanding of Nginx module development and architecture analysis version 2
  • Practical nginx
  • Detailed explanation of Nginx high performance Web server
  • Nginx module development guide uses C++11 and Boost library

Get link:

Must see classic books of Nginx (including download method)https://mp.weixin.qq.com/s/uP6_2UaTFZkwvGhNopWzSg

Posted by Arab Prince on Fri, 17 Sep 2021 16:43:10 -0700