LibevTable of Contents
- 1 libev
- 1.1 About The Code
- 1.2 EventLoop
- 1.3 Watcher
- 1.4 How it works
- 1.4.1 ev_run
- 1.4.2 fd_reify
- 1.4.3 backend_poll
- 1.4.4 timers_reify
- 1.4.5 EV_INVOKE_PENDING
- 1.5 Example
- 1.5.1 common. h
- 1.5.2 echo-client.cc
- 1.5.3 echo-server.cc
1 libev
Home http://software.schmorp.de/pkg/libev.html
Document http://software.schmorp.de/pkg/libev.html
Libev implements a powerful reactor, which may include the following events:
- Ev_io // IO readable and writable
- Ev_stat // File Attribute Change
- Ev_async // activate the thread
- Ev_signal // signal processing
- Ev_timer // timer
- Ev_periodic // periodic task
- Ev_child // sub-process status change
- Ev_fork // process
- Ev_cleanup // event loop exit trigger event
- Ev_idle // The idle trigger event of each event loop
- Ev_embed // todo (zhangyan04): I have no idea.
- Ev_prepare // events before each event loop
- Ev_check // events after each event loop
1.1 About The Code
The code style is rigorous and the layout is neat, and the author is German from the domain name. However, it is not very convenient to read the code because a large number of macros are used internally. In addition, from the code perspective, a default event_loop should be supported at the beginning, but multiple event_loop may be used in practical applications when multiple cores are generated, I guess the author should use a lot of macros to replace them for convenience. The macro that allows multiple event_loop operations is EV_MULTIPLICITY. For example, the following code
Void noinlineev_io_start (EV_P _ ev_io * w) {int fd = w-> fd; if (expect_false (ev_is_active (w) return; assert ("libev: ev_io_start called with negative fd ", fd> = 0); assert (" libev: ev_io_start called with illegal event mask ",! (W-> events &~ (Ev1_iofdset | EV_READ | EV_WRITE); EV_FREQUENT_CHECK; ev_start (EV_A _ (W) w, 1); array_needsize (ANFD, anfds, anfdmax, fd + 1, bytes ); wlist_add (& anfds [fd]. head, (WL) w); fd_change (EV_A _ fd, w-> events & ev1_iofdset | EV_ANFD_REIFY); w-> events & = ~ EV__IOFDSET; EV_FREQUENT_CHECK ;}
Reading this code for the first time will be hard to understand.
Macro |
Description |
Definition |
EV_P |
Event parameter |
Struct ev_loop * loop |
EV_P _ |
|
EV_P, |
EV_A |
Event argument |
Loop |
EV_A _ |
|
EV_A, |
Then many variables are encapsulated into macros as long as they are ev_loop members. For example, the macro definition of anfds in the code is
# Define anfds (loop)-> anfds)
In fact, there are quite a few fields in an ev_loop, but it is also normal that it is a powerful reactor. however, this has a direct consequence: it is difficult to understand the full picture of ev_loop, so it is difficult to thoroughly understand libev, so we can only try to understand it at the application level.
1.2 EventLoop
First, let's take a look at the reactor itself. In libev, the reactor object is called event_loop.event_loop. It allows dynamic creation and destruction, and allows binding of custom data.
Struct ev_loop * ev_loop_new (unsigned int flags); void ev_loop_destroy (EV_P); void ev_set_userdata (EV_P _ void * data); void * ev_userdata (EV_P );
Here, we will focus on flags. Here we will mainly choose what backend is used for poll Operations. The options include:
- EVBACKEND_SELECT
- EVBACKEND_POLL
- EVBACKEND_EPOLL // We usually choose this
- EVBACKEND_KQUEUE
- EVBACKEND_DEVPOLL
- EVBACKEND_PORT
However, there are three more important options:
- EVFLAG_NOINOTIFY // inofity call is not applicable to ev_stat. This reduces fd usage.
- EVFLAG_SIGNALFD // use signalfd to detect whether a signal has occurred. This can also reduce fd usage.
Most of the time, EVFLAG_AUTO (0) is enough to meet the requirements. From the code perspective, if epoll is supported, epoll will be selected first. because the watcher callback function can know the current event_loop, so that you can get custom data. Then let's see how this event_loop runs and stops.
Void ev_run (EV_P _ int flags); void ev_break (EV_P _ int how );
We also pay attention to the flags and how parameters here. Flags has the following:
- 0. Default value. It is processed cyclically until the external reference count is = 0 or it is displayed and exited.
- EVRUN_NOWAIT. Run once and poll will not wait. If there is a pending event to process, otherwise return immediately.
- EVRUN_ONCE. Run once, poll will wait for at least one event to occur, and return after processing is complete.
And how has the following:
- EVBREAK_ONE. Only one ev_run call is exited. Generally, this is enough.
- EVBREAK_ALL. Exit all ev_run calls. This situation occurs when ev_run is recursively called during pengding processing.
At the underlying layer of backend/epoll, libev provides interface callback that can be called before and after epoll_wait.
Void ev_set_loop_release_cb (loop, void (* release) (EV_P), void (* acquire) (EV_P) static voidepoll_poll (EV_P _ ev_tstamp timeout) {/* epoll wait times cannot be larger than (LONG_MAX-999UL)/HZ msecs, which is below * // * the default libev max wait time, however. */EV_RELEASE_CB; eventcnt = epoll_wait (backend_fd, epoll_events, epoll_eventmax, epoll_epermcnt? 0: ev_timeout_to_ms (timeout); EV_ACQUIRE_CB ;}
In event_loop, we also care about the length of each event_loop round-robin. This is usually not a big problem, but in the case of high performance, we need to set
Void ev_set_io_collect_interval (EV_P _ ev_tstamp interval); void ev_set_timeout_collect_interval (EV_P _ ev_tstamp interval );
The code for using these parameters in ev_run is troublesome. However, if we use timeout_interval, we must use ev_sleep during each timeout_interval check. however, this will affect io_interval, so some internal conversions are made. The conversion result is used as the epoll_wait timeout time. However, we do not need to care about it most of the time. The default value is 0.0. The system will use the fastest response method for processing.
1.3 Watcher
Next, let's take a look at EventHandler. watcher under libev is equivalent to EventHandler. Usually, the fd callback function and the events we need to pay attention to are bound. Then, once an event is triggered, the callback function we use will be triggered. The callback function parameters usually include reactor, watcher, and the triggered event. Here we do not intend to repeat the watcher-related content and corresponding APIs in the document, but some content may be mentioned and accompanied by some annotations. Before that, let's take a look at the general process. Here we use TYPE to distinguish different types of watcher.
Typedef void (*) (struct ev_loop * loop, ev_TYPE * watcher, int revents) callback; // callback is of this type ev_init (ev_TYPE * watcher, callback ); // initialize watcherev_TYPE_set (ev_TYPE * watcher, [args]); // Set watcherev_TYPE_init (ev_TYPE * watcher, callback, [args]); // This function is most convenient to use, ev_TYPE_start (loop, ev_TYPE * watcher); // register watcherev_TYPE_stop (loop, ev_TYPE * watcher); // deregister watcherev_set_priority (ev_TY PE * watcher, int priority); // sets the priority ev_feed_event (loop, ev_TYPE * watcher, int revents); // This cross-thread notification is very useful, which is equivalent to triggering an event. Bool ev_is_active (ev_TYPE * watcher); // whether the watcher is active. bool ev_is_pending (ev_TYPE * watcher); // whether watcher is pending.int ev_clear_pending (loop, ev_TYPE * watcher); // clear the watcher pending status and return the event
Wacther has the following statuses:
- Initialiased. Call the init function to initialize
- Active. Call start to register
- Pending. The event has been triggered but not handled.
- Inactive. Call stop to log out. This status is equivalent to initialised.
In fact, there is not much meaning about how each watcher is implemented, because most of the existing code is similar. In The next section, we will talk about how to arrange The internal data structure. After understanding The internal data structure and The process, we can avoid many problems, such as The special problem of disappearing file descriptors.
1.4 How it works 1.4.1 ev_run
The most important thing is to look at the ev_run code. We don't want to read it carefully, just look at the synopsis and analyze the data structure in a general way.
Voidev_run (EV_P _ int flags) {assert ("libev: ev_loop recursion during release detected", loop_done! = Success); loop_done = EVBREAK_CANCEL; EV_INVOKE_PENDING;/* in case we recurse, ensure ordering stays nice and clean */do {if (expect_false (loop_done) break; /* update fd-related kernel structures */fd_reify (EV_A);/* calculate blocking time */{ev_tstamp waittime = 0 .; ev_tstamp sleeptime = 0 .; /* remember old timestamp for io_blocktime calculation */ev_tstamp prev_mn_now = mn_n Ow;/* update time to cancel out callback processing overhead */time_update (EV_A _ 1e100); if (expect_true (! (Flags & EVRUN_NOWAIT | idleall |! Activecnt) {waittime = MAX_BLOCKTIME; if (timercnt) {ev_tstamp to = ANHE_at (timers [HEAP0])-mn_now + backend_fudge; if (waittime> to) waittime = ;} /* don't let timeouts decrease the waittime below timeout_blocktime */if (expect_false (waittime <timeout_blocktime) waittime = timeout_blocktime; /* extra check because io_blocktime is commonly 0 */if (expect_false (io_blocktime) {slee Ptime = io_blocktime-(mn_now-prev_mn_now); if (sleeptime> waittime-backend_fudge) sleeptime = waittime-backend_fudge; if (expect_true (sleeptime> 0 .)) {ev_sleep (sleeptime); waittime-= sleeptime ;}} assert (loop_done = EVBREAK_RECURSE, 1);/* assert for side effect */backend_poll (EV_A _ waittime ); assert (loop_done = EVBREAK_CANCEL, 1);/* assert for side effect * // * update ev _ Rt_now, do magic */time_update (EV_A _ waittime + sleeptime);}/* queue pending timers and reschedule them */timers_reify (EV_A ); /* relative timers called last */EV_INVOKE_PENDING;} while (expect_true (activecnt &&! Loop_done &&! (Flags & (EVRUN_ONCE | EVRUN_NOWAIT); if (loop_done = EVBREAK_ONE) loop_done = EVBREAK_CANCEL ;}
We can summarize the general steps, which are similar to most event loops.
- First, trigger those watchers that have been pending.
- Determine whether loop_done
- Fd_reify. This will be discussed separately later.
- Calculate the waittime and perform the necessary sleep.
- Backend_poll starts polling and completes the pending event
- Timers_reify. This is different from fd_reify.
- Call EV_INVOKE_PENDING to trigger the pending io event
Very simple. Next let's take a look at fd_reify, backend_poll, timers_reify, and EV_INVOKE_PENDING.
1.4.2 fd_reify
The following is the fd_reify code snippet. We can see that this part is modifying the events of fd.
Inline_size voidfd_reify (EV_P) {int I; for (I = 0; I <fdchangecnt; ++ I) {int fd = fdchanges [I]; ANFD * anfd = anfds + fd; ev_io * w; unsigned char o_events = anfd-> events; unsigned char o_reify = anfd-> reify; anfd-> reify = 0;/* if (expect_true (o_reify & EV_ANFD_REIFY )) probably a deoptimisation */{anfd-> events = 0; for (w = (ev_io *) anfd-> head; w = (ev_io *) (WL) w) -> next) anfd-> events | = (u Nsigned char) w-> events; if (o_events! = Anfd-> events) o_reify = ev1_iofdset;/* actually | = */} if (o_reify & ev1_iofdset) backend_modify (EV_A _ fd, o_events, anfd-> events );} fdchangecnt = 0 ;}
Where is this fdchanges called. We can see that it is in the ev_io_start part. That is to say, if we want to modify the fd attention event, we must display ev_io_stop and then correct it and re-run ev_io_start. If the underlying layer calls fd_change, the underlying layer maintains the array fdchanges to save the fd with the events change.
Void noinlineev_io_start (EV_P _ ev_io * w) {int fd = w-> fd; if (expect_false (ev_is_active (w) return; assert ("libev: ev_io_start called with negative fd ", fd> = 0); assert (" libev: ev_io_start called with illegal event mask ",! (W-> events &~ (Ev1_iofdset | EV_READ | EV_WRITE); EV_FREQUENT_CHECK; ev_start (EV_A _ (W) w, 1); array_needsize (ANFD, anfds, anfdmax, fd + 1, bytes ); wlist_add (& anfds [fd]. head, (WL) w); fd_change (EV_A _ fd, w-> events & ev1_iofdset | EV_ANFD_REIFY); w-> events & = ~ EV__IOFDSET; EV_FREQUENT_CHECK;} inline_size voidfd_change (EV_P _ int fd, int flags) {unsigned char reify = anfds [fd]. reify; anfds [fd]. reify | = flags; if (expect_true (! Reify) {++ fdchangecnt; array_needsize (int, fdchanges, fdchangemax, fdchangecnt, EMPTY2); fdchanges [fdchangecnt-1] = fd ;}}
1.4.3 backend_poll
Backend_poll supports many poll implementations at the underlying layer. Here we only need to look at ev_epoll.c. the code is not listed here. If a fd triggers an event, the fd_event (EV_A _, fd, event) will be called for notification. So let's look at fd_event.
Inline_speed voidfd_event_nocheck (EV_P _ int fd, int revents) {ANFD * anfd = 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 (EV_A _ (W) w, ev) ;}} void noinlineev_feed_event (EV_P _ void * w, int revents) {W w _ = (W) w; int pri = ABSPRI (w _); if (expect_false (w _-> pending) pendings [pri] [w _-> pending-1]. events | = revents; else {w _-> pending = ++ pendingcnt [pri]; array_needsize (ANPENDING, pendings [pri], pendingmax [pri], w _-> pending, EMPTY2); // set the watcher and revents. pendings [pri] [w _-> pending-1]. w = w _; pendings [pri] [w _-> pending-1]. events = revents ;}}
The bottom layer is an array of ANFD, which is offset by fd. If the fd is too large, it seems that the performance is not as good as the demuxtable implementation method in hpserver. Get all watcher under this fd, and record all the triggered watcher in loop-> pendings.
1.4.4 timers_reify
HEAP0 is the minimum heap subscript. If repeat is repeated, the timestamp will be adjusted again. If it is not repeat, the ev_timer_stop method will be called internally to remove the timer. All scheduled tasks are added through feed_reverse. Feed_reverse maintains a dynamic array to store all timer tasks, and then traverses these tasks in feed_reverse_done to trigger these timer tasks.
Inline_size voidtimers_reify (EV_P) {EV_FREQUENT_CHECK; if (timercnt & ANHE_at (timers [HEAP0]) <mn_now) {do {ev_timer * w = (ev_timer *) ANHE_w (timers [HEAP0]);/* assert ("libev: inactive timer on timer heap detected", ev_is_active (w ))); * // * first reschedule or stop timer */if (w-> repeat) {ev_at (w) + = w-> repeat; if (ev_at (w) <mn_now) ev_at (w) = mn_now; assert ("libev: negative ev_timer repeat value found while processing timers", w-> repeat> 0 .)); ANHE_at_cache (timers [HEAP0]); downheap (timers, timercnt, HEAP0);} else ev_timer_stop (EV_A _ w);/* nonrepeating: stop timer */EV_FREQUENT_CHECK; feed_reverse (EV_A _ (W) w);} while (timercnt & ANHE_at (timers [HEAP0]) <mn_now); feed_reverse_done (EV_A _ EV_TIMER );}}
1.4.5 EV_INVOKE_PENDING
The final function called by this macro is the following. It traverses all pendings events and triggers them one by one.
Void noinlineev_invoke_pending (EV_P) {int pri; for (pri = NUMPRI; pri --;) while (pendingcnt [pri]) {ANPENDING * p = pendings [pri] + -- pendingcnt [pri]; p-> w-> pending = 0; EV_CB_INVOKE (p-> w, p-> events ); EV_FREQUENT_CHECK ;}}
1.5 Example
By writing a simple echo-server and echo-client with timeout, we can find that there are a lot of other workloads, such as the implementation of the buffer management state machine. Therefore, I did not write a complete example, but simply wrote the link information and closed it if echo-client is connected to the server.
1.5.1 common. h
# Ifndef _ COMMON_H _ # define _ COMMON_H _ # include <unistd. h> # include <fcntl. h> # include <sys/types. h> # include <sys/socket. h> # include <arpa/inet. h> # include <strings. h >#include <cstdlib> # include <cstdio> # include <cstddef> # include <string> namespace common {# define D (exp, fmt ,...) do {if (! (Exp) {fprintf (stderr, fmt, ##__ VA_ARGS _); abort () ;}} while (0) static void setnonblock (int fd) {fcntl (fd, F_SETFL, fcntl (fd, F_GETFL) | O_NONBLOCK);} static void setreuseaddr (int fd) {int OK = 1; setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, & OK, sizeof (OK);} static void setaddress (const char * ip, int port, struct sockaddr_in * addr) {bzero (addr, sizeof (* addr )); addr-> sin_family = AF_INET; inet_ton (AF_INET, ip, & (addr-> sin_addr); addr-> sin_port = htons (port);} static std :: string address_to_string (struct sockaddr_in * addr) {char ip [128]; inet_ntop (AF_INET, & (addr-> sin_addr), ip, sizeof (ip )); char port [32]; snprintf (port, sizeof (port), "% d", ntohs (addr-> sin_port); std: string r; r = r + "(" + ip + ":" + port + ")"; return r;} static int new_tcp_server (int port) {int fd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); D (fd> 0, "socket failed (% m) \ n"); setnonblock (fd); setreuseaddr (fd); sockaddr_in addr; setaddress ("0.0.0.0", port, & addr); bind (fd, (struct sockaddr *) & addr, sizeof (addr); listen (fd, 64 ); // backlog = 64 return fd;} static int new_tcp_client (const char * ip, int port) {int fd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); setnonblock (fd ); sockaddr_in addr; setaddress (ip, port, & addr); connect (fd, (struct sockaddr *) (& addr), sizeof (addr); return fd ;}}; // namespace common # endif // _ COMMON_H _
1.5.2 echo-client.cc
# Include "ev. h "# include" common. h "static void do_connected (struct ev_loop * reactor, ev_io * w, int events) {close (w-> fd); ev_break (reactor, EVBREAK_ALL);} int main () {struct ev_loop * reactor = generator (EVFLAG_AUTO); int fd = common: new_tcp_client ("127.0.0.1", 34567); ev_io io; ev_io_init (& io, & do_connected, fd, EV_WRITE); ev_io_start (reactor, & io); ev_run (reactor, 0); close (fd); ev_loop_destroy (reactor); return 0 ;}
1.5.3 echo-server.cc
# Include "ev. h "# include" common. h "static void do_accept (struct ev_loop * reactor, ev_io * w, int events) {struct sockaddr_in addr; socklen_t addr_size = sizeof (addr ); int conn = accept (w-> fd, (struct sockaddr *) & addr, & addr_size); std: string r = common: address_to_string (& addr ); fprintf (stderr, "accept % s \ n", r. c_str (); close (conn);} int main () {struct ev_loop * reactor = ev_loop_new (EVFLAG_AUTO); int fd = common: new_tcp_server (34567); ev_io w; ev_io_init (& w, do_accept, fd, EV_READ); ev_io_start (reactor, & w); ev_run (reactor, 0); close (fd); ev_loop_destroy (reactor );}
Address: http://dirlt.com/libev.html
[Go to] Libev tutorial