विषय पर बढ़ें

अंतिम अद्यतन:2026-10-12

libev event loop का सिद्धांत

रूपरेखा

libev एक cross-platform, efficient event notification library है, जिसका उपयोग मुख्य रूप से multiplexing event mechanisms (जैसे select, poll, epoll) के implementation के लिए किया जाता है। इसका design लक्ष्य सरल और तेज़ event loop mechanism उपलब्ध कराना है, यह उन scenarios के लिए विशेष रूप से उपयुक्त है जहाँ high concurrency और low latency चाहिए, जैसे high-performance Web servers, network proxies और अन्य IO-intensive applications। इसका code बहुत कम मात्रा में है, और कुछ production environments में उपयोग होता है, पढ़ने की बहुत सिफारिश है, इस section में इसके implementation सिद्धांत और उपयोग पर बात करते हैं।

1. मूल ढाँचा

libev का केंद्र event loop है, जो application और operating system के बीच का interface है, विभिन्न प्रकार के events को सुनने और dispatch करने के लिए। libev का architecture बहुत संक्षिप्त है, इसमें मुख्य रूप से निम्न भाग शामिल हैं:

1.1 Event handle (ev_watcher)

libev विभिन्न events को manage और monitor करने के लिए event handles (watchers) उपयोग करता है। हर event type (जैसे IO events, timer events) का एक संगत watcher type है। सामान्य event handles में शामिल हैं:

  • ev_io: file descriptor के readable/writable events monitor करने के लिए।
  • ev_timer: timer events के लिए, one-shot और repeating timers दोनों का समर्थन।
  • ev_signal: signal events (जैसे SIGINT, SIGTERM) पकड़ने के लिए।

ऊपर वाले सभी को ev_watcher के subclasses कहा जा सकता है:

typedef struct ev_watcher
{
  int active;
  int pending;
  int priority;
  void *data;
  void (*cb)(struct ev_loop *loop, struct ev_watcher *w, int revents);
} ev_watcher;

वास्तविक उपयोग में दो key functions आते हैं, उदाहरण के लिए: readable standard input event register करना

ev_io stdin_watcher;
ev_io_init(&stdin_watcher, stdin_cb, /*STDIN_FILENO*/ 0, EV_READ);
ev_io_start(loop, &stdin_watcher);

ev_io_init एक macro है, जो अंत में नीचे वाले code पर expand होता है, यह मुख्य रूप से watcher को initialize करता है, जैसे: callback, fd, events आदि सेट करना।

  do
  {
    do
    {
      ((ev_watcher *)(void *)((&stdin_watcher)))->active = ((ev_watcher *)(void *)((&stdin_watcher)))->pending = 0;
      ((ev_watcher *)(void *)(((&stdin_watcher))))->priority = (0);
      ((((&stdin_watcher)))->cb = ((stdin_cb)), memmove(&((ev_watcher *)(((&stdin_watcher))))->cb, &(((&stdin_watcher)))->cb, sizeof((((&stdin_watcher)))->cb)));
    } while (0);
    do
    {
      ((&stdin_watcher))->fd = ((0));
      ((&stdin_watcher))->events = ((EV_READ)) | EV__IOFDSET;
    } while (0);
  } while (0);

ev_io_start मुख्य रूप से anfs और fdchanges arrays को modify करता है।

  • पहले से active watcher के लिए सीधे return
  • अगर अभी active नहीं है, तो active और priority सेट करें
  • Head insertion से anfds में संगत fd की linked list में जोड़ें, और current watcher को head बनाएँ
wlist_add(&((loop)->anfds)[fd].head, (WL)w);

static __inline__ void
wlist_add(WL *head, WL elem)
{
  elem->next = *head;
  *head = elem;
}
  • fd को fdchanges array के अंत में insert करें
++((loop)->fdchangecnt);
((loop)->fdchanges)[((loop)->fdchangecnt) - 1] = fd;
  • Flag सेट करें
w->events &= ~EV__IOFDSET;

1.2 IO multiplexing

libev में epoll, poll, select आदि का समर्थन है, जिनमें loop में 2 callback functions होते हैं:

void (*backend_modify)(struct ev_loop *loop, int fd, int oev, int nev);
void (*backend_poll)(struct ev_loop *loop, ev_tstamp timeout);

हम epoll का उदाहरण लेते हैं, backend_modify यानी epoll_modify, और backend_poll यानी epoll_poll।

epoll_modify वही है जो आमतौर पर सब epoll_ctl से उपयोग करते हैं, जब event बदल जाए तो EPOLL_CTL_MOD, अन्यथा EPOLL_CTL_ADD।

epoll_ctl (backend_fd, oev && oldmask != nev ? EPOLL_CTL_MOD : EPOLL_CTL_ADD, fd, &ev)

