Nginx进程管理

本文详细介绍了 Nginx 的进程管理机制,包括 master 进程如何管理 worker 进程,以及它们之间的通信方式。重点讲解了 master 进程的角色、如何创建 worker 进程、如何处理不同信号以及 worker 进程的生命周期。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Nginx进程管理

1. Nginx进程管理之master进程

监控进程充当整个进程组与用户的交互接口,同时对进程进行监护。它不需要处理网络事件,不负责业务的执行,只会通过管理worker进程来实现重启服务、平滑升级、更换日志文件、配置文件实时生效等功能。

master进程全貌图(来自阿里集团数据平台博客):

master进程中for(::)无限循环内有一个关键的sigsuspend()函数调用,该函数调用是的master进程的大部分时间都处于挂起状态,直到master进程收到信号为止。

master进程通过检查一下7个标志位来决定ngx_master_process_cycle方法的运行:
sig_atomic_t ngx_reap;
sig_atomic_t ngx_terminate;
sig_atomic_t ngx_quit;
sig_atomic_t ngx_reconfigure;
sig_atomic_t ngx_reopen;
sig_atomic_t ngx_change_binary;
sig_atomic_t ngx_noaccept;

进程中接收到的信号对Nginx框架的意义:
信号对应进程中的全局标志位变量意义
QUITngx_quit优雅地关闭整个服务
TERM或INTngx_terminate强制关闭整个服务
USR1ngx_reopen重新打开服务中的所有文件
WINCHngx_noaccept所有子进程不再接受处理新的连接,实际相当于对所有子进程发送QUIT信号
USR2ngx_change_binary平滑升级到新版本的Nginx程序
HUPng_reconfigure重读配置文件
CHLDngx_reap有子进程以外结束,需要监控所有子进程

还有一个标志位会用到:ngx_restart,它仅仅是在master工作流程中作为标志位使用,与信号无关。
我们知道在main函数中完成了Nginx启动初始化过程,启动初始化过程中的一个重要环节就是解析配置文件,回调各个配置指令的回调函数,因此完成了各个模块的配置及相互关联。在所有的这些重要及不重要的初始化完成后,main函数就开始为我们打开进程的“大门”——调用ngx_master_process_cycle(cycle); 接下来的文字里面,我们就重点来看看这个函数里做了一些什么事情。
    sigemptyset(&set);
    sigaddset(&set, SIGCHLD);
    sigaddset(&set, SIGALRM);
    sigaddset(&set, SIGIO);
    sigaddset(&set, SIGINT);
    sigaddset(&set, ngx_signal_value(NGX_RECONFIGURE_SIGNAL));
    sigaddset(&set, ngx_signal_value(NGX_REOPEN_SIGNAL));
    sigaddset(&set, ngx_signal_value(NGX_NOACCEPT_SIGNAL));
    sigaddset(&set, ngx_signal_value(NGX_TERMINATE_SIGNAL));
    sigaddset(&set, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
    sigaddset(&set, ngx_signal_value(NGX_CHANGEBIN_SIGNAL));
    if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "sigprocmask() failed");
    }
上面为了屏蔽一系列的信号。
    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进程就是通过依次调用这两个函数来创建子进程。第一个调用的函数创建的子进程我们称为worker进程,第二调用的函数创建的是有关cache的子进程。接收请求,完成响应的就是worker进程。光光是调用这个函数好像没什么看头,我们深入“虎穴”窥探一下究竟。
    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_start_worker_processes函数挺短小精干的,再截取主体就剩下这么一个for循环了。此处就是循环创建起n个worker进程,fork新进程的具体工作在ngx_spawn_process函数中完成。这里涉及到了一个全局数组ngx_processes(定义在src/os/unix/ngx_process.c文件中),这个数组的长度为NGX_MAX_PROCESSES(默认1024),存储的元素类型是ngx_process_t(定义在src/os/unix/ngx_process.h文件中)。全局数组ngx_processes就是用来存储每个子进程的相关信息,如:pid,channel,进程做具体事情的接口指针等等,这些信息就是用结构体ngx_process_t来描述的。在ngx_spawn_process创建好一个worker进程返回后,master进程就将worker进程的pid、worker进程在ngx_processes数组中的位置及channel[0]传递给前面已经创建好的worker进程,然后继续循环开始创建下一个worker进程。刚提到一个channel[0],这里简单说明一下:channel就是一个能够存储2个整型元素的数组而已,这个channel数组就是用于socketpair函数创建一个进程间通道之用的。master和worker进程以及worker进程之间都可以通过这样的一个通道进行通信,这个通道就是在ngx_spawn_process函数中fork之前调用socketpair创建的。有兴趣的自己读读ngx_spawn_process吧。
