分享

BOA代码笔记 4

 lchjczw 2012-12-26

main.c (完?)

从上次继续

上次我们看到了这个地方:

  1. if (max_connections < 1) {  
  2.         struct rlimit rl;  
  3.   
  4.         /* has not been set explicitly */  
  5.         c = getrlimit(RLIMIT_NOFILE, &rl);  
  6.         if (c < 0) {  
  7.             perror("getrlimit");  
  8.             exit(1);  
  9.         }  
  10.         max_connections = rl.rlim_cur;  
  11.     }  
  12.   
  13.     /* background ourself */  
  14.     if (do_fork) {  
  15.         switch(fork()) {  
  16.         case -1:  
  17.             /* error */  
  18.             perror("fork");  
  19.             exit(1);  
  20.             break;  
  21.         case 0:  
  22.             /* child, success */  
  23.             break;  
  24.         default:  
  25.             /* parent, success */  
  26.             exit(0);  
  27.             break;  
  28.         }  
  29.     }  
  30.   
  31.     /* main loop */  
  32.     timestamp();  
  33.   
  34.     status.requests = 0;  
  35.     status.errors = 0;  
  36.   
  37.     start_time = current_time;  
  38.     select_loop(server_s);  
  39.     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就是一段小程序:

  1. void timestamp(void)  
  2. {  
  3.     log_error_time();  
  4.     fprintf(stderr, "boa: server version %s\n", SERVER_VERSION);  
  5.     log_error_time();  
  6.     fprintf(stderr, "boa: server built " __DATE__ " at " __TIME__ ".\n");  
  7.     log_error_time();  
  8.     fprintf(stderr, "boa: starting server pid=%d, port %d\n",  
  9.             (int) getpid(), server_port);  
  10. }  

程序之前好像已经将stderr重定向到自定义的错误处理文件中了。


然后,清空status的两个域,记录一下start_time。

然后就开始select_loop了~



select_loop()

ps:博主没啥经验,并未通读过源码,这也是第一次看正经点儿的源代码。这篇写完最后仍然云里雾里,较多猜测。如有错误欢迎指正。如果想知道代码背后的思想流程架构,那就得等博主看完全部代码再总结了(如果我有这能力和耐心的话……)。

ps2:如果由于博主没有全局把握,看到哪儿讲哪儿的方式引起你的极度不适,我表示非常抱歉。并强烈推荐你自己去看源码,也许效果拔群。:)

先粘一下select_loop的代码:

  1. void select_loop(int server_s)  
  2. {  
  3.     FD_ZERO(&block_read_fdset);  
  4.     FD_ZERO(&block_write_fdset);  
  5.     /* set server_s and req_timeout */  
  6.     req_timeout.tv_sec = (ka_timeout ? ka_timeout : REQUEST_TIMEOUT);  
  7.     req_timeout.tv_usec = 0l;   /* reset timeout */  
  8.   
  9.     /* preset max_fd */  
  10.     max_fd = -1;  
  11.   
  12.     while (1) {  
  13.         if (sighup_flag)  
  14.             sighup_run();  
  15.         if (sigchld_flag)  
  16.             sigchld_run();  
  17.         if (sigalrm_flag)  
  18.             sigalrm_run();  
  19.   
  20.         if (sigterm_flag) {  
  21.             if (sigterm_flag == 1)  
  22.                 sigterm_stage1_run(server_s);  
  23.             if (sigterm_flag == 2 && !request_ready && !request_block) {  
  24.                 sigterm_stage2_run();  
  25.             }  
  26.         }  
  27.   
  28.         /* reset max_fd */  
  29.         max_fd = -1;  
  30.   
  31.         if (request_block)  
  32.             /* move selected req's from request_block to request_ready */  
  33.             fdset_update();  
  34.   
  35.         /* any blocked req's move from request_ready to request_block */  
  36.         process_requests(server_s);  
  37.   
  38.         if (!sigterm_flag && total_connections < (max_connections - 10)) {  
  39.             BOA_FD_SET(server_s, &block_read_fdset); /* server always set */  
  40.         }  
  41.   
  42.         req_timeout.tv_sec = (request_ready ? 0 :  
  43.                               (ka_timeout ? ka_timeout : REQUEST_TIMEOUT));  
  44.         req_timeout.tv_usec = 0l;   /* reset timeout */  
  45.   
  46.         if (select(max_fd + 1, &block_read_fdset,  
  47.                    &block_write_fdset, NULL,  
  48.                    (request_ready || request_block ? &req_timeout : NULL)) == -1) {  
  49.             /* what is the appropriate thing to do here on EBADF */  
  50.             if (errno == EINTR)  
  51.                 continue;   /* while(1) */  
  52.             else if (errno != EBADF) {  
  53.                 DIE("select");  
  54.             }  
  55.         }  
  56.   
  57.         time(¤t_time);  
  58.         if (FD_ISSET(server_s, &block_read_fdset))  
  59.             pending_requests = 1;  
  60.     }  
  61. }  

