﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-Just enjoy programming-随笔分类-nginx</title><link>http://www.cppblog.com/tankzhouqiang/category/16721.html</link><description /><language>zh-cn</language><lastBuildDate>Mon, 11 Jul 2011 14:16:25 GMT</lastBuildDate><pubDate>Mon, 11 Jul 2011 14:16:25 GMT</pubDate><ttl>60</ttl><item><title>Nginx源码分析-Epoll模块(转载)</title><link>http://www.cppblog.com/tankzhouqiang/archive/2011/07/10/150564.html</link><dc:creator>周强</dc:creator><author>周强</author><pubDate>Sat, 09 Jul 2011 16:54:00 GMT</pubDate><guid>http://www.cppblog.com/tankzhouqiang/archive/2011/07/10/150564.html</guid><wfw:comment>http://www.cppblog.com/tankzhouqiang/comments/150564.html</wfw:comment><comments>http://www.cppblog.com/tankzhouqiang/archive/2011/07/10/150564.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tankzhouqiang/comments/commentRss/150564.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tankzhouqiang/services/trackbacks/150564.html</trackback:ping><description><![CDATA[转载自：<a href="http://www.tbdata.org/archives/1296">http://www.tbdata.org/archives/1296</a><br /><br /><br />
<div id="nav"><a class="current" href="http://www.tbdata.org" index="0">首页</a><a href="http://www.tbdata.org/about" index="1">关于我们</a><a class="last" href="http://www.tbdata.org/join" index="2">招聘</a> </div><span class="cf"></span><!--header-->

<div id="content">
<div class="column1 fL">
<div id="post-1296" class="post-1296 post type-post hentry category-66 tag-nginx tag-66 story pt15">
<h1 class="post_title pb10"><a title="Permanent Link to Nginx源码分析-Epoll模块" href="http://www.tbdata.org/archives/1296" rel="bookmark">Nginx源码分析-Epoll模块</a></h1>
<div class="underTitle colorbf pb10"><span class="fR commentCount">3 comments</span> 十二月 26th, 2010&nbsp;|&nbsp;by&nbsp;yixiao&nbsp;in&nbsp;<a title="查看 高性能服务器 的全部文章" href="http://www.tbdata.org/archives/category/%e9%ab%98%e6%80%a7%e8%83%bd%e6%9c%8d%e5%8a%a1%e5%99%a8" rel="category tag">高性能服务器</a> </div><!--underTitle--><!--<div class="entryImg fL"></div>-->

