分享

Lenky个人站点 ? nginx对Linux native AIO机制的应用(实现篇)

 mediatv 2014-01-02

从前面文章已经了解到,在Linux系统上,要使用native AIO机制,可以利用libaio库,也可以手动利用syscall做一层自己的封装,而nginx采用就是后者,这样做的好处是既使用简单(即:用户无需安装libaio库),又能满足nginx的本身需求。

看具体代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#if (NGX_HAVE_FILE_<span class="wp_keywordlink_affiliate"><a href="http:///archives/tag/aio" title="查看 AIO 中的全部文章">AIO</a></span>)
 
/*
 * We call io_setup(), io_destroy() io_submit(), and io_getevents() directly
 * as syscalls instead of libaio usage, because the library header file
 * supports eventfd() since 0.3.107 version only.
 *
 * Also we do not use eventfd() in glibc, because glibc supports it
 * since 2.8 version and glibc maps two syscalls eventfd() and eventfd2()
 * into single eventfd() function with different number of parameters.
 */
 
static int
io_setup(u_int nr_reqs, aio_context_t *ctx)
{
    return syscall(SYS_io_setup, nr_reqs, ctx);
}
 
static int
io_destroy(aio_context_t ctx)
{
    return syscall(SYS_io_destroy, ctx);
}
 
static int
io_getevents(aio_context_t ctx, long min_nr, long nr, struct io_event *events,
    struct timespec *tmo)
{
    return syscall(SYS_io_getevents, ctx, min_nr, nr, events, tmo);
}
 
static void
ngx_epoll_aio_init(ngx_cycle_t *cycle, ngx_epoll_conf_t *epcf)
{
    int                 n;
    struct epoll_event  ee;
 
    ngx_eventfd = syscall(SYS_eventfd, 0);
 
    if (ngx_eventfd == -1) {
        ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
                      "eventfd() failed");
        ngx_file_aio = 0;
        return;
    }
...
 
static int
io_submit(aio_context_t ctx, long n, struct iocb **paiocb)
{
    return syscall(SYS_io_submit, ctx, n, paiocb);
}

nginx封装了四个重点接口函数:io_setup()、io_submit()、io_getevents()、io_destroy(),这 些已经无需多说。另外,由于对eventfd()函数的使用仅有一处,因此nginx连封装都没做,直接syscall对应的系统函数标识 SYS_eventfd(由nginx定义,与系统的__NR_eventfd对应,其它几个定义标识类似)。
说明:linux下的__NR_eventfd和__NR_eventfd2基本一致,只是参数有点不同,具体如下:

1
2
3
4
5
6
7
SYSCALL_DEFINE2(eventfd2, unsigned int, count, int, flags)
...
 
SYSCALL_DEFINE1(eventfd, unsigned int, count)
{
    return sys_eventfd2(count, 0);
}

