四、nginx启动过程中的进程创建(参考《深入剖析Nginx》)

  1. 通过前文(《nginx的函数调用》),已知nginx启动时有如下顺序:

main --> ngx_master_process_cycle --> ngx_start_worker_processes

大致是先启动主进程,再通过ngx_start_worker_processes启动子进程。在main函数末尾,有如下代码:

 if (ngx_process == NGX_PROCESS_SINGLE) {
    ngx_single_process_cycle(cycle);
} else {
    ngx_master_process_cycle(cycle);
}

从本段代码看,如果用户没有配置单进程运行的话,就会进入ngx_master_process_cycle()函数。该函数的参数cycle是一个ngx_cycle_s结构体,存储着许多nginx运行需要的全局信息,但在本节不是重点专注的范围。

2.下面看一看ngx_master_process_cycle()函数的内容。该函数位于 ngx_process_cycle.c中,其中有如下片段:

ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);       
ngx_start_worker_processes(cycle, ccf->worker_processes,NGX_PROCESS_RESPAWN);
ngx_start_cache_manager_processes(cycle, 0);

第一行代码估计是获取配置信息,第二行代码便是上文nginx启动顺序中的ngx_start_worker_processes函数。该函数有三个参数,第一个参数是ngx_cycle_s结构体,第二个参数用于指示要创建的工作进程的个数,第三个参数被定义为:
“#define NGX_PROCESS_RESPAWN -3”
这个参数大概是用来定义进程意外终止后是否重启的一个常量,此处不属于重点关注的内容。

3.关于ngx_start_worker_processes()函数。该函数也位于ngx_process_cycle.c中。其定义很简洁:

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");
ch.command = NGX_CMD_OPEN_CHANNEL;
for (i = 0; i < n; i++) {
    cpu_affinity = ngx_get_cpu_affinity(i);
    ngx_spawn_process(cycle, ngx_worker_process_cycle, NULL,
                      "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_int_t被定义为:”typedef intptr_t ngx_int_t”。也就是说,ngx_int_t也就是intptr_t类型,而intptr_t存在的意义跨平台,其长度总是所在平台的位数,作用是用来存放地址。

  • ngx_channel_t 定义为

    typedef struct {
     ngx_uint_t  command;
     ngx_pid_t   pid;
     ngx_int_t   slot;
     ngx_fd_t    fd;
    } ngx_channel_t;
    

    这是nginx用于进程间通信准备的结构体。

  • for循环n次,创建n个子进程,也就是参数ccf->worker_processes个子进程。而具体fork产生子进程的代码,位于循环中的ngx_spawn_process方法中。

  1. 接下来就是子进程的创建了。ngx_spawn_process函数也位于ngx_process_cycle.c中,共五个参数。其函数头定义为:

    ngx_pid_t
    ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
    char *name, ngx_int_t respawn)
    

    其中第二个参数是函数指针,这里传入了一个用于处理子进程的函数ngx_worker_process_cycle。现在先看看ngx_spawn_process函数中创建子进程的代码:

     pid = 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:
        ngx_pid = ngx_getpid();
        proc(cycle, data);
        break;
     default:
        break;
    

    定义很清晰,使用fork产生子进程:

    • 若出错则处理错误。
    • 若为父进程则直接退出switch语句,继续往下运行,直到ngx_spawn_process末尾,将子函数的pid返回。
    • 若为子进程,则先获取pid,然后执行 proc(cycle, data),而proc就是上文中传入ngx_spawn_process的函数指针ngx_worker_process_cycle。

5.至此,父子进程开始同时运行。

  • 下面先看看ngx_worker_process_cycle函数中子进程的运行模式,下面ngx_worker_process_cycle函数的大致结构:

     ngx_worker_process_init(cycle, 1);
     #if (NGX_THREADS){
     ...
     }
    #endif
     for ( ;; ) {
      ...//事件处理
     }  
    

    在ngx_worker_process_cycle函数中,先执行了子进程的初始化函数,之后会进入无限for循环,在for循环中进行工作。

  • 而在父进程中,将会结束ngx_spawn_process,返回子进程的pid,回到ngx_start_worker_processes函数的for循环中,也就是

    for (i = 0; i < n; i++) {
      cpu_affinity = ngx_get_cpu_affinity(i);
      ngx_spawn_process(cycle, ngx_worker_process_cycle, NULL,
                        "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);
    }
    

    父进程在对子进程的信息做一下记录和处理后,又会进行下一次循环,产生新的子进程,直到产生的子进程数量达到参数ccf->worker_processes个。

6.当所有子进程创建完毕后,父进程将结束ngx_start_worker_processes,回到ngx_master_process_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_RESPAWN);
ngx_start_cache_manager_processes(cycle, 0);
...
for ( ;; ) {
...//信号处理
}

显然,父进程也进入了无限for循环,在循环中工作。

7.总结一下nginx启动时的进程创建过程。从调用函数的顺序上看,若配置为只产生一个工作进程,则大致如下:


一个主进程,一个工作进程
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355

推荐阅读更多精彩内容