从server.c的main函数看起,启动流程大体如下:
server_init 函数初始化lighttpd 的server 数据结构,涉及到其中一些buffer,array的结构初始化工作,主要参考的数据结构buffer,array,chunkqueue等等
通过启动参数-f 读取配置文件 config_read
这段代码是当lighttpd binary 被setuid(类似 chmod 4755)时,禁止运行,不明白为什么要这样处理,回头再了解一下
i_am_root 是 getuid==0的结果,即root用户 getuid会返回 0,非root会返回正整数
后面比较奇怪的地方时
默认没有修改配置文件的情况下,linux 2.6的srv->event_handler应该是epoll,就是执行else分支,我用gdb也是这个结果,为什么max_fds最多只有4096呢?libevent里直接就是rlimit里的 rlim_cur,通常会被设置成65535
接下来比较重要的是 network_init 调用(参考network.c文件)
在network_init中也就是调用 socket,setsocketopt,bind,listen等操作,创建出一个listen socket
这段代码的功能是fork出若干child进程,然后父进程投入睡眠等待子进程退出,如果有退出的子进程,父进程会马上在fork出新的子进程,就是所谓的 watcher - worker的模型,这个网上的资料也有很清楚的论述
接下来子进程(可能很多个)开始注册事件
lighttpd也是使用event-driven模型,fdevents是全局的事件处理器数据结构,实现的功能类似于libevent
fdevent_init初始化事件处理器
具体的事件选择应该已经通过读取配置文件设置好了,以epoll为例子,实际执行的是
#define SET(x) \
ev->x = fdevent_linux_sysepoll_##x;
##的作用是字符连接即初始化函数指针
ev->free=fdevent_linux_sysepoll_free等操作
通过上面代码初始化好了事件处理器srv->ev
接下来调用 network_register_fdevents (文件network.c)
此时srv->srv_sockets.used应该为1,就是刚刚建立的listen fd
fdevent_register代码
fdarray用于保存fd与对应回调handler及参数ctx之间的关系,将来能以O(1)的性能查找到某个fd对应的回调函数及参数
server.c最后就是一个巨大的循环
[size=medium][b]
由于fork会原样复制父进程的heap和stack区,并保持独立,所以fork方式的好处就是完全不需要共享任何东西,代价是内存占用量要比较多
[/b][/size]
server_init 函数初始化lighttpd 的server 数据结构,涉及到其中一些buffer,array的结构初始化工作,主要参考的数据结构buffer,array,chunkqueue等等
通过启动参数-f 读取配置文件 config_read
if (!i_am_root && (geteuid() == 0 || getegid() == 0)) {
/* we are setuid-root */
log_error_write(srv, __FILE__, __LINE__, "s",
"Are you nuts ? Don't apply a SUID bit to this binary");
server_free(srv);
return -1;
}
这段代码是当lighttpd binary 被setuid(类似 chmod 4755)时,禁止运行,不明白为什么要这样处理,回头再了解一下
i_am_root 是 getuid==0的结果,即root用户 getuid会返回 0,非root会返回正整数
后面比较奇怪的地方时
if (srv->event_handler == FDEVENT_HANDLER_SELECT) {
/* select limits itself
*
* as it is a hard limit and will lead to a segfault we add some safety
* */
srv->max_fds = FD_SETSIZE - 200;
} else {
srv->max_fds = 4096;
}
默认没有修改配置文件的情况下,linux 2.6的srv->event_handler应该是epoll,就是执行else分支,我用gdb也是这个结果,为什么max_fds最多只有4096呢?libevent里直接就是rlimit里的 rlim_cur,通常会被设置成65535
接下来比较重要的是 network_init 调用(参考network.c文件)
在network_init中也就是调用 socket,setsocketopt,bind,listen等操作,创建出一个listen socket
#ifdef HAVE_FORK
/* start watcher and workers */
num_childs = srv->srvconf.max_worker;
if (num_childs > 0) {
int child = 0;
while (!child && !srv_shutdown && !graceful_shutdown) {
if (num_childs > 0) {
switch (fork()) {
case -1:
return -1;
case 0:
child = 1;
break;
default:
num_childs--;
break;
}
} else {
int status;
if (-1 != wait(&status)) {
/**
* one of our workers went away
*/
num_childs++;
} 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) {
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);
}
}
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
这段代码的功能是fork出若干child进程,然后父进程投入睡眠等待子进程退出,如果有退出的子进程,父进程会马上在fork出新的子进程,就是所谓的 watcher - worker的模型,这个网上的资料也有很清楚的论述
接下来子进程(可能很多个)开始注册事件
lighttpd也是使用event-driven模型,fdevents是全局的事件处理器数据结构,实现的功能类似于libevent
if (NULL == (srv->ev = fdevent_init(srv->max_fds + 1, srv->event_handler))) {
log_error_write(srv, __FILE__, __LINE__,
"s", "fdevent_init failed");
return -1;
}
fdevent_init初始化事件处理器
具体的事件选择应该已经通过读取配置文件设置好了,以epoll为例子,实际执行的是
ev->type = FDEVENT_HANDLER_LINUX_SYSEPOLL;
#define SET(x) \
ev->x = fdevent_linux_sysepoll_##x;
SET(free);
SET(poll);
SET(event_del);
SET(event_add);
SET(event_next_fdndx);
SET(event_get_fd);
SET(event_get_revent);
if (-1 == (ev->epoll_fd = epoll_create(ev->maxfds))) {
fprintf(stderr, "%s.%d: epoll_create failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n",
__FILE__, __LINE__, strerror(errno));
return -1;
}
if (-1 == fcntl(ev->epoll_fd, F_SETFD, FD_CLOEXEC)) {
fprintf(stderr, "%s.%d: epoll_create failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n",
__FILE__, __LINE__, strerror(errno));
close(ev->epoll_fd);
return -1;
}
ev->epoll_events = malloc(ev->maxfds * sizeof(*ev->epoll_events));
#define SET(x) \
ev->x = fdevent_linux_sysepoll_##x;
##的作用是字符连接即初始化函数指针
ev->free=fdevent_linux_sysepoll_free等操作
通过上面代码初始化好了事件处理器srv->ev
接下来调用 network_register_fdevents (文件network.c)
int network_register_fdevents(server *srv) {
size_t i;
if (-1 == fdevent_reset(srv->ev)) {
return -1;
}
/* register fdevents after reset */
for (i = 0; i < srv->srv_sockets.used; i++) {
server_socket *srv_socket = srv->srv_sockets.ptr[i];
fdevent_register(srv->ev, srv_socket->fd, network_server_handle_fdevent, srv_socket);
fdevent_event_add(srv->ev, &(srv_socket->fde_ndx), srv_socket->fd, FDEVENT_IN);
}
return 0;
}
此时srv->srv_sockets.used应该为1,就是刚刚建立的listen fd
fdevent_register代码
int fdevent_register(fdevents *ev, int fd, fdevent_handler handler, void *ctx) {
fdnode *fdn;
fdn = fdnode_init();
fdn->handler = handler;
fdn->fd = fd;
fdn->ctx = ctx;
ev->fdarray[fd] = fdn;
return 0;
}
fdarray用于保存fd与对应回调handler及参数ctx之间的关系,将来能以O(1)的性能查找到某个fd对应的回调函数及参数
server.c最后就是一个巨大的循环
/* main-loop */
while (!srv_shutdown) {
//巨大的循环代码段
}
if (srv->srvconf.pid_file->used &&
srv->srvconf.changeroot->used == 0 &&
0 == graceful_shutdown) {
if (0 != unlink(srv->srvconf.pid_file->ptr)) {
if (errno != EACCES && errno != EPERM) {
log_error_write(srv, __FILE__, __LINE__, "sbds",
"unlink failed for:",
srv->srvconf.pid_file,
errno,
strerror(errno));
}
}
}
#ifdef HAVE_SIGACTION
log_error_write(srv, __FILE__, __LINE__, "sdsd",
"server stopped by UID =",
last_sigterm_info.si_uid,
"PID =",
last_sigterm_info.si_pid);
#else
log_error_write(srv, __FILE__, __LINE__, "s",
"server stopped");
#endif
/* clean-up */
log_error_close(srv);
network_close(srv);
connections_free(srv);
plugins_free(srv);
server_free(srv);
return 0;
[size=medium][b]
由于fork会原样复制父进程的heap和stack区,并保持独立,所以fork方式的好处就是完全不需要共享任何东西,代价是内存占用量要比较多
[/b][/size]
本文详细解析了lighttpd Web服务器的启动流程,包括初始化数据结构、读取配置文件、事件驱动模型的选择、监听套接字的创建、worker进程的fork以及事件处理器的注册过程。
6005

被折叠的 条评论
为什么被折叠?