即eventfd的flags参数默认为0。
在linux下,nginx把aio结合到epoll里使用,下面就来看这个具体过程。
除了提供前面讲到的几个接口函数,nginx还需要做的就是初始准备工作,这实现在函数ngx_epoll_aio_init()内,调用关系为:
ngx_epoll_init() -> ngx_epoll_aio_init()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
static void
ngx_epoll_aio_init(ngx_cycle_t *cycle, ngx_epoll_conf_t *epcf)
{
    int                 n;
    struct epoll_event  ee;
 
    ngx_eventfd = syscall(SYS_eventfd, 0);
...
    n = 1;
 
    if (ioctl(ngx_eventfd, FIONBIO, &n) == -1) {
...
 
    if (io_setup(epcf->aio_requests, &ngx_aio_ctx) == -1) {
...
 
    ngx_eventfd_event.data = &ngx_eventfd_conn;
    ngx_eventfd_event.handler = ngx_epoll_eventfd_handler;
    ngx_eventfd_event.log = cycle->log;
    ngx_eventfd_event.active = 1;
    ngx_eventfd_conn.fd = ngx_eventfd;
    ngx_eventfd_conn.read = &ngx_eventfd_event;
    ngx_eventfd_conn.log = cycle->log;
 
    ee.events = EPOLLIN|EPOLLET;
    ee.data.ptr = &ngx_eventfd_conn;
 
    if (epoll_ctl(ep, EPOLL_CTL_ADD, ngx_eventfd, &ee) != -1) {
        return;
    }

只看正常流程,这里做了几个工作:创建一个ngx_eventfd(全局变量)并将其设置为非阻塞,创建aio上下文环境 ngx_aio_ctx(全局变量),初始化ngx_eventfd_event和ngx_eventfd_conn(两者都是全局变量,利用conn和 event来进行统一描述,便于将eventfd、aio融合并适用到nginx的整体逻辑里),最后将代表aio的文件描述符ngx_eventfd加 入到epoll机制里,即完成eventfd与epoll的关联。

当提交一下aio请求时,再把ngx_eventfd设置到aio请求内,即把eventfd与aio关联起来:

1
2
3
4
5
6
7
8
9
ssize_t
ngx_file_aio_read(ngx_file_t *file, u_char *buf, size_t size, off_t offset,
    ngx_pool_t *pool)
{
...
    aio->aiocb.aio_flags = IOCB_FLAG_RESFD;
    aio->aiocb.aio_resfd = ngx_eventfd;
 
    ev->handler = ngx_file_aio_event_handler;

根据nginx本身的应用场景(Web服务器,读多写少),因此暂只有read的情况才使用了native AIO,当然,到底在什么情况下才真正启用了native AIO请求,还需根据nginx的配置以及每个请求的文件来决定,这在前面《配置篇》已简单分析过。
当有aio请求完成时,文件描述符ngx_eventfd将变得可读,阻塞点epoll_wait()函数返回,并将走如下可能的流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static ngx_int_t
ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
{
...
    events = epoll_wait(ep, event_list, (int) nevents, timer);
...
    for (i = 0; i < events; i++) {
        c = event_list[i].data.ptr;
 
        instance = (uintptr_t) c & 1;
        c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);
 
        rev = c->read;
...
        if ((revents & EPOLLIN) && rev->active) {
...
                rev->handler(rev);
...
        }
...

上面之所以说是可能的流程,是因为会根据具体配置而有所变化,但不管怎样,如上面的rev->handler回调那样,最终总会调入到函数ngx_epoll_eventfd_handler()内,而该函数的具体逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
static void
ngx_epoll_eventfd_handler(ngx_event_t *ev)
{
...
    n = read(ngx_eventfd, &ready, 8);
...
    ts.tv_sec = 0;
    ts.tv_nsec = 0;
 
    while (ready) {
 
        events = io_getevents(ngx_aio_ctx, 1, 64, event, &ts);
...
        if (events > 0) {
            ready -= events;
 
            for (i = 0; i < events; i++) {
...
                e = (ngx_event_t *) (uintptr_t) event[i].data;
 
                e->complete = 1;
                e->active = 0;
                e->ready = 1;
 
                aio = e->data;
                aio->res = event[i].res;
 
                ngx_post_event(e, &ngx_posted_events);
            }
 
            continue;
        }
...

忽略异常逻辑,先read文件描述符ngx_eventfd,以获取完成的aio请求个数ready,然后while循环获取所有这些完成的aio 请求,并且把每一个具体的aio请求结果封装成event事件对象,post提交到ngx_posted_events处理队列里,这样在下一轮 nginx事件处理循环里就会被实际执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void
ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
...
    if (ngx_posted_events) {
        if (ngx_threaded) {
            ngx_wakeup_worker_thread(cycle);
 
        } else {
            ngx_event_process_posted(cycle, &ngx_posted_events);
        }
    }
}
 
void
ngx_event_process_posted(ngx_cycle_t *cycle,
    ngx_thread_volatile ngx_event_t **posted)
{
    ngx_event_t  *ev;
 
    for ( ;; ) {
 
        ev = (ngx_event_t *) *posted;
...
        ngx_delete_posted_event(ev);
 
        ev->handler(ev);
    }
}

函数ngx_event_process_posted()会调用event事件对象的handler回调,根据aio请求的初始设置,也就是函数ngx_file_aio_event_handler():

1
2
3
4
5
6
7
8
9
10
11
12
static void
ngx_file_aio_event_handler(ngx_event_t *ev)
{
    ngx_event_aio_t  *aio;
 
    aio = ev->data;
 
    ngx_log_debug2(NGX_LOG_DEBUG_CORE, ev->log, 0,
                   "aio event handler fd:%d %V", aio->fd, &aio->file->name);
 
    aio->handler(ev);
}

aio->handler回调指向函数ngx_http_copy_aio_event_handler(),而后续的流程主要就是将aio从磁盘里读取到缓存的数据发送到最终客户端:
ngx_http_copy_aio_event_handler() -> ngx_http_request_handler() -> ngx_http_writer() -> ngx_http_output_filter() -> ngx_http_top_body_filter()

转载请保留地址:http:///archives/2013/01/12/2186http:///?p=2186


备注:如无特殊说明,文章内容均出自Lenky个人的真实理解而并非存心妄自揣测来故意愚人耳目。由于个人水平有限,虽力求内容正确无误,但仍然难免出错,请勿见怪,如果可以则请留言告之,并欢迎来讨论。另外值得说明的是,Lenky的部分文章以及部分内容参考借鉴了网络上各位网友的热心分享,特别是一些带有完全参考的文章,其后附带的链接内容也许更直接、更丰富,而我只是做了一下归纳&转述,在此也一并表示感谢。关于本站的所有技术文章,欢迎转载,但请遵从CC创作共享协议,而一些私人性质较强的心情随笔,建议不要转载。

法律:根据最新颁布的《信息网络传播权保护条例》,如果您认为本文章的任何内容侵犯了您的权利,请以Email或书面等方式告知,本站将及时删除相关内容或链接。

  1. 本文目前尚无任何评论.
NOTICE: You should type some Chinese word (like “你好”) in your comment to pass the spam-check, thanks for your patience!

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

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多