<div class="entry ">
<p>Linux平台上，Nginx使用epoll完成事件驱动，实现高并发；本文将不对epoll本身进行介绍（网上一堆一堆的文章介绍epoll的原理及使用方法，甚至源码分析等），仅看一下Nginx是如何使用epoll的。</p>
<p>Nginx在epoll模块中定义了好几个函数，这些函数基本都是作为回调注册到事件抽象层的对应接口上，从而实现了事件驱动的具体化，我们看如下的一段代码：</p><pre>ngx_event_module_t  ngx_epoll_module_ctx = {
    &amp;epoll_name,
    ngx_epoll_create_conf,               /* create configuration */
    ngx_epoll_init_conf,                 /* init configuration */
    {
        ngx_epoll_add_event,             /* add an event */
        ngx_epoll_del_event,             /* delete an event */
        ngx_epoll_add_event,             /* enable an event */
        ngx_epoll_del_event,             /* disable an event */
        ngx_epoll_add_connection,        /* add an connection */
        ngx_epoll_del_connection,        /* delete an connection */
        NULL,                            /* process the changes */
        ngx_epoll_process_events,        /* process the events */
        ngx_epoll_init,                  /* init the events */
        ngx_epoll_done,                  /* done the events */
    }
};
</pre>
<p><span id="more-1296"></span><br />这段代码就是epoll的相关函数注册到事件抽象层，这里所谓的事件抽象层在前面的博文中有提过，就是Nginx为了方便支持和开发具体的I/O模型，从而实现的一层抽象。代码后面的注释将功能说明得很详细了，本文就只重点关注ngx_epoll_init和ngx_epoll_process_events两个函数，其他几个函数就暂且忽略了。</p>
<p>ngx_epoll_init主要是完成epoll的相关初始化工作，代码分析如下：</p><pre>static ngx_int_t
ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
{
    ngx_epoll_conf_t  *epcf;
	/*取得epoll模块的配置结构*/
    epcf = ngx_event_get_conf(cycle-&gt;conf_ctx, ngx_epoll_module);
	/*ep是epoll模块定义的一个全局变量，初始化为-1*/
    if (ep == -1) {
    	/*创一个epoll对象，容量为总连接数的一半*/
        ep = epoll_create(cycle-&gt;connection_n / 2);
        if (ep == -1) {
            ngx_log_error(NGX_LOG_EMERG, cycle-&gt;log, ngx_errno,
                          "epoll_create() failed");
            return NGX_ERROR;
        }
    }
	/*nevents也是epoll模块定义的一个全局变量，初始化为0*/
    if (nevents events) {
        if (event_list) {
            ngx_free(event_list);
        }

		/*event_list存储产生事件的数组*/
        event_list = ngx_alloc(sizeof(struct epoll_event) * epcf-&gt;events,
                               cycle-&gt;log);
        if (event_list == NULL) {
            return NGX_ERROR;
        }
    }
    nevents = epcf-&gt;events;
	/*初始化全局变量ngx_io, ngx_os_is定义为：
		ngx_os_io_t ngx_os_io = {
    		ngx_unix_recv,
    		ngx_readv_chain,
    		ngx_udp_unix_recv,
    		ngx_unix_send,
    		ngx_writev_chain,
    		0
		};（位于src/os/unix/ngx_posix_init.c）
	*/
    ngx_io = ngx_os_io;
	/*这里就是将epoll的具体接口函数注册到事件抽象层接口ngx_event_actions上。
	具体是上文提到的ngx_epoll_module_ctx中封装的如下几个函数
        ngx_epoll_add_event,
        ngx_epoll_del_event,
        ngx_epoll_add_event,
        ngx_epoll_del_event,
        ngx_epoll_add_connection,
        ngx_epoll_del_connection,
        ngx_epoll_process_events,
        ngx_epoll_init,
        ngx_epoll_done,
	*/
    ngx_event_actions = ngx_epoll_module_ctx.actions;
#if (NGX_HAVE_CLEAR_EVENT)
	/*epoll将添加这个标志,主要为了实现边缘触发*/
    ngx_event_flags = NGX_USE_CLEAR_EVENT
#else
	/*水平触发*/
    ngx_event_flags = NGX_USE_LEVEL_EVENT
#endif
                      |NGX_USE_GREEDY_EVENT /*io的时候，直到EAGAIN为止*/
                      |NGX_USE_EPOLL_EVENT; /*epoll标志*/
    return NGX_OK;
}
</pre>
<p>epoll初始化工作没有想象中的复杂，和我们平时使用epoll都一样，下面看ngx_epoll_process_events，这个函数主要用来完成事件的等待并处理。</p><pre>static ngx_int_t
ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
{
    int                events;
    uint32_t           revents;
    ngx_int_t          instance, i;
    ngx_uint_t         level;
    ngx_err_t          err;
    ngx_log_t         *log;
    ngx_event_t       *rev, *wev, **queue;
    ngx_connection_t  *c;
	/*一开始就是等待事件，最长等待时间为timer；nginx为事件
	专门用红黑树维护了一个计时器。后续对这个timer单独分析。
	*/
    events = epoll_wait(ep, event_list, (int) nevents, timer);
    if (events == -1) {
        err = ngx_errno;
    } else {
        err = 0;
    }
    if (flags &amp; NGX_UPDATE_TIME || ngx_event_timer_alarm) {
        /*执行一次时间更新, nginx将时间缓存到了一组全局变量中，方便程序高效的获取事件。*/
        ngx_time_update();
    }
	/*处理wait错误*/
    if (err) {
        if (err == NGX_EINTR) {
            if (ngx_event_timer_alarm) {
                ngx_event_timer_alarm = 0;
                return NGX_OK;
            }
            level = NGX_LOG_INFO;
        } else {
            level = NGX_LOG_ALERT;
        }
        ngx_log_error(level, cycle-&gt;log, err, "epoll_wait() failed");
        return NGX_ERROR;
    }
	/*wait返回事件数0，可能是timeout返回，也可能是非timeout返回；非timeout返回则是error*/
    if (events == 0) {
        if (timer != NGX_TIMER_INFINITE) {
            return NGX_OK;
        }
        ngx_log_error(NGX_LOG_ALERT, cycle-&gt;log, 0,
                      "epoll_wait() returned no events without timeout");
        return NGX_ERROR;
    }
    log = cycle-&gt;log;
	/*for循环开始处理收到的所有事件*/
    for (i = 0; i read;
		。。。。。。。。。。。。。

		/*取得发生一个事件*/
        revents = event_list[i].events;

		/*记录wait的错误返回状态*/
        if (revents &amp; (EPOLLERR|EPOLLHUP)) {
            ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0,
                           "epoll_wait() error on fd:%d ev:%04XD",
                           c-&gt;fd, revents);
        }
        if ((revents &amp; (EPOLLERR|EPOLLHUP))
             &amp;&amp; (revents &amp; (EPOLLIN|EPOLLOUT)) == 0)
        {
            /*
             * if the error events were returned without EPOLLIN or EPOLLOUT,
             * then add these flags to handle the events at least in one
             * active handler
             */
            revents |= EPOLLIN|EPOLLOUT;
        }
		/*该事件是一个读事件，并该连接上注册的读事件是active的*/
        if ((revents &amp; EPOLLIN) &amp;&amp; rev-&gt;active) {
            if ((flags &amp; NGX_POST_THREAD_EVENTS) &amp;&amp; !rev-&gt;accept) {
                rev-&gt;posted_ready = 1;
            } else {
                rev-&gt;ready = 1;
            }

			/*事件放入相应的队列中；关于此处的先入队再处理，在前面的文章中已经介绍过了。*/
            if (flags &amp; NGX_POST_EVENTS) {
                queue = (ngx_event_t **) (rev-&gt;accept ?
                               &amp;ngx_posted_accept_events : &amp;ngx_posted_events);
                ngx_locked_post_event(rev, queue); /*入队*/
            } else {
                rev-&gt;handler(rev);
            }
        }
        wev = c-&gt;write;
		/*发生的是一个写事件，和读事件完全一样的逻辑过程*/
        if ((revents &amp; EPOLLOUT) &amp;&amp; wev-&gt;active) {
            if (flags &amp; NGX_POST_THREAD_EVENTS) {
                wev-&gt;posted_ready = 1;
            } else {
                wev-&gt;ready = 1;
            }
			/*先入队再处理*/
            if (flags &amp; NGX_POST_EVENTS) {
                ngx_locked_post_event(wev, &amp;ngx_posted_events);
            } else {
                wev-&gt;handler(wev);
            }
        }
    }
    return NGX_OK;
}
</pre>
<p>本文将关注的两个epoll函数也就这么一点代码了，但整个epoll还有添加事件和删除事件等的相关函数，代码都很简单，本文就不做具体的分析了。</p></div></div></div></div><img src ="http://www.cppblog.com/tankzhouqiang/aggbug/150564.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tankzhouqiang/" target="_blank">周强</a> 2011-07-10 00:54 <a href="http://www.cppblog.com/tankzhouqiang/archive/2011/07/10/150564.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Nginx的内存池实现（转载）</title><link>http://www.cppblog.com/tankzhouqiang/archive/2011/06/15/148718.html</link><dc:creator>周强</dc:creator><author>周强</author><pubDate>Wed, 15 Jun 2011 08:27:00 GMT</pubDate><guid>http://www.cppblog.com/tankzhouqiang/archive/2011/06/15/148718.html</guid><wfw:comment>http://www.cppblog.com/tankzhouqiang/comments/148718.html</wfw:comment><comments>http://www.cppblog.com/tankzhouqiang/archive/2011/06/15/148718.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tankzhouqiang/comments/commentRss/148718.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tankzhouqiang/services/trackbacks/148718.html</trackback:ping><description><![CDATA[<div><div> 					<p>来源：http://www.tbdata.org/archives/1390#more-1390</p><p><br /></p><p>Nginx的内存池实现得很精巧，代码也很简洁。总的来说，所有的内存池基本都一个宗旨：申请大块内存，避免&#8220;细水长流&#8221;。</p> <h1>一、创建一个内存池</h1> <p>nginx内存池主要有下面两个结构来维护，他们分别维护了内存池的头部和数据部。此处数据部就是供用户分配小块内存的地方。</p> <pre>//该结构用来维护内存池的数据块，供用户分配之用。<br />typedef struct {<br />    u_char               *last; 	//当前内存分配结束位置，即下一段可分配内存的起始位置<br />    u_char               *end;  	//内存池结束位置<br />    ngx_pool_t           *next; 	//链接到下一个内存池<br />    ngx_uint_t            failed; 	//统计该内存池不能满足分配请求的次数<br />} ngx_pool_data_t;<br />//该结构维护整个内存池的头部信息。<br />struct ngx_pool_s {<br />    ngx_pool_data_t       d; 		//数据块<br />    size_t                max;		//数据块的大小，即小块内存的最大值<br />    ngx_pool_t           *current;	//保存当前内存池<br />    ngx_chain_t          *chain;	//可以挂一个chain结构<br />    ngx_pool_large_t     *large;	//分配大块内存用，即超过max的内存请求<br />    ngx_pool_cleanup_t   *cleanup;	//挂载一些内存池释放的时候，同时释放的资源。<br />    ngx_log_t            *log;<br />};<br /></pre> <p><br /> 有了上面的两个结构，就可以创建一个内存池了，nginx用来创建一个内存池的接口是：ngx_pool_t  *ngx_create_pool(size_t size, ngx_log_t  *log)（位于src/core/ngx_palloc.c中）;调用这个函数就可以创建一个大小为size的内存池了。这里，我用内存池的结构图来展 示，就不做具体的代码分析了。<br /> <a href="http://www.tbdata.org/wp-content/uploads/2011/01/m1.jpg"><img src="http://www.tbdata.org/wp-content/uploads/2011/01/m1.jpg" alt="" size-full="" wp-image-1391="" height="210" width="650" /></a><br /> ngx_create_pool接口函数就是分配上图这样的一大块内存，然后初始化好各个头部字段（上图中的彩色部分）。红色表示的四个字段就是来自于上 述的第一个结构，维护数据部分，由图可知：last是用户从内存池分配新内存的开始位置，end是这块内存池的结束位置，所有分配的内存都不能超过 end。蓝色表示的max字段的值等于整个数据部分的长度，用户请求的内存大于max时，就认为用户请求的是一个大内存，此时需要在紫色表示的large 字段下面单独分配；用户请求的内存不大于max的话，就是小内存申请，直接在数据部分分配，此时将会移动last指针。</p> <h1>二、分配小块内存(size &lt;= max)</h1> <p>上面创建好了一个可用的内存池了，也提到了小块内存的分配问题。nginx提供给用户使用的内存分配接口有：<br /> void *ngx_palloc(ngx_pool_t *pool, size_t size);<br /> void *ngx_pnalloc(ngx_pool_t *pool, size_t size);<br /> void *ngx_pcalloc(ngx_pool_t *pool, size_t size);<br /> void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);</p> <p>ngx_palloc和ngx_pnalloc都是从内存池里分配size大小内存，至于分得的是小块内存还是大块内存，将取决于size的大小； 他们的不同之处在于，palloc取得的内存是对齐的，pnalloc则否。ngx_pcalloc是直接调用palloc分配好内存，然后进行一次0初 始化操作。ngx_pmemalign将在分配size大小的内存并按alignment对齐，然后挂到large字段下，当做大块内存处理。下面用图形 展示一下分配小块内存的模型：<br /> <a href="http://www.tbdata.org/wp-content/uploads/2011/01/m2.jpg"><img src="http://www.tbdata.org/wp-content/uploads/2011/01/m2.jpg" alt="" size-full="" wp-image-1392="" height="498" width="650" /></a><br /> 上图这个内存池模型是由上3个小内存池构成的，由于第一个内存池上剩余的内存不够分配了，于是就创建了第二个新的内存池，第三个内存池是由于前面两个内存 池的剩余部分都不够分配，所以创建了第三个内存池来满足用户的需求。由图可见：所有的小内存池是由一个单向链表维护在一起的。这里还有两个字段需要关 注，failed和current字段。failed表示的是当前这个内存池的剩余可用内存不能满足用户分配请求的次数，即是说：一个分配请求到来后，在 这个内存池上分配不到想要的内存，那么就failed就会增加1；这个分配请求将会递交给下一个内存池去处理，如果下一个内存池也不能满足，那么它的 failed也会加1，然后将请求继续往下传递，直到满足请求为止（如果没有现成的内存池来满足，会再创建一个新的内存池）。current字段会随着 failed的增加而发生改变，如果current指向的内存池的failed达到了4的话，current就指向下一个内存池了。猜测：4这个值应该是 作者的经验值，或者是一个统计值。</p> <h1>三、大块内存的分配(size &gt; max)</h1> <p>大块内存的分配请求不会直接在内存池上分配内存来满足，而是直接向操作系统申请这么一块内存（就像直接使用malloc分配内存一样），然后将这块 内存挂到内存池头部的large字段下。内存池的作用在于解决小块内存池的频繁申请问题，对于这种大块内存，是可以忍受直接申请的。同样，用图形展示大块 内存申请模型：<br /> <a href="http://www.tbdata.org/wp-content/uploads/2011/01/m3.jpg"><img src="http://www.tbdata.org/wp-content/uploads/2011/01/m3.jpg" alt="" size-full="" wp-image-1393="" height="440" width="650" /></a><br /> 注意每块大内存都对应有一个头部结构（next&amp;alloc），这个头部结构是用来将所有大内存串成一个链表用的。这个头部结构不是直接向操作系 统申请的，而是当做小块内存（头部结构没几个字节）直接在内存池里申请的。这样的大块内存在使用完后，可能需要第一时间释放，节省内存空间，因此 nginx提供了接口函数：ngx_int_t ngx_pfree(ngx_pool_t *pool, void  *p)；此函数专门用来释放某个内存池上的某个大块内存，p就是大内存的地址。ngx_pfree只会释放大内存，不会释放其对应的头部结构，毕竟头部结 构是当做小内存在内存池里申请的；遗留下来的头部结构会作下一次申请大内存之用。</p> <h1>四、cleanup资源</h1> <p><a href="http://www.tbdata.org/wp-content/uploads/2011/01/m4.jpg"><img src="http://www.tbdata.org/wp-content/uploads/2011/01/m4.jpg" alt="" size-full="" wp-image-1395="" height="462" width="650" /></a><br /> 可以看到所有挂载在内存池上的资源将形成一个循环链表，一路走来，发现链表这种看似简单的数据结构却被频繁使用。由图可知，每个需要清理的资源都对应有一 个头部结构，这个结构中有一个关键的字段handler，handler是一个函数指针，在挂载一个资源到内存池上的时候，同时也会注册一个清理资源的函 数到这个handler上。即是说，内存池在清理cleanup的时候，就是调用这个handler来清理对应的资源。比如：我们可以将一个开打的文件描 述符作为资源挂载到内存池上，同时提供一个关闭文件描述的函数注册到handler上，那么内存池在释放的时候，就会调用我们提供的关闭文件函数来处理文 件描述符资源了。</p> <h1>五、内存的释放</h1> <p>nginx只提供给了用户申请内存的接口，却没有释放内存的接口，那么nginx是如何完成内存释放的呢？总不能一直申请，用不释放啊。针对这个问 题，nginx利用了web server应用的特殊场景来完成；一个web  server总是不停的接受connection和request，所以nginx就将内存池分了不同的等级，有进程级的内存池、connection级 的内存池、request级的内存池。也就是说，创建好一个worker进程的时候，同时为这个worker进程创建一个内存池，待有新的连接到来后，就 在worker进程的内存池上为该连接创建起一个内存池；连接上到来一个request后，又在连接的内存池上为request创建起一个内存池。这样， 在request被处理完后，就会释放request的整个内存池，连接断开后，就会释放连接的内存池。因而，就保证了内存有分配也有释放。</p> <p>总结：通过内存的分配和释放可以看出，nginx只是将小块内存的申请聚集到一起申请，然后一起释放。避免了频繁申请小内存，降低内存碎片的产生等问题</p> 				</div></div><img src ="http://www.cppblog.com/tankzhouqiang/aggbug/148718.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tankzhouqiang/" target="_blank">周强</a> 2011-06-15 16:27 <a href="http://www.cppblog.com/tankzhouqiang/archive/2011/06/15/148718.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Web Bench</title><link>http://www.cppblog.com/tankzhouqiang/archive/2011/04/26/145054.html</link><dc:creator>周强</dc:creator><author>周强</author><pubDate>Tue, 26 Apr 2011 06:50:00 GMT</pubDate><guid>http://www.cppblog.com/tankzhouqiang/archive/2011/04/26/145054.html</guid><wfw:comment>http://www.cppblog.com/tankzhouqiang/comments/145054.html</wfw:comment><comments>http://www.cppblog.com/tankzhouqiang/archive/2011/04/26/145054.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tankzhouqiang/comments/commentRss/145054.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tankzhouqiang/services/trackbacks/145054.html</trackback:ping><description><![CDATA[今天开始要看nginx 源码了，首先先看一个web服务器压力测试工具Web Bench
。<br><br>Web Bench is very simple tool for benchmarking WWW
or proxy servers. Uses fork() for simulating multiple clients and
can use HTTP/0.9-HTTP/1.1 requests. This benchmark is not
very realistic, but it can test if your HTTPD can realy handle that
many clients at once (try to run some CGIs) without taking
your machine down. Displays pages/min and bytes/sec. Can be used
in more aggressive mode with -f switch.
<br><br>Web Bench 下载主页 http://home..cz/~cz210552/webbench.html
<br><br><br>用Web Bench 简单测试下nginx服务器的性能。结果如下<br>测试参数 10000个用户并发请求30秒
<br><br><img src="http://www.cppblog.com/images/cppblog_com/tankzhouqiang/webResult.png" border="0"><br><br><br> <img src ="http://www.cppblog.com/tankzhouqiang/aggbug/145054.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tankzhouqiang/" target="_blank">周强</a> 2011-04-26 14:50 <a href="http://www.cppblog.com/tankzhouqiang/archive/2011/04/26/145054.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>