Lighttpd 1.4.20ソースコード分析のfdeventシステム(3)---使用

59926 ワード

前にlighttpdのfdeventシステムの初期化過程について述べた.この記事では、lighttpdがfdeventシステムをどのように使用しているかを見てみましょう.説明の過程で、fdeventのソースコードを詳しく分析します.まずserver.cのmain関数から始めます.プログラムの初期化過程において、fdeventの初期化が完了した後、fdevent処理が最初に必要とされることは、ネットワークの初期化過程で得られた傍受fd(socket関数の戻り値)を登録するfdeventシステムである.呼び出しはnetwork_register_fdevents()関数.network.cファイルに定義されます.

  
    
1 /* *
2 * fd events socket。
3 * 。
4 */
5   int network_register_fdevents(server * srv)
6 {
7 size_t i;
8 if ( - 1 == fdevent_reset(srv -> ev)){ return - 1 ;}
9 /*
10 * register fdevents after reset
11 */
12 for (i = 0 ; i < srv -> srv_sockets.used; i ++ )
13 {
14 server_socket * srv_socket = srv -> srv_sockets.ptr[i];
15 fdevent_register(srv -> ev, srv_socket -> fd, network_server_handle_fdevent, srv_socket);
16 fdevent_event_add(srv -> ev, & (srv_socket -> fde_ndx), srv_socket -> fd, FDEVENT_IN);
17 }
18 return 0 ;
19 }

    関数の重点はforループであり、すべての傍受fdを遍歴し、fdeventシステムに登録する.ネットワークの初期化中にsocket関数を呼び出した後、server構造体のsrv_に戻り値(傍受fd)を保存するsocketsメンバーで、このメンバーはserver_です.socket_Array構造体、server_socket_Array構造体はserver_socket構造体のポインタ配列.server_socket構造体は次のように定義されています.  

  
    
1 typedef struct
2 {
3 sock_addr addr; // socket fd 。
4   int fd; // socket() fd
5   int fde_ndx; // fd 。
6   buffer * ssl_pemfile;
7 buffer * ssl_ca_file;
8 buffer * ssl_cipher_list;
9 unsigned short ssl_use_sslv2;
10 unsigned short use_ipv6; // ipv6
11   unsigned short is_ssl;
12 buffer * srv_token;
13 #ifdef USE_OPENSSL
14 SSL_CTX * ssl_ctx;
15   #endif
16 unsigned short is_proxy_ssl;
17 } server_socket;

    ここでは主に最初の3つのメンバーを見るが、最初の2つのメンバーは簡単で、3番目のメンバーについては、著者らの本意はfdに対応するfdnodeをfdevents構造体におけるfdarray配列の下に保存することであるべきであるが、プログラムはfdnodeを格納する際にfdが最も下に格納される(後のfdevent_register関数)ため、通常fde_ndx==fd.    次はfdevent_を見てみましょうregister()関数、fdevent.cで定義:

  
    
1 int fdevent_register(fdevents * ev, int fd, fdevent_handler handler, void * ctx)
2 {
3 fdnode * fdn;
4 fdn = fdnode_init();
5 fdn -> handler = handler;
6 fdn -> fd = fd;
7 fdn -> ctx = ctx;
8 ev -> fdarray[fd] = fdn; //
9   // O(1)
10   return 0 ;
11 }

    この関数では、fdnode構造体のインスタンスを作成し、メンバーに値を割り当てます.最後に、fdを下付きとして、このインスタンスをfdevents構造体のfdarray配列のように格納する.3番目のパラメータについて:fdevent_handler handler typedef handlerとして定義された関数ポインタt(*fdevent_handler) (void *srv, void *ctx, int revents).この関数ポインタはXXX_に対応しています.handle_fdevent()タイプの関数です.例えばnetwork.c/network_server_handle_fdevent() ,connections.c/connection_handle_fdevent().これらの関数の役割は、fdeventシステムがfdにIOイベントが発生したことを検出したときに、これらのIOイベントを処理することである.例えばnetwork_server_handle_fdevent()は、fd(socket関数の戻り値)を傍受して発生するIOイベントを処理し、connection_handle_fdevent()は、fd(accept関数の戻り値)を接続して発生するIOイベントを処理する.上の2つの関数のほかにstat_cacahe.c/stat_cache_handle_fdevent(),mod_cgi.c/cgi_handle_fdevent(),mod_fastcgi.c/fcgi_handle_fdevent(),mod_proxy.c/proxy_handle_fdevent()とmod_scgi.c/scgi_handle_fdevent()など.後述の説明では、主にnetwork_をめぐってserver_handle_fdevent()とconnection_handle_fdevent()は、他の関数に興味のある読者が自分で見ることができます.    次に、forループで(fdevent.c)fdevent_を呼び出すevent_add()関数:

  
    
