Nginx事件處理(epoll)

轉自:http://bollaxu.iteye.com/blog/855457

 

事件處理是Nginx處理請求的核心,每個子進程在ngx_wrker_process_cycle()的循環裏面不斷調用

ngx_procss_events_and_timers()函數來處理各種事件。下面,分析使用epoll機制下的Nginx事件處理過程,

用源碼分析和debug信息追蹤兩種方法。我們從ngx_worker_process_cycle()函數(即工作進程處理請求的循環)切入:

static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
	/*...*/
	//第一部分:初始化
	ngx_worker_process_init(cycle, 1);
	/*...*/
	for ( ;; ) {
		/*...*/
		//第二部分:處理事件
		ngx_process_events_and_timers(cycle);
		/*...*/
	}
	/*...*/
}
//第一部分:初始化
static void ngx_worker_process_init(ngx_cycle_t *cycle, ngx_uint_t priority)
{
	//配置一些環境變量
	//...
	//設置uid,groupid等
	//...
	//如果有設置CPU affinity
	//...
	//換到當前工作的目錄下
	//...
	//清空所有的信號
	//...
	//清掉監聽socket上以前的事件
	//...

	//調用所有模塊的init_process鉤子函數
	//所有模塊-->每個子進程可以調用這些模塊的功能
	//init_process
	for (i = 0; ngx_modules[i]; i++) {
		if (ngx_modules[i]->init_process) {
			//如果是event module: ngx_event_process_init()被調用
			if (ngx_modules[i]->init_process(cycle) == NGX_ERROR) {
				exit(2);
			}
		}
	}
	//將其他進程的channel[1]關閉,自己的除外
	//子進程繼承了父進程的ngx_processes數組,但子進程只監聽自己的channel[1]
	//...
	//將自己的channel[0]關閉
	//因爲自己的channel[0]是給其他子進程,用來發送消息的sendmsg
	//...
	//調用ngx_add_channel_event()函數,給ngx_channel註冊一個讀事件處理函數。
	//在ngx_start_worker_processes()函數中,ngx_channel = ngx_processes[s].channel[1];
	//ngx_channel就是進程自身的channel[1],用來讀取的socket
	//ngx_channel_handler處理從channel中收到的信號,當事件觸發時,調用這個方法
	if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT, ngx_channel_handler) == NGX_ERROR)
	{
		exit(2);
	}
}

每個模塊(module)都有一個全局的ngx_module_t結構變量,在worker process被創建(fork)以後,在ngx_worker_process_init()內調用到每個模塊的init_process鉤子。其中ngx_event_core_module的init_process鉤子指向的是ngx_event_process_init()函數,這個函數是這樣的:

static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle)
{
	/*...*/

	for (m = 0; ngx_modules[m]; m++) {
		//只有ngx_event_core_module和ngx_epoll_module是NGX_EVENT_MODULE類型的
		if (ngx_modules[m]->type != NGX_EVENT_MODULE) {
			continue;
		}
		if (ngx_modules[m]->ctx_index != ecf->use) {
			continue;
		}
		//獲取模塊上下文
		module = ngx_modules[m]->ctx;
		//初始化模塊
		//ngx_epoll_module(類型ngx_module_t)是全局的結構變量,在初始化的時候由ngx_epoll_module_ctx傳入參數,而init函數也在這個時候確定
		//如epoll就是ngx_epoll_init
		if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) {
			exit(2);
		}
		break;
	}

	/*...*/

	/*=========初始化connections=========*/

	//分配connection_n個空間給connections
	cycle->connections = ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);
	c = cycle->connections;
	//分配connection_n個空間給read_events和write_events,初始化這些read_events和write_events
	cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n, cycle->log);
	rev = cycle->read_events;
	for (i = 0; i < cycle->connection_n; i++) {
		rev[i].closed = 1;
		rev[i].instance = 1;
		/*不考慮線程*/
	}
	cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n, cycle->log);
	wev = cycle->write_events;
	for (i = 0; i < cycle->connection_n; i++) {
		wev[i].closed = 1;
		/*不考慮線程*/
	}

	//把connection和read_event,write_event聯繫起來,每個connection都指向一個read_event和write_event
	i = cycle->connection_n;
	next = NULL;
	do {
		//把connection鏈接起來
		i--;
		c[i].data = next;
		c[i].read = &cycle->read_events[i];
		c[i].write = &cycle->write_events[i];
		c[i].fd = (ngx_socket_t) -1;
		next = &c[i];
		/*不考慮線程*/
	} while (i);

	//free_connections指向connections的頭
	cycle->free_connections = next;
	//初始化free connection的數目
	cycle->free_connection_n = cycle->connection_n;

	/* for each listening socket */
	ls = cycle->listening.elts;
	for (i = 0; i < cycle->listening.nelts; i++) {
		c = ngx_get_connection(ls[i].fd, cycle->log);//獲取一個空閒的connection,並設置其file descriptor
		c->log = &ls[i].log;
		c->listening = &ls[i];//獲取ngx_listening_s結構
		ls[i].connection = c; //設置ngx_listening_s.connection
		rev = c->read;
		rev->log = c->log;
		rev->accept = 1;      //接受請求
	
		/*...*/

		//設置c->read的handler爲ngx_event_accept
		rev->handler = ngx_event_accept;
		if (ngx_use_accept_mutex) {
			continue;
		}
		if (ngx_event_flags & NGX_USE_RTSIG_EVENT) {
			if (ngx_add_conn(c) == NGX_ERROR) {
				return NGX_ERROR;
			}
		} 
		else {
			if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
				return NGX_ERROR;
			}
		}
	}
}


