lighttpd-1.4.39 : watcher and worker

本文详细介绍了lighttpd服务器的工作模式、信号处理机制及worker进程的具体运作方式。lighttpd采用多进程模型,包括一个watcher和多个worker进程,通过信号进行进程间通信。

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

参考:
http://bbs.chinaunix.net/thread-1251434-1-1.html
http://www.cnblogs.com/kernel_hcy/archive/2010/02/11/1667654.html

工作模式

lighttpd采用多进程模型,有一个watcher和N个worker进程。watcher负责创建worker并且监控是否有worker退出, 如果有, 那么再次创建出worker;而子进程是worker,是具体执行服务器操作的工作者。
这就好比在工厂中,一个老板(watcher)雇佣了N个工人(worker)。当有新的任务到达时,就由某个工人去完成该任务。如果某个工人发生了意外离开了工厂,那么老板会重新招入工人。

在下面的源码中:
num_childs : 表示worker的数量
child :child=0表示当前进程是watcher,否则是worker

#ifdef HAVE_FORK
    /* start watcher and workers */
    num_childs = srv->srvconf.max_worker; // 最大worker的数量可以通过配置文件设置
    if (num_childs > 0) {
        int child = 0;
        while (!child && !srv_shutdown && !graceful_shutdown) { // 父进程(watcher)进入此循环,创建worker(初始化时,创建worker; 有worker退出时,创建worker)
            if (num_childs > 0) {
                switch (fork()) {
                case -1:
                    return -1;
                case 0:
                    child = 1; // 标识当前进程是worker进程,然后worker进程会退出该循环。
                    break;
                default:
                    num_childs--; // watcher 需要创建的worker数量减一
                    break;
                }
            } else {
                int status;

                if (-1 != wait(&status)) {
                    /** 
                     * one of our workers went away 
                     */
                    num_childs++; // 有worker退出,数量加一,那么再次进入循环时,会重新创建worker进程
                } else {
                    switch (errno) {
                    case EINTR: // 捕捉到了信号
                        /**
                         * if we receive a SIGHUP we have to close our logs ourself as we don't 
                         * have the mainloop who can help us here
                         */
                        if (handle_sig_hup) { // 捕捉到的信号是 SIGHUP,那么需要自己关闭log
                            handle_sig_hup = 0;

                            log_error_cycle(srv);

                            /**
                             * forward to all procs in the process-group
                             * 
                             * we also send it ourself
                             */
                            if (!forwarded_sig_hup) { 
                                forwarded_sig_hup = 1; // 表示当前进程是发送者,不需要处理该信号。(避免无限循环)
                                kill(0, SIGHUP); // 给所有子进程发送SIGHUP,包括自己,但是前面已经将变量forwarded_sig_hup 置1,所以该进程不会再次处理SIGHUP信号。
                            }
                        }
                        break;
                    default:
                        break;
                    }
                }
            }
        }

        /**
         * for the parent this is the exit-point 
         */
        if (!child) {
            /** 
             * kill all children too 
             */
            if (graceful_shutdown) {
                kill(0, SIGINT);
            } else if (srv_shutdown) {
                kill(0, SIGTERM);
            }

            log_error_close(srv);
            network_close(srv);
            connections_free(srv);
            plugins_free(srv);
            server_free(srv);
            return 0;
        }
    }
#endif

信号处理

lighttpd是基于多进程的,包含一个watcher进程和N个worker进程。通过“信号”实现进程间的通信。(进程间通信,IPC: 管道,FIFO,消息队列,信号,信号量,socket,详见UNP vol2)
关于信号的知识,可以查看man文档 man 7 signal

这里解释一下后面用到的信号:
SIGHUP : 当该进程的控制终端挂起,或者该进程的控制进程挂起时,会给该进程发送SIGHUP信号
SIGINT : 使用键盘Ctrl + C 触发
SIGQUIT: 使用键盘Ctrl + \ 触发
SIGCHLD : 子进程结束
SIGTERM:终止信号
SIGALRM : 定时器信号

lighttpd的信号处理函数如下所示:

