参考:
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