libev Implementation Analysis
libev
is basically an event loop, which watches and dispatches the target events. It includes the following parts:
- Event Handle
- IO Multiplexing
- Timer
- Event Loop
1. Event Handle ev_watcher
libev
uses ev_watcher
to monitor and manage various events. Each event type has a corresponding watcher type. Common handles include:
ev_io
: monitorfd
R/W eventsev_timer
: timer, support both one-time and repeating timeev_signal
: signal event, E.g.SIGINT
The above can be referred to as sub-classes of ev_watcher
:
1 | typedef struct ev_watcher { |
An example of registering a readable standard input event:
1 | ev_io stdin_watcher; |
ev_io_init
is a macro that expands to the following code, mainly for initializing the watcher, such as setting the callback, fd
, events, etc.:
1 | do { |
ev_io_start
mainly modifies the anfds
and fdchanges
arrays. For watchers that are already active, it returns directly. If it is not active, it sets the active
and priority
and uses the head insertion method to insert it into the linked list of the corresponding fd in anfds
, and sets the current watcher as the head:
1 | wlist_add(&((loop)->anfds)[fd].head, (WL)w); |
Insert the fd into the end of the fdchanges
array:
1 | ++((loop)->fdchangecnt); |
Set the flag w->events &= ~EV__IOFDSET;
2. IO Multiplexing
libev
supports select
, poll
, epoll
1 | void (*backend_modify)(struct ev_loop *loop, int fd, int oev, int nev); |
Taking epoll
as an example, backend_modify
is epoll_modify
, and backend_poll
is epoll_poll
. epoll_modify
is the epoll_ctl
commonly used by everyone. When an event changes, use EPOLL_CTL_MOD
, otherwise use EPOLL_CTL_ADD
.
1 | epoll_ctl(backend_fd, oev && oldmask != nev ? EPOLL_CTL_MOD : EPOLL_CTL_ADD, fd, &ev) |
The execution logic of epoll_poll
:
epoll_wait
obtains the list of ready events (loop->epoll_events
)- For ready events, execute
fd_event
and put them into thependings
array of the corresponding priority in the loop.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24static __inline__ void
fd_event(struct ev_loop *loop, int fd, int revents) {
ANFD *anfd = ((loop)->anfds) + fd;
if (__builtin_expect((!!(!anfd->reify)), (1)))
fd_event_nocheck(loop, fd, revents);
}
static __inline__ void
fd_event_nocheck(struct ev_loop *loop, int fd, int revents) {
ANFD *anfd = ((loop)->anfds) + fd;
ev_io *w;
for (w = (ev_io *)anfd->head; w; w = (ev_io *)((WL)w)->next) {
int ev = w->events & revents;
if (ev)
ev_feed_event(loop, (W)w, ev);
}
}
void __attribute__((__noinline__))
ev_feed_event(struct ev_loop *loop, void *w, int revents) {
w_->pending = ++((loop)->pendingcnt)[pri];
((loop)->pendings)[pri][w_->pending - 1].w = w_;
((loop)->pendings)[pri][w_->pending - 1].events = revents;
}
3. Timer
The internal implementation of the timer uses a Binary Heap and a Quaternary Heaps (for better cache efficiency).
1 |
Note that HEAP0
is used as the first element, that is, the offset. The internal implementation of the heap is upheap
and downheap
, which are relatively simple and will not be described here. Its structure is:
1 | typedef ev_watcher_time *WT; |
The heap is sorted according to at
, which is the expiration time of the timer. The heap sort is sorted according to the value of at
from small to large.
4. Event Loop - ev_loop
1 | struct ev_loop *loop = EV_DEFAULT; |
EV_DEFAULT
will call the ev_default_loop
function to initialize an ev_loop
structure, which mainly initializes the loop
structure through loop_init
, such as choosing epoll or poll, select. The most critical point: epoll_init
occurs when the above loop is created.
ev_run
is more complicated and mainly does the following:
fd_reify
: Traverse thefdchanges
array mentioned earlier, take out the linked list fromanfds
according to the fd, traverse all event linked lists, get all events, and determine whether the latest event is consistent with the previous old event (events
). If they are inconsistent, callepoll_modify
(or add the event for the first time).1
2
3
4
5typedef struct {
WL head; // event linked list
unsigned char events;
// other
} ANFD;backend_poll
: See the implementation ofbackend_poll
above, it will fetch the ready events and put the events into thependings
array, that is, put them at the end of the corresponding priority queue.timer_reify
: If the event has not expired, no processing is done. Otherwise, the executes the following:- Call
ev_timer_stop
to clear the timer that has norepeat
set. - Set the
repeat
timer withev_timer_init()
call
- Call
invoke_cb
: it isev_invoke_pending
by default, the callback will be take out of from the priority queue then be triggered respectively.
Reference
libev Implementation Analysis
http://example.com/2025/01/15/libev-Implementation-Overview/