Original address: http://c4fun.cn/blog/2014/03/06/libev-study/
----
Intro
Libev is a reactor-based event library with high efficiency (Benchmark) and code refinement (4.15 versions over 8,000 lines), which is a great resource for learning event-driven programming.
This article will not introduce the reactor model, nor introduce the Libev API, the main content is I study Libev after some summary, introduced the Livev design method and implementation method, and some of the core code is annotated.
For a more detailed introduction to the API, refer to the Libev manual. Feature
Libev is a full-featured, high-performance, lightweight event-driven library written in C that supports a variety of backend IO multiplexing interfaces and can register up to more than 10 events.
Supported backend IO multiplexing interfaces:
1
2
3)
4
5
|
Select
Poll
epoll
kqueue
Solaris event Port
|
Types of events Supported:
1
2
3
4
5
6
7
8
9
ten
13
|
Ev_io //IO readable writable
ev_stat //File attribute change
ev_signal //Signal processing
ev_timer// relative timer
ev_ Periodic //absolute timer
ev_child //Sub-process status change
ev_fork //fork Event
ev_cleanup //Event Loop exit Trigger Event
Ev_idle //Event loop idle Trigger event
ev_embed/ /embed another background loop
ev_prepare //Event Loop events before event
Ev_check //Event loop Ev_async// asynchronous events between threads
|
Sample
Before introducing Libev's code structure, take a look at an example from the Libev manual that registers two events, a timeout event, an IO event, and invokes a callback function and terminates the main loop after any event has occurred. This is also the standard mode of event-driven programming--waiting for the event to trigger and invoke the callback function after registering it .
The comments in the code are already very detailed and I will not repeat them. To compile this code (EVTEST.C), first download the Libev source compilation installation on the Libev home page and then compile it using gcc Evtest.c-libev (remember Ldconfig). Evtest.c
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 4 1---------------- |
A single header file was required #include <ev.h> #include <stdio.h>//for puts//every watcher type have
Its own typedef ' d-struct//with the name Ev_type Ev_io stdin_watcher;
Ev_timer Timeout_watcher; All watcher callbacks has a similar signature//This callback are called when data are readable on stdin static void St
DIN_CB (ev_p_ ev_io *w, int revents) {puts ("stdin ready");
For one-shot events, one must manually stop of the Watcher//with its corresponding stop function.
Ev_io_stop (Ev_a_ W);
This causes all nested Ev_run's to stop iterating Ev_break (Ev_a_ evbreak_all); }//Another callback, this time for a time-out static void Timeout_cb (Ev_p_ ev_timer *w, int revents) {puts ("time
Out ");
This causes the innermost ev_run to stop iterating Ev_break (Ev_a_ evbreak_one); } int main (void) {//Use the default event loop unless special needs struct ev_loop *loop = Ev_default
; Initialise an Io Watcher, then start it//This one would watch for stdin to become readable ev_io_init (&stdin_watcher, STD
IN_CB,/*stdin_fileno*/0, Ev_read);
Ev_io_start (Loop, &stdin_watcher); Initialise a timer watcher, then start it//simple non-repeating 5.5 second timeout ev_timer_init (&timeou
T_watcher, TIMEOUT_CB, 5.5, 0.);
Ev_timer_start (Loop, &timeout_watcher);
Now wait for events to arrive Ev_run (loop, 0);
Break is called, so exit return 0;
} |
Main Structures
Wather
An important part of the event-driven library is the encapsulation of events. In Libev, events are encapsulated in the watcher structure, and events can be added to the main loop by registering the watcher and specifying parameters such as the corresponding callback function.
Explain the Ev_p,ev_p_,ev_a,ev_a_ these macros, which are almost ubiquitous in the code, mainly to simplify the interface of function calls in single-threaded mode, which are defined as follows.
1
2
3
4
5
6
7
8
9
ten
12
|
#if ev_multiplicity
struct ev_loop;
# define ev_p struct Ev_loop *loop/ * a loop as sole parameter in a declaration *
# define EV_P_ EV_P,
/* a loop as first of multiple parameters */
# define Ev_a Loop/ * A loop as sole argument to a function Call */
# define Ev_a_ ev_a,/ * A Loop as first of multiple arguments * *
#else
# define Ev_p void
# define EV_P_
# define EV_A
# define Ev_a_
#endif
|
Ev_loop is the main loop, and Ev_multiplicity is a conditionally compiled macro that indicates whether multiple Ev_loop instances are supported, and in general, there is only one Ev_loop instance per thread. If the entire program is single-threaded, you can use the global default Ev_loop in your program, and you do not need to pass parameters in the function. Calling functions in multiple threads often specifies loops for function operations. For example, to start an IO event, call the function is void Ev_io_start (Ev_p_ ev_io *w), if not defined ev_multiplicity, will be compiled into Ev_io_start (IO *w), otherwise it will be compiled into Ev_io_start ( struct Ev_loop *loop, Ev_io *w).
For each event, there are structural ev_type corresponding to it, such as Ev_io,ev_timer and so on. In order to unify the event structure, Libev uses struct layout in C to achieve polymorphism, and the ev_watcher struct can be considered as the base class for all ev_type structures, which contains the same fields in all Ev_type.
These structures are defined in the code as follows, and I have restored some of the macros for ease of understanding. Only partial macros are restored, not all, because these macros embody the idea of the author designing these structures.
Related macros
1
2
3
4
5
6
7
8
9
17
|
This macro defines all parts of the Ev_type beginning
#define Ev_watcher (type) \
int active;/* Private *
/INT pending; * Private *
/int priority;/* Private *
/\ void *data;/* RW/ \
void (*CB) (ev_p_ struct Type *w, int revents); /* Private
///This macro adds a timestamp based on the ev_watcher, mainly used to define the EV
#define EV_WATCHER_TIME (type) \
ev_ Watcher (type) \
Ev_tstamp at; /* Private *
//This macro adds a next to the Ev_watcher content to form the event list
#define Ev_watcher_list (type) \
Ev_watcher ( Type) \
struct ev_watcher_list *next;/* Private */
|
"Base class"
1
2
3
4
5
6
7
8
9
17
|
Content is the content of the Ev_watcher macro, which can be understood as "base class"
typedef struct Ev_watcher
{
ev_watcher (ev_watcher)
} ev_ Watcher;
Content is the content of the Ev_watcher_time macro, or it can be understood as the "base class"
typedef struct EV_WATCHER_TIME
{
ev_watcher_time (ev_watcher_ Time)
} ev_watcher_time;
Can be understood as a base class
typedef struct EV_WATCHER_LIST
{
ev_watcher_list (ev_watcher_list)} with next pointer
ev_ Watcher_list;
|
"Derived class", here is only listed Ev_io,ev_timer and ev_signal, these three kinds of events are more commonly used, the other event structure of the code are similar, specific can see the source.
1
2
3
4
5
6
7
8
9
24 off
|
Ev_io encapsulates the "derived class" of IO events, the front of the struct is macro EV_WATCHER_LIST,FD and events is a "derived class" variable
typedef struct EV_IO
{
ev_watcher_ LIST (ev_io)
int fd; /* RO */
int events;/* RO */
} ev_io;
Ev_signal encapsulates a "derived class" of signal events, and also Signum is a "derived class" variable
typedef struct ev_signal
{
ev_watcher_list (ev_signal)
int signum;/* RO */
} ev_signal;
Ev_timer Package Relative Timer event "derived class", timer with heap management, do not need next pointer
typedef struct EV_TIMER
{
ev_watcher_time (Ev_timer)
Ev_tstamp repeat;/* RW */
} Ev_timer;
|
As can be seen from the above code, the common fields in each event structure represent the state, precedence, parameters, and callback functions of the event, while the private field is the unique information of the type event, such as the corresponding FD for the IO event, the timing of the timer event, etc.
There is also a union called Ev_any_watcher that can accommodate all types of events.
1
2
3
4
5
6
7
8
9
18
|
Union ev_any_watcher
{
struct ev_watcher w;
struct ev_watcher_list wl;
struct Ev_io io;
struct Ev_timer timer;
struct Ev_periodic periodic;
struct ev_signal signal;
struct Ev_child child;
struct Ev_stat stat;
struct Ev_idle idle;
struct Ev_prepare prepare;
struct Ev_check check;
struct Ev_fork fork;
struct Ev_cleanup cleanup;
struct ev_embed embed;
struct Ev_async async;
};
|
Ev_loop
Ev_loop is a very important and very large structure, can be called the event controller, event scheduling is basically controlled by it.
The definition of the structure is very obscure, from the following code can be seen, the code will be based on the definition of ev_multiplicity conditional compilation, in a single-threaded environment, because there is only one loop, so all variables are directly used as global variables, and in multithreaded mode there will be multiple loop instances, It is therefore necessary to encapsulate the variable in the Ev_loop struct, and to specify the loop to be manipulated when calling the function. These variables are defined in Ev_vars.h and are expanded by include.
In addition, after defining the EV_LOOP structure in multithreaded mode, the Ev_wrap.h is also include, which defines a bunch of macros like # define ANFDS ((loop)->anfds) for all variables in ev_vars.h. The purpose of this macro is to write the unified code, when the ev_multiplicity is not turned on Anfds represents the global variable Anfds, and after the ev_multiplicity is turned on, the function will generally pass a struct ev_loop *loop,
The Anfds also expands into (loop)->anfds. This allows the code to not write a bunch of # if #else #endif, but also makes the code more obscure.
1
2
3
4
5
6
7
8
9
21
|
#if ev_multiplicity
struct ev_loop
{
ev_tstamp ev_rt_now;
#define Ev_rt_now (Loop)->ev_rt_now
#define VAR (name,decl) decl;
#include "ev_vars.h"
#undef VAR
};
#include "ev_wrap.h"
static struct Ev_loop default_loop_struct;
ev_api_decl struct Ev_loop *ev_default_loop_ptr = 0; /* needs to being initialised to make it a definition despite extern */
#else
ev_api_decl ev_tstamp ev_rt_now = 0; /* needs to being initialised to make it a definition despite extern *
/#define VAR (name,decl) static decl;
#include "ev_vars.h"
#undef VAR
static int ev_default_loop_ptr;
#endif
|
ANFD
In managing IO events, it is an issue to consider how to quickly find events related to FD. The Libev method is to use an array to store all the FD information in the structure, and then use the FD value as the index to find the corresponding structure, the structure is the following ANFD structure (omitted on the Windows system variables). This method can be indexed in O (1) complexity, and the question is how much space it occupies. If we open 1 million fd at the same time, the space occupied is 10^6*sizeof (ANFD), about 12M, this is completely acceptable.
The meaning of the fields in the struct is gradually mentioned later in the introduction of the Libev process.
1
2
3
4
5
6
7
8
9
ten
13
|
typedef ev_watcher_list *WL;
/* File Descriptor Info Structure */
typedef struct
{
WL head;
unsigned char events; /* The events watched for */
unsigned char reify; /* Flag set when this ANFD needs reification (ev_anfd_reify, Ev__iofdset) */
unsigned char emask; /* The Epoll backend stores the actual kernel mask in here */
unsigned char unused;
#if ev_use_epoll
unsigned int egen; /* Generation counter to counter epoll bugs */
#endif
} anfd;
|
How it works?
This section mainly describes how the Libev code works, mainly divided into event registration, event scheduling and background I/O reuse three parts. Event Registration
Event registration, which is to tell the event drive program to be concerned about the occurrence of an event. Here is an example of IO event to analyze how to register and destroy an event, the code logic of the other events is basically the same, no longer repeat.
From the code in sample above, we can see that starting an IO event invokes the following two functions:
1
2
3
|
Ev_io Stdin_watcher;
Ev_io_init (&stdin_watcher, STDIN_CB,/*stdin_fileno*/0, ev_read);
Ev_io_start (Loop, &stdin_watcher);
|
First look at Ev_io_init, and its related code mainly has the following several macros. The basic is to initialize the value of each field in the Ev_io struct, the priority will be initialized to 0, if need to change needs to call ev_set_priority separately. Above the Ev_io_init, is actually registered a concern read IO event, the corresponding FD is 0 is the standard input.
1
2
3
4
5
6
7
8
|
#define EV_IO_INIT (ev,cb,fd,events) do {ev_init (EV), (CB)) Ev_io_set ((EV), (FD), (events));} while (0)
# Define EV_INIT (Ev,cb_) do { \
((Ev_watcher *) (void *) (EV))->active = \
((Ev_watcher *) (void *) ( EV))->pending = 0; \
ev_set_priority ((EV), 0); \
EV_SET_CB ((EV), cb_); \
} while (0)
#define EV_IO_SET (ev,fd_,events_) do {(ev)->FD = (fd_); (ev)->events = (Events_) | Ev__iofdset; } while (0)
|
Then is Ev_io_start, the core work is to add ev_io to the relevant FD's event linked list, the following is the main code, I gave a more detailed comment in the code. Where Noinline and so are the authors because of compiler differences defined by some macros, and Ev_frequent_check is also to verify the correctness of the program added macros, in the production environment will not generate anything, these macros can now be ignored, we only care about the main logic.
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 4 1----------------------- |
typedef ev_watcher *W;
typedef ev_watcher_list *WL;
void Noinline Ev_io_start (ev_p_ ev_io *w) ev_throw {int FD = w->fd;
If Ev_io_start has been called on a ev_io, the IF (Expect_false (Ev_is_active (w))) return is not repeated;
Assert (("Libev:ev_io_start called with negative fd", FD >= 0)); Assert (("Libev:ev_io_start called with illegal event mask",! ( W->events & ~ (Ev__iofdset | Ev_read |
Ev_write)));
Ev_frequent_check;
Set the event status to start Ev_start (Ev_a_ (W) w, 1);
Determines whether the currently allocated FD array can put the FD, if not, allocate more space to the FD array (realloc).
This is a macro, Anfds is actually Loop->anfds, which stores the ANFD array associated with the loop.
Anfdmax is the largest fd,array_init_zero that the current array can put, and a macro that memeset the newly allocated space to 0.
Array_needsize (ANFD, Anfds, Anfdmax, FD + 1, Array_init_zero);
The IO event is added to the event chain of the FD counterpart structure, using the head interpolation method (&anfds[fd].head, (WL) w) Wlist_add;
/* Common bug, apparently */assert (("Libev:ev_io_start called with Corrupted Watcher", ((WL) w)->next! = (WL) w)); Adding the FD to the fdchanges array, fdchanges the FD//event driver that saved the event listener state Change will traverse the fdchanges array at the appropriate time, depending on the useBackend IO multiplexing mechanism to apply changes such as Epoll_ctl Fd_change (Ev_a_ fd, w->events & Ev__iofdset |
Ev_anfd_reify);
Start end, cancels the iofdset tag of the event (this tag is added at init) w->events &= ~ev__iofdset;
Ev_frequent_check;
}//Set event status to start Inline_speed void Ev_start (ev_p_ w w, int active) {//Adjust priority to legal range Pri_adjust (Ev_a_ W);
Active==1 indicates that w->active = active has been started;
Increase the reference ev_ref (ev_a) of the Ev_loop;
}//Add event to the corresponding list of inline_size void Wlist_add (WL *head, wl elem) {elem->next = *head;
*head = Elem;
}//Mark FD listener status changed inline_size void Fd_change (ev_p_ int FD, int flags) {unsigned char reify = Anfds [fd].reify;
Anfds [fd].reify |= flags;
Reify before is 0, to add FD to the fdchanges array if (Expect_true (!reify)) {++fdchangecnt;
Array_needsize (int, fdchanges, Fdchangemax, fdchangecnt, EMPTY2);
fdchanges [fdchangecnt-1] = FD;
}
} |
Finally look at the ev_io_stop, logic is basically the ev_io_start of the reverse process.
1
2
3
4
5
6
7
8
9
30 of each of the above.
38 off
|
void Noinline ev_io_stop (ev_p_ ev_io *w) Ev_throw {//If the event is pending (waiting for an event to be executed), remove the event from the pending list.
One of the tricks here is not to really remove (array delete complexity O (n)), as long as the pointer to the corresponding position of the pending list points to an empty event.
Clear_pending (Ev_a_ (w) w);
if (Expect_false (!ev_is_active (w))) return; Assert (("Libev:ev_io_stop called with illegal FD (must stay constant after start!)", w->fd >= 0 && w->f
D < Anfdmax));
Ev_frequent_check;
Deleting a node from a linked list is also a Linus advocated method, which does not need to record prev pointers and whether the head node or not.
Wlist_del (&anfds[w->fd].head, (WL) w);
Cancels the active state of FD Ev_stop (Ev_a_ (w) w); The FD is added to the fdchanges array, only the reify tag is set, indicating that there is a change (if none of the tags set the FD will not be put into fdchanges)//After the event drive scans the Fdchanges array will find that the FD no longer listens for any events, and makes the appropriate action Fd_
Change (Ev_a_ w->fd, ev_anfd_reify);
Ev_frequent_check; }//delete a node from the list, a very classical method inline_size void Wlist_del (WL *head, WL elem) {while (*head) {if (Expect_true (*head
= = Elem)) {*head = elem->next;
Break
} head = & (*head)->next;
}
} |
Main Loop
The operation of the Ev_io series function is basically to fill the ev_io structure and put it on the corresponding FD event linked list, while the listener event state changes in the FD exists fdchanges array, the driver controller will change the background listening events according to the array, The driver controller automatically invokes the callback function of the corresponding event when the event occurs. Throughout the process, the driver controller, the Ev_loop, sticks together the event and the background reuse mechanism like glue.
The whole event scheduling process is basically in the function ev_run, the whole function is longer, about more than 200 lines, here does not list the code, only the main logic of the program written out.
1
2
3
4
5
6
7
8
9
Ten
-
16
28 of the same.
|
int
ev_run (ev_p_ int flags)
{
...
Keep circulating ....
Do
{
...
If this process is a new fork, execute the callback
of the Ev_fork event ... Executes the Ev_prepare callback, which is the function
executed before each poll ... Perform monitoring of changed events ...
Calculate the time that poll should wait, this time and the setting and timer timeout time related
...
Invoke the background I/O multiplexing Port waits for event trigger
Backend_poll (Ev_a_ waittime);
...
Put the timer event into the
pending array ... The Ev_check event is flipped into the
pending array ... Executes all callback ev_invoke_pending in the PENDING array
;
}
while (condition established);
}
|
The logic of Ev_run can be said to be relatively clear. The program first executes some callbacks that need to be executed before poll, and then calculates the time that poll needs to wait based on the timer that first timed out, and then calls poll waits for the I/O event to occur, and finally executes the callback that the event occurred.
In the specific code, the program uses the event that the queue_events will run into a two-dimensional array called Pending, whose first dimension is priority, the second dimension is dynamically allocated, and the specific event is stored. The program then calls the macro ev_invoke_pending where appropriate and executes the events in the PENDING array in order of precedence from high to low. I/O multiplexing
Libev uses function pointers to support a variety of I/O multiplexing mechanisms, each of which implements Init, modify, poll, destroy functions, which are initialization, modification of concern events, waiting for events to occur, and destroying these functions. This part of the code I only see the implementation of the Epoll, the sense of implementation is still very ingenious, the reader can according to their familiar I/O multiplexing mechanism to choose which part of the code to see. More
At this point, Libev's main design methods and implementation of the basic introduction of the concept of almost, confined to space, there are many details can not be described in an article, if there is time, I will try to perfect this article.
As an event repository, Libev's design can be said to be very sophisticated, and the various tips in the code have benefited me a lot. However, Libev almost does not involve network programming, if you want to implement network library on this basis, there is still a lot of work to do.