leevis.com
leevis.com copied to clipboard
Blog
### 概述 nginx作为代理服务器,处理网络读写事件是其核心。在linux平台上使用epoll管理网络读写事件使得nginx有很高的性能,再配合定时器事件可以实现一些超时和控制速率的一些功能。 而nginx又是如何使用epoll,又是何时添加可读、可写、定时事件的呢? ### 代码分析 以下是部分事件的回调函数,缩进代表了调用层级。 后续会补充对应事件是被何时添加到epoll中的,是使用的水平触发还是边沿触发。定时器是何时使用的。 ```c // 监听的fd事件回调 listen:rev->handler = ngx_event_accept; // -> ls->handler(); ls->handler = ngx_http_init_connection; // 建立起连接的fd的事件回调http模块 connect: c->recv = ngx_recv; c->rev->handler = ngx_http_wait_request_handler; r->read_event_handler...
lua c API 是一组能让lua和c代码通信的接口。lua和c是两种不同类型的语言,他们之间通过lua VM建立的一个虚拟的栈实现数据传递,在c语言中调用c API操作栈来传递、获取lua代码中的数据。 ## 栈 lua 和 c 传递数据,因两个语言差异,lua的设计者使用了虚拟栈作为二者交换数据的介质。c语言中使用lua c API 操作栈中元素从而改变lua VM的全局变量。栈底index为0(或-n),栈顶为n(或-1)。 ## c API ### 压入元素 c语言中通过调用下述函数,把元素压入栈中。而压入的是一个副本,调用`lua_push*`后,再修改c中的变量也不会影响到lua中的值,也就是说调用了`lua_pushstring`后就可以释放`*s`指向的内存了。 ``` c void lua_pushnil(lua_State* L); // nil值 void...
### 概述 ngx提供了下面两个配置指令来设置可接收请求的header的大小。client_header_buffer_size默认大小为1k,对于大部分的请求够用了。如果有超大header的请求超过1k大小,nginx还会根据large_client_header_buffers默认4个8k的配置来分配8k内存处理,不会分配超过4块8k内存的。 请求包含了请求行和请求头还有body。 如果请求行大于large_client_header_buffers设置的size会返回414,也就是说请求行大于默认的8k就会返回414。 如果请求头的一个key value长度超过large_client_header_buffers设置的size会返回494,494可能是非标准code,nginx最终返回用户的是400,在body中会有"Request Header Or Cookie Too Large"类似的内容。 或者如果请求行和请求头超过large_client_header_buffers配置的num个size大小的内存也会返回494。 ```ini client_header_buffer_size size; large_client_header_buffers number size; ``` ### 代码分析 了解了这两个指令的意义后,来看下代码实现。http请求过来的时,nginx调用可读事件回调函数ngx_event_accept接收tcp请求,并调用ls->handler回调函数ngx_http_init_connection初始化http请求,该函数会把accept返回的fd可写回调函数设置为ngx_http_wait_request_handler。 当新的fd有可读事件就会调用ngx_http_wait_request_handler函数处理,该可读事件也就是客户端发送http请求引起的。 该函数先会分配一块大小为client_header_buffer_size大小的内存赋到r->header_in后调用recv函数接收请求的内容。接下来会调用ngx_http_process_request_line继续接收并解析http请求,并同时把可读事件回调函数也改为ngx_http_process_request_line。 调用ngx_http_read_request_header函数接收请求内容,然后调用ngx_http_parse_request_line函数解析请求行内容(GET/POST(流的组织(请求)方式) URL(地址+目录) 版本号)返回NGX_OK表示请求行解析完毕,返回 NGX_AGAIN表示请求行内容不完整。 解析完请求行以后,需要调用ngx_http_process_request_headers解析请求头,并修改可读事件回调函数。...
## 概述 相关代码glibc的malloc.h malloc.c hooks.c ### 源码分析 前提是x86-64位系统,sizeof(size_t) == 8 。 #### 相关结构体描述 ``` chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Size of previous chunk, if unallocated (P clear) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Size of...
### 概述 在nginx中可以通过[error_page](http://nginx.org/en/docs/http/ngx_http_core_module.html#error_page)指令指定错误页面。和error_page 相关的还有recursive_error_pages指令,该指令制定是否递归解析error_page指令的错误页面,例如如下配置,如果recursive_error_pages 为off;则404会解析为405,如果为on,则会解析到406. ``` error_page 404 =405 /uri; error_page 405 =406 /uri; ``` ### 代码分析 #### 配置解析 error_page是在ngx_http_core_module模块中实现的, 有ngx_http_core_error_page函数处理配置信息。 ```c // 每一个错误码(code)将会对应一个这样的结构体 typedef struct { ngx_int_t status; //...
### 概述 在nginx网站上有一篇帖子[《If Is Evil》](https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/) 该帖子描述了在if使用的过程中,如果你不是非常熟悉nginx的流程或者没有充分的测试,可能会发生一些你预想不到的情况。并给出了一些“邪恶的”实例。 下面,会通过代码结合实例分析一下if为什么是“邪恶的”。 ### 代码分析 + 在分析代码之前先了解以下知识: 0. if是通过rewrite模块提供的配置指令,添加到server rewrite阶段和rewrite阶段执行。post rewrite阶段也和rewrite模块有关。 1. nginx http的处理是分成11个阶段执行的(post read、server rewrite、find config、rewrite、post rewrite、preaccess、access、post access、precontent、content、log)。通常阶段是按照顺序执行的,例如:preaccess阶段生成的变量绝对不能在if中使用,因为if所在的阶段先执行。有时候会从post rewrite跳回到find config阶段重新执行。 2. nginx http相关的配置分3个以上的级别(http{server{location / {...
### 概述 ngx提供[动态升级](http://nginx.org/en/docs/control.html#upgrade),在不阶段提供服务的情况下更新可执行文件。 在Linux不支持reuseport的时期,老的nginx进程绑定了端口,新的进程再绑定监听会失败。 如果把老的进程停止,再启动新的进程。会有一段时间端口是不通的。 虽然Linux新的内核支持了reuseport,在开启了以后。老的进程不停止,新的进程也可以绑定监听。这样虽然可以解决端口不通的问题,老的进程如果先关闭监听,等所有请求结束再推出,基本可以避免上述问题了。 但是,nginx为了跨平台,自己实现了动态升级。 来看下nginx动态升级的流程: 1. 把./sbin/nginx 改名。再把新的nginx可执行文件放到./sbin/ 目录下。 2. 向nginx的master进程发送SIGUSR2信号。 3. 向旧的nginx的master进程发送SIGWINCH信号。 4. 此时应检查新的进程是否可以正确的处理请求。如果可以则执行6步。如果不可以则执行第5步。 5. 向旧的master进程发送SIGHUP唤醒老的worker进程起来处理请求。 6. 向旧的nginx的master进程发送SIGQUIT信号。 ### 源码分析 动态升级是通过向进程发送信号驱动的,如果不熟悉ngx的信号处理,先看上一篇。 #### 处理SIGUSR2信号 + 向master进程发送SIGUSR2信号前,ngx的master进程阻塞在ngx_master_process_cycle函数的sigsuspend(&set)调用上,接收到信号时会被信号唤醒执行信号处理函数ngx_signal_handler,把ngx_change_binary改为1,action改为 ",...
### 概述 通过向nginx的master进程发送信号来[管理nginx](http://nginx.org/en/docs/control.html)。例如:向nginx进程发送sighup信号重新加载配置。操作:kill -1 pid 或者nginx -s reload。 ### 知识回顾 linux信号是进程间通信的一种方式,信号是一种软中断,实际上也属于进程控制的一部分。 软中断信号signal用来通知进程发生了异步事件,进程之间通过kill系统调用发送软中断,内核也会因内部事件而给进程发送信号,通知发生了某个事件。信号仅通知发生了什么事件,并不给进程传递数据。 收到信号的进程可以有3种方式来处理,1: 进程通过signal等函数指定对应的处理函数。2: 忽略信号。3: 系统默认的处理方式。 实际执行信号的处理动作称为信号递达(delivery),信号从产生到递达之间的状态称为信号未决(pending)。进程可以选择阻塞信号,被阻塞的信号产生时保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。sigsuspend函数可以临时解除信号阻塞。 下面是信号相关的函数: 注册信号回调函数 ```c // sig 指定信号编号 // func 处理回调函数,SIG_IGN表示忽略信号。SIG_DFL表示采用系统默认处理方式。 void (*signal(int sig, void...
### 概述 nginx的master进程和worker进程之间通过管道来通信,master控制worker进程的重启等等。 master除了通过管道发送了控制信息,还通过管道把描述符传递给子进程。 这样做的结果是所有的worker进程都拥有其他进程的管道的fd[0],最后启动的工作进程是通过fork直接继承父进程的。 ### 知识回顾 linux系统下,子进程会自动继承父进程已经打开的描述符。实际应用中可能需要子进程把后来打开的描述符传递回父进程。或者也有可能把描述符传递到一个无关的进程中。linux下是提供了这种机制的。 首先需要在两个进程之间建立一个unix域套接字作为传递消息的通道,然后发送进程调用sendmsg向通道发送一个特殊的消息,然后接收进程调用recvmsg从通道接收消息,从而打开描述符。 先来看下sendmsg和recvmsg的原型以及数据结构: ```c ssize_t sendmsg(int socket, const struct msghdr *message, int flags); ssize_t recvmsg(int socket, struct msghdr *message, int flags); struct msghdr...
### 概述 main函数中,根据master_process的配置,如果开启则为master worker模式,则会调用ngx_master_process_cycle函数启动worker进程。在调用该函数启动worker进程前,会调用ngx_init_signals函数注册信号回调。 + ngx_master_process_cycle函数: 在该函数中,调用了ngx_start_worker_processes启动worker进程。 ```c // 启动worker进程 ngx_start_worker_processes(cycle, ccf->worker_processes, NGX_PROCESS_RESPAWN); // 启动cache进程 ngx_start_cache_manager_processes(cycle, 0); ``` + ngx_start_worker_processes函数: 在该函数中ngx_spawn_process是封装了fork函数和创建父子进程通信的channel,用来启动worker进程的。ngx_worker_process_cycle是函数指针,worker实际的运行函数。 ```c static void ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type)...