至于ngx_start_cache_manager_processes函数,和start_worker的工作相差无几,这里暂时就不纠结了。至此,master进程就完成了worker进程的创建工作了,此时此刻系统中就有一个master进程+N个worker进程在工作了哦,接下来master进程将“陷入”死循环中守护着worker进程,担当起伟大的幕后工作。在master cycle中调用了sigsuspend(),因而将master进程挂起,等待信号的产生。master cycle所做的事情虽然不算复杂,但却比较多;主要过程就是:【收到信号】,【调用信号处理函数(在初始化过程中注册了)】,【设置对应的全局变量】,【sigsuspend函数返回,判断各个全局变量的值并采取相应的动作】。在这里,我们不对每个信号的处理情况进行分析,随便看看两个信号就好了。
        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;
        }

这段位于master cycle中的代码是对SIGQUIT信号进行的处理动作。ngx_quit就那个全局变量之一,当master进程收到这个信号的时候,就调用ngx_signal_handler(定义在src/os/unix/ngx_process.c文件中)设置ngx_quit为1。因此master从sigsuspend返回后,检测到ngx_quit为1,就调用ngx_signal_worker_processes函数向每个worker进程递送SIGQUIT信号,通知worker进程们开始退出工作。然后就关闭所有的监听套接字。最后居然来了一个continue就又回到了cycle中,不是退出吗?为什么是continue而不是exit呢。前面已经提过了,master进程是幕后者,需要守护着worker进程们,既然是守护哪能worker进程没撤退,自己就先撤退了呢。由于,worker进程是master的子进程,所以worker退出后,将发送SIGCHLD信号给master进程,好让master进程为其善后(否则将出现“僵尸”进程)。在master进程收到SIGCHLD信号,就会设置全局变量ngx_reap为1了。

        if (ngx_reap) {
            ngx_reap = 0;
            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "reap children");
            live = ngx_reap_children(cycle);
        }
此时,ngx_reap为1了,master进程调用ngx_reap_children处理所有的worker子进程。这个ngx_reap_children函数不光担任起为worker进程善后的工作(子进程的收尸处理是在信号处理函数直接完成的),还担任了重启worker进程的任务。当然,这个重启worker进程是在一些异常情况下导致worker进程退出后的重启,并不是在“君要臣死、臣不得不死”的时候的顽强抵抗。Nginx具有高度的模块化优势,每个人都可以开发自己需要的模块程序,难免会出现一些bug引起worker进程的崩溃,因此master进程就肩负起了容错任务,这样才能够保证24小时的提供服务。

2. Nginx进程管理之worker进程

首先找到worker进程的入口地方——ngx_worker_process_cycle。这个函数不光是worker进程的入口函数,同时也是worker进程循环工作的主体函数,看函数名含有一个cycle嘛。进入这个cycle函数,第一件事就是调用ngx_worker_process_init(cycle, 1);对worker进程进行初始化操作。先看看这个worker进程的初始化过程。
    ngx_process = NGX_PROCESS_WORKER;
    if (ngx_set_environment(cycle, NULL) == NULL) {
        /* fatal */
        exit(2);
    }
进入初始化就将全局变量ngx_process设置为worker进程的标志,由于这个变量是从master进程复制过来的,所以没设置前就是master进程的标志。然后设置相应的环境变量。接下去就是设置了一些列的资源限制,id等玩意,这里就忽略代码了。
    for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->init_process) {
            if (ngx_modules[i]->init_process(cycle) == NGX_ERROR) {
                /* fatal */
                exit(2);
            }
        }
    }
此处循环调用每个模块的init_process,完成每个模块自定义的进程初始化操作,一般在模块定义的时候设置这个回调指针的值,即注册一个函数给它。做模块开发的时候,貌似使用得挺少的,遇到的时候好好关注下。
    
    /*
    此处循环用于关闭其他worker进程的无用channel资源
    */
    for (n = 0; n < ngx_last_process; n++) {
	/*
	ngx_processes数组中n位置的进程不存在。
	*/	
        if (ngx_processes[n].pid == -1) {
            continue;
        }
	/*
	全局变量ngx_process_slot的值是创建worker进程的时候,从
	master进程复制过来的,所以此处ngx_process_slot就指本worker
	进程在ngx_process_slot数组中的索引位置。此处不处理本worker
	进程,所以跳过。
	*/
        if (n == ngx_process_slot) {
            continue;
        }
	/*
	channel不存在,继续跳过。
	*/
        if (ngx_processes[n].channel[1] == -1) {
            continue;
        }
	/*
	ngx_processes数组中存储的是每个worker进程的资源,是master进程负责创建的。
	因此创建一个worker进程的时候,就一同将这些资源复制过来了,所以此处就关闭
	无用的channel——其他worker进程的读端文件描述符,保留写端文件描述符做
	worker间的通信之用。
	*/
        if (close(ngx_processes[n].channel[1]) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "close() channel failed");
        }
    }
	/*
	关闭本worker进程channel的写端文件描述符,因为每个worker进程只从自己的channel
	上读,而不会写。写自己channel的是master和其他worker进程。这也是上面为什么要
	关闭其他worker进程channel的读端。
	*/
    if (close(ngx_processes[ngx_process_slot].channel[0]) == -1) {
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "close() channel failed");
    }
    if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT,
                              ngx_channel_handler)
        == NGX_ERROR)
    {
        /* fatal */
        exit(2);
    }

