Nginx专题(七、启动流程)

终于艰难的把使用nginx使用高完了,接下来看看nginx源码分析,分析一下前辈任务写的优秀源码。

7.1 nginx架构设计

7.1.1 核心模块

我们先来看看nginx核心数据结构,以前也讲过了,

struct ngx_module_s {
    ngx_uint_t            ctx_index;    //对于一类模块来说,ctx_index表示当前模块在这类模块中的序号。
    ngx_uint_t            index;	//index表示当前模块ngx_modules数组中的序号。跟ctx_index的区别是这是所有模块的下标

    char                 *name;		//模块的名字

    //spare0保留变量
    ngx_uint_t            spare0;
    ngx_uint_t            spare1;

    ngx_uint_t            version;	//模块的版本,便于将来的扩展。目前只有一种,默认为1
    const char           *signature; //签名?

    void                 *ctx;		//ctx用于指向一类模块的上下文的结构体,就是需要挂载一个自己模块的变量,http模块的话,要指向ngx_http_module_t结构体
    ngx_command_t        *commands;	//command将处理nginx.conf中的配置项
    ngx_uint_t            type;	//表示该模块的类型,官方有5种类型:NGX_HTTP_MODULE、NGX_CORE_MODULE、NGX_CONF_MODULE、NGX_EVENT_MODULE、NGX_MAIL_MODULE,也可以自己定义新类型

    //7个函数指针,代表这nginx启动停止过程中的,钩子函数
    
    ngx_int_t           (*init_master)(ngx_log_t *log);  //理解为master进程启动时回调init_master,但到目前为止,框架代码从来不调用它

    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);		//初始化所有模块时被调用,在master/worker模式下,这个阶段在启动worker子进程前完成

    ngx_int_t           (*init_process)(ngx_cycle_t *cycle);  //在正常服务前被调用,在master/worker模式下,多个worker子进程已经产生,每个worker进程的初始化过程会调用所有模块的init_process
    ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);		//由于nginx暂不支持多线程模式,所以这个还没使用
    void                (*exit_thread)(ngx_cycle_t *cycle);   //同时。不支持线程
    void                (*exit_process)(ngx_cycle_t *cycle);	//服务停止调用前调用,在master/worker模式下,worker进程会在进程退出前调用它

    void                (*exit_master)(ngx_cycle_t *cycle);		//将在master进程退出前被调用

    //以下都是保留字段,目前没有被使用
    uintptr_t             spare_hook0;
    uintptr_t             spare_hook1;
    uintptr_t             spare_hook2;
    uintptr_t             spare_hook3;
    uintptr_t             spare_hook4;
    uintptr_t             spare_hook5;
    uintptr_t             spare_hook6;
    uintptr_t             spare_hook7;
};

ngx_module_t接口有一个type成员,它指明了nginx允许在设计模块时定义模块类型这个概念,允许专注于不同领域的模块按照类型来区别。这个变量的定义,官方有5种类型:NGX_HTTP_MODULE、NGX_CORE_MODULE、NGX_CONF_MODULE、NGX_EVENT_MODULE、NGX_MAIL_MODULE。

那我们先来看看NGX_CORE_MODULE这个模块,这个模块也是nginx中核心模块,核心类型模块共有6个具体的模块,分别是:ngx_core_modules、ngx_errlog_module、ngx_events_module、ngx_openssl_module、ngx_http_module、ngx_mail_module模块。为什么这样定义核心模块,是因为这样可以简化nginx设计,使得其他非模块化的代码调用核心模块。

我们看看核心模块的结构体:

typedef struct {
    ngx_str_t             name;				// 核心模块的名称
    void               *(*create_conf)(ngx_cycle_t *cycle);		//解析配置项前,nginx框架会调用creat_conf方法
    char               *(*init_conf)(ngx_cycle_t *cycle, void *conf);   //解析配置完成后,nginx框架会调用init_conf方法
} ngx_core_module_t;

之后查了一下,找到了一些都是核心模块的定义,看着这些模块确实都是比较核心的部分了。

在这里插入图片描述

7.1.2 核心结构体ngx_cycle_t

介绍了核心模块之后,来看看nginx的核心结构体,如果不知道这个结构体的同学,可以先往后看,等看到启动分析的之后,就会明白这个结构体的重要之处,我们先在这里介绍一下ngx_cycle_t模块。

