Libev is an open source event-driven library based on the infrastructure provided by OS such as Epoll,kqueue. Known for its efficiency, it can unify IO events, timers, and signals to be processed in a single set of event-handling frameworks.
The basic use of Libev is as follows:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21st
int main (void)
{
Use the default event loop unless special needs
struct Ev_loop *loop = Ev_default;
Initialise an IO watcher and then start it
This one would watch for stdin to become readable
Ev_io_init (&stdin_watcher, STDIN_CB,/*stdin_fileno*/0, ev_read);//set to Stdin_watcher this FD focuses on read events and specifies the callback function
Ev_io_start (Loop, &stdin_watcher);//Activate Stdin_watcher this FD, set it to loop
Initialise a timer watcher, then start it
Simple Non-repeating 5.5 Second timeout
Ev_timer_init (&timeout_watcher, TIMEOUT_CB, 5.5, 0.); /Set a timer, and specify a callback function, this timer only executes once, 5.5s executes after
Ev_timer_start (Loop, &timeout_watcher);//Activate this timer and set it to loop
Now wait for events to arrive
Ev_run (loop, 0);//Cycle start
Break is called, so exit
return 0;
}
There is an abstract concept in Libev, called Watcher (Ev_watcher), Libev has a variety of watcher, such as timer watcher (struct ev_timer), I/O watcher (struct ev_io), Signal Watcher (struct ev_signal)
Wait a minute. These three specific watcher correspond to subclasses of the parent class watcher. This kind of inheritance is built into the high-level language of C + + and requires some tricks to implement in C, and Libev's authors use macros.
The "Parent class" Ev_watcher is defined as follows:
typedef structev_watcher
{
"Color: #ff0000;" >ev_watcher (Ev_watcher)
} Ev_watcher;
The macro ev_watcher is defined as follows:
/* Shared by all watchers */
#define Ev_watcher (type) \
int active;/* Private */\//The Watcher is activated, added to loop
int pending;/* Private */\//The Watcher concerns whether events have been triggered
ev_decl_priority/* private */\//int priority; Priority, Watcher is a priority
ev_common/* rw */\//void *data;
Ev_cb_declare (type)/* private *///Void (*CB) (struct Ev_loop *loop, type *w, int revents); callback function
Then look at the definition of a "parent" ev_watcher_list:
Typedefstructev_watcher_list
{
Ev_watcher_list (Ev_watcher_list)
} ev_watcher_list;
The macro ev_watcher_list is defined as follows:
#define EV_WATCHER_LIST (type) \
"Color: #ff0000;" > ev_watcher (type) \
Structev_watcher_list *next; /* Private */
As can be seen, ev_watcher_list is actually a "subclass" of Ev_watcher, it has a member variable struct ev_watcher_list *next;
This member variable is used to string the watcher together.
Now look at an I/O watcher the most important "subclass":
typedef struct EV_IO
{
Ev_watcher_list (Ev_io)
int FD; /* Ro *///Obviously, the FD associated with IO
int events;/* ro *///This watcher event of interest on FD
} ev_io;
As can be seen, Ev_io is a concrete watcher, it has two own proprietary member variables FD and Events
Here's a look at one of the most critical data structures:
struct EV_LOOP
{
Ev_tstamp Ev_rt_now;
#define Ev_rt_now ((loop)->ev_rt_now)
Here decl is declare meaning, ev_vars.h inside will define a bunch of variables, these variables
Are members of this structure, EV_VARS.H will use the following line of VAR macros when expanding
#define VAR (NAME,DECL) decl;
#include "Ev_vars.h"
#undef VAR
};
The ev_vars.h contains a number of key members, such as:
Epoll-related member variables:
#if Ev_use_epoll | | Ev_genwrap
Varx (struct epoll_event *, epoll_events)//equivalent to struct epoll_event *epoll_events
Varx (int, Epoll_eventmax)//Current epoll_events array size can be expanded to twice times the size of each expansion
Varx (int, BACKEND_FD)//For Epoll, it is the FD used by Epoll
For Epoll, the actual function is the epoll_modify function in ev_epoll.c, and this function executes EPOLL_CTL
VAR (backend_modify, Void (*backend_modify) (Ev_p_intfd,intoev,intnev))
For Epoll, the actual function is the Epoll_poll function in ev_poll.c, and this function executes epoll_wait
VAR (Backend_poll, Void (*backend_poll) (Ev_p_ ev_tstamp timeout))
FD-related member variables:
Varx (ANFD *, Anfds)//This array is indexed with FD
Varx (int, Anfdmax)//size of the above array
Varx (int*, fdchanges)//Fdchangemax An array of size, each element is an FD, and this array has all the epoll needed to poll the FD
Varx (int, Fdchangemax)//array capacity
Varx (int, fdchangecnt)//the size of the actual element in the array
ANFD and FD one by one correspond to the structure ANFD as follows:
Typedefstruct
{
WL Head; typedef ev_watcher_list *WL; Focusing on the Watcher linked list of events of the same FD, an FD can have multiple watcher to monitor its events
unsigned char events;/* the event watched for *///watcher list of all watcher-focused events for the bitwise AND
unsigned char reify; /* Flag set when this ANFD needs reification (ev_anfd_reify, Ev__iofdset) *///If the structure needs to be re-epoll_ctl then set, indicating that the event of concern has changed
Unsignedcharemask; /* The Epoll backend stores the actual kernel mask in here *///events actually occurred
unsignedcharunused;
#if Ev_use_epoll
unsigned int egen; /* Generation counter to counter epoll bugs * *
#endif
#if Ev_select_is_winsocket | | Ev_use_iocp
SOCKET handle;
#endif
#if EV_USE_IOCP
OVERLAPPED or, ow;
#endif
} ANFD;
To maintain all the "Watcher of the event of concern", these watcher callback eventually need to be called
VAR (pendings, anpending *pendings [Numpri])//watcher has a priority, Libev maintains an array for each priority watcher
VAR (Pendingmax, Intpendingmax [NUMPRI])//capacity of each priority watcher array
VAR (pendingcnt, intpendingcnt [NUMPRI])//Per priority Watcher array actual size
The struct anpending is as follows:
Typedefstruct
{
W W; typedef ev_watcher *W;
int events;/* the pending event set for the given watcher *///events refers only to events that have not been handled by this watcher concern and have already occurred
} anpending;
As you can see from the sample program, there are several main ways to use Libev:
Ev_io_init
Ev_io_start
Ev_timer_init
Ev_timer_start
Ev_run
Look at:
Ev_io_init is a macro, which is primarily about setting up individual members in a ev_io
The void Ev_io_start (struct ev_loop *loop, ev_io *w) will do several things as follows:
1. Watcher activation (w->active=1) of the parameter W representation
2. Add Watcher W to the Watcher list list in the structure ANFD in the corresponding position of the FD in anfds[] (loop) where W is concerned.
3. Add the FD that is interested in W to the int *fdchanges array.
The most important function of void Ev_run (struct ev_loop *loop, int flags) is to do several things as follows:
1. Call Fd_reify.
Traversing the fdchanges array: For each of these FD, do the following:
All the watcher that focus on this FD are taken out of the ANFD list (by FD go to anfds[] to find the corresponding ANFD structure), and then all of the watcher-focused events are set by Epoll_ctl to
The kernel. Then empty the fdchanges array, in fact, the fdchangecnt is set to 0.
2. Time_update update time, calibration time
3. Calculate the timeout used for epoll_wait (), take the heap top (timers [HEAP0]) from the smallest heap that maintains all the timer, and subtract the current time to get a timeout. The minimum heap is implemented using an array, and each element is
struct Anhe.
Timers is defined as follows:
Varx (Anhe *, timers)
The struct anhe is defined as follows:
/* A HEAP element */
typedef struct{
Ev_tstamp At;//timer Watcher Expiry time
WT w;//typedef ev_watcher_time *WT;
} Anhe;
typedef struct EV_WATCHER_TIME
{
Ev_watcher_time (Ev_watcher_time)
} ev_watcher_time;
#define EV_WATCHER_TIME (type) \
Ev_watcher (type) \
Ev_tstamp at; /* Private */
4. Calling Backend_poll (Loop, waittime) will do several things:
For a system that uses epoll, it is actually called the Epoll_poll () function in ev_epoll.c, the main flow of the function is as follows:
4.1. Call Epoll_wait ()
4.2. Iterate through each returned struct epoll_event, take out the FD, and activate the event in Epoll_event, call the Fd_event (loop, FD, got) function.
This function updates the loop's pending array, watcher those that have focused on some of the events, and those watcher that do occur to the pending array set into the loop.
Note here that got is the sum of all the events that watcher are concerned about. Events in the anpending structure are only a single watcher concern.
5. Time_update Update the calibration time again
6. Timers_reify (loop) If the most recent timer has been triggered, re-select the next closest timer and place it on top of the heap.
7.periodics_reify (loop) and timers treatment are almost
8. Idle_reify (loop) not paying attention
9. Ev_invoke_pending calls the callback functions of each watcher in order from the PENDING array in loop, with priority from high to low
10. If there are also active watcher in the current loop, and Loop_done ==0 and the parameters of the boot Ev_run (flags) are not set evrun_once and evrun_nowait, continue starting with 1.
So far, the timer and I/O events have been unified, and now see how the signal is unified into the event processing framework.
Look at the definition of signal watcher:
/* Invoked when the given signal have been received */
/* revent ev_signal */
Typedefstructev_signal
{
Ev_watcher_list (ev_signal)
intsignum;/* RO *///Signal ID
} ev_signal;
Ansig signals [ev_nsig-1]; The information for each signal is represented by a ANSIG structure
Typedefstruct
{
ev_atomic_t pending;
#if ev_multiplicity
ev_p;
#endif
WL head;//can have more than one watcher focus on the same signal, using a linked list to string up
}ansig;
The main members associated with loop and signal are as follows:
VAR (Evpipe, Intevpipe [2])//For combining signal processing and event processing frameworks, Evpipe[0] for reading, evpipe[1] for writing
Varx (Ev_io, Pipe_w)//This I/O ev is used to encapsulate the read end of the pipe above, let the epoll listen to this pipe_w, when the signal is received, only need to in the signal processing function to evpipe[1] Write
Similar to the I/O event, the timer event, has the following two procedures:
EV_SIGNAL_INIT: Macro, initialize this ev_signal struct
void Ev_signal_start (struct ev_loop *loop, ev_signal *w) do several things:
1. Activate ev_signal this watcher (w->active = 1)
2. String W to the Watcher list in the corresponding position in the signals array
3. Initialize a pair of pipe, which is used to combine the signal and event processing framework, and register a ev_io pipe_w to Epoll, pipe_w encapsulated int evpipe[2] read-end evpipe[0]
4. Register this signal processing function ev_sighandler, the signal processing function will signals the corresponding slot in the array pending 1, meaning that it has received this signal, and to the write end of the int evpipe[2]
EVPIPE[1] Writes a byte so that in Pipe_w this IO event has a read event that epoll_wait () returns, successfully combining the signal and event processing framework.
At this point, Libev how to unify IO events, timer events and signals into the event processing framework has been analyzed. It can be seen that the Libev and libevent practices are the same, but the implementation is not the same, for example, on the implementation of the inheritance, Libev use of macros to achieve quite difficult to read, but this is efficient, borrow the words of the boss: "In the Libev World, efficiency first". This is probably the reason most people say that Libev is harder to read than Libevent code
Analysis of Libev Source code