main.c (完?)
从上次继续
上次我们看到了这个地方:
- if (max_connections < 1) {
- struct rlimit rl;
-
-
- c = getrlimit(RLIMIT_NOFILE, &rl);
- if (c < 0) {
- perror("getrlimit");
- exit(1);
- }
- max_connections = rl.rlim_cur;
- }
-
-
- if (do_fork) {
- switch(fork()) {
- case -1:
-
- perror("fork");
- exit(1);
- break;
- case 0:
-
- break;
- default:
-
- exit(0);
- break;
- }
- }
-
-
- 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);
-
- req_timeout.tv_sec = (ka_timeout ? ka_timeout : REQUEST_TIMEOUT);
- req_timeout.tv_usec = 0l;
-
-
- 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();
- }
- }
-
-
- max_fd = -1;
-
- if (request_block)
-
- fdset_update();
-
-
- process_requests(server_s);
-
- if (!sigterm_flag && total_connections < (max_connections - 10)) {
- BOA_FD_SET(server_s, &block_read_fdset);
- }
-
- req_timeout.tv_sec = (request_ready ? 0 :
- (ka_timeout ? ka_timeout : REQUEST_TIMEOUT));
- req_timeout.tv_usec = 0l;
-
- if (select(max_fd + 1, &block_read_fdset,
- &block_write_fdset, NULL,
- (request_ready || request_block ? &req_timeout : NULL)) == -1) {
-
- if (errno == EINTR)
- continue;
- 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:
-
- retval = req_flush(current);
-
-
-
- if (retval == -2) {
- 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:
- trailer = current;
- current = current->next;
- block_request(trailer);
- break;
- case 0:
- current->time_last = current_time;
- trailer = current;
- current = current->next;
- free_request(&request_ready, trailer);
- break;
- case 1:
- 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没说,以后如果再碰到再说。
|