static volatile sig_atomic_t srv_shutdown = 0;
static volatile sig_atomic_t graceful_shutdown = 0;
static volatile sig_atomic_t handle_sig_alarm = 1;
static volatile sig_atomic_t handle_sig_hup = 0;
static volatile sig_atomic_t forwarded_sig_hup = 0;

#if defined(HAVE_SIGACTION) && defined(SA_SIGINFO)
static volatile siginfo_t last_sigterm_info;
static volatile siginfo_t last_sighup_info;

static void sigaction_handler(int sig, siginfo_t *si, void *context) {
    static siginfo_t empty_siginfo;
    UNUSED(context); // UNUSED是一个宏,仅仅用来表示次函数没有使用context

    if (!si) si = &empty_siginfo;

    switch (sig) { // 对捕捉到的信号进行处理
    case SIGTERM:
        srv_shutdown = 1;
        last_sigterm_info = *si;
        break;
    case SIGINT:
        if (graceful_shutdown) {
            srv_shutdown = 1;
        } else {
            graceful_shutdown = 1;
        }
        last_sigterm_info = *si;

        break;
    case SIGALRM: 
        handle_sig_alarm = 1; 
        break;
    case SIGHUP:
        /** 
         * we send the SIGHUP to all procs in the process-group
         * this includes ourself
         * 
         * make sure we only send it once and don't create a 
         * infinite loop
         */
        if (!forwarded_sig_hup) { // 变量forwarded_sig_hup 是为了防止重复向自己发送SIGHUP信号
            handle_sig_hup = 1; // 表示中断返回后,需要对该信号进行进一步处理,即:给所有子进程发送SIGHUP信号
            last_sighup_info = *si;
        } else {
            forwarded_sig_hup = 0;
        }
        break;
    case SIGCHLD:
        break;
    }
}

worker的工作

  • fdevent_poll 取得需要处理的事件个数
  • 对每一个需要处理的事件:
    • fdevent_event_next_fdndx 获取事件对应的fd在fdarray中的索引
    • fdevent_event_get_revent 获取事件类型
    • fdevent_event_get_fd 获取事件对应的文件描述符fd
    • fdevent_get_handler 获取事件对应的handler
    • fdevent_get_context 获取事件的上下文相关信息
    • 最后使用handler处理该事件

        if ((n = fdevent_poll(srv->ev, 1000)) > 0) {
            /* n is the number of events */
            int revents;
            int fd_ndx;
#if 0
            if (n > 0) {
                log_error_write(srv, __FILE__, __LINE__, "sd",
                        "polls:", n);
            }
#endif
            fd_ndx = -1;
            do {
                fdevent_handler handler;
                void *context;
                handler_t r;

                fd_ndx  = fdevent_event_next_fdndx (srv->ev, fd_ndx);
                if (-1 == fd_ndx) break; /* not all fdevent handlers know how many fds got an event */

                revents = fdevent_event_get_revent (srv->ev, fd_ndx);
                fd      = fdevent_event_get_fd     (srv->ev, fd_ndx);
                handler = fdevent_get_handler(srv->ev, fd);
                context = fdevent_get_context(srv->ev, fd);

                /* connection_handle_fdevent needs a joblist_append */
#if 0
                log_error_write(srv, __FILE__, __LINE__, "sdd",
                        "event for", fd, revents);
#endif
                switch (r = (*handler)(srv, context, revents)) {
                case HANDLER_FINISHED:
                case HANDLER_GO_ON:
                case HANDLER_WAIT_FOR_EVENT:
                case HANDLER_WAIT_FOR_FD:
                    break;
                case HANDLER_ERROR:
                    /* should never happen */
                    SEGFAULT();
                    break;
                default:
                    log_error_write(srv, __FILE__, __LINE__, "d", r);
                    break;
                }
            } while (--n > 0);

关于信号的一些博文:
https://www.quora.com/Whats-the-difference-between-Ctrl+C-and-Ctrl+Z-used-while-stopping-a-process
https://www.quora.com/What-are-all-of-the-keyboard-shortcuts-for-sending-signals-from-the-shell

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值