若是一個應用程序去處理多個設備,例如應用程序讀取網路數據,按鍵,串口,通常能想到的有三種方法:css
方法1:
串行+阻塞的方式讀取:
while(1) {
read(標準輸入);
read(網絡);
}
缺點:每當阻塞讀取標準輸入時,若是用戶不進行標準輸入的操做,而此時客戶端給服務器發送數據,致使服務器沒法讀取客戶端發送來的數據!html
方法2:
採用多線程或者多進程機制來實現讀取:
開闢多個線程,每個線程處理一個設備,不會致使的數據的沒法讀取,可是系統的開銷相比方法1要大!linux
方案3:採用linux系統提供的高級IO的處理機制
select/poll:二者同樣,主進程可以利用select或者poll可以對多個設備進行監聽!服務器
其原理好像:方法1至關於有一個保安,看十戶房子,若是小偷進來從第十戶開始偷,保安卻從第一戶挨個檢查,沒有小偷確還在第一家等着。
方法2至關於僱了十個保安,開銷大
方法3至關於買了10套監控設備,一個保安看監控錄像,有狀況報警網絡
************************************************************************************數據結構
select函數原型:
int select(int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout);
函數功能:
主進程利用此函數可以對多個設備進行監聽,一旦發現監聽的設備都不可用(不可讀、也不可寫、也沒有異常),那麼主進程進入休眠狀態,一旦監聽的設備中,只要有一個設備可用(可讀或者可寫或者有異常)都會喚醒休眠的主進程,select也就會返回。多線程
注意這個函數僅僅起到一個監聽的功能,數據的後續處理,例如讀寫都是經過read,write,ioctl來進行!函數
參數說明:
nfds:
對設備的訪問永遠先open獲取fd;
監聽的設備中,最大的文件描述符fd+1;
數據類型fd_set:文件描述符集合,用來保存描述監聽的設備,裏面存放是被監聽設備的文件描述符;若是select要監聽某一個設備,必須把這個設備的fd添加到對應的文件描述符集合中!學習
readfds:讀文件描述符集合指針,若是select要監聽設備是否可讀,需將設備的fd添加到這個集合中!ui
writefds:寫文件描述符集合指針,若是select要監聽設備是否可寫,需將設備的fd添加到這個集合中!
exceptfds:異常文件描述符集合指針,若是select要監聽設備是否有異常,需將設備的fd添加到這個集合中!
注意:一個設備的fd能夠同時添加到三個集合中!
timeout:指定監聽的超時時間,若是此參數指定了一個時間,例如5秒鐘,select發現設備不可用,主進程進入休眠狀態,若是5秒以內設備還不可用,5秒到期,主進程主動喚醒;若是此參數指定爲NULL,休眠爲永久休眠!
返回值:有三種
若是等於0:代表是超時;
若是小於0:代表系統出錯;
若是大於0:代表設備可用(至少是一個設備,或者所有);
文件描述符集合操做的方法:
fd_set rfds; //定義讀文件描述符集合
//從集合中解除對fd設備的監聽
void FD_CLR(int fd, fd_set *set);
//判斷是不是設備fd引發的主進程的喚醒,若是是返回true,不然返回false
int FD_ISSET(int fd, fd_set *set);
//添加一個新的被監聽的設備
void FD_SET(int fd, fd_set *set);
//清空文件描述符集合
void FD_ZERO(fd_set *set);
注意:若是要重複監聽,須要再次清空集合和添加監聽設備!
***********************************************************************************
以上是應用程序層面上的函數調用
其在內核層面上:
在sys_select中作休眠,poll不引發休眠
select系統調用過程:
1.應用程序調用select,首先調用C庫的select函數實現;
2.C庫的select保存select系統調用號到R7寄存器中,調用SVC(新的
)或者SWI(老的)觸發軟中斷,至此由用戶空間陷入內核空間,ARM
的工做模式由用戶模式轉變爲SVC管理模式;
3.跳轉到內核準備好的異常向量表的入口地址,根據R7保存的系統調
用號,以它爲索引在系統調用表中找到對應的實現函數sys_select
4.sys_select要完成:
1.把被監聽的設備對應驅動程序的poll函數挨個調用一遍,
被監聽的設備都不可用時,它們的驅動的poll函數都返回0;
2.判斷是不是驅動主動喚醒,仍是超時喚醒,仍是接收到信號喚醒;
3.若是即沒有驅動主動喚醒,也沒有超時喚醒,沒有接收到信號,
sys_select調用poll_schedule_timeout主動讓進程進入休眠;
4.假設被監聽的設備中,有一個設備可用(可讀或者可寫或者異常
,硬件經過中斷來判斷),都會喚醒休眠的主進程;
5.sys_select的poll_schedule_timeout函數返回,再也不休眠
6.再次把被監聽的設備驅動的poll函數挨個調用一遍,此時可用
的設備對應的驅動poll函數會返回非0;
7.if (ret || time_out || ...) //ret = 1,當即返回到用戶空
間,返回值爲ret值
總結:
1.明確原本應該底層驅動的poll函數利用等待隊列機制讓進程休眠,
可是等待隊列休眠9步驟並不都是驅動的poll來編寫,有一部分是有內
核sys_select來實現;
2.驅動poll函數完成以下內容便可:
1.調用poll_wait,將當前進程添加到驅動定義的等待隊列頭中
2.根據設備是否可用,決定返回0仍是非0
3.明確:監聽機制,底層poll函數不是必須的,若是要監聽設備還可
以使用多線程機制也可以完成監聽;可是若是要使用select/poll監
聽設備,驅動必須有poll實現!
下圖是sys_select的簡單實現:
經過對內核代碼的分析,真正的休眠實現是在內核中實現的
poll_schedule_timeout函數中的schedule_hrtimeout_range中的schedule_hrtimeout_range_clock函數實現的
並非在poll函數中實現的
poll(輪詢)操做在應用程序中用於同時阻塞在多個文件上,當其中任何一個文件有應用程序所等待的事件(可讀、可寫、出錯等)時,poll返回相應的掩碼通知應用程序,使得應用程序知道應該對哪一個文件作何種操做。按照個人理解,poll的本質能夠這樣解釋:休眠等待多個指定文件中的任何一個發生特定的事件,並將被該文件喚醒;醒來後輪詢全部相關文件(經過再次調用全部文件對應驅動的poll方法),獲取全部被監控文件的事件信息返回給應用程序。
從這裏就能夠看出:
(1)其中等待隊列的使用是必不可少的。實際上調用poll的進程將會休眠在多個等待隊列(通常全部被監控文件的都有至少一個的等待隊列)上,從其中任何一個隊列上喚醒該進程,均可能使poll函數返回。
(2)驅動中的poll方法不實現休眠,而是:
經過對內核源碼、《深刻Linux設備驅動程序內核機制》的學習,我對Poll系統調用和內核驅動的poll方法的關係和結構有了總體且深刻的瞭解,基本搞清了poll系統調用的執行脈絡。對於poll系統調用的內核原理,請你們先看《深刻Linux設備驅動程序內核機制》那本書寫的比較詳細了,我不廢話了。之後我會把我本身以爲須要注意的地方寫出來。這裏我把這個關係和數據結構圖繪製了出來,請你們指正:
對於等待隊列的狀況,我用下面一個例子和圖來示意一下:
例若有3個進程:
task-1:使用poll檢測文件1~3
task-2:使用poll檢測文件2~3
task-3:使用poll檢測文件3