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ファイルに定義されます.
関数の重点はforループであり、すべての傍受fdを遍歴し、fdeventシステムに登録する.ネットワークの初期化中にsocket関数を呼び出した後、server構造体のsrv_に戻り値(傍受fd)を保存するsocketsメンバーで、このメンバーはserver_です.socket_Array構造体、server_socket_Array構造体はserver_socket構造体のポインタ配列.server_socket構造体は次のように定義されています.
ここでは主に最初の3つのメンバーを見るが、最初の2つのメンバーは簡単で、3番目のメンバーについては、著者らの本意はfdに対応するfdnodeをfdevents構造体におけるfdarray配列の下に保存することであるべきであるが、プログラムはfdnodeを格納する際にfdが最も下に格納される(後のfdevent_register関数)ため、通常fde_ndx==fd. 次はfdevent_を見てみましょうregister()関数、fdevent.cで定義:
この関数では、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()関数:
関数でfdevents構造体event_が呼び出されましたadd関数ポインタに対応する関数.システムがepollを使用していると仮定しましたが、fdevent_を見てみましょう.linux_Sysepoll.cのfdevent_linux_sysepoll_event_add()関数、この関数のアドレスは初期化時にfdeventsのevent_に割り当てられますaddポインタ:
まず,関数の3番目のパラメータeventsを見ると,IOイベントに対応するとは思わなかった整数型である.上fdevent_event_add()関数の3番目のパラメータはFDEVENT_です.IN、これはマクロです.
ここでBVもマクロであり、settings.cファイルに定義されています.
その役割は、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は接続要求の待機を開始する. 次に、プロセスは次の文に実行されます.
この文は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イベントを取得し、最終的に呼び出されたのは:
この関数は変換されました.fdevent_get_handlerとfdevent_get_contextは、fdに対応するfdnodeのhandlerおよびctxを返します.これらの関数はすべて簡単で、ここではソースコードはリストされません. 最後に、switch文でfd対応handler関数を呼び出してイベントを処理します.傍受fdの場合、呼び出される関数は次のとおりです.
傍受fdにはIOイベントがあり、クライアントが接続を要求していることを示し、その処理は接続を確立することである.この関数では、接続を確立した後、急いで終了するのではなく、100回の接続が確立されるまで新しい接続を確立し続けます.これにより効率が向上します.connection_accept()関数は、接続要求を受け入れ、connection構造体ポインタを返します.次にこの接続に対してステータスマシンを起動します(ステータスマシンは面白いです...).次に、ジョブキューに接続を追加します. ここでは,接続が100個未満ではプログラムがこの関数にブロックされ,確立された接続に対しても処理できないという問題がある.どうする?読者は、傍受fdをfdeventシステムに登録すると、非ブロックに設定されていることを忘れないでください.したがって、accept()関数を呼び出すときに接続要求がなければ、accept()関数は直接エラーで返され、connection_acceptはNULLを返し、forループを終了します.これにより、fdeventシステムは、fdを傍受する処理に対して1つのループを完了する.IOイベントが処理された後、fdはepollで次のイベントを待つ. 次の記事では、fd(accept関数の戻り値)を接続するfdeventシステムの処理と、タイムアウト接続の処理について説明します.
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システムの処理と、タイムアウト接続の処理について説明します.