nginxのソースコードを読む1

今回はnginxのメインループをたどっていきます。

ソースコードの入手

ソースコードは(ここ)https://nginx.org/en/download.htmlからダウンロードできます。 今回は1.11.2を利用しています。

ソースのディレクトリ構造は以下の通りです。

/
├── core(エントリポイントや内部で汎用的に使用するデータ構造等)
├── event(イベント処理の仕組み)
│   └── modules(イベントを通知する際に選べるシステムコール毎のモジュール実装)
├── http(httpを処理する部分?)
│   ├── modules(ssl, auth, アクセス制限等configで設定するような細かな項目のモジュール)
│   │   └── perl(perlモジュール?)
│   └── v2(不明)
├── mail(メール関係 stmp, imap等)
├── misc(google_perftoolとそのためのc++用の何かがある)
├── os
│   └── unix(ソケットやforkの処理等システムコールを用いる処理)
└── stream(不明)

ここからざっくばらんにnginxのソースコードを読んでいきます。

エントリポイントからメインループまで

nginxはmaster-worker構成のためメインループが複数存在することになりますが、 まずはそれぞれのメインループに入るまでと入ってからの処理を大まかに観ていきたいと思います。

まずnginx起動時、/core/nginx.cのmain関数から実行されます。

//core/nginx.c
main(int argc, char *const *argv){
/* ポインタ作成 */
/* debugとerrorの初期化 */
/* オプションのパース */
/* 各種初期化及びシグナル送信等*/
 if (ngx_process == NGX_PROCESS_SINGLE) {
    ¦   ngx_single_process_cycle(cycle);

    } else {
    ¦   ngx_master_process_cycle(cycle);
    }

    return 0;
}

いろいろ省略していますが、 -vや-tや-s等のすぐに処理が終わる場合でなければ、 上記のngx_single_process_cycleかngx_master_process_cycleが呼ばれます。 single_processは詳細は知りませんがnginxをシングルプロセスで動かすオプションを つけた場合に実行されると思われます。 そこで今回はmaster_processの方に注目して追っていきます。

ngx_master_process_cycleは/os/unix/ngx_process_cycle.cに定義されています。

/os/unix/ngx_process_cycle.c

void
ngx_master_process_cycle(ngx_cycle_t *cycle){
/* 変数の初期化 */
/* シグナル受信時の処理を設定 */
/* プロセスのタイトル等設定 */
 ngx_setproctitle(title);

/* コンフィグの読み取り */
    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);

/* masterのメインループ */
 for ( ;; ) {
/* 何か */
sigsuspend(&set);
/* 何か */
if (ngx_reap) {
/* 子プロセスの削減? */
 live = ngx_reap_children(cycle);
}
if (!live && (ngx_terminate || ngx_quit)) {
/* マスタの終了 */
ngx_master_process_exit(cycle);
}

if (ngx_terminate) {
/* 子を終了させる */
/* 省略 */
 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) {
/* 設定ファイルのリロード */
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) {
/* ログファイルのリオープン */

    if (ngx_change_binary) {
/* バイナリを起動しながら交換 gracefull*/
    if (ngx_noaccept) {
/* 何か */

}
}

これがマスタプロセスの起動時の動きとメインループ内の処理です。 基本的には子プロセスとキャッシュマネージャの起動予約※をした後、 シグナルを受け付けるだけになります。 マスタプロセスが子を制御しかしていないことがよくわかりますね。 ※キャッシュマネージャは常に存在しているわけではないので定期的に起動しているはず

子プロセスの起動

次にngx_start_worker_processesで何しているかを観ます。 ほげ この関数は先ほどと同じ/os/unix/ngx_process_cycle.cに記述されています。

/os/unix/ngx_process_cycle.c

static void
ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type)
{
/* ログ記入、変数初期化 */
 ngx_channel_t    ch;

 for (i = 0; i < n; i++) {

    ¦   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_spawn_processはnginx内部でプロセス生成する際に用いる汎用的な関数です。第二引数に生成したプロセスで実行したい関数へのポインタを指定します。このプロセス生成部については後程取り上げます。 ngx_process_slotは生成したプロセスの通し番号のため生成したばかりのプロセスの情報をchに格納していることになります。(たぶん生成したプロセスの遠し番号だと思う)

ここの子プロセス生成部で着目すべきはとngx_pass_open_channelです。少し追ってみましょう。(※本当か?)

ngx_pass_open_channelは同じ/os/unix/ngx_process_cycle.cにありまぁす

/os/unix/ngx_process_cycle.c

static void
ngx_pass_open_channel(ngx_cycle_t *cycle, ngx_channel_t *ch)
{
    ngx_int_t  i;

    for (i = 0; i < ngx_last_process; i++) {

    ¦   if (i == ngx_process_slot
    ¦   ¦   || ngx_processes[i].pid == -1
    ¦   ¦   || ngx_processes[i].channel[0] == -1)
    ¦   {
    ¦   ¦   continue;
    ¦   }

    ¦   ngx_log_debug6(NGX_LOG_DEBUG_CORE, cycle->log, 0,
    ¦   ¦   ¦   ¦   ¦ "pass channel s:%i pid:%P fd:%d to s:%i pid:%P fd:%d",
    ¦   ¦   ¦   ¦   ¦ ch->slot, ch->pid, ch->fd,
    ¦   ¦   ¦   ¦   ¦ i, ngx_processes[i].pid,
    ¦   ¦   ¦   ¦   ¦ ngx_processes[i].channel[0]);

    ¦   /* TODO: NGX_AGAIN */

    ¦   ngx_write_channel(ngx_processes[i].channel[0],
    ¦   ¦   ¦   ¦   ¦   ¦ ch, sizeof(ngx_channel_t), cycle->log);
    }
}

ここの処理は子プロセスを生成するごとに行われます。 ①既存のプロセスをひとつ取り出す ①-a 自分自身か無効なプロセスな場合continue ①-b 取り出したプロセスと生成したプロセスを引数にngx_write_channel

個々から見たいもの
ngx_write_channel
ngx_spawn_process(ngx_process_slot)
ngx_channel_t

つづく