在上面的函數裏,如果我們使用了epoll,那麼epoll模塊的ngx_epoll_init()函數就會被調用,而這個函數中最重要的就是

ngx_event_actions = ngx_epoll_module_ctx.actions


在工作進程的for循環中用來處理事件的ngx_process_events_and_timers()中,每次調用ngx_process_events()的時候,其實就是調用ngx_epoll_module_ctx.actions裏面的ngx_epoll_process_events()。

//第二部分:處理事件
//被循環調用
//先接收連接(並不處理事件),以及處理進程間信號(如有)
//處理accept queue和event queue裏面的事件
void ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
    	
	//如果使用了accept mutex  
	//nginx uses accept mutex to serialize accept() syscalls
	//多進程需要使用mutex
	if (ngx_use_accept_mutex) {
		//空閒連接數過少
		if (ngx_accept_disabled > 0) {
			ngx_accept_disabled--;
		}
		else {
			//調用ngx_trylock_accept_mutex()試着給cycle上鎖,並把ngx_accept_mutex_held設爲1
			//如果成功獲得lock,將調用ngx_enable_accept_events()
			//ngx_enable_accept_events()中會調用ngx_add_event()/ngx_add_conn()
			//即,獲得lock的worker process纔會添加一個“接受請求”的事件
			if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
				return;
			}
			//ngx_accept_mutex_held表示當前是否已經持有鎖
			//如果持有的話,就把flags添加NGX_POST_EVENTS,這樣表明可以去accept請求
			//如果不持有,就去處理其他事件,在ngx_epoll_process_events裏會調用
			//rev/wev->handler()
			if (ngx_accept_mutex_held) {
				flags |= NGX_POST_EVENTS;
			}
			else {
				if (timer == NGX_TIMER_INFINITE || 
				    timer > ngx_accept_mutex_delay) {
					//ngx_accept_mutex_delay 當獲得鎖失敗後,再次去請求鎖的間隔時間
					timer = ngx_accept_mutex_delay;
				}
			}
		}
	}

    	//#define ngx_process_events   ngx_event_actions.process_events
	//In epoll, ngx_event_actions = ngx_epoll_module_ctx.actions;
	//全局變量ngx_epoll_module_ctx(類型ngx_event_module_t),內有actions(類型ngx_event_actions_t),定義process_events鉤子
	//鉤子調用epoll的ngx_epoll_process_events()
	(void) ngx_process_events(cycle, timer, flags);

	delta = ngx_current_msec - delta;//計算process所用時間

	//ngx_posted_accept_events隊列不爲空-->有accept事件發生,就去處理
	//accept事件,其實最後就是調用accept函數接收新的連接
	//rev->handler = ngx_event_accept在ngx_event_process_init裏面設置
	if (ngx_posted_accept_events) {
		ngx_event_process_posted(cycle, &ngx_posted_accept_events);
	}

	//已經處理完接收新連接的事件了,如果前面獲取到了accept鎖,那就解鎖
	if (ngx_accept_mutex_held) {
		ngx_shmtx_unlock(&ngx_accept_mutex);
	}

	if (delta) {
		ngx_event_expire_timers();
	}

	//除了accept事件之外的其他事件放在這個隊列中,如果隊列不爲空,就去處理相關的事件
	if (ngx_posted_events) {
		//一般不用線程來處理
		if (ngx_threaded) {
			ngx_wakeup_worker_thread(cycle);
		} else {
			ngx_event_process_posted(cycle, &ngx_posted_events);
		}
	}
}


 說到這裏不得不提一下一個很重要的結構ngx_module_t,這裏面定義了一些非常重要的函數鉤子:

