一.libev簡介
Libev是一個事件循環:你注冊感興趣的特定事件(比如一個文件可以讀取時或者發生超時時),它將管理這些事件源,將這些事件反饋給你的程序。為了實現這些,至少要在你的進程(或線程)中執行事件循環句柄控制,然后就能通過回調機制進行事件通信。你通過所謂的watchers注冊感興趣的特定事件,這些watchers都是相對較小的C語言結構體,它們通過初始化具體的事件得到,然后交由libev啟動那個watcher。
Libev支持 select,poll,Linux特有的epoll,BSD特有的kqueue以及Solaris特有的文件描述符事件端口機制(ev_io),Linux信息通知接口(ev_stat),Linux事件文件/信號文件(為了更快更完整的喚醒沉睡線程(ev_async)/信號捕捉(ev_signal))相對定時器(ev_timer),用戶自定義的絕對定時器(ev_periodic),同步信號(ev_signal),進程狀態改變事件(ev_child),和通過事件循環機制實現的事件觀察者管理本身(ev_idle,ev_embed,ev_prepare和ev_check監控)也和文件監控(ev_stat)和有限支持的派生子進程事件(ev_fork)一樣。
二.libev特點
1.關於時間:時間類型為ev_tstamp (Libev描述時間采用一個浮點數,它來自於距離(POSIX)時期的一個(帶小數)的秒數)。
2.錯誤處理:Libev 知道三類錯誤:操作系統錯誤、用法錯誤和內部錯誤(bugs)
當 libev 捕捉到無法處理的操作系統錯誤時(如一個系統調用返回了libev不能識別的返回值),它通過調用 ev_set_syserr_cb設定的回調函數,它假設這個回調函數可修正這個問題或者是退出。默認行為是打印一個摘要信息並調用 abort()。
當 libev 檢測到用法錯誤(如負的時間),它會打印一條摘要信息並退出(使用 assert 機制,因此宏 NDEBUG 會關閉這種檢測); 表明這里有libev調用端的編程錯誤需要修正。
Libev也有一些內部錯誤檢查,也有大多數的代碼錯誤檢查。這些在正常情況下不會觸發,他們表明在libev存在一個bug或者更大的錯誤。
3.全局函數(這些函數可以在任何時候被調用,甚至可以在初始化libev庫之前調用)
ev_tstamp ev_time () //返回libev使用它時的當前時間。請注意,ev_now函數通常更快,也經常用來返回你想知道的時間戳。ev_now_update和ev_now組合在一起使用更好
ev_sleep (ev_tstamp interval) //休眠指定的時間間隔:當前線程將被阻塞直到它被中斷或者給定的時間間隔已經過去。當interval <= 0.時將立即返回.基本上這是一個精度稍低的sleep()函數.Interval的值是有限制的-libev只保證休眠時間最長為一天(interval <= 86400
int ev_version_major ()
int ev_version_minor () //通過調用ev_version_major 和ev_version_minor函數,你可以找出主要的和次要的ABI鏈接庫版本號。如果需要,你可以比較全局字段EV_VERSION_MAJOR 和 EV_VERSION_MINOR,它們表明了你的程序編譯完成后的庫版本。
ev_set_allocator (void *(*cb)(void *ptr, long size)) //這是申請或釋放內存的,如果申請內存是返回0,當內存需要被分配(大小!= 0),庫可能中止或采取一些潛在的破壞性的行動
例:

static void * persistent_realloc (void *ptr, size_t size) { for (;;) { void *newptr = realloc (ptr, size); if (newptr) return newptr; sleep (60); } } ... ev_set_allocator (persistent_realloc);
ev_set_syserr_cb (void (*cb)(const char *msg)) //設置發生回系統錯誤時的調函數
例:

static void fatal_error (const char *msg) { perror (msg); abort (); } ... ev_set_syserr_cb (fatal_error);
ev_feed_signal (int signum) //這個函數可以用來“模擬”一個信號接收。它是完全安全的調用這個函數在任何時間,從任何上下文,包括信號處理程序或隨機線程。
4.該庫知道兩種類型的循環,默認的循環(支持子進程事件)和動態創建事件循環
struct ev_loop *ev_default_loop (unsigned int flags)
常用:ev_default_loop (EVBACKEND_POLL | EVBACKEND_SELECT | EVFLAG_NOENV); //限制測試程序的select、poll的后端,不允許環境設置要考慮
struct ev_loop *ev_loop_new (unsigned int flags) //這個函數是線程安全的,常用方式:使用ev_loop_new創建一個時間循環在每個線程中,在主線程中使用默認事件循環。 The flags argumentis usually specified as 0 (or EVFLAG_AUTO).
flags argumentis:
EVFLAG_AUTO
:默認值,常用
-
-
EVFLAG_NOENV
:指定 libev 不使用LIBEV_FLAGS
環境變量。常用於調試和測試 -
EVFLAG_FORKCHECK
:與ev_loop_fork()
相關,本文暫略 -
EVFLAG_NOINOTIFY
:在ev_stat
監聽中不使用inotify
API -
EVFLAG_SIGNALFD
:在ev_signal
監聽中使用signalfd
API -
EVFLAG_NOSIGMASK
:使 libev 避免修改 signal mask。這樣的話,你要使 signal 是非阻塞的。在未來的 libev 中,這個 mask 將會是默認值。 -
EVBACKEND_SELECT
:通用后端 -
EVBACKEND_POLL
:除了 Windows 之外的所有后端都可以用 -
EVBACKEND_EPOLL
:Linux 后端 -
EVBACKEND_KQUEUE
:大多數 BSD 的后端 -
EVBACKEND_DEVPOLL
:Solaris 8 后端 -
EVBACKEND_PORT
:Solaris 10 后端
-
ev_loop_destroy (loop) //Destroys an event loop object
ev_loop_fork (loop)
int ev_is_default_loop (loop) //判斷是否是默認事件循環,是返回ture
unsigned int ev_iteration (loop) //返回當前的 loop 的迭代數。等於 libev pool 新事件的數量(?)。這個值對應ev_prepare
和ev_check
調用,並在 prepare 和 check 之間增一
unsigned int ev_depth (loop) //返回ev_run()
進入減去退出次數的差值
unsigned int ev_backend (struct ev_loop *loop);
返回EVBACKEND_*
值
ev_tstamp ev_now (loop)
得到當前的“event loop time”。在 callback 調用期間,這個值是不變的。
void ev_new_update (loop)
更新從ev_now()
中返回的時間。不必要的話,不要使用,因為這個函數的開銷相對是比較大的。
void ev_suspend (struct ev_loop *loop); void ev_resume (struct ev_loop *loop);
暫停當前的 loop,使其刮起當前的所有工作。同時其 timeout 也會暫停。如果恢復后,timer 會從上一次暫停狀態繼續及時——這一點對於實現一些要連同時間也一起凍結的功能時,非常有用。
注意已經 resume 的loop不能再 resume,反之已經 suspend 的 loop 不能再 suspend。
bool ev_run (struct ev_loop *loop, int flags);
初始化 loop 結束后,調用這個函數開始 loop。如果 flags == 0,直至 loop 沒有活躍的時間或者是調用了 ev_bread 之后停止。
Loop 可以是異常使能的,你可以在 callback 中調用longjmp
來終端回調並且跳出 ev_run,或者通過拋出 C++ 異常。這些不會導致 ev_depth 值減少。
EVRUN_NOWAIT
會檢查並且執行所有未解決的 events,但如果沒有就緒的時間,ev_run 會立刻返回。EVRUN_ONCE
會檢查所有的 events,在至少每一個 event 都執行了一次事件迭代之后才返回。但有時候,使用ev_prepare
/ev_check
更好。
以下是ev_run
的大致工作流程:
-
-
loop depth ++
-
重設
ev_break
狀態 -
在首次迭代之前,調用所有 pending watchers
-
LOOP:
-
-
如果置了
EVFLAG_FORKCHECK
,則檢查 fork,如果檢測到 fork,則排隊並調用所有的 fork watchers -
排隊並且調用所有 ready 的watchers
-
如果
ev_break
被調用了,則直接跳轉至 FINISH -
如果檢測到了 fork,則分離並且重建 kernel state
-
使用所有未解決的變化更新 kernel state
-
更新
ev_now
的值 -
計算要 sleep 或 block 多久
-
如果指定了的話,sleep
-
loop iteration ++
-
阻塞以等待事件
-
排隊所有未處理的I/O事件
-
更新
ev_now
的值,執行 time jump 調整 -
排隊所有超時事件
-
排隊所有定期事件
-
排隊所有優先級高於 pending 事件的 idle watchers
-
排隊所有 check watchers
-
按照上述順序的逆序,調用 watchers (check watchers -> idle watchers -> 定期事件 -> 計時器超時事件 -> fd事件)。信號和 child watchers 視為 fd watchers。
-
如果
ev_break
被調用了,或者使用了EVRUN_ONCE
或者EVRUN_NOWAIT
,則如果沒有活躍的 watchers,則 FINISH,否則 continue
-
FINISH:
-
-
如果是
EVBREAK_ONE
,則重設 ev_break 狀態 -
loop depth --
-
return
-
void ev_break (struct ev_loop *loop, how);
中斷 loop。參數可以是 EVBREAK_ONE
(執行完一個內部調用后返回)或EVBREAK_ALL
(執行完所有)。
下一次調用 ev_run 的時候,相應的標志會清除
void ev_ref (struct ev_loop *loop); void ev_unref (struct ev_loop *loop);
類似於 Objective-C 中的引用計數,只要 reference count 不為0,ev_run 函數就不會返回。
在做 start 之后要 unref;stop 之前要 ref。
void ev_set_io_collect_interval (struct ev_loop *loop, ev_tstamp interval); void ev_set_timeout_collect_interval (struct ev_loop *loop, ev_tstamp interval);
兩個值均默認為0,表示盡量以最小的延遲調用 callback。但這是理想的情況,實際上,比如 select 這樣低效的系統調用,由於可以一次性讀取很多,所以可以適當地進行延時。通過使用比較高的延遲,但是增加每次處理的數據量,以提高 CPU 效率。
void ev_invoke_pending (struct ev_loop *loop);
調用所有的 pending 的 watchers。這個除了可以在 callback 中調用(少見)之外,更多的是在重載的函數中使用。參見下一個函數
void ev_set_invoke_pending_cb (struct ev_loop *loop, void (*invoke_pending_cb(EV_P)));
重載 ev_loop 調用 watchers 的函數。新的回調應調用 ev_invoke_pending
。如果要恢復默認值,則置喙 ev_invoke_pending 即可。
int ev_pending_count (struct ev_loop *loop);
返回當前有多少個 pending 的 watchers。
void ev_set_loop_release_cb (struct ev_loop *loop, void (*release)(EV_P)throw(), void (*acquire)(EV_P)throw());
這是一個 lock 操作,你可以自定義 lock。其中 release 是 unlock,acquire 是 lock。release 是在 loop 掛起以等待events 之前調用,並且在開始回調之前調用 acquire。
void ev_set_userdata (struct ev_loop *loop, void *data); void *ev_userdata (struct ev_loop *loop);
設置 / 讀取 loop 中的用戶 data。這一點和 libevent 很不同,libevent 的參數 / 用戶數據是以 event 為單位的,而 libev 的原生用戶數據是以 loop 為單位的。
void ev_verify (struct ev_loop *loop);
驗證當前 loop 的設置。如果發現問題,則打印 error msg 並 abort()
。
ps: 來自libev手冊
https://my.oschina.net/haopengstack/blog/511585