Linux I/O多工

來源:互聯網
上載者:User

Linux I/O多工

Linux I/O多工

1 I/O多工

I/O多工通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程式進行相應的讀寫操作。

I/O 多工技術是為瞭解決進程或線程阻塞到某個I/O系統調用而出現的技術,使進程不阻塞於某個特定的 I/O 系統調用。

2 I/O多工select

該函數准許進程指示核心等待多個事件中的任何一個發送,並只在有一個或多個事件發生或經曆一段指定的時間後才喚醒。

2.1 select函數

2.1.1 需要標頭檔

#include

#include

#include

#include

2.1.2 聲明和傳回值

1 聲明

int select(int nfds,fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

2傳回值

成功:就緒描述符的數目,逾時返回 0。

出錯:-1。

2.1.3 功能

監視並等待多個檔案描述符的屬性變化(可讀、可寫或錯誤異常)。select()函數監視的檔案描述符分 3 類,分別是writefds、readfds、和exceptfds。調用後select() 函數會阻塞,直到有描述符就緒(有資料可讀、可寫、或者有錯誤異常),或者逾時( timeout 指定等待時間),函數才返回。當select()函數返回後,可以通過遍曆 fdset,來找到就緒的描述符。

2.1.4 參數

1nfds: 要監視的檔案描述符的範圍,一般取監視的描述符數的最大值+1,如這裡寫 10,這樣的話,描述符 0,1, 2 …… 9 都會被監視,在 Linux 上最大值一般為1024。

2readfd: 監視的可讀描述符集合,只要有檔案描述符即將進行讀操作,這個檔案描述符就儲存到這。

3writefds: 監視的可寫描述符集合。

4exceptfds: 監視的錯誤異常描述符集合。

5timeout告知核心等待所指定描述字中的任何一個就緒可花多少時間。 其timeval結構用於指定這段時間的秒數和微秒數。

struct timeval{

long tv_sec; //seconds

long tv_usec; //microseconds

};

timeout可以設定的值:

1、把該參數設定為空白指標NULL。表示永遠等待下去,當有一個描述字準備好I/O時才返回。

2、把該參數設定為指定了timeval結構中的秒數和微秒數的值。表示等待指定了逾時時間,當逾時後還沒有描述字準備好I/O時直接返回。

3、把該參數設定為指定了timeval結構中的秒數和微秒數的值,而且秒數和微秒都為0。表示不檢查描述字是否準備好I/O後立即返回,這稱為輪詢。

2.1.5 fd_set

fd_set可以理解為一個集合,這個集合中存放的是檔案描述符,可通過以下四個宏進行設定:

1void FD_ZERO(fd_set *fdset);//清空集合

2void FD_SET(int fd, fd_set *fdset);//將一個給定的檔案描述符加入集合之中

3void FD_CLR(int fd, fd_set *fdset);//將一個給定的檔案描述符從集合中刪除

4int FD_ISSET(int fd, fd_set *fdset);//檢查集合中指定的檔案描述符是否可以讀寫

2.3 select優點和缺點

2.3.1 優點

select()目前幾乎在所有的平台上支援,其良好跨平台支援也是它的一個優點。

2.3.2 缺點

1、每次調用 select(),都需要把fd集合從使用者態拷貝到核心態,這個開銷在fd很多時會很大,同時每次調用select()都需要在核心遍曆傳遞進來的所有fd,這個開銷在fd很多時也很大。

2、單個進程能夠監視的檔案描述符的數量存在最大限制,在Linux上一般為1024,可以通過修改宏定義甚至重新編譯核心的方式提升這一限制,但是這樣也會造成效率的降低。

3 I/O多工poll

select()和poll()系統調用的本質一樣,前者在BSD UNIX中引入的,後者在System V中引入的。poll()的機制與 select() 類似,與 select() 在本質上沒有多大差別,管理多個描述符也是進行輪詢,根據描述符的狀態進行處理,但是 poll() 沒有最大檔案描述符數量的限制(但是數量過大後效能也是會下降)。poll() 和 select() 同樣存在一個缺點就是,包含大量檔案描述符的數組被整體複製於使用者態和核心的地址空間之間,而不論這些檔案描述符是否就緒,它的開銷隨著檔案描述符數量的增加而線性增大。

3.1 poll函數

3.1.1 需要標頭檔

#include

3.1.2 聲明和傳回值

1聲明

intpoll(struct pollfd *fds, nfds_t nfds, int timeout);

2傳回值

成功時,poll()返回結構體中revents域不為0的檔案描述符個數;如果在逾時前沒有任何事件發生,poll()返回 0;

失敗時,poll()返回 -1,並設定 errno 為下列值之一:

EBADF:一個或多個結構體中指定的檔案描述符無效。

EFAULT:fds 指標指向的地址超出進程的地址空間。

EINTR:請求的事件之前產生一個訊號,調用可以重新發起。

EINVAL:nfds參數超出 PLIMIT_NOFILE 值。

ENOMEM:可用記憶體不足,無法完成請求。

3.1.3 功能

監視並等待多個檔案描述符的屬性變化。

3.1.4 參數

1fds 不同與select()使用三個位元影像來表示三個 fdset 的方式,poll()使用一個pollfd的指標實現。一個pollfd 結構體數組,其中包括了你想測試的檔案描述符和事件, 事件由結構中事件域 events 來確定,調用後實際發生的時間將被填寫在結構體的revents 域。

struct pollfd{

int fd; //檔案描述符

short events; //等待的事件

short revents; //實際發生了的事件

};

fd 每一個 pollfd 結構體指定了一個被監視的檔案描述符,可以傳遞多個結構體,指示poll()監視多個檔案描述符。

events:每個結構體的 events 域是監視該檔案描述符的事件掩碼,由使用者來設定這個域。events 等待事件的掩碼取值如下:

處理輸入:

POLLIN 普通或優先順序帶資料可讀

POLLRDNORM 普通資料可讀

POLLRDBAND 優先順序帶資料可讀

POLLPRI 高優先順序資料可讀

處理輸出:

POLLOUT 普通或優先順序帶資料可寫

POLLWRNORM 普通資料可寫

POLLWRBAND 優先順序帶資料可寫

處理錯誤:

POLLERR發生錯誤

POLLHUP發生掛起

POLLVAL 描述字不是一個開啟的檔案

poll() 處理三個層級的資料,普通normal,優先順序帶priority band,高優先順序high priority,這些都是出於流的實現。

POLLIN | POLLPRI 等價於select()的讀事件。

POLLOUT | POLLWRBAND等價於select() 的寫事件。

POLLIN等價於POLLRDNORM | POLLRDBAND。

POLLOUT等價於POLLWRNORM。

例如,要同時監視一個檔案描述符是否可讀和可寫,我們可以設定events為 POLLIN | POLLOUT。

revents域是檔案描述符的操作結果事件掩碼,核心在調用返回時設定這個域。events 域中請求的任何事件都可能在revents域中返回。每個結構體的 events 域是由使用者來設定,告訴核心我們關注的是什麼,而revents域是返回時核心設定的,以說明對該描述符發生了什麼事件。

2nfds 用來指定第一個參數數組元素個數。

3timeout: 指定等待的毫秒數。

如果timeout設定為等待的毫秒數,無論I/O是否準備好,poll()都會返回。

如果timeout設定為 0時,poll() 函數立即返回。

如果timeout設定為 -1時,poll()一直阻塞到一個指定事件發生。

4 I/O多工epoll

epoll是在2.6核心中提出的,是之前的 select()和 poll()的增強版本。相對於 select()和 poll()來說,epoll更加靈活,沒有描述符限制。epoll使用一個檔案描述符管理多個描述符,將使用者關係的檔案描述符的事件存放到核心的一個事件表中,這樣在使用者空間和核心空間的copy只需一次。

4.1 需要標頭檔

#include

4.2 聲明

int epoll_create(int size);

int epoll_ctl(int epfd, int op, intfd, struct epoll_event *event);

int epoll_wait(int epfd, structepoll_event * events, int maxevents, int timeout);

4.3 epoll_create 函數

int epoll_create(int size);

4.3.1 功能

該函數產生一個epoll 專用的檔案描述符(建立一個 epoll 的控制代碼)。

4.3.2 參數

size 用來告訴核心這個監聽的數目一共有多大,參數size並不是限制了 epoll 所能監聽的描述符最大個數,只是對核心初始分配內部資料結構的一個建議。

自從linux2.6.8之後,size 參數是被忽略的,也就是說可以填只有大於0 的任意值。需要注意的是,當建立好epoll控制代碼後,它就是會佔用一個fd值,在linux下如果查看 /proc/ 進程 id/fd/,是能夠看到這個fd的,所以在使用完epoll後,必須調用close()關閉,否則可能導致fd被耗盡。

4.3.3 傳回值

成功:epoll專用的檔案描述符

失敗:-1

4.4 epoll_ctl函數

int epoll_ctl(int epfd, int op, int fd,struct epoll_event *event);

4.4.1 功能

epoll的事件註冊函數,它不同於select()是在監聽事件時告訴核心要監聽什麼類型的事件,而是在這裡先註冊要監聽的事件類型。

4.4.2 參數

1 epfd epoll 專用的檔案描述符,epoll_create()的傳回值

2 op 表示動作,用三個宏來表示:

EPOLL_CTL_ADD:註冊新的 fd 到 epfd 中;

EPOLL_CTL_MOD:修改已經註冊的fd的監聽事件;

EPOLL_CTL_DEL:從 epfd 中刪除一個 fd;

3 fd需要監聽的檔案描述符

4 event告訴核心要監聽什麼事件,struct epoll_event 結構如下:

// 儲存觸發事件的某個檔案描述符相關的資料(與具體使用方式有關)

typedef union epoll_data{

void*ptr;

intfd;

__uint32_tu32;

__uint64_tu64;

} epoll_data_t;

// 感興趣的事件和被觸發的事件

struct epoll_event{

__uint32_tevents; /* Epoll events */

epoll_data_tdata; /* User data variable */

};

events 可以是以下幾個宏的集合:

EPOLLIN :表示對應的檔案描述符可以讀(包括對端 SOCKET 正常關閉);

EPOLLOUT:表示對應的檔案描述符可以寫;

EPOLLPRI:表示對應的檔案描述符有緊急的資料可讀(這裡應該表示有帶外資料到來);

EPOLLERR:表示對應的檔案描述符發生錯誤;

EPOLLHUP:表示對應的檔案描述符被掛斷;

EPOLLET :將 EPOLL 設為邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來說的。

EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個 socket 的話,需要再次把這個 socket 加入到 EPOLL 隊列裡

4.4.3 傳回值

成功:0

失敗:-1

4.5 epoll_wait函數

int epoll_wait(int epfd, structepoll_event * events, int maxevents, int timeout);

4.5.1 功能

等待事件的產生,收集在epoll 監控的事件中已經發送的事件,類似於 select() 調用。

4.5.2 參數

1 epfdepoll 專用的檔案描述符,epoll_create()的傳回值

2 events 分配好的 epoll_event 結構體數組,epoll 將會把發生的事件賦值到events 數組中(events 不可以是null 指標,核心只負責把資料複製到這個 events 數組中,不會去協助我們在使用者態中分配記憶體)。

3 maxevents maxevents 告之核心這個 events 有多大 。

4 timeout 逾時時間。

如果timeout設定為等待的毫秒數,無論I/O是否準備好,都會返回。

如果timeout設定為 0時,函數立即返回。

如果timeout設定為 -1時,一直阻塞到一個指定事件發生。

4.5.3 傳回值

成功:返回需要處理的事件數目,如返回 0 表示已逾時。

失敗:-1

4.6 LT模式與ET模式

epoll 對檔案描述符的操作有兩種模式:LT(level trigger)和 ET(edge trigger)。LT 模式是預設模式。

4.6.1 LT模式

當epoll_wait 檢測到描述符事件發生並將此事件通知應用程式,應用程式可以不立即處理該事件。下次調用 epoll_wait 時,會再次響應應用程式並通知此事件。

4.6.2 ET模式

當epoll_wait 檢測到描述符事件發生並將此事件通知應用程式,應用程式必須立即處理該事件。如果不處理,下次調用 epoll_wait 時,不會再次響應應用程式並通知此事件。

4.6.3 LT模式與ET模式比較

ET模式在很大程度上減少了epoll 事件被重複觸發的次數,因此效率要比 LT 模式高。epoll 工作在 ET 模式的時候,必須使用非阻塞套介面,以避免由於一個檔案控制代碼的阻塞讀/阻塞寫操作把處理多個檔案描述符的任務餓死。

4.7 epoll 的優點

1、在 select/poll中,進程只有在調用一定的方法後,核心才對所有監視的檔案描述符進行掃描,而epoll()事先通過epoll_ctl()來註冊一個檔案描述符,一旦基於某個檔案描述符就緒時,核心會採用類似callback的回調機制(軟體中斷 ),迅速啟用這個檔案描述符,當進程調用 epoll_wait()時便得到通知。

2、監視的描述符數量不受限制,它所支援的 FD 上限是最大可以開啟檔案的數目,這個數字一般遠大於2048,舉個例子,在 1GB 記憶體的機器上大約是 10 萬左右,具體數目可以 cat /proc/sys/fs/file-max 察看,一般來說這個數目和系統記憶體關係很大。select()的最大缺點就是進程開啟的 fd 是有數量限制的。這對於串連數量比較大的伺服器來說根本不能滿足。雖然也可以選擇多進程的解決方案( Apache 就是這樣實現的),不過雖然 Linux 上面建立進程的代價比較小,但仍舊是不可忽視的,加上進程間資料同步遠比不上線程間同步的高效,所以也不是一種完美的方案。

3、I/O 的效率不會隨著監視 fd 的數量的增長而下降。select(),poll() 實現需要自己不斷輪詢所有 fd 集合,直到裝置就緒,期間可能要睡眠和喚醒多次交替。而 epoll 其實也需要調用 epoll_wait() 不斷輪詢就緒鏈表,期間也可能多次睡眠和喚醒交替,但是它是裝置就緒時,調用回呼函數,把就緒 fd 放入就緒鏈表中,並喚醒在 epoll_wait() 中進入睡眠的進程。雖然都要睡眠和交替,但是 select() 和 poll() 在“醒著”的時候要遍曆整個fd集合,而 epoll 在“醒著”的時候只要判斷一下就緒鏈表是否為空白就行了,這節省了大量的 CPU 時間。這就是回調機制帶來的效能提升。

4、select(),poll() 每次調用都要把 fd 集合從使用者態往核心態拷貝一次,而epoll只要一次拷貝,這也能節省不少的開銷。

參考連結:

http://blog.csdn.net/lingfengtengfei/article/details/12392449

http://blog.csdn.net/god2469/article/details/8761346

http://blog.csdn.net/u010155023/article/details/53507788

http://blog.csdn.net/tennysonsky/article/details/45745887

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.