?
在Linux驅(qū)動開發(fā)中,應(yīng)用程序通過循環(huán)讀取或者中斷的方式都會使得CPU的占用率很高。本文介紹五種IO模型,可以用來優(yōu)化文件讀寫方式,降低CPU的使用率
?
1. 阻塞式I/O模型
阻塞式I/O模型是最常用、最簡單的模型。當應(yīng)用程序?qū)υO(shè)備驅(qū)動進行操作時,若不能獲取到設(shè)備資源,阻塞式IO就會將應(yīng)用程序?qū)?yīng)的線程掛起,直到設(shè)備資源可以獲取為止 阻塞就是進程被休息, CPU處理其它進程去了。如下圖,應(yīng)用程序進行recefrom系統(tǒng)調(diào)用,操作系統(tǒng)收到recefrom系統(tǒng)調(diào)用請求,經(jīng)過等待數(shù)據(jù)準備好和內(nèi)核將數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到用戶緩沖區(qū)這兩個階段后,調(diào)用返回,應(yīng)用程序解除阻塞阻塞訪問代碼示例:
int?fd;
int?data?=?0;
fd?=?open("/dev/xxx_dev",?O_RDWR);??? /*?阻塞方式打開?*/
ret?=?read(fd,?&data,?sizeof(data));?? /*?讀取數(shù)據(jù)?*/
?
2. 非阻塞式I/O模型
對于非阻塞IO,當設(shè)備不可用或數(shù)據(jù)未準備好時會立即向內(nèi)核返回一個錯誤碼,表示數(shù)據(jù)讀取失敗。應(yīng)用程序會再次重新讀取數(shù)據(jù),這樣一直往復(fù)循環(huán),直到數(shù)據(jù)讀取成功 非阻塞就是輪詢的方式,在該模型中,I/O操作不會立即完成。例如下圖示,應(yīng)用程序進行recefrom系統(tǒng)調(diào)用,操作系統(tǒng)收到recefrom系統(tǒng)調(diào)用請求,若無數(shù)據(jù)會立刻返回一個錯誤狀態(tài);應(yīng)用程序則需要不斷輪詢,直到內(nèi)核緩沖區(qū)數(shù)據(jù)準備好非阻塞訪問代碼示例:
int?fd;
int?data?=?0;
fd?=?open("/dev/xxx_dev",?O_RDWR?|?O_NONBLOCK);??/*?非阻塞方式打開?*/
ret?=?read(fd,?&data,?sizeof(data));????? /*?讀取數(shù)據(jù)?*
?
3. I/O復(fù)用模型
由于非阻塞I/O方式需要不斷輪詢,會消耗大量CPU時間,而后臺又可能有多個任務(wù)在同時輪詢,為此人們想到了一種方式:循環(huán)查詢多個任務(wù)的完成狀態(tài),只要有任何一個任務(wù)完成,就去處理它
IO多路復(fù)用有三個特別的系統(tǒng)調(diào)用select、poll和epoll
select函數(shù):能夠監(jiān)視的文件描述符數(shù)量最大為1024
/********************select函數(shù)原型***************************************/
int?select(?int?nfds,
???fd_set?*readfds,
???fd_set?*writefds,
???fd_set?*exceptfds,
???struct?timeval?*timeout?)
// nfds:所要監(jiān)視的三類文件描述集合中,最大文件描述符加1
// readfds:用于監(jiān)視這些文件是否可以讀取
// writefds:用于監(jiān)視些文件是否可以寫操作
// exceptfds:用于監(jiān)視這些文件的異常
// timeout:超時時間
//?返回值:0,超時發(fā)生;-1,發(fā)生錯誤;其他值,表示可進行操作的文件描述符個數(shù)
/************************************************************************/
/******?fd_set類型變量的每一個位都代表一個文件描述符?*****/
//?例如:從一個設(shè)備文件中讀取數(shù)據(jù),需要定義一個fd_set變量,并傳遞給參數(shù)readfds
void?FD_ZERO(fd_set?*set)???//將fd_set變量的所有位都清零
void?FD_SET(int?fd,?fd_set?*set)?//將fd_set某個位置1,即向變量中添加文件描述符fd
void?FD_CLR(int?fd,?fd_set?*set)?//將fd_set某個位清零,即從變量中刪除文件描述符fd
int?FD_ISSET(int?fd,?fd_set?*set)?//測試一個文件fd是否屬于某個集合
/******?timeval結(jié)構(gòu)體定義?*****/
struct?timeval?{
?long?tv_sec;??//秒?
?long?tv_usec;??//微妙
};
poll函數(shù):能夠監(jiān)視的文件描述符數(shù)量沒有限制
/********************poll函數(shù)原型***************************************/
int?poll(struct?pollfd?*fds,
???nfds_t?nfds,
???int?timeout)
// fds:要監(jiān)視的文件描述符集合以及要監(jiān)視的事件,pollfd結(jié)構(gòu)體類型
// nfds:要監(jiān)視的文件描述符數(shù)量
// timeout:超時時間(ms)
//?返回值:0,超時發(fā)生;-1,發(fā)生錯誤;其他值,發(fā)生事件或錯誤的文件描述符數(shù)量
/******?pollfd?結(jié)構(gòu)體定義?*****/
struct?pollfd?{
?int?fd;???//文件描述符:若fd無效,則events監(jiān)視事件也無效,revents返回0
?short?events;??//請求的事件:可監(jiān)視的事件類型如下
?short?revents;??//返回的事件
};
/***?events可監(jiān)視的事件類型?***/
POLLIN???//有數(shù)據(jù)可以讀取。
POLLPRI??//有緊急的數(shù)據(jù)需要讀取。
POLLOUT??//可以寫數(shù)據(jù)。
POLLERR??//指定的文件描述符發(fā)生錯誤。
POLLHUP??//指定的文件描述符掛起。
POLLNVAL??//無效的請求。
POLLRDNORM??//等同于?POLLIN
epoll函數(shù):selcet 和 poll 函數(shù)會隨著所監(jiān)聽的 fd 數(shù)量的增加,出現(xiàn)效率低下的問題,epoll 就是為處理大并發(fā)而準備的
/************************************************************************/
/******?使用時應(yīng)用程序需要先使用?epoll_create?函數(shù)創(chuàng)建一個?epoll?句柄?******/
int?epoll_create(int?size)???
//?size?為大于0的數(shù)值
//?返回值:epoll句柄;返回-1,表示創(chuàng)建失敗
/************************************************************************/
/**?句柄創(chuàng)建后使用?epoll_ctl?函數(shù)向其中添加要監(jiān)視的文件描述符以及監(jiān)視的事件?**/
int?epoll_ctl(int?epfd,
?????int?op,
?????int?fd,
?????struct?epoll_event?*event)
// epfd:要操作的epoll句柄
// op:對epfd進行的操作(包括EPOLL_CTL_ADD/EPOLL_CTL_MOD/EPOLL_CTL_DEL)
// fd:要監(jiān)視的文件描述符
// event:要監(jiān)視的事件類型,為 epoll_event 結(jié)構(gòu)體類型指針
//?返回值:?0,成功;?-1,失敗,并且設(shè)置 errno 的值為相應(yīng)的錯誤碼
/******?epoll_event?結(jié)構(gòu)體定義?*****/
struct?epoll_event?{
?uint32_t?events;??/*?epoll?事件?*/
?epoll_data_t?data;??/*?用戶數(shù)據(jù)?*/
};
/******?events可選的事件如下?******/
EPOLLIN??//有數(shù)據(jù)可以讀取
EPOLLOUT??//可以寫數(shù)據(jù)
EPOLLPRI??//有緊急的數(shù)據(jù)需要讀取
EPOLLERR??//指定的文件描述符發(fā)生錯誤
EPOLLHUP??//指定的文件描述符掛起
EPOLLET??//設(shè)置 epoll 為邊沿觸發(fā),默認觸發(fā)模式為水平觸發(fā)
EPOLLONESHOT?//一次性的監(jiān)視,若完成后還要再次監(jiān)視,就需要將fd重新添加到epoll里
/************************************************************************/
/****?上述步驟設(shè)置好后應(yīng)用程序就可以通過?epoll_wait?函數(shù)來等待事件的發(fā)生?*****/
int?epoll_wait?(int?epfd,
????struct?epoll_event?*events,
????int?maxevents,
????int?timeout)
// epfd:要等待的epoll
// events:指向epoll_event結(jié)構(gòu)體的數(shù)組
// maxevents:events數(shù)組大小,必須大于?0
// timeout:超時時間,單位為ms
//?返回值:?0,超時;?-1,錯誤;其他值,準備就緒的文件描述符數(shù)量
?
4. 信號驅(qū)動I/O模型
信號類似于硬件上使用的"中斷",只不過信號是軟件層面上的??衫斫鉃檐浖哟紊蠈χ袛嗟囊环N模擬,驅(qū)動通過主動向應(yīng)用程序發(fā)送可訪問的信號,應(yīng)用程序獲取到信號后即可從驅(qū)動設(shè)備中讀取或?qū)懭霐?shù)據(jù)了 例如下圖示,應(yīng)該程序進行read系統(tǒng)調(diào)用,進程繼續(xù)運行不會阻塞,立即返回,等待內(nèi)核緩沖區(qū)數(shù)據(jù)準備好后,通過SIGIO信號通知應(yīng)用程序,應(yīng)用程序再進行read系統(tǒng)調(diào)用,內(nèi)核將內(nèi)核緩沖區(qū)中的數(shù)據(jù)拷貝到用戶緩沖區(qū),調(diào)用完成。整個過程中應(yīng)用程序沒有去查詢驅(qū)動設(shè)備是否可以訪問,是由驅(qū)動設(shè)備通過SIGIO信號告訴給應(yīng)用程序的信號驅(qū)動模型的核心就是信號,內(nèi)核arch/xtensa/include/uapi/asm/signal.h文件中定義了Linux所支持的所有信號:
#define?SIGHUP???1???/*?終端掛起或控制進程終止?*/
#define?SIGINT???2???/*?終端中斷(Ctrl+C?組合鍵)?*/
#define?SIGQUIT??3???/*?終端退出(Ctrl+組合鍵)?*/
#define?SIGILL???4???/*?非法指令?*/
#define?SIGTRAP??5???/*?debug?使用,有斷點指令產(chǎn)生?*/
#define?SIGABRT??6???/*?由?abort(3)發(fā)出的退出指令?*/
#define?SIGIOT???6???/*?IOT?指令?*/
#define?SIGBUS???7???/*?總線錯誤?*/
#define?SIGFPE???8???/*?浮點運算錯誤?*/
#define?SIGKILL??9???/*?殺死、終止進程?*/
#define?SIGUSR1??10???/*?用戶自定義信號?1?*/
#define?SIGSEGV??11???/*?段違例(無效的內(nèi)存段)?*/
#define?SIGUSR2??12???/*?用戶自定義信號?2?*/
#define?SIGPIPE??13???/*?向非讀管道寫入數(shù)據(jù)?*/
#define?SIGALRM??14???/*?鬧鐘?*/
#define?SIGTERM??15???/*?軟件終止?*/
#define?SIGSTKFLT??16???/*?棧異常?*/
#define?SIGCHLD??17???/*?子進程結(jié)束?*/
#define?SIGCONT??18???/*?進程繼續(xù)?*/
#define?SIGSTOP??19???/*?停止進程的執(zhí)行,只是暫停?*/
#define?SIGTSTP??20???/*?停止進程的運行(Ctrl+Z?組合鍵)?*/
#define?SIGTTIN??21???/*?后臺進程需要從終端讀取數(shù)據(jù)?*/
#define?SIGTTOU??22???/*?后臺進程需要向終端寫數(shù)據(jù)?*/
#define?SIGURG???23???/*?有"緊急"數(shù)據(jù)?*/
#define?SIGXCPU??24???/*?超過?CPU?資源限制?*/
#define?SIGXFSZ??25???/*?文件大小超額?*/
#define?SIGVTALRM??26???/*?虛擬時鐘信號?*/
#define?SIGPROF??27???/*?時鐘信號描述?*/
#define?SIGWINCH??28???/*?窗口大小改變?*/
#define?SIGIO???29???/*?可以進行輸入/輸出操作?*/
#define?SIGPOLL??SIGIO?/*?#define?SIGLOS?29?*/
#define?SIGPWR???30???/*?斷點重啟?*/
#define?SIGSYS???31???/*?非法的系統(tǒng)調(diào)用?*/
#define?SIGUNUSED??31???/*?未使用信號?*/
這些信號就相當于中斷號,不同的中斷號代表了不同的中斷,不同的中斷所做的處理不同,因此,驅(qū)動程序可以通過向應(yīng)用程序發(fā)送不同的信號來實現(xiàn)不同的功能使用中斷時需要設(shè)置中斷處理函數(shù),同樣的,在應(yīng)用程序中使用信號,就必須設(shè)置信號所使用的信號處理函數(shù),在應(yīng)用程序中使用signal函數(shù)來設(shè)置指定信號的處理函數(shù),其函數(shù)原型如下所示:
/***********************signal函數(shù)原型*****************************/
sighandler_t?signal(int?signum,?sighandler_t?handler)
// signum:要設(shè)置處理函數(shù)的信號
// handler:信號的處理函數(shù)
//?返回值:成功,返回信號的前一個處理函數(shù);失敗,返回 SIG_ERR
/***********************信號處理函數(shù)原型***************************/
typedef?void?(*sighandler_t)(int)
?
5. 異步I/O模型
相對于同步IO,異步IO不是順序執(zhí)行。例如下圖示,用戶進程進行aio_read系統(tǒng)調(diào)用之后,無論內(nèi)核數(shù)據(jù)是否準備好,都會直接返回給用戶進程,然后用戶態(tài)進程可以去做別的事情。等到socket數(shù)據(jù)準備好了,內(nèi)核直接復(fù)制數(shù)據(jù)給進程,然后從內(nèi)核向進程發(fā)送通知。IO兩個階段,進程都是非阻塞的
?
6. 五種I/O模型的比較
對于Liunx的五種I/O模型,主要在等待數(shù)據(jù)和數(shù)據(jù)復(fù)制這兩個時間段不同,它們的區(qū)別詳見下表
審核編輯:湯梓紅
評論
查看更多