ngx_channel就是worker进程channel的读端,这里调用ngx_add_channel_event将channel放入Nginx关心的集合中,同时关注起这个channel上的读事件,也即这个channel上有数据到来后,就立马采取读channel操作。此处的添加一个channel的读事件是worker进程初始化的关键之处。到此,初始化过程就结束了,回到worker循环主体看看吧。
 	for ( ;; ) {
	/*
	ngx_exiting是在worker进程收到SIGQUIT信号后设置的,稍后就能看到庐山真面目了。
	*/
        if (ngx_exiting) {
            c = cycle->connections;
		/*
		worker进程退出前,先得处理完每个connection上已经发生的事件。
		*/
            for (i = 0; i < cycle->connection_n; i++) {
                /* THREAD: lock */
                if (c[i].fd != -1 && c[i].idle) {
                    c[i].close = 1;
                    c[i].read->handler(c[i].read);
                }
            }
			
		/*
		处理完所有事件后,调用ngx_worker_process_exit函数,worker进程退出。
		*/
            if (ngx_event_timer_rbtree.root == ngx_event_timer_rbtree.sentinel)
            {
                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");
	/*
	这里是worker进程处理事件的核心开始。也即是,worker进程从里开始做一些特定的事情了,
	我们完全可以修改此处的代码,让Nginx为我们做一些其他的事情,呵呵。
	*/
        ngx_process_events_and_timers(cycle);
	/*
	worker进程收到了SIGINT信号,退出。
	*/
        if (ngx_terminate) {
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
            ngx_worker_process_exit(cycle);
        }
		
	/*
	worker进程收到了SIGQUIT信号,如果此时worker进程不是出于exiting状态,
	就将设置ngx_exiting为1,让其进入exiting状态;同时关闭监听套接口。
	*/
        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_close_listening_sockets(cycle);
                ngx_exiting = 1;
            }
        }
	/*
	worker进程收到了SIGUSR1信号
	*/
        if (ngx_reopen) {
            ngx_reopen = 0;
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
            ngx_reopen_files(cycle, -1);
        }
    }

3. master进程与worker进程间通信

这部分实现的源码主要分布于src/os/unix/channel.h和channel.c两个文件中。实现极其简单,没有什么复杂的逻辑。下面,我绘制了一个简单的master进程和worker进程间的关系,图中的箭头符号指出数据是由master进程传给worker进程,而没有从worker到master;这是因为channel不是一个普通的数据传输管道,在Nginx中它仅仅是用着master发送指令给worker的一个管道,master借此channel来告诉worker进程该做什么了,worker却不需要告诉master该做什么,所以是一个单向的通道。

master进程每次发送给worker进程的指令用如下一个结构来完成封装:

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

这个结构中的4个字段分别是发送的指令、worker进程的pid、worker进程的slot(在ngx_proecsses中的索引)及一个文件描述符。master进程可能会将一个打开的文件描述符发送给worker进程进行读写操作,那么此时就需要填写fd这个字段了。worker进程在收到一个这样的结构数据后,通过判断command的值来采取相应的动作;command就是master给worker下达的命令。

 


 

master进程用于处理SIGCHLD信号的函数ngx_reap_children中就有向worker进程发送关闭channel的指令,我们看看这个例子是怎么做的。

    ch.command = NGX_CMD_CLOSE_CHANNEL;
    ch.fd = -1;
    ch.pid = ngx_processes[i].pid;
    ch.slot = i;
    ngx_write_channel(ngx_processes[n].channel[0],
                                      &ch, sizeof(ngx_channel_t), cycle->log);

这几行代码是我从ngx_reap_children函数中拼凑起来的,所以看上去好像有点奇怪,不那么顺畅;但却清晰的给我们展现了master进程怎么给一个worker进程发送指令,此处发送的指令时NGX_CMD_CLOSE_CHANNEL。发送指令的函数ngx_write_channel是利用sendmsg来完成,《Unix网络编程》可以详细了解sendmsg。

 

