Nginx进程模型分为两大类:监控进程(主进程)、工作进程(子进程);
多进程模型入口函数:ngx_master_process_cycle();主要任务:设置信号处理,然后调用ngx_start_worker_process()生成子进程,这时,主进程主要循环监听信号,而子进程主要循环监听连接。
主进程:在没有收到信号时,用suspend()进入睡眠状态,当有信号到达时,调用相应的信号处理函数ngx_signal_handler().该信号处理函数主要对信号旗标变量做修改,主要的信号处理逻辑代码还是要在主体内进行。在设置完旗标变量后,主体循环中检测有哪些旗标变量被改变,然后做出相应的处理,比如检测到ngx_reap则代表有子进程退出了,要做相关的回收处理,检测到ngx_terminate则代表要强行终止当前进程,检测到ngx_quit则代表要处理完子进程相关工作之后再完成退出工作,检测到ngx_reconfigure则代表要重新加载相关的配置。当处理完相关的信号之后,继续调用suspend进入睡眠,等待信号的到来。
void ngx_master_process_cycle(nginx_cycle_t cycle) { <span style="white-space:pre"> </span>for(;;){ <span style="white-space:pre"> </span> suspend(&set); <span style="white-space:pre"> </span>if(...){ <span style="white-space:pre"> </span> ... <span style="white-space:pre"> </span> } } }
这里的set是一个设置好的信号集,类型为sigset_t;
子进程:监听客户端和后端真实服务器的读写事件,进程阻塞点是I/O服用的调用处,比如select,epoll_wait等,真正的事件处理是通过ngx_process_events_and_timer()实现,假设以epoll_wait处理监听,那么该过程就是ngx_process_events_and_timer()->ngx_epoll_process()->epoll_wait();
如果Nginx开启了缓存功能,会创建两个相关的cache进程,cache manager process和cache loader process。其中cache manager process进程会嗲用每一个磁盘缓存管理对象的manager函数,比如检查一个缓存是否超过了缓存大小限制,如果超过了缓存限制,则删除缓存。管理进程不接受客户端请求,所以该进程关闭了套接字,并对事件对象设置了超时时间。
对于缓存加载进程则是加载缓存对象到内存,该过程是一次性的,在Nginx启动一段时间(一般是60s)之后加载缓存,然后就自动退出了.
Nginx的进程间通讯:(1)使用socketpair()来创建UNIX进程专用套接字管道,由于socketpair实现的管道是全双工的,并且在进程之间相同的描述符可能代表不同的文件,而用socketpair创建的管道则很好的解决了文件描述符的传递问题,所以用socketpair来实现具有亲缘关系的进程间的通讯是很有效的,但是nginx一般只使用socketpair来传递进程间的资源状态。
(2)共享内存
//nignx一块完整的共享内存结构 typedef ngx_shm_zone_s{ void * data; ngx_shm_t shm; nsg_shm—_zone_init_pt init; void * tag //用来标记是创建内存还是使用已有内存 }ngx_shm_zone_t; //nginx在加载配置文件时,遇到limit_req_zone配置项, //则将对应的ngx_shm_zone_t结构体变量加入全局链表内 //当全部的配置文件解析完毕后,会初始化该全局链表中所有的共享内存 //ngx_cycle.c part = &cycle->shared_memory.part; shm_zone = part->elts; //遍历整个链表完成共享内存的初始化 for(i = 0;;++i){ if(ngx_shm_aloc(&shm_zone[i].shm) != NGX_OK){...} //alloc指向实际的内存分配接口,可能是mmap也有可能是shmget if(ngx_init_zone_pool(cycle,&shm_zone[i]) != NGX_OK){...} //pool是ngx的独有内存管理机制 if(shm_zone[i].init(&shm_zone[i],NULL) != NGX_OK){...} }