struct ngx_cycle_s {
    void                  ****conf_ctx;			//保存着所有模块存储配置项的结构体的指针
    ngx_pool_t               *pool;				//内存池

    ngx_log_t                *log;				//日志模块
    ngx_log_t                 new_log;

    ngx_uint_t                log_use_stderr;  /* unsigned  log_use_stderr:1; */

    //对于poll/rtsig这样的事件模块,会以有效文件句柄数来预先建立这些ngx_connection_t结构体,以加速事件的收集、分发
    ngx_uint_t               files_n;
    ngx_connection_t        **files;			
    ngx_connection_t         *free_connections;
    ngx_uint_t                free_connection_n;	//可用连接池中连接的总数

    ngx_module_t            **modules;		//指向所有模块的指针,ngx_module.c中
    ngx_uint_t                modules_n;
    ngx_uint_t                modules_used;    /* unsigned  modules_used:1; */

    ngx_queue_t               reusable_connections_queue;		//双向链表,可重复使用连接队列
    ngx_uint_t                reusable_connections_n;

    ngx_array_t               listening;		//动态数组,每个数组元素存储者ngx_listening_t成员,表示监听端口几相关的参数
    ngx_array_t               paths;	//动态数组容器,保存着nginx所有要操作的目录

    ngx_array_t               config_dump;
    ngx_rbtree_t              config_dump_rbtree;
    ngx_rbtree_node_t         config_dump_sentinel;

    ngx_list_t                open_files;		//单链表容量,表示nginx已经打开的所有文件,nginx框架不会向open_files链表中添加文件,而是模块自行添加,nginx框架会在ngx_init_cycle方法中打开这些文档
    
    //单链表容器,元素类型是ngx_shm_zone_t结构体,每个元素表示一块共享内存
    ngx_list_t                shared_memory;

    //当前进程中所有连接对象的总数
    ngx_uint_t                connection_n;
    ngx_connection_t         *connections;		//指向当前进程中所有连接对象,与connection_n
    ngx_event_t              *read_events;		//指向当前进程中所有读事件对象
    ngx_event_t              *write_events;		//指向当前进程中所有写事件对象

    ngx_cycle_t              *old_cycle;		//主要是保存启动前期的ngx_cycle_t对象

   	//配置文件相对于安装目录的路径名称
    ngx_str_t                 conf_file;
    //nginx处理配置文件时需要特殊处理的在命令行携带的参数
    ngx_str_t                 conf_param;
    //nginx配置文件所在目录的路径
    ngx_str_t                 conf_prefix;
    //nginx安装目录的路径
    ngx_str_t                 prefix;
    //用于进程间同步的文件锁名称
    ngx_str_t                 lock_file;
    //使用gethostname系统调用得到的主机名
    ngx_str_t                 hostname;
};

感觉也不多,不知道是一个一个的看,先简单认识一下吧。

7.2 nginx启动流程

自己分析了一波,感觉确实没那么难,不过重点还是有书指导,这样才能事半功倍

1.ngx_get_options 		//解析命令行参数
2.ngx_add_inherited_sockets		//平滑升级,我们现在不做重点分析
3.ngx_init_cycle		//重点核心来了,nginx的启动过程就是在这个函数中完成的
4.xxx					//前面这部分只要是初始化一些基本的容器,数组等,就不用写了
5.ngx_cycle_modules			//初始化所有模块
6.rv = module->create_conf(cycle);		//调用create_conf初始化函数,并把配置文件结构体返回
7.ngx_conf_parse				//开始解析配置文件,以后可以单独分析一下配置文件模块
8.module->init_conf			//调用核心模块的init_conf函数
9.ngx_create_paths			//创建这些文件夹
10.ngx_log_open_default		//打开这些文件
11.ngx_init_zone_pool 		//初始化共享内存,以后分析
12.ngx_open_listening_sockets		//在前面已经解析完了配置项时,会把需要监听的端口加入到listening的数组中,这一步进行监听,虽然我也还没看到解析配置文件的内容,就先这样写吧
13.ngx_init_modules			//现在开始就调用所有模块的init_modules函数
14.ngx_init_signals			//初始化信号
15. if (ngx_process == NGX_PROCESS_SINGLE)		//判断nginx运行模式
16.ngx_single_process_cycle		//如果是单进程模式,就调用这个函数
17.ngx_master_process_cycle		//如果是多进程模式,就会调用这个
18.cycle->modules[i]->init_process		//不管是哪种方式,都会调用模块的init_process函数

