您的位置:首页 > 运维架构 > Nginx

【NGINX进程模型】源码分析

2017-07-05 00:00 489 查看
本文主要用于个人笔记整理,以此抛砖引玉,阐述过程未免会有所疏漏,希望此文能获得相关领域专家的指正和建议。

内容概要

本章主要分析nginx进程之间的关系、进程职责和进程启动的过程,Nginx 启动的时候一般是一个master进程和多个工作进程,可以通过一下配置设置工作进程的个数:

worker_processes 8;

master进程的职责是接收外界信号、管理工作进程;work进程的职责是处理用户请求。下图是nginx进程模型核心函数,如图1-1(请自行在新窗口打开):



图1-1:nginx进程模型函数调用
一、主入口函数(省略部分初始化代码,以下类同)

主入口函数是ngx_master_process_cycle,该方法主要用于启动工作进程、如果设置了缓存,还用于启动缓存管理进程;启动完工作进程后,主函数会挂起监听外界信号。

void ngx_master_process_cycle(ngx_cycle_t *cycle)
{
// 清空信号集,用于接收新的信号
sigemptyset(&set);
// 省略添加信号的方法,xxx代表SIGCHLD,SIGALRM,SIGIO等
sigaddset(&set, xxx);
// 开始启动工作线程
ngx_start_worker_processes(cycle, ccf->worker_processes,
NGX_PROCESS_RESPAWN);
// 开始启动cache管理进程
ngx_start_cache_manager_processes(cycle, 0);
//挂起进程监听外界信息(kill –QUIT `cat /data/logs/nginx.pid`)
sigsuspend(&set);
}

ngx_master_process_cycle方法的核心逻辑是通过调用方法ngx_start_worker_processes启动工作进程;调用方法ngx_start_cache_manager_processes用于启动缓存管理进程;调用sigsuspend用于挂起master进程监听外界信号,当有外界信号时,master进程会暂停,等待ngx_signal_handler去处理信号。

二、循环创建工作线程

ngx_start_worker_processes方法是通过主函数ngx_master_process_cycle调用的方法,用于遍历用户设置的工作进程数<worker_processes 8>创建工作进程:

static void
ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type)
{
// 根据配置创建n个进程
for (i = 0; i < n; i++) {
// 开始fork()工作线程
// ngx_worker_process_cycle用于启动进程,即 proc(cycle, data);
ngx_spawn_process(cycle, ngx_worker_process_cycle,
(void *) (intptr_t) i, "worker process", type);
// 将创建的子进程相关信息传递已经创建的进程
//ch中包含了进程的pid,slot,fd等信息
// 其中channel就是一个能够存储2个整型元素的数组而已,
// 这个channel数组就是用于socketpair函数创建一个进程间通道之用的
ngx_pass_open_channel(cycle, &ch);
}

如果用户设置worker_processes为8,则对应上述代码中n=8,改代码主要调用了两个方法,ngx_spawn_process用于fork工作进程,ngx_pass_open_channel用于将新创建的进程信息通过master进程传递给已创建的进程。

三、创建具体工作进程

工作进程的创建包括socketpair,fork,proc(ngx_spawn_proc_pt)这几个核心方法:

ngx_pid_t
ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
char *name, ngx_int_t respawn)
{
// 创建一对socket套接字
socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel)
// fork进程
pid = fork();
// 执行fork成功的进程
proc(cycle, data);
}

其中socketpair用于创建可以相互通信的套接字对;fork这个方法会将master进程拷贝一份成工作进程,当然中间还有些操作在下文具体阐释;proc实际类型是ngx_worker_process_cycle,该方法用于执行创建好的进程。

① socketpair

顾名思义,创建一对相同的匿名scoket套接字,他们之间可以相互通信,其中一个套接字指定给父进程使用,另外一个给子进程,父子进程之间就可以通信。该方法原型如下:

/**
*
*domain-套接口的域
*type-套接口类型
*protocol-使用的协议
*sv[2]-指向存储文件描述符的指针
*/

int socketpair(int domain, int type, int protocol, int sv[2]);

② fork

通过系统调用创建一个和当前进程完全一样的进程:

/**
* @return
* 父类中调用返回pid>0,则pid为子类pid
* 子类中调用返回pid=0,则表示当前是子类
* 返回的pid=-1,表示fork失败
*/
pid = fork();

forksocketpair一起使用,其中一个套接字给父类,另外一个套接字给子类,就可以实现父子进程的双向通信,例如:如果父进程调用scoketpair创建了一对套接字(F1,F2),在调用fork后,会生成子进程,那么子进程也继承了父类的匿名套接字(Z1,Z2),如果父进程使用F1(sv[0].channel),那么子进程就使用Z2(sv[1].channel),这样就必须关闭F2和Z1。

③ proc(ngx_worker_process_cycle)

该方法用于启动创建好的工作进程:

static void
ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
// 启动前先初始化一些参数,比如
// 设置环境变量、设置资源限制,绑定cpu等
ngx_worker_process_init(cycle, worker);
// 启动for循环处理事件
for ( ;; ) {
// 阻塞等待事件发生
ngx_process_events_and_timers(cycle);
}
}

四、传递子进程信息

子进程之间相互传递信息是为了子进程之间的通信,但目前来说,子进程之间通过亲缘的socket套接字通信的方式还没有使用到,这种通信方式主要用于master和work之间的信息传递:

static void
ngx_pass_open_channel(ngx_cycle_t *cycle, ngx_channel_t *ch)
{
for (i = 0; i < ngx_last_process; i++)
{
ngx_write_channel(ngx_processes[i].channel[0], ch, sizeof(ngx_channel_t), cycle->log);
}
}

以上代码中,会遍历当前存活的工作进程数,将新创建的进程信息ch,包括pid、socket套接字等信息传递给其他进程,执行过程也是通过父类进程channel[0]进行消息传递给其余子进程。在ngx_worker_process_init中会将channel和回调函数ngx_channel_handler关联,但channel中有数据传递过来,就会触发回调函数的调用。所以此处执行ngx_write_channel实际上是执行回调函数ngx_channel_handler:

static void
ngx_channel_handler(ngx_event_t *ev)
{
switch (ch.command) {
case NGX_CMD_OPEN_CHANNEL:
// 保存新进程的pid
ngx_processes[ch.slot].pid = ch.pid;
// 保存文件描述符
ngx_processes[ch.slot].channel[0] = ch.fd;
break;
// 一堆case语句,将在以后的文章中体现
}
}

该函数内有大概5个不同的case语句:

// 打开channel,即创建子进程的操作
#define NGX_CMD_OPEN_CHANNEL   1
// 关闭已经打开过的channel
#define NGX_CMD_CLOSE_CHANNEL  2
// 优雅的退出命令
#define NGX_CMD_QUIT           3
// 强制退出命令
#define NGX_CMD_TERMINATE      4
//重新打开之前打开又关闭的文件
#define NGX_CMD_REOPEN         5

我们当前使用到了NGX_CMD_OPEN_CHANNEL,即最开始的ngx_pass_open_channel方法调用。

五、cache进程的创建就不在赘述了,方法如上。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息