清空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里有总结,如下:

  1. sigchld_flag = 0;  
  2.   
  3.     while ((pid = waitpid(-1, &status, WNOHANG)) > 0)  
  4.         if (verbose_cgi_logs) {  
  5.             time(¤t_time);  
  6.             log_error_time();  
  7.             fprintf(stderr, "reaping child %d: status %d\n", (int) pid, status);  
  8.         }  
  9.     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做规范处理。

其他状态处理如下:

  1. switch (current->status) {  
  2.             case READ_HEADER:  
  3.             case ONE_CR:  
  4.             case ONE_LF:  
  5.             case TWO_CR:  
  6.                 retval = read_header(current);  
  7.                 break;  
  8.             case BODY_READ:  
  9.                 retval = read_body(current);  
  10.                 break;  
  11.             case BODY_WRITE:  
  12.                 retval = write_body(current);  
  13.                 break;  
  14.             case WRITE:  
  15.                 retval = process_get(current);  
  16.                 break;  
  17.             case PIPE_READ:  
  18.                 retval = read_from_pipe(current);  
  19.                 break;  
  20.             case PIPE_WRITE:  
  21.                 retval = write_from_pipe(current);  
  22.                 break;  
  23.             case DONE:  
  24.                 /* a non-status that will terminate the request */  
  25.                 retval = req_flush(current);  
  26.                 /* 
  27.                  * retval can be -2=error, -1=blocked, or bytes left 
  28.                  */  
  29.                 if (retval == -2) { /* error */  
  30.                     current->status = DEAD;  
  31.                     retval = 0;  
  32.                 } else if (retval > 0) {  
  33.                     retval = 1;  
  34.                 }  
  35.                 break;  
  36.             case DEAD:  
  37.                 retval = 0;  
  38.                 current->buffer_end = 0;  
  39.                 SQUASH_KA(current);  
  40.                 break;  
  41.             default:  
  42.                 retval = 0;  
  43.                 fprintf(stderr, "Unknown status (%d), "  
  44.                         "closing!\n", current->status);  
  45.                 current->status = DEAD;  
  46.                 break;  
  47.             }  

每个状态的处理函数可能有自己的返回值,个别的需要规范化处理,以尽量满足:

返回值-1表示需要进入block queue;返回值0表示请求结束;返回值1表示还要在ready queue里。

最后总的处理retval:

  1. if (pending_requests)  
  2.             get_request(server_s);  
  3.   
  4.         switch (retval) {  
  5.         case -1:               /* request blocked */  
  6.             trailer = current;  
  7.             current = current->next;  
  8.             block_request(trailer);  
  9.             break;  
  10.         case 0:                /* request complete */  
  11.             current->time_last = current_time;  
  12.             trailer = current;  
  13.             current = current->next;  
  14.             free_request(&request_ready, trailer);  
  15.             break;  
  16.         case 1:                /* more to do */  
  17.             current->time_last = current_time;  
  18.             current = current->next;  
  19.             break;  
  20.         default:  
  21.             log_error_time();  
  22.             fprintf(stderr, "Unknown retval in process.c - "  
  23.                     "Status: %d, retval: %d\n", current->status, retval);  
  24.             current = current->next;  
  25.             break;  
  26.         }  

每一轮最后检查一次,是否还有pending_requests。有的话加入ready_queue。



这次先到这儿,看起来还是挺头晕的。

留下get_requests没说,以后如果再碰到再说。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多