参考了https://blog.csdn.net/xiajun07061225/article/details/9383883
先进入nginx工作目录 /usr/local/nginx/sbin/
使用gdb -q -tui(q选项是以安静模式启动,不显示GDB版本等信息。tui选项可以显示代码界面)
然后在gdb中启动nginx shell ./nginx
启动之后,可以查看当前nginx中的进程号:shell pidof nginx
也可以使用ps -ef|grep nginx 查看(在另一个窗口中)
可以使用attach命令跟踪子进程 attach <port>
可以发现子进程即worker进程在运行后会停留在epoll_wait处等待相应的事件发生
测试之前的hello module
首先给hello module中的ngx_http_hello_handler函数打上断点,也就是这种handler模块的处理函数,这种函数是真正用来处理请求并发回响应包体给客户端的。使用 b ngx_http_hello_handler打断点。(使用i b可以查询断点)然后用命令c让nginx一直运行,直到遇到第一个断点。(使用delete breakpoint b_num删除断点,如delete breakpoint 1)
使用bt显示当前函数的调用栈,使用frame n(eg:frame 1)可进入具体某一帧
然后可以在另一个终端中输入 wget 100.100.60.199/hello或者使用postman 如图
send即可,然后就可以看到代码界面了。
使用n命令向下运行,主要是跟踪最后调用的输出函数return ngx_http_output_filter,运行到这一句时使用s命令进入该函数
继续执行到rc=ngx…这句,再次使用s命令进入该函数
发现进入的是这个ngx_http_range_body_filter函数,继续执行发现进入了ctx==null这句,使用p ctx可以查看ctx的值,是0x0,进入ngx_http_next_body_filter函数,发现进入了ngx_http_copy_filter函数
最后运行到rc=…时,进入ngx_output_chain函数,
继续运行,发现进入了in->next==NULL分支,可以使用p in->next查看,然后进入output_filter,发现进入了ngx_http_trailers_filter函数
这里就不一一截图了,就是很多filter函数在把处理往下推,大致进程如下
上方为调用(进入)的函数,下方为进入时的条件,可以看到,最后请求的处理被丢给了ngx_http_write_filter函数,该函数在运行到
时,使用s命令进入,发现进入了ngx_linux_sendfile_chain函数,该函数调用了ngx_output_chain_to_iovec函数,大致的流程如下
最后在ngx_writev函数中调用了writev函数,运行到这一步时postman中收到了输出。
writev函数介绍,参照http://blog.lucode.net/linux/talk-about-the-problem-of-writev.html
writev函数:是POSIX提供的一个比write函数更加高级的writev,在很多场景下,它相对于write有一定的优势。大体而言,write面向的是连续内存块,writev面向的是分散的数据块,两个函数的最终结果都是将内容写入连续的空间。分散的数据块,显然与nginx链式输出的规则符合。
函数原型:writev(int fd, struct iovect* iov, int iovcnt);
iov_base就是每个pair的基址,iov_len则是长度,不用包含“0”。
ngx_iovec_t结构体的定义
typedef struct {
struct iovec *iovs;
ngx_uint_t count; //当前所使用的iovs的个数
size_t size; //当前存放的总的字节数
ngx_uint_t nalloc; //总的iovs个数
} ngx_iovec_t;
iovec结构体
#include <sys/uio.h>
struct iovec {
ptr_t iov_base; /* Starting address */
size_t iov_len; /* Length in bytes */
};