nginx启动过程就先介绍到这里把,到后面nginx运行方式就不出现分歧了。

7.3 worker进程如何工作

上一节只是简单讲到了启动worker进程,这一节也简单说说,哈哈哈。

7.3.1 单进程运行

现在单进程运行的还是比较少了,不过可以简单浏览一下:

void
ngx_single_process_cycle(ngx_cycle_t *cycle)
{
    ngx_uint_t  i;

    if (ngx_set_environment(cycle, NULL) == NULL) {
        /* fatal */
        exit(2);
    }
	
    //调用所有模块的init_process
    for (i = 0; cycle->modules[i]; i++) {
        if (cycle->modules[i]->init_process) {
            if (cycle->modules[i]->init_process(cycle) == NGX_ERROR) {
                /* fatal */
                exit(2);
            }
        }
    }

    for ( ;; ) {
        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");

        ngx_process_events_and_timers(cycle);	//event事件,暂时忽略
		
        //处理信号的
        if (ngx_terminate || ngx_quit) {

            for (i = 0; cycle->modules[i]; i++) {
                if (cycle->modules[i]->exit_process) {
                    cycle->modules[i]->exit_process(cycle);
                }
            }

            ngx_master_process_exit(cycle);
        }

        if (ngx_reconfigure) {
            ngx_reconfigure = 0;
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reconfiguring");

            cycle = ngx_init_cycle(cycle);
            if (cycle == NULL) {
                cycle = (ngx_cycle_t *) ngx_cycle;
                continue;
            }

            ngx_cycle = cycle;
        }

        if (ngx_reopen) {
            ngx_reopen = 0;
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
            ngx_reopen_files(cycle, (ngx_uid_t) -1);
        }
    }
}

其实也比较简单,注释也写的挺清楚了,这里就不强加描述了

7.3.2 多进程运行

现在nginx的工作模式基本都是多进程运行了,worker进程创建几个,还是需要配置文件配置的,这里我们来看看源码,

我们知道这个函数才是启动worker进程的函数:ngx_start_worker_processes

// n = ccf->worker_processes
static void
ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type)
{
    ngx_int_t      i;
    ngx_channel_t  ch;

    ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start worker processes");

    ngx_memzero(&ch, sizeof(ngx_channel_t));

    ch.command = NGX_CMD_OPEN_CHANNEL;

    for (i = 0; i < n; i++) {		//n是进程数
		//创建worker进程,ngx_worker_process_cycle是子进程最终执行的函数
        ngx_spawn_process(cycle, ngx_worker_process_cycle,
                          (void *) (intptr_t) i, "worker process", type);

        ch.pid = ngx_processes[ngx_process_slot].pid;
        ch.slot = ngx_process_slot;
        ch.fd = ngx_processes[ngx_process_slot].channel[0];

        ngx_pass_open_channel(cycle, &ch);
    }
}

我们就来看看ngx_spawn_process函数做了什么:

ngx_pid_t
ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
    char *name, ngx_int_t respawn)
{
    u_long     on;
    ngx_pid_t  pid;
    ngx_int_t  s;

   	//....前面不知道在干啥,先删掉

    ngx_process_slot = s;


    pid = fork();		//这里就看到我们出名的fork进程

    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:							//fork创建的子进程
        ngx_parent = ngx_pid;
        ngx_pid = ngx_getpid();		//获取pid
        proc(cycle, data);			//子进程的处理函数,就是我们刚刚传参的函数
        break;

    default:
        break;
    }

	//后面是父进程
  	//..省略后面的父进程

    return pid;
}

所以我们现在又回到了重点的函数ngx_worker_process_cycle:

