main.c (完?)
从上次继续
上次我们看到了这个地方:
if (max_connections < 1) {
struct rlimit rl;
/* has not been set explicitly */
c = getrlimit(RLIMIT_NOFILE, &rl);
if (c < 0) {
perror("getrlimit");
exit(1);
}
max_connections = rl.rlim_cur;
}
/* background ourself */
if (do_fork) {
switch(fork()) {
case -1:
/* error */
perror("fork");
exit(1);
break;
case 0:
/* child, success */
break;
default:
/* parent, success */
exit(0);
break;
}
}
/* main loop */
timestamp();
status.requests = 0;
status.errors = 0;
start_time = current_time;
select_loop(server_s);
return 0;
第一个if块确定最大连接数。如果未指定,使用getrlimit来获得。
getrlimit(RLIMIT_NOFILE, &rl); specifies a value one greater than the maximum file descriptor number that can be opened by this process.
第二个if块,如果do_fork为1,那么我们从子进程运行,父进程结束。
timestamp就是一段小程序:
void timestamp(void)
{
log_error_time();
fprintf(stderr, "boa: server version %s\n", SERVER_VERSION);
log_error_time();
fprintf(stderr, "boa: server built " __DATE__ " at " __TIME__ ".\n");
log_error_time();
fprintf(stderr, "boa: starting server pid=%d, port %d\n",
(int) getpid(), server_port);
}
程序之前好像已经将stderr重定向到自定义的错误处理文件中了。
然后,清空status的两个域,记录一下start_time。
然后就开始select_loop了~
select_loop()
ps:博主没啥经验,并未通读过源码,这也是第一次看正经点儿的源代码。这篇写完最后仍然云里雾里,较多猜测。如有错误欢迎指正。如果想知道代码背后的思想流程架构,那就得等博主看完全部代码再总结了(如果我有这能力和耐心的话……)。
ps2:如果由于博主没有全局把握,看到哪儿讲哪儿的方式引起你的极度不适,我表示非常抱歉。并强烈推荐你自己去看源码,也许效果拔群。:)
先粘一下select_loop的代码:
void select_loop(int server_s)
{
FD_ZERO(&block_read_fdset);
FD_ZERO(&block_write_fdset);
/* set server_s and req_timeout */
req_timeout.tv_sec = (ka_timeout ? ka_timeout : REQUEST_TIMEOUT);
req_timeout.tv_usec = 0l; /* reset timeout */
/* preset max_fd */
max_fd = -1;
while (1) {
if (sighup_flag)
sighup_run();
if (sigchld_flag)
sigchld_run();
if (sigalrm_flag)
sigalrm_run();
if (sigterm_flag) {
if (sigterm_flag == 1)
sigterm_stage1_run(server_s);
if (sigterm_flag == 2 && !request_ready && !request_block) {
sigterm_stage2_run();
}
}
/* reset max_fd */
max_fd = -1;
if (request_block)
/* move selected req's from request_block to request_ready */
fdset_update();
/* any blocked req's move from request_ready to request_block */
process_requests(server_s);
if (!sigterm_flag && total_connections < (max_connections - 10)) {
BOA_FD_SET(server_s, &block_read_fdset); /* server always set */
}
req_timeout.tv_sec = (request_ready ? 0 :
(ka_timeout ? ka_timeout : REQUEST_TIMEOUT));
req_timeout.tv_usec = 0l; /* reset timeout */
if (select(max_fd + 1, &block_read_fdset,
&block_write_fdset, NULL,
(request_ready || request_block ? &req_timeout : NULL)) == -1) {
/* what is the appropriate thing to do here on EBADF */
if (errno == EINTR)
continue; /* while(1) */
else if (errno != EBADF) {
DIE("select");
}
}
time(¤t_time);
if (FD_ISSET(server_s, &block_read_fdset))
pending_requests = 1;
}
}
清空block_read_fdset和block_write_fdset,这两个看名字我猜是用在select里,具体用来表示哪些fd的集合以后才知道。
req_timeout用作select的时间限制,#define REQUEST_TIMEOUT 60
max_fd置为-1
然后进入了while(1)循环。
首先是检测这么几个flag:sighup_flag,sigchld_flag,sigalrm_flag,sigterm_flag。
boa的信号处理
回头看一下boa对信号的处理策略(void init_signals()中):
boa处理10个信号,其中SIGPIPE,SIGUSR1,SIGUSR2忽略掉。
SIGSEGV,SIGBUS,SIGTERM,SIGHUP,SIGINT,SIGCHLD,SIGALRM有自己的信号处理函数。
对于段错误SIGSEGV,记录一下出错时间写到日志里,然后就abort了。毕竟无法恢复。
对于SIGBUS,在另外两处视情况可能要好好的处理SIGBUS,之后讲到再说。默认情况下像SIGSEGV一样,也是记录一下,abort掉。
SIGBUS这个信号,印象中在mmap后错误访问时会产生,百度一下发现,在一些体系结构上,访问未对齐的地址会产生。
对于SIGINT,收到这个信号时,记录一下,正常退出。这个信号可以由ctrl+c发送给foreground process产生。
剩下了这四个在while循环里处理的信号。
SIGHUP用来重新读取config_file。先清空fdset,清空read_config_file里动态分配的内存,清空request_free链表,然后调用read_config_file。
对于SIGCHLD的处理是典型的子进程处理方式,UNP里有总结,如下:
sigchld_flag = 0;
while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
if (verbose_cgi_logs) {
time(¤t_time);
log_error_time();
fprintf(stderr, "reaping child %d: status %d\n", (int) pid, status);
}
return;
SIGALRM只用来将mime_hashtable和passwd_hashtable里的数据写到日志文件里。
SIGTERM两种处理方式
sigterm_stage1_run,记录一下时间,清空block_read_set,关掉server_s,意味着不再接受新的连接。然后设置sigterm_flag = 2; 下一次由sigterm_stage2_run来处理。
sigterm_stage2_run,里完成正常结束的第二阶段:clear_common_env(); dump_mime(); dump_passwd(); dump_alias(); free_requests(); 然后exit(0)。
SIGTERM通过两个函数使程序适当的中断。
fd_update()
信号处理部分结束。
之后到达这么一段:
if (request_block)
/* move selected req's from request_block to request_ready */
fdset_update();
源代码里对函数fdset_update();的说明如下:
/*
* Name: fdset_update
*
* Description: iterate through the blocked requests, checking whether
* that file descriptor has been set by select. Update the fd_set to
* reflect current status.
*
* Here, we need to do some things:
* - keepalive timeouts simply close
* (this is special:: a keepalive timeout is a timeout where
keepalive is active but nothing has been read yet)
* - regular timeouts close + error
* - stuff in buffer and fd ready? write it out
* - fd ready for other actions? do them
*/
一句话总结:fdset_update将合适的request从block链表里移动到ready链表里。
boa里边有三个请求链表
request *request_ready = NULL; /* ready list head */
request *request_block = NULL; /* blocked list head */
request *request_free = NULL; /* free list head */
新的连接需要reqeust结构体时优先从request_free中提取。如果为空,将malloc一个reqeust。
简单的描述一下,fdset_update进行如下处理:
首先,获取time_since为距离上次成功操作经历的时间。
如果请求出于keepalive中,time_since已经大于ka_timeout(配置文件里可以配置),而且还没有读取到任何东西,那么request的status变为DEAD。
如果time_since大于REQUEST_TIMEOUT(60),那么status变为DEAD。
如果缓冲区有数据,而且status小于DEAD:
如果不在block_write_fdset里,那么放到block_write_fdset里。
如果fd已经在block_write_fdset里,调用ready_request,将request从block队列里转移到ready队列里,同时清除block_write_fdset里的标志
ready_request函数的功能是根据status,从fdset中清除对应fd。
其他情况:
状态为WRITE,PIPE_WRITE,DONE的请求,如果没有那就放到block_write_fdset里,如果已经在了就调用ready_request。
状态为BODY_WRITE,将request的post_data_fd做以上处理。post_data_fd注释为/* fd for post data tmpfile */,应该是客户端POST方法时的临时文件,以后再详看。
状态为PIPE_READ,将request的data_fd做类似处理,不过检查的是block_read_fdset。
状态为DEAD,直接调用ready_request。
其他的,检查fd是否在block_read_fdset,并作相应处理。
这块儿目前看代码只能知道这么做,但不明白作者背后的思想,模型。先慢慢来,整体看完一遍后应该能更好了解。
process_requests()
之后是process_requests(),按注释来看,功能与之前的fdset_update()正好相反,将适合的reqeust从ready链表移动到block链表。
process_requests()注释如下:
/*
* Name: process_requests
*
* Description: Iterates through the ready queue, passing each request
* to the appropriate handler for processing. It monitors the
* return value from handler functions, all of which return -1
* to indicate a block, 0 on completion and 1 to remain on the
* ready list for more procesing.
*/
对于每一个ready queue里的请求遍历处理,返回值-1表示需要进入block queue;返回值0表示请求结束;返回值1表示还要在ready queue里。
首先检查是否有pending_requests,如果有调用get_request(server_s);,接受一个connection,加入ready_queue。
get_request(server_s);其实比较复杂,这里先不详细说了。大体功能是,接受一个请求,并做一些简单的初始化,加入ready_queue。
然后开始poll ready链表:
如果有数据要写,状态不是DEAD或DONE,req_flush。之所以特殊处理,因为返回值特殊 -2=error, -1=blocked, or bytes left。然后对retval做规范处理。
其他状态处理如下:
switch (current->status) {
case READ_HEADER:
case ONE_CR:
case ONE_LF:
case TWO_CR:
retval = read_header(current);
break;
case BODY_READ:
retval = read_body(current);
break;
case BODY_WRITE:
retval = write_body(current);
break;
case WRITE:
retval = process_get(current);
break;
case PIPE_READ:
retval = read_from_pipe(current);
break;
case PIPE_WRITE:
retval = write_from_pipe(current);
break;
case DONE:
/* a non-status that will terminate the request */
retval = req_flush(current);
/*
* retval can be -2=error, -1=blocked, or bytes left
*/
if (retval == -2) { /* error */
current->status = DEAD;
retval = 0;
} else if (retval > 0) {
retval = 1;
}
break;
case DEAD:
retval = 0;
current->buffer_end = 0;
SQUASH_KA(current);
break;
default:
retval = 0;
fprintf(stderr, "Unknown status (%d), "
"closing!\n", current->status);
current->status = DEAD;
break;
}
每个状态的处理函数可能有自己的返回值,个别的需要规范化处理,以尽量满足:
返回值-1表示需要进入block queue;返回值0表示请求结束;返回值1表示还要在ready queue里。
最后总的处理retval:
if (pending_requests)
get_request(server_s);
switch (retval) {
case -1: /* request blocked */
trailer = current;
current = current->next;
block_request(trailer);
break;
case 0: /* request complete */
current->time_last = current_time;
trailer = current;
current = current->next;
free_request(&request_ready, trailer);
break;
case 1: /* more to do */
current->time_last = current_time;
current = current->next;
break;
default:
log_error_time();
fprintf(stderr, "Unknown retval in process.c - "
"Status: %d, retval: %d\n", current->status, retval);
current = current->next;
break;
}
每一轮最后检查一次,是否还有pending_requests。有的话加入ready_queue。
这次先到这儿,看起来还是挺头晕的。
留下get_requests没说,以后如果再碰到再说。