epoll_poll का निष्पादन तर्क इस प्रकार है:

  • epoll_wait से ready events की सूची लें (loop->epoll_events)
  • Ready events के लिए fd_event चलाकर loop के pendings की संगत priority वाली array में डालें।
static __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;
}

1.3 Timer

Timer के आंतरिक implementation में binary heap और 4-ary heap उपयोग हुए हैं, यहाँ 4-ary heap इसलिए उपयोग किया गया क्योंकि उसकी cache efficiency अधिक है, इसका implementation इस प्रकार है:

#define DHEAP 4
#define HEAP0 (DHEAP - 1) /* index of first element in heap */
#define HPARENT(k) ((((k) - HEAP0 - 1) / DHEAP) + HEAP0)
#define UPHEAP_DONE(p,k) ((p) == (k))

ध्यान दें ऊपर HEAP0 को पहले element यानी offset के रूप में उपयोग किया गया है, heap का आंतरिक implementation upheap, downheap है, काफी सरल है इसलिए विस्तार नहीं करेंगे। इसका structure है:

typedef ev_watcher_time *WT;

typedef struct
{
  ev_tstamp at;
  WT w;
} ANHE;

heap की sorting at के आधार पर होती है, at timer का expiry time है, heap sorting at की value के आरोही क्रम में होती है।

1.4 Event loop (ev_loop)

आमतौर पर इस प्रकार उपयोग होता है:

struct ev_loop *loop = EV_DEFAULT;
ev_run(loop, 0);

EV_DEFAULT, ev_default_loop function को call करता है, जो एक ev_loop structure initialize करता है, इसमें मुख्य रूप से loop_init से loop structure initialize होता है, जैसे: epoll चुनना या poll, select।

सबसे केंद्रीय बात: epoll_init ऊपर loop के निर्माण के समय होता है।

ev_run काफी जटिल है, यह मुख्य रूप से कुछ काम करता है:

  • fd_reify

पहले बताई गई fdchanges array को traverse करें, fd से anfds में से linked list निकालें, सभी event linked lists को traverse करके सभी events पाएँ, जांचें कि current नए event पहले के पुराने event (events) के समान हैं या नहीं, असमान होने पर epoll_modify call करें (या पहली बार event जोड़ना)।

typedef struct
{
  WL head;      // 事件链表
  unsigned char events; // 前面事件
    // other
} ANFD;
  • backend_poll

आगे बताए backend_poll का implementation देखें, यह ready events लेकर उन्हें pendings array में डालता है, यानी संगत priority queue के अंत में।

  • timer_reify, event expire न हुआ हो तो कुछ नहीं करता, expire होने पर निम्न तर्क चलाता है:

  • repeat सेट न किया गया timer

    • ev_timer_stop से साफ़ करता है
  • repeat सेट किया गया timer

    उदाहरण के लिए: `ev_timer_init(&timer_watcher, timer_cb, 2.0, 3.0);``

    `ev_timer का at वर्तमान समय + 2 seconds सेट होता है (यानी timer 2 seconds बाद trigger होगा), और repeat 3 seconds सेट होता है, तो timer पहले 2 seconds बाद trigger होगा, फिर हर 3 seconds में trigger होगा।

    if (w->repeat)
    {
      // 更新下次触发时间
      ((WT)(w))->at += w->repeat;
      // 过期了
      if (((WT)(w))->at < ((loop)->mn_now))
        ((WT)(w))->at = ((loop)->mn_now);   // 设置当前时间
      // 设置堆顶元素为前面w->at
      (((loop)->timers)[HEAP0]).at = (((loop)->timers)[HEAP0]).w->at;
      // 调整堆
      downheap(((loop)->timers), ((loop)->timercnt), HEAP0);
    }
    
  • Invoke_cb

Invoke_cb डिफ़ॉल्ट रूप से ev_invoke_pending है, संगत priority queue से event निकालकर callback function से event processing करता है।

ANPENDING *p = ((loop)->pendings)[((loop)->pendingpri)] + --((loop)->pendingcnt)[((loop)->pendingpri)];
(p->w)->cb(loop, (p->w), (p->events));

2. उपयोग कैसे करें?

git clone git@github.com:enki/libev.git
./configure
make && make install

Project compilation:

gcc main.c -lev

3. अभ्यास प्रश्न

अंत में, एक अभ्यास प्रश्न छोड़ते हैं, क्या आप libev से ऐसा example लिख सकते हैं?

जब हम data input करते हैं, stdio event ready return करे, और अगर input timeout हो जाए, तो timeout return करे, demo प्रभाव:

[light@i-7dwsk0ty libev]$ ./a.out 
timeout
[light@i-7dwsk0ty libev]$ ./a.out 
21
stdin ready

संबंधित विषय

टिप्पणियाँ