BOA代码笔记 4


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没说,以后如果再碰到再说。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值