Nginx源码分析——worker进程源码与工作原理(一)

一、说明

一般说到nginx整体架构的话,会用这样的架构图进行概述。worker进程的运行模块是整个nginx最为核心的代码模块。还有下面都是基于Unix操作系统的,windows的可以了解下。
二、核心问题
由于worker的工作原理这个命题比较大,我们就列了几个核心问题去解决他,解决了这些核心问题,那么基本上面worker进程是如何工作的就知道了。
问题一:worker进程是如何启动的?
问题二:worker进程里面的数据结构是怎么样的?
问题三:worker进程是如何实现请求监听的?
问题四:worker进程是如何实现反向代理的?
如果大家有什么好的想法,或者想知道的也可以留言。
三、worker进程启动
./src/os/unix/ngx_process_cycle.c
ngx_start_worker_processes(cycle, ccf->worker_processes,
                            NGX_PROCESS_RESPAWN);
以Nginx首次启动为例子,看看nginx启动worker进程的时候做了什么。这里ccf->worker_processes先不考虑多个的情况,先看看只有一个进程的时候。
方法里面的内容很简单,一看就知道。
ngx_int_t  i;
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start worker processes");

for (i = 0; i < n; i++) {
    ngx_spawn_process(cycle, ngx_worker_process_cycle,
                      (void *) (intptr_t) i, "worker process", type);
    ngx_pass_open_channel(cycle);
}
为了以后我们可以了解更多的信息,我们调整一些error日志级别为debug,修改nginx.conf文件
error_log logs/error.log debug;
reload一下,结合上一章的知识,就可以理解,先看是一个进程然后结束一个进程。过程中都会有信号发出,由master进程来处理。
./src/os/unix/ngx_process.c
ngx_spawn_process(cycle, ngx_worker_process_cycle,
                      (void *) (intptr_t) i, "worker process", type);
方法cycle数据结构,ngx_worker_process_cycle方法,i为0,type为-3,这些作为入参。这个方法主要完成的就是生成子进程,并且进程创建完成之后执行ngx_worker_process_cycle。
这里就会涉及到ngx_process_t数据结构,我们先了解下。
./src/os/unix/ngx_process.h
typedef struct {
    ngx_pid_t           pid; //进程的pid
    int                 status;//描述子进程的状态
    ngx_socket_t        channel[2];//进程的channel,通过socketpair来创建,套接字所对应的句柄    

    ngx_spawn_proc_pt   proc;//进程创建完成调用的函数
    void               *data;//proc函数入参
    char               *name;//进程名

    unsigned            respawn:1;        //是否需要重新由master启动
    unsigned            just_spawn:1;    //是否是一个新的进程
    unsigned            detached:1;    //热部署中
    unsigned            exiting:1;    //进程状态退出中
    unsigned            exited:1;     //已退出
} ngx_process_t;
开始看看创建子进程的代码
 
    if (respawn >= 0) {
        s = respawn;


    } else {
        for (s = 0; s < ngx_last_process; s++) {
            if (ngx_processes[s].pid == -1) {
                break;
            }
        }


        if (s == NGX_MAX_PROCESSES) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
                          "no more than %d processes can be spawned",
                          NGX_MAX_PROCESSES);
            return NGX_INVALID_PID;
        }
    }
里面有两个全局变量,ngx_last_process记录当前进程的数量,ngx_processes保存所有子进程的数组。
如果ngx_processes数组中有进程失效了,那么就复用对应的进程的位置。
并且进程数量不能超过nginx最大进程数。
    if (respawn != NGX_PROCESS_DETACHED) {

        /* Solaris 9 still has no AF_LOCAL */

        if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1)
        {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "socketpair() failed while spawning \"%s\"", name);
            return NGX_INVALID_PID;
        }
这块的判断NGX_PROCESS_DETACHED表示是不是在进行nginx热升级,在上一章提到,nginx提供了热升级的能力,如果是热升级的新的worker进程与原来的master进程是没有关系的。这里我们先不去关注,主要是下面这块代码,生成套接字这块。
socketpair函数生成一个可以相互读写的套接字,生成成功之后,channel[0]与channel[1]可以进行双向的通信。
         if (ngx_nonblocking(ngx_processes[s].channel[0]) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          ngx_nonblocking_n " failed while spawning \"%s\"",
                          name);
            ngx_close_channel(ngx_processes[s].channel, cycle->log);
            return NGX_INVALID_PID;
        }
这是定义了对套接字的io方式—— ioctl(FIONBIO),为一个非阻塞io。如果设置失败,就关闭掉这个套接字。
        if (ngx_nonblocking(ngx_processes[s].channel[1]) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          ngx_nonblocking_n " failed while spawning \"%s\"",
                          name);
            ngx_close_channel(ngx_processes[s].channel, cycle->log);
            return NGX_INVALID_PID;
        }