1 int fdevent_event_add(fdevents * ev, int * fde_ndx, int fd, int events)
2 {
3 int fde = fde_ndx ? * fde_ndx : - 1 ;
4 if (ev -> event_add)
5 fde = ev -> event_add(ev, fde, fd, events)
6 if (fde_ndx)
7 * fde_ndx = fde;
8 return 0 ;
9 }

関数でfdevents構造体event_が呼び出されましたadd関数ポインタに対応する関数.システムがepollを使用していると仮定しましたが、fdevent_を見てみましょう.linux_Sysepoll.cのfdevent_linux_sysepoll_event_add()関数、この関数のアドレスは初期化時にfdeventsのevent_に割り当てられますaddポインタ:

  
    
1 static int fdevent_linux_sysepoll_event_add(fdevents * ev, int fde_ndx, int fd, int events)
2 {
3 struct epoll_event ep;
4 int add = 0 ;
5 if (fde_ndx == - 1 ) // epoll , 。
6   add = 1 ;
7 memset( & ep, 0 , sizeof (ep));
8 ep.events = 0 ;
9 /* *
10 * ep IO 。
11 * EPOLLIN : 。
12 * EPOLLOUT : 。
13 * :EPOLLRDHUP , EPOLLPRI, EPOLLERR, EPOLLHUP, EPOLLET, EPOLLONESHOT 。
14 */
15 if (events & FDEVENT_IN)
16 ep.events |= EPOLLIN;
17 if (events & FDEVENT_OUT)
18 ep.events |= EPOLLOUT;
19 /*
20 * EPOLLERR : 。
21 * EPOLLHUP : 。 。
22 */
23 ep.events |= EPOLLERR | EPOLLHUP /* | EPOLLET */ ;
24 ep.data.ptr = NULL;
25 ep.data.fd = fd;
26 /*
27 * EPOLL_CTL_ADD : fd ev->epoll_fd , ep fd 。
28 * EPOLL_CTL_MOD : fd 。
29 */
30 if ( 0 != epoll_ctl(ev -> epoll_fd, add ? EPOLL_CTL_ADD : EPOLL_CTL_MOD, fd, & ep))
31 {
32 fprintf(stderr, " %s.%d: epoll_ctl failed: %s, dying
" ,__FILE__,__LINE__, strerror(errno));
33 SEGFAULT();
34 return 0 ;
35 }
36 return fd;
37 }

まず,関数の3番目のパラメータeventsを見ると,IOイベントに対応するとは思わなかった整数型である.上fdevent_event_add()関数の3番目のパラメータはFDEVENT_です.IN、これはマクロです.

  
    
1 /*
2 *
3 */
4   #define FDEVENT_IN BV(0) //
5   #define FDEVENT_PRI BV(1) // poll
6   #define FDEVENT_OUT BV(2) //
7 #define FDEVENT_ERR BV(3) //
8 #define FDEVENT_HUP BV(4) // poll
9 #define FDEVENT_NVAL BV(5) // poll

ここでBVもマクロであり、settings.cファイルに定義されています.

  
    
1 #define BV(x) (1 << x)

その役割は、1つの整数変数のx番目の位置1を、残りは0にすることです.では上にFDEVENT_XXのマクロ定義は一連の者養の整数を定義して、これらのマクロのあるいは操作を通じて、1つの整数の中で異なるビットで異なる事件を表すことができます.これらのマクロとstruct epoll_event構造体におけるevents変数の対応するマクロ定義対応(少し回ります...).はっきり言ってepoll.hの列挙EPOLL_とEVENTS対応.    この関数の主な仕事は、いくつかの値を設定してepollを呼び出すことです.ctl関数.関数の名前はevent_ですがaddですが、2番目のパラメータfde_ndxは-1(それは必ず後のパラメータfdに等しい)に等しくなく、このときfdがepollのモニタリング中であることを肯定することができ、このときfdをepollに追加するのではなく、傍受するIOイベントを修正することができ、最後のパラメータが示すイベントである.fdevent_linux_sysepoll_event_add()の増加に成功したらfdを返し、fdevent_event_addでは、この戻り値をfde_に付与するndxだからfde_ndx==fd.    これで、傍受fdの登録プロセスはすべて終了し、接続要求がある場合、傍受fdの表現はデータの授業読みであるため、そのFDEVENT_のみ傍受するINイベント.登録後、傍受fdは接続要求の待機を開始する.    次に、プロセスは次の文に実行されます.

  
    
1 // 。 IO 。
2 if ((n = fdevent_poll(srv -> ev, 1000 )) > 0 )
3 {
4 /*
5 * nn ( , 。。。)
6 */
7 int revents;
8 int fd_ndx = - 1 ;
9 /* *
10 * , 。
11 */
12 do
13 {
14 fdevent_handler handler;
15 void * context;
16 handler_t r;
17
18 fd_ndx = fdevent_event_next_fdndx(srv -> ev, fd_ndx);
19 revents = fdevent_event_get_revent(srv -> ev, fd_ndx);
20 fd = fdevent_event_get_fd(srv -> ev, fd_ndx);
21 handler = fdevent_get_handler(srv -> ev, fd);
22 context = fdevent_get_context(srv -> ev, fd);
23 /*
24 * connection_handle_fdevent needs a joblist_append
25 */
26 /* *
27 * , handler !
28 */
29 switch (r = ( * handler) (srv, context, revents))
30 {
31 case HANDLER_FINISHED:
32 case HANDLER_GO_ON:
33 case HANDLER_WAIT_FOR_EVENT:
34 case HANDLER_WAIT_FOR_FD:
35 break ;
36 case HANDLER_ERROR:
37 SEGFAULT();
38 break ;
39 default :
40 log_error_write(srv, __FILE__, __LINE__, " d " , r);
41 break ;
42 }
43 } while ( -- n > 0 );
44 }
45 else if (n < 0 && errno != EINTR)
46 {
47 log_error_write(srv, __FILE__, __LINE__, " ss " , " fdevent_poll failed: " , strerror(errno));
48 }

この文はworkerサブプロセスの作業の重心です.まずfdevent_を呼び出すPoll()関数はIOイベントの発生を待つが,IOイベントがなければプログラムはこの関数にブロックされる.fdにIOイベントが発生した場合、fdevent_からPoll関数では、IOイベントが発生したfdの数を返します.次に、プログラムはdo-whileループに入り、ループの中で各fdに対して、いくつかの列fdeventシステムのインタフェース関数を呼び出し、最後にevent_を呼び出す.handlerはIOイベントを処理します.    fdevent_Poll()関数はfdevents構造体のpollを呼び出し、最終的にepoll_を呼び出す.wait()関数.epoll_wait()関数はIOイベントのfdに対応するepoll_が発生するevet構造体インスタンスのfdevents構造体に格納されたepoll_events配列メンバー.fdevent_event_next_fdndx関数はepoll_を返しますevents配列の次の要素の下付き文字、fdevent_event_get_revent関数呼び出しev->event_get_revent()はfdで発生したIOイベントを取得し、最終的に呼び出されたのは:

  
    
1 static int fdevent_linux_sysepoll_event_get_revent(fdevents * ev, size_t ndx)
2 {
3 int events = 0 , e;
4 e = ev -> epoll_events[ndx].events;
5 if (e & EPOLLIN)
6 events |= FDEVENT_IN;
7 if (e & EPOLLOUT)
8 events |= FDEVENT_OUT;
9 if (e & EPOLLERR)
10 events |= FDEVENT_ERR;
11 if (e & EPOLLHUP)
12 events |= FDEVENT_HUP;
13 if (e & EPOLLPRI) // ( )
14 events |= FDEVENT_PRI;
15 return e;
16 }

この関数は変換されました.fdevent_get_handlerとfdevent_get_contextは、fdに対応するfdnodeのhandlerおよびctxを返します.これらの関数はすべて簡単で、ここではソースコードはリストされません.    最後に、switch文でfd対応handler関数を呼び出してイベントを処理します.傍受fdの場合、呼び出される関数は次のとおりです.

  
    
1 /* *
2 * socket IO 。
3 * socket 。 。 ,
4 * 100 , 。
5 */
6 handler_t network_server_handle_fdevent( void * s, void * context, int revents)
7 {
8 server * srv = (server * ) s;
9 server_socket * srv_socket = (server_socket * ) context;
10 connection * con;
11 int loops = 0 ;
12 UNUSED(context);
13 /*
14 * fd FDEVENT_IN , 。
15 */
16 if (revents != FDEVENT_IN)
17 {
18 log_error_write(srv, __FILE__, __LINE__, " sdd " , " strange event for server socket " , srv_socket -> fd, revents);
19 return HANDLER_ERROR;
20 }
21 /*
22 * accept()s at most 100 connections directly we jump out after 100 to give the waiting connections a chance
23 * fd IO , , 。 ,
24 * , 100 。 。
25 */
26 for (loops = 0 ; loops < 100 && NULL != (con = connection_accept(srv, srv_socket)); loops ++ )
27 {
28 handler_t r;
29 // , con , 。
30 connection_state_machine(srv, con);
31 switch (r = plugins_call_handle_joblist(srv, con))
32 {
33 case HANDLER_FINISHED:
34 case HANDLER_GO_ON:
35 break ;
36 default :
37 log_error_write(srv, __FILE__, __LINE__, " d " , r);
38 break ;
39 }
40 }
41 return HANDLER_GO_ON;
42 }

    傍受fdにはIOイベントがあり、クライアントが接続を要求していることを示し、その処理は接続を確立することである.この関数では、接続を確立した後、急いで終了するのではなく、100回の接続が確立されるまで新しい接続を確立し続けます.これにより効率が向上します.connection_accept()関数は、接続要求を受け入れ、connection構造体ポインタを返します.次にこの接続に対してステータスマシンを起動します(ステータスマシンは面白いです...).次に、ジョブキューに接続を追加します.    ここでは,接続が100個未満ではプログラムがこの関数にブロックされ,確立された接続に対しても処理できないという問題がある.どうする?読者は、傍受fdをfdeventシステムに登録すると、非ブロックに設定されていることを忘れないでください.したがって、accept()関数を呼び出すときに接続要求がなければ、accept()関数は直接エラーで返され、connection_acceptはNULLを返し、forループを終了します.これにより、fdeventシステムは、fdを傍受する処理に対して1つのループを完了する.IOイベントが処理された後、fdはepollで次のイベントを待つ.    次の記事では、fd(accept関数の戻り値)を接続するfdeventシステムの処理と、タイムアウト接続の処理について説明します.