struct ngx_module_s {
	//ctx_index是分類的模塊計數器,nginx的模塊可以分爲四種:core、event、http和
	//mail,每一種的模塊又會各自計數一下,這個ctx_index就是每個模塊在其所屬類組的計數值

	ngx_uint_t            ctx_index;    

	//index是一個模塊計數器,按照每個模塊在ngx_modules[]數組中的聲明順序
	//(見objs/ngx_modules.c),從0開始依次給每個模塊進行編號
	ngx_uint_t            index; 

	/*...*/
	ngx_uint_t            version;

	//ctx是模塊的上下文,不同種類的模塊有不同的上下文,四類模塊就有四種模塊上下文,實現爲四個不同的結構體,所以ctx是void *
	void                 *ctx; 

	//commands 是模塊的指令集,nginx的每個模塊都可以實現一些自定義的指令,這些指令
	//寫在配置文件的適當配置項中,每一個指令在源碼中對應着一個 ngx_command_t結構的
	//變量,nginx會從配置文件中把模塊的指令讀取出來放到模塊的commands指令數組中,
	//這些指令一般是把配置項的 參數值賦給一些程序中的變量或者是在不同的變量之間合併或
	//轉換數據(例如include指令),指令可以帶參數也可以不帶參數,你可以把這些指令想象
	//爲 unix的命令行或者是一種模板語言的指令。
	ngx_command_t        *commands;

	//type就是模塊的種類,前面已經說過,nginx模塊分爲core、event、http和mail四類,type用宏定義標識四個分類。 
	ngx_uint_t            type; 

	//init_master、 init_module、init_process、init_thread、exit_thread、
	//exit_process、 exit_master是函數指針,指向模塊實現的自定義回調函數,
	//這些回調函數分別在初始化master、初始化模塊、初始化工作進程、初始化線程、
	//退出線程、退出工作進程和退出master的時候被調用,如果模塊需要在這些時機做處理,
	//就可以實現對應的函數,並把它賦值給對應的函數指針來註冊一個回調 函數接口
	ngx_int_t           (*init_master)(ngx_log_t *log);
	ngx_int_t           (*init_module)(ngx_cycle_t *cycle);
	ngx_int_t           (*init_process)(ngx_cycle_t *cycle);
	ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);
	void                (*exit_thread)(ngx_cycle_t *cycle);
	void                (*exit_process)(ngx_cycle_t *cycle);
	void                (*exit_master)(ngx_cycle_t *cycle);

	/*...*/

};

 