worker进程在调用ngx_worker_process_init进行初始化的时候,使用了如下两行代码将channel放到epoll等事件处理模块中。

    if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT,
                              ngx_channel_handler)
        == NGX_ERROR)
    {
        /* fatal */
        exit(2);
    }
当master进程发来指令后,就调用ngx_channel_handler函数进行事件的响应。下面浓缩的代码给出了ngx_channel_handler所做的事情。

		/*
			读出master进程发送给过来的指令数据, ngx_read_channel
                        是利用recvmsg实现,详细介绍见《unix网络编程》
		 */
        n = ngx_read_channel(c->fd, &ch, sizeof(ngx_channel_t), ev->log);
		/*
		   	判断command的值,从而采取具体的动作,代码意图都写得很明显,
                           就不在这里多说了。
		*/
        switch (ch.command) {
        case NGX_CMD_QUIT:
            ngx_quit = 1;
            break;
        case NGX_CMD_TERMINATE:
            ngx_terminate = 1;
            break;
        case NGX_CMD_REOPEN:
            ngx_reopen = 1;
            break;
        case NGX_CMD_OPEN_CHANNEL:
            ngx_processes[ch.slot].pid = ch.pid;
            ngx_processes[ch.slot].channel[0] = ch.fd;
            break;
        case NGX_CMD_CLOSE_CHANNEL:
            if (close(ngx_processes[ch.slot].channel[0]) == -1) {
                ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
                              "close() channel failed");
            }
            ngx_processes[ch.slot].channel[0] = -1;
            break;
        }


参考: http://www.alidata.org/archives/1174
<think>好的,我现在要回答用户关于如何在Windows系统中终止nginx进程的问题。首先,我需要回忆一下常见的Windows命令任务管理器的操作。用户可能已经尝试过一些基本方法,但可能遇到了问题,比如进程无法正常结束。 首先,任务管理器是最直观的方法。用户可以通过Ctrl+Shift+Esc或者右键任务栏打开任务管理器,然后在进程选项卡里找到nginx进程,右键结束任务。不过有时候nginx可能有多个进程,比如主进程工作进程,需要全部结束。如果用户在这里没找到,可能需要检查后台进程或者详细信息选项卡。 接下来是命令行方法。使用命令提示符或者PowerShell,可以通过tasklist命令查找nginx进程ID(PID),然后用taskkill命令终止。例如,taskkill /f /im nginx.exe。这里需要注意的是,/f参数是强制终止,可能在某些情况下需要管理员权限。如果用户没有以管理员身份运行命令行,可能会遇到权限问题,这时候需要提醒用户使用管理员模式启动CMD或PowerShell。 另外,有时候nginx可能没有完全关闭,可能是因为还有残留进程。这时候可以再次运行tasklist确认是否还有nginx进程存在。如果用户之前启动了多个nginx实例,可能需要多次执行终止命令,或者使用taskkill /f /fi "imagename eq nginx.exe"来一次性终止所有相关进程。 还有可能用户是通过start nginx启动的,这时候可能需要先停止服务,但nginx通常不是以服务方式运行,除非用户特别配置过。如果有服务存在,可以使用net stop nginx命令,但这种情况较少见。 用户可能需要知道如何验证进程是否已经终止。可以建议用户在终止后再次运行tasklist | findstr nginx,如果没有输出,说明成功结束。此外,尝试访问本地服务器(如localhost:80)看是否无法连接,也能确认nginx已停止。 另外,用户可能在关闭nginx后需要重新启动它,这时候要确保所有旧进程已经终止,否则端口可能仍被占用,导致启动失败。因此,彻底终止所有nginx进程是关键。 最后,考虑到用户可能有不同的使用场景,比如开发环境或临时测试,需要简单有效的方法,所以分步骤说明任务管理器命令行的两种主要方法,确保覆盖不同用户的操作习惯。同时,提醒用户注意权限问题残留进程,避免常见陷阱。</think>### 如何在 Windows 上终止 nginx 进程 #### 方法一:通过任务管理器终止 1. 按下 `Ctrl + Shift + Esc` 打开任务管理器 2. 切换到 **"进程"** 或 **"详细信息"** 选项卡 3. 找到所有 `nginx.exe` 进程(可能包含主进程工作进程) 4. 右键选择 **"结束任务"** 强制终止进程 #### 方法二:使用命令行终止 1. 以管理员身份打开命令提示符(CMD)或 PowerShell 2. 查找 nginx 进程的 PID: ```cmd tasklist | findstr nginx.exe ``` 3. 强制终止所有 nginx 进程: ```cmd taskkill /f /im nginx.exe ``` - `/f` 表示强制终止 - `/im` 按映像名称匹配进程 #### 方法三:通过服务停止(如配置为服务) ```cmd net stop nginx ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值