static void
ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
    ngx_int_t worker = (intptr_t) data;

    ngx_process = NGX_PROCESS_WORKER;
    ngx_worker = worker;

    ngx_worker_process_init(cycle, worker);		//调用init_process函数

    ngx_setproctitle("worker process");

    //下面就跟单进程的执行函数一样了
    for ( ;; ) {

        if (ngx_exiting) {
            if (ngx_event_no_timers_left() == NGX_OK) {
                ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
                ngx_worker_process_exit(cycle);
            }
        }

        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");

        ngx_process_events_and_timers(cycle);

        if (ngx_terminate) {
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
            ngx_worker_process_exit(cycle);
        }

        if (ngx_quit) {
            ngx_quit = 0;
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
                          "gracefully shutting down");
            ngx_setproctitle("worker process is shutting down");

            if (!ngx_exiting) {
                ngx_exiting = 1;
                ngx_set_shutdown_timer(cycle);
                ngx_close_listening_sockets(cycle);
                ngx_close_idle_connections(cycle);
            }
        }

        if (ngx_reopen) {
            ngx_reopen = 0;
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
            ngx_reopen_files(cycle, -1);
        }
    }
}

7.3.3 总结

worker的工作主要是监听事件的,当然我们现在先不分析,之后会分析一下事件模块,剩下的就是判断标志位 了,

如果ngx_exiting和ngx_terminate这个标志位置1,就暴力退出。

如果ngx_quit置1就优雅的退出,以前有讲过

ngx_reopen这个置1,就是重新打开一次文件

7.4 master进程如何工作

接下来我们回来看看master进程,master进程不需要处理网络事件,它不负责业务的执行,只会通过管理worker等子进程来实现重启服务,平滑升级,更换文件等。

7.4.1 master进程函数体

我们先来看看master进程执行的函数体:

 for ( ;; ) {
        if (delay) {
            if (ngx_sigalrm) {
                sigio = 0;
                delay *= 2;
                ngx_sigalrm = 0;
            }

            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                           "termination cycle: %M", delay);

            itv.it_interval.tv_sec = 0;
            itv.it_interval.tv_usec = 0;
            itv.it_value.tv_sec = delay / 1000;
            itv.it_value.tv_usec = (delay % 1000 ) * 1000;

            if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                              "setitimer() failed");
            }
        }

        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "sigsuspend");

        sigsuspend(&set);

        ngx_time_update();

        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                       "wake up, sigio %i", sigio);

        if (ngx_reap) {
            ngx_reap = 0;
            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "reap children");

            live = ngx_reap_children(cycle);
        }

        if (!live && (ngx_terminate || ngx_quit)) {
            ngx_master_process_exit(cycle);
        }

        if (ngx_terminate) {
            if (delay == 0) {
                delay = 50;
            }

            if (sigio) {
                sigio--;
                continue;
            }

            sigio = ccf->worker_processes + 2 /* cache processes */;

            if (delay > 1000) {
                ngx_signal_worker_processes(cycle, SIGKILL);
            } else {
                ngx_signal_worker_processes(cycle,
                                       ngx_signal_value(NGX_TERMINATE_SIGNAL));
            }

            continue;
        }

        if (ngx_quit) {
            ngx_signal_worker_processes(cycle,
                                        ngx_signal_value(NGX_SHUTDOWN_SIGNAL));

            ls = cycle->listening.elts;
            for (n = 0; n < cycle->listening.nelts; n++) {
                if (ngx_close_socket(ls[n].fd) == -1) {
                    ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
                                  ngx_close_socket_n " %V failed",
                                  &ls[n].addr_text);
                }
            }
            cycle->listening.nelts = 0;

            continue;
        }

        if (ngx_reconfigure) {
            ngx_reconfigure = 0;

            if (ngx_new_binary) {
                ngx_start_worker_processes(cycle, ccf->worker_processes,
                                           NGX_PROCESS_RESPAWN);
                ngx_start_cache_manager_processes(cycle, 0);
                ngx_noaccepting = 0;

                continue;
            }

            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reconfiguring");

            cycle = ngx_init_cycle(cycle);
            if (cycle == NULL) {
                cycle = (ngx_cycle_t *) ngx_cycle;
                continue;
            }

            ngx_cycle = cycle;
            ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,
                                                   ngx_core_module);
            ngx_start_worker_processes(cycle, ccf->worker_processes,
                                       NGX_PROCESS_JUST_RESPAWN);
            ngx_start_cache_manager_processes(cycle, 1);

            /* allow new processes to start */
            ngx_msleep(100);

            live = 1;
            ngx_signal_worker_processes(cycle,
                                        ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
        }

        if (ngx_restart) {
            ngx_restart = 0;
            ngx_start_worker_processes(cycle, ccf->worker_processes,
                                       NGX_PROCESS_RESPAWN);
            ngx_start_cache_manager_processes(cycle, 0);
            live = 1;
        }

        if (ngx_reopen) {
            ngx_reopen = 0;
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
            ngx_reopen_files(cycle, ccf->user);
            ngx_signal_worker_processes(cycle,
                                        ngx_signal_value(NGX_REOPEN_SIGNAL));
        }

        if (ngx_change_binary) {
            ngx_change_binary = 0;
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "changing binary");
            ngx_new_binary = ngx_exec_new_binary(cycle, ngx_argv);
        }

        if (ngx_noaccept) {
            ngx_noaccept = 0;
            ngx_noaccepting = 1;
            ngx_signal_worker_processes(cycle,
                                        ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
        }
}