//epoll處理事件的函數
static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
{

	//得到發生的事件表event_list
	events = epoll_wait(ep, event_list, (int) nevents, timer);

	if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
		ngx_time_update();
	}

	//上鎖ngx_posted_events_mutex
	//ngx_posted_events_mutex只有在NGX_THREAD宏定義有效時纔有效
	ngx_mutex_lock(ngx_posted_events_mutex);

	for (i = 0; i < events; i++) {
		//獲取事件的connection
		c = event_list[i].data.ptr;
		instance = (uintptr_t) c & 1;
		c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);
		rev = c->read;

		revents = event_list[i].events;

		if ((revents & (EPOLLERR|EPOLLHUP)) && 
		   (revents & (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;
		}

		//default rev->active is 1
		if ((revents & EPOLLIN) && rev->active) {
			if ((flags & NGX_POST_THREAD_EVENTS) && !rev->accept) {
				rev->posted_ready = 1;
			}
		} 
		else {
			rev->ready = 1;
		}
		if (flags & NGX_POST_EVENTS) {
		//如果是新的連接,accept就會被設爲1;accept()之後還沒有斷開(timeout),accept就是0
		//這個步驟不處理連接,只是把連接放在queue(ngx_posted_accept_events或者ngx_posted_events)裏面
			queue = (ngx_event_t **) (rev->accept ? &ngx_posted_accept_events : &ngx_posted_events);
			ngx_locked_post_event(rev, queue);
		} 
		else {
			rev->handler(rev);
		}

		wev = c->write;
		if ((revents & EPOLLOUT) && wev->active) {
			if (flags & NGX_POST_THREAD_EVENTS) {
			wev->posted_ready = 1;
		}
		else {
			wev->ready = 1;
		}
		if (flags & NGX_POST_EVENTS) {
			ngx_locked_post_event(wev, &ngx_posted_events);
		}
		else {
			wev->handler(wev);
		}
	}
	//解鎖
	ngx_mutex_unlock(ngx_posted_events_mutex);
	return NGX_OK;
}

 

//Debug追蹤
下面,以單進程和多進程處理一個http請求爲例,分析一下事件處理的流程。我用nginx裏面已有的ngx_log_debugX()來插入事件處理的主要函數ngx_epoll_process_events()和ngx_event_process_posted()。在編譯的時候,需要加上"--with-debug"參數。並指定nginx.conf裏面的"error_log   logs/debug.log  debug_core | debug_event | debug_http;"。重新啓動nginx。

單進程(work_processes 1):
1. 在初始化即ngx_worker_process_init()中調用兩次ngx_epoll_add_event()。第一次是在ngx_event_process_init()裏面,即給每個監聽的端口(在我的例子裏只監聽80端口)添加一個NGX_READ_EVENT事件;第二次是ngx_add_channel_event(),即給進程間通行的socketpair添加NGX_READ_EVENT事件。
2. 不斷調用ngx_epoll_process_events()函數,探測監聽的事件是否發生。如果此時有一個http請求進來,就會觸發epoll的事件。由於之前每個監聽的端口已經設置handler是ngx_event_accept(),這樣,就會在ngx_epoll_process_events()裏面調用rev->handler(rev),即調用ngx_event_accept()。在這個函數裏,accept()被調用,即接收請求併爲其分配一個新的連接,初始化這個新連接,並調用listening socket的handler,即ls->handler(c)。因爲ls->handler在http_block()(讀取配置之後)裏面已經設置了(ls->handler = ngx_http_init_connection;),那麼就會調用ngx_http_init_connection()。而在這個函數裏,又會添加一個讀事件,並設置其處理鉤子是ngx_http_init_request()。
3. epoll觸發新的事件調用ngx_http_init_request(),並繼續http請求處理的每一個環節。(如process request line,process headers,各個phase等)
4. 最後client關閉了連接(我用的是Linux下的curl)。調用了ngx_http_finalize_request() => ngx_http_finalize_connection() => ngx_http_set_keepalive()。ngx_http_set_keepalive()函數設置事件的處理函數是ngx_http_keepalive_handler(),並調用ngx_post_event()把它添加到ngx_posted_events隊列裏。然後ngx_event_process_posted()函數就會一一處理並刪除隊列裏所有的事件。在ngx_http_keepalive_handler()函數裏,調用ngx_http_close_connection() => ngx_close_connection() => ngx_del_conn(c,NGX_CLOSE_EVENT)。ngx_del_conn()即ngx_epoll_del_connection(),即把這個處理請求的connection從epoll監聽的事件列表中刪除。


多進程(我設置了work_processes 2):和單進程不同,單進程設置epoll timer爲-1,即沒有事件就一直阻塞在那裏,直到監聽的端口收到請求。而多進程則不同,每個進程會設置一個epoll_wait()的timeout,去輪番嘗試獲取在監聽端口接受請求的權利,如果沒有事件就去處理其它的事件,如果獲得了就阻塞(*直到有任意事件發生)
1. 在ngx_event_process_init()裏面,只會調用ngx_add_channel_event()給進程間通信的socketpair添加事件,而不給http監聽的端口添加事件(爲了保證不會有多個工作進程來同時接受請求)。而每個進程被fork()之後,父進程(master process)都會調用ngx_pass_open_channel() => ngx_write_channel()  => sendmsg()來通知所有已經存在的進程(這會觸發接收方的事件,調用ngx_channel_handler()函數)
2. 在ngx_process_events_and_timers()裏,用一個鎖來同步所有的進程ngx_trylock_accept_mutex(),並只有一個進程能夠得到ngx_accept_mutex這個鎖。得到這個鎖的進程會調用ngx_enable_accept_events()添加一個監聽端口的事件。
3. 在ngx_epoll_process_events()裏,調用了ngx_locked_post_event()添加了一個讀事件到accept queue(即ngx_posted_accept_events),然後在ngx_event_process_posted()裏面處理,即調用ngx_event_accept(),並添加一個讀事件(後面和單進程是一樣的)。在處理完ngx_posted_accept_events隊列裏面的所有accept事件之後,ngx_accept_mutex這個鎖也會被釋放,即把接受請求的權利讓給其它的進程。

*在多進程的模式下,每當有新的子進程啓動的時候,父進程(master process)都會向其餘所有進程的socketpair channel廣播新的子進程的channel。這樣,就會導致之前獲取監聽端口權限(即ngx_accept_mutex)的進程觸發epoll事件,從而釋放ngx_accept_mutex,雖然這個是發生在初始化階段(之後子進程間一般不通信),一般不會產生兩個或多個進程同時在epoll添加監聽端口事件的情況。但是在理論上,這樣的設計可能會導致系統的bug(比如有人通過給子進程發送信號的辦法來實現一些特殊的功能時,就有可能讓其中一個進程放棄ngx_accept_mutex,而另外某一個進程在之後先於它再次獲取到ngx_accept_mutex)。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章