💓博主CSDN主頁:杭電碼農-NEO💓
?
?專欄分類:Linux從入門到精通?
?
🚚代碼倉庫:NEO的學習日記🚚
?
🌹關注我🫵帶你學更多操作系統知識
? 🔝🔝
Linux高級IO
- 1. 前言
- 2. 初識epoll
- 3. epoll的工作原理
- 4. epoll的優缺點
- 5. epoll的工作模式
- 6. ET模式和LT模式的對比
- 7. 總結以及拓展
1. 前言
其實在select和epoll之間還夾了一個poll, 但是由于epoll過于優秀, 所以poll基本沒人使用(甚至沒有select使用的多), 這里就直接跳過poll了, epoll本質上也是一種多路轉接的實現方案
本章重點:
本篇文章著重講解epoll的概念和它的底層原理, 最后會講解epoll的兩種工作模式: ET模式和LT模式, 本篇文章沒有epoll編碼, 全是干貨概念
2. 初識epoll
在centos下的man手冊中, 對epoll是這樣介紹的: 是為處理大批量句柄而作了改進的poll, 相信聰明的你一定發現了, select和poll的編程非常麻煩, 它需要我們程序員自行維護一些數據結構, 并且還有輸入輸出型參數, 編程成本高, 并且學習成本也高. 所以有了epoll
epoll相關的三個系統調用:
epoll_create, 用于創建epoll模型
epoll_ctl,用于設置關心的文件描述符和關心的事件
events可以是以下幾個宏的集合:
- EPOLLIN : 表示對應的文件描述符可以讀 (包括對端SOCKET正常關閉);
- EPOLLOUT : 表示對應的文件描述符可以寫;
- EPOLLPRI : 表示對應的文件描述符有緊急的數據可讀 (這里應該表示有帶外數據到來);
- EPOLLERR : 表示對應的文件描述符發生錯誤;
- EPOLLHUP : 表示對應的文件描述符被掛斷;
- EPOLLET : 將EPOLL設為邊緣觸發(Edge Triggered)模式, 這是相對于水平觸發(Level Triggered)來說的.
- EPOLLONESHOT:只監聽一次事件, 當監聽完這次事件之后, 如果還需要繼續監聽這個socket的話, 需要再次把這個socket加入到EPOLL隊列里…
epoll_wait,用于等待是否有事件就緒
3. epoll的工作原理
當程序中調用epoll_create時, 會在底層創建一個eventpoll結構體, 此結構體中有兩個非常重要的字段:
一顆紅黑樹的根節點指針
一個隊列的頭指針
紅黑樹的用處:
紅黑樹中存放著所有正在關心的文件描述符, 當我們調用epoll_ctl設置關心事件時, 實際上會在底層的這顆紅黑樹中添加/刪除/修改節點
隊列的用處:
隊列中存放的是所有存在就緒事件的文件描述符, 當我們調用epoll_wait時, 實際上就是在等待此隊列中是否有就緒的文件描述符到來
關于epoll的幾個細節:
- 紅黑樹的節點是需要key值的, 而文件描述符恰好可以充當這一值
- 用戶只需要設置關心, 獲取結果即可, 不用再關心任何對fd和event的管理
- 底層只要有fd就緒, OS會自動給我構建節點, 并且插入到就緒隊列中, 上層只需不斷從就緒隊列中將數據拿走, 就完成了獲取就緒事件的任務(生產者消費者模型)
struct epitem{ struct rb_node rbn;//紅黑樹節點 struct list_head rdllink;//雙向鏈表節點 struct epoll_filefd ffd; //事件句柄信息 struct eventpoll *ep; //指向其所屬的eventpoll對象 struct epoll_event event; //期待發生的事件類型
}
當調用epoll_wait檢查是否有事件發生時,只需要檢查eventpoll對象中的rdlist雙鏈表中是否有epitem元素即可. 如果rdlist不為空,則把發生的事件復制到用戶態,同時將事件數量返回給用戶. 這個操作的時間復雜度是O(1)
總結一下, epoll的使用過程就是三部曲:
- 調用epoll_create創建一個epoll句柄;
- 調用epoll_ctl, 將要監控的文件描述符注冊到紅黑樹;
- 調用epoll_wait, 等待文件描述符就緒后, 去隊列中拿;
4. epoll的優缺點
對比select的優點:
- 接口使用方便: 雖然拆分成了三個函數, 但是反而使用起來更方便高效. 不需要每次循環都設置關注的文件描述符, 也做到了輸入輸出參數分離開
- 數據拷貝輕量: 只在合適的時候調用 EPOLL_CTL_ADD 將文件描述符結構拷貝到內核中, 這個操作并不頻繁(而select/poll都是每次循環都要進行拷貝)
- 事件回調機制: 避免使用遍歷, 而是使用回調函數的方式, 將就緒的文件描述符結構加入到就緒隊列中,
- epoll_wait 返回直接訪問就緒隊列就知道哪些文件描述符就緒. 這個操作時間復雜度O(1). 即使文件描述符數目很多, 效率也不會受到影響.
- 沒有數量限制: 文件描述符數目無上限.
正是上訴的優點,使得epoll成為了現代高并發服務器中, 使用的最多的方法, 沒有之一!
5. epoll的工作模式
epoll有兩種工作方式:
- 水平觸發(LT模式)
- 邊緣觸發(ET模式)
水平觸發模式講解:
想象一下你一次性買了5個快遞, 快遞員張三把快遞送到你家樓下了, 打電話讓你下來拿快遞, 但是這個時候你正在和室友開黑, 你就對快遞員說: 你等等我, 我打完這把就下來取, 張三也沒說什么, 就在樓下等你. 你終于打完了這把游戲, 現在你下去取快遞, 但是你一次性只能拿4個快遞, 還剩一個在下面, 你給快遞員張三說: 你能不能再等我一下, 我上樓后再下來取剩下的一個快遞, 張三也沒說什么, 就在樓下等你. 你不下來取, 張三就一直等你, 一直給你打電話.
這就是水平觸發模式:
聰明的你也能想到, 水平觸發模型下, 效率會很低, 因為快遞員張三一直在樓下等你, 不能做其他事, 所以有了邊緣觸發來替代它
邊緣觸發模式詳解:
想象一下, 假設是李四給你送外賣, 他和張三不一樣, 他脾氣非常火爆, 你快遞到來后他給你打了個電話說: 你快遞到了, 我只等你十分鐘, 如果你不下樓取快遞, 我就把你的快遞放在路邊了, 并且我只給你打一次電話. 這個時候你會怎么辦? 當然你會選擇掛機坑隊友, 直接跑下樓取快遞, 因為你不去你的快遞可能就丟失了!
這就是邊緣觸發模型:
6. ET模式和LT模式的對比
首先, 一定是ET模式更加高效.
具體表現為下面幾點:
- ET模式通知用戶的次數變少, 減少了從內核態到用戶態的轉換, 提高了效率
- ET模式會使程序員一次性將所有數據取走, 減少了拷貝的次數
- ET模式會倒逼程序員盡快將接收緩沖區的數據取走, 那么就可以給對方發送一個更大的接受窗口(TCP協議), 對方就可以有一個更大的滑動窗口, 一次性給我們發送更多數據, 提高IO吞吐量
7. 總結以及拓展
本篇文章是epoll的理論知識, 后面會帶大家實踐編寫代碼, 如果你對epoll感興趣, 你可以去了解一下reactor模式, 也稱為反應堆模式, 很多大型框架的底層都是通過reactor來提高服務器效率的!