对于ngx_processes[s].channel[1]这个套接字也进行相同的操作。
        if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "ioctl(FIOASYNC) failed while spawning \"%s\"", name);
            ngx_close_channel(ngx_processes[s].channel, cycle->log);
            return NGX_INVALID_PID;
        }
ioctl(ngx_processes[s].channel[0]设置 ioctl(FIOASYNC)设置为信号驱动异步模式——收到SIGIO和SIGPOLL信号才能进行IO。
        if (fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "fcntl(F_SETOWN) failed while spawning \"%s\"", name);
            ngx_close_channel(ngx_processes[s].channel, cycle->log);
            return NGX_INVALID_PID;
        }
通过fcntl(F_SETOWN)让ngx_processes[s].channel[0]属于ngx_pid这个进程。既channel[0]这个套接字是属于master进程d的。
        if (fcntl(ngx_processes[s].channel[0], F_SETFD, FD_CLOEXEC) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
                           name);
            ngx_close_channel(ngx_processes[s].channel, cycle->log);
            return NGX_INVALID_PID;
        }

        if (fcntl(ngx_processes[s].channel[1], F_SETFD, FD_CLOEXEC) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
                           name);
            ngx_close_channel(ngx_processes[s].channel, cycle->log);
            return NGX_INVALID_PID;
        }
当fork子进程后,仍然可以使用channel[0],channel[1]的fd。但执行exec后系统就会字段关闭子进程中的fd了。
master进程可以向channel[0]写入事件之后,channel[1]就可以接收到这个事件并且是在子进程中进行。
    ngx_channel = ngx_processes[s].channel[1];
}

ngx_process_slot = s;
将ngx_channel指向channel[1]。
ngx_process_slot中保存的是现在子进程的数量。
下面的代码开始创建子进程
pid = fork();
在fork之后,就是生成一个新的进程,子进程里面的数据结构完全复制了父进程的数据结构。
在fork之后分成了两个进程同时在执行,也就是说下面代码的逻辑父进程与子进程都会在跑。
能够区分他们的就是父进程里面pid返回的是子进程的进程号,子进程返回的pid为0。看下面的代码:
   switch (pid) {

    case -1:
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "fork() failed while spawning \"%s\"", name);
        ngx_close_channel(ngx_processes[s].channel, cycle->log);
        return NGX_INVALID_PID;

    case 0:
        ngx_parent = ngx_pid;
        ngx_pid = ngx_getpid();
        proc(cycle, data);
        break;

    default:
        break;
    }

 

就是说如果是父进程就走的是default,什么都不做,如果是子进程走的就是case 0。
在启动的时候,由于fork出来的内存数据是与主进程完全相同的,所有ngx_pid为主进程的pid,所以用ngx_parent保存ppid,在去获取子进程的pid。
proc是外部传入的函数,即是proc(cycle, data)执行的时候就是执行ngx_worker_process_cycle(cycle, data)函数。
./src/os/unix/ngx_process_cycle.c
ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
这个方法里面有一个无条件循环,这样这个子进程就在运行了。具体这块逻辑,在我们解决问题二、三、四的时候需要去分析的。
剩下的代码就简单了,都是由父进程执行的
    ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start %s %P", name, pid);

    ngx_processes[s].pid = pid;
    ngx_processes[s].exited = 0;

    if (respawn >= 0) {
        return pid;
    }

    ngx_processes[s].proc = proc;
    ngx_processes[s].data = data;
    ngx_processes[s].name = name;
    ngx_processes[s].exiting = 0;

    switch (respawn) {

    case NGX_PROCESS_NORESPAWN:
        ngx_processes[s].respawn = 0;
        ngx_processes[s].just_spawn = 0;
        ngx_processes[s].detached = 0;
        break;

    case NGX_PROCESS_JUST_SPAWN:
        ngx_processes[s].respawn = 0;
        ngx_processes[s].just_spawn = 1;
        ngx_processes[s].detached = 0;
        break;
    case NGX_PROCESS_RESPAWN:
        ngx_processes[s].respawn = 1;
        ngx_processes[s].just_spawn = 0;
        ngx_processes[s].detached = 0;
        break;

    case NGX_PROCESS_JUST_RESPAWN:
        ngx_processes[s].respawn = 1;
        ngx_processes[s].just_spawn = 1;
        ngx_processes[s].detached = 0;
        break;

    case NGX_PROCESS_DETACHED:
        ngx_processes[s].respawn = 0;
        ngx_processes[s].just_spawn = 0;
        ngx_processes[s].detached = 1;
        break;
    }

    if (s == ngx_last_process) {
        ngx_last_process++;
    }

    return pid;
主要还是完善ngx_processes[s],就是对应进程的数据结构,代码不具体讲了。
四、总结
子进程创建的过程中,通过套接字的方式实现了master进程与worker进程之间的通信,ngx_processes中的channel[0]交给了master进程,后续worker进程会监听channel[1]管道。
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sinom21

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值