是不是很熟悉,也是判断各个标志位,来进行工作的

7.4.2 master 进程绑定的信号

进程中接收到的信号对nginx框架的意义:

信号对应进程中的全局变量意义
QUITngx_quit优雅的关闭整个服务
TERM或INTngx_terminate强制关闭整个服务
USR1ngx_reopen重新打开服务中的所有文件
WINCHngx_noaccept所有子进程不再接受处理新的连接
USR2ngx_change_binary平滑升级到新版本的程序
HUPngx_reconfigure重读配置文件并使能服务对新配置项神效
CHILDngx_reap有子进程意外结束,监控所有子进程

通过这个表格就知道nginx的进程间通信使用了大量的信号,这些信号的细节,这里就不描述了,等以后再一篇专门 介绍信号的文章。

7.4.3 监控子进程

其他信号都是捕获到信号之后,就通过信号发送函数发送给worker进程,这个暂时不分析了,以后有空分析,这里先分析一下管理子进程的方式。

看看相关变量:

#define NGX_MAX_PROCESSES         1024

ngx_int_t        ngx_process_slot;		//当前操作的进程在ngx_processes数组中下标
ngx_socket_t     ngx_channel;
ngx_int_t        ngx_last_process;		//ngx_processes数组中有意义的ngx_process_t元素中最大下标
ngx_process_t    ngx_processes[NGX_MAX_PROCESSES];	//存储所有子进程的数组

typedef struct {
    ngx_pid_t           pid;		//进程pid
    int                 status;		//由waitpid系统调用获取到的进程状态
    ngx_socket_t        channel[2];	//这是由socketpair系统调用产生出的用于进程间通信的socket句柄,目前用于master和worker子进程通信

    ngx_spawn_proc_pt   proc;	//子进程循环执行方法
    void               *data;
    char               *name;	//进程的名称

    unsigned            respawn:1;		//标志位,为1表示在重新生成子进程
    unsigned            just_spawn:1;	//标志位,为1表示正在生成子进程
    unsigned            detached:1;		//标志位,为1表示在进行父子进程分离
    unsigned            exiting:1;		//标志位,为1表示进程正在提出
    unsigned            exited:1;		//标志位,为1表示进程已经提出
} ngx_process_t;

在master启动一个子进程的时候,是会 在ngx_processs数组中 选择一个空的元素,存储这个子进程的信息:

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

创建的时候会绑定一个回调函数ngx_worker_process_cycle,这个在之前见过了,所以子进程的会 执行这个回调函数。

当每个子进程意外退出的时候,master父进程会接收到linux内核发来的CHLD信号,而处理信号的ngx_signal_handler方法 会把ngx_reap标志位置1,并且调用ngx_process_get_status函数,ngx_process_get_status这个函数的主要作用是,pidwait等待子进程的退出,然后循环判断是哪个子进程,设置exit标志位,接下来的工作就交回到master进程了

master进程循环判断ngx_reap_children这个标记位,如果这个置1了,就会调用ngx_reap_children函数 管理子进程,这个函数 首先会判断exit状态,如果是退出了,就做清场 工作,然后判断respawn标记,如果这个标记是1的话,就重新拉取worker进程,respawn标记是在初始化中创建这个子进程的时候传参进来的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值