- 論壇徽章:
- 0
|
第六章 高級I/O
第六章 高級I/O
翻譯:雨絲風片@chinaunix.net
6.1 高級I/O和進程資源
正如我們在前面章節(jié)中看到的,程序可以同時打開多個文件描述符。這些文件描述符并不一定就是文件,還可以是fifo、pipe或者socket。于是,如何復用這些打開的描述符就很重要了。例如,考慮一個簡單的郵件閱讀程序,比如pine。它顯然應當允許用戶在讀寫email的同時也能去檢查是否有新郵件。這就意味著在任一給定時刻都至少能夠接收兩個來源的輸入:一個來源是用戶,另一個是用來檢查新郵件的描述符。處理描述符的復用是個復雜的問題。一種方法是把所有打開的描述符都標記為非阻塞的(O_NONBLOCK),然后在它們之中循環(huán),直到找到一個可以進行I/O操作的描述符為止。這種方法的問題是程序會一直在循環(huán),如果長時間內(nèi)沒有I/O可用,進程就會一直占據(jù)CPU。當有多個進程在一組很少的描述符上循環(huán)時,你的CPU的負載就會惡化。
另一種方法就是設置信號處理器去捕獲I/O變?yōu)榭捎玫氖录,然后就讓進程進入休眠狀態(tài)。如果你只打開了少量的描述符,而且并不經(jīng)常請求I/O的話,這種方法從理論上看倒是不錯。由于進程已經(jīng)休眠,就不會再占用CPU,僅當I/O可用時它才恢復執(zhí)行。然而,這種方法的問題在于信號處理的開銷有點大。比如一個web服務器,每分鐘收到100個請求,那就幾乎一直都在捕獲信號。每秒鐘捕獲上百個信號的開銷是相當大的,不單是進程,對于內(nèi)核發(fā)送信號的開銷而言也是一樣的。
到目前為止,我們看到的兩種選擇都有限制,效率也不高,它們需要解決的共同問題就是進程需要知道I/O究竟什么時候能用?然而,這個信息實際上只有內(nèi)核才能事先知道,因為是內(nèi)核在最終處理系統(tǒng)中的所有打開的描述符。例如,當一個進程通過fifo向另一個進程發(fā)送數(shù)據(jù)的時候,發(fā)送進程會調(diào)用write,這是一個系統(tǒng)調(diào)用,因此會進入內(nèi)核。在發(fā)送方的write系統(tǒng)調(diào)用執(zhí)行完畢之前接收方對此是一無所知的。于是就引出了一個更好的復用文件描述符的方法:由內(nèi)核來替進程管理描述符。換句話說,就是把一個打開描述符的鏈表發(fā)送給內(nèi)核,然后等待,直到內(nèi)核發(fā)現(xiàn)某個或多個描述符已經(jīng)準備好了或者已經(jīng)超時了為止。
這就是select()、poll()和kqueue()接口采用的方法。通過這些接口,內(nèi)核就會管理文件描述符,當I/O可用時就去喚醒進程。這些接口巧妙地處理了上述問題。進程不必再在打開的文件描述符中循環(huán),也不必再去設置信號了。但進程在使用這些函數(shù)的時候還是會產(chǎn)生一點小問題。這是因為I/O操作是在從這些接口返回之后才去執(zhí)行的。所以它至少需要兩個系統(tǒng)調(diào)用才能完成其操作。例如,你的程序有兩個用于讀的描述符。你對它們使用select,然后等待它們直至有數(shù)據(jù)可讀。這就需要進程首先調(diào)用select,在select返回之后,就對該描述符調(diào)用read。更妙的是,你還可以對所有打開的描述符執(zhí)行一個整體的read。一旦其中有某個描述符準備好讀之后,read就會返回,并把數(shù)據(jù)放在緩沖區(qū)中,同時還會給出一個標識,用來指示這個數(shù)據(jù)是從哪個描述符讀進來的。
6.2 select
我首先要講的接口是select()。格式如下:
- int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
復制代碼
傳給select的第一個參數(shù)已經(jīng)造成了多年的混亂。nfds參數(shù)的正確用法是把它設成文件描述符的最大值加1。換句話說,如果你有一組文件描述符{0,1,8},nfds參數(shù)就應當被設置成9,因為你的描述符的最大值為8。有些人錯誤地以為這個參數(shù)的意思是文件描述符的總數(shù)加1,對于我們的例子而言就是4。記住,一個文件描述符只是一個整數(shù)而已,所以你的程序就需要指出你所想要在其上select的最大的描述符值。
select接下來會按順序針對所有尚未完成的讀、寫以及異常條件檢查其余的三個參數(shù),readfds、writefds和exceptfds。(詳細信息請參見man(2) select)。注意,如果readfds、writefds和execptfds中沒有設置描述符,那么傳給select的對應參數(shù)應當被設置成NULL。
readfds、writefds和execptfds參數(shù)通過以下4個宏進行設置。
FD_ZERO(&fdset);
FD_ZERO宏用來對指定的描述符集合中的bit進行清零。有一點需要特別注意:只要使用select,就應當調(diào)用這個宏;否則select的行為將是不可預知的。
FD_SET(fd, &fdset);
FD_SET宏用于向一組激活的描述符中添加一個描述符。
FD_CLR(fd, &fdset);
FD_CLR宏用于從一組激活的描述符中刪除一個描述符。
FD_ISSET(fd, &fdset);
FD_ISSET宏是在select返回之后使用的,用于測試某個描述符是否已準備好進行I/O操作。
select的最后的參數(shù)是一個超時值。如果超時值被設置為NULL,則對select的調(diào)用將以不確定的方式被阻塞,直至某個操作已準備好為止。如果你需要一個確定的超時時間,那么超時值就得是一個非空的timeval結(jié)構(gòu)體。timeval結(jié)構(gòu)體如下:
- struct timeval {
- long tv_sec; /* seconds */
- long tv_usec; /* microseconds */
- };
復制代碼
如果select調(diào)用成功,將返回準備好的描述符的數(shù)目。如果select因為超時而返回,則返回值為0。如果有錯誤發(fā)生,則返回-1,同時會相應地設置errno。
6.3 poll
我們在這里對I/O的討論主要是針對BSD的。System V支持一種特殊類型的I/O,即所謂的STREAMS。和socket一樣,STREAMS也具有優(yōu)先級屬性,這種屬性有時也被成為數(shù)據(jù)帶。數(shù)據(jù)帶可用來給STREAMS中的特定數(shù)據(jù)設置較高的優(yōu)先級。BSD最初并不支持這一特性,不過有些人添加了System V仿真功能,可以對某些類型提供支持。由于我們并不關(guān)注System V,因此我們只會引用數(shù)據(jù)帶或數(shù)據(jù)優(yōu)先級帶的概念。詳細信息請參見System V STREAMS。
poll函數(shù)和select很相似:
- int poll(struct pollfd *fds, unsigned int nfds, int timeout);
復制代碼
和原產(chǎn)于BSD的select不同,poll是由System V Unix創(chuàng)建的,在早期的BSD版本中并不支持它。目前主流BSD系統(tǒng)中都已經(jīng)支持poll了。
和select相似,poll也是在一組給定的文件描述符上進行復用。在指定這些描述符的時候,你必須使用一個結(jié)構(gòu)體數(shù)組,其中每個結(jié)構(gòu)體代表一個文件描述符。和select相比,poll的好處就是你可以判斷一些很罕見的條件,而select則無法做到。這些條件是POLLERR、POLLHUP和POLLNVAL,我們稍后討論。盡管對于選擇select還是poll的問題已經(jīng)有了相當多的討論,但這在很大程度上還是取決于你的個人愛好。poll所使用的結(jié)構(gòu)體是pollfd結(jié)構(gòu)體,如下:
- struct pollfd {
- int fd; /* which file descriptor to poll */
- short events; /* events we are interested in */
- short revents; /* events found on return */
- };
復制代碼
fd
fd成員用于指定你想要poll的文件描述符。如果你想刪除一個描述符,那就把那個描述符的fd成員設置成-1。通過這種方法,你可以避免對整個數(shù)組進行混洗,同時還可以清除revents成員中列出的所有事件。
events, revents
events成員是一個bit掩碼,用于指定針對指定描述符所關(guān)心的事件。revents成員也是一個bit掩碼,但它的值是由poll設置的,用于記錄在指定描述符上發(fā)生的事件。這些事件的定義如下:
POLLIN事件表明你的程序?qū)⑦x擇該描述符上的可讀數(shù)據(jù)事件。注意,此處的數(shù)據(jù)不包括高優(yōu)先級數(shù)據(jù),比如socket上的帶外數(shù)據(jù)。
POLLPRI事件表明你的程序準備選擇該描述符上的任何高優(yōu)先級事件。
- #define POLLOUT 0x0004
- #define POLLWRNORM POLLOUT
復制代碼
POLLOUT和POLLWRNOMR事件表明你的程序想知道什么時候可以對一個描述符執(zhí)行寫操作了。在FreeBSD和OpenBSD上這兩個事件是相同的;你可以在你的系統(tǒng)頭文件(/usr/include/poll.h)中查證這一點。從技術(shù)角度來說,它們之間的區(qū)別在于POLLWRNOMR僅當數(shù)據(jù)優(yōu)先帶等于0的時候才去檢測是否可以進行寫操作。
- #define POLLRDNORM 0x0040
復制代碼
POLLRDNORM事件表明你的程序準備選擇該描述符上的常規(guī)數(shù)據(jù)。注意,在某些系統(tǒng)上,這個事件指定的操作和POLLIN完全一樣。但在NetBSD和FreeBSD上,這個事件和POLLIN并不相同。同樣,請去查看你的系統(tǒng)頭文件(/usr/include/poll.h)。嚴格地說,POLLRDNORM僅當數(shù)據(jù)優(yōu)先帶等于0的時候采取檢測是否可以進行讀操作。
- #define POLLRDBAND 0x0080
復制代碼
POLLRDBAND事件表明你的程序想知道什么時候能夠以一個非0的數(shù)據(jù)帶值從該描述符讀數(shù)據(jù)。
- #define POLLWRBAND 0x0100
復制代碼
POLLWRBAND事件表明你的程序想知道什么時候能夠以一個非0的數(shù)據(jù)帶值向該描述符寫數(shù)據(jù)。
專用于FreeBSD的選項
下面的選項是專用于FreeBSD的,知道的和使用的人都不是太多。但它們還是值得提一下,因為它們可以提供更多的靈活性。這些都是新的選項,poll并不保證能夠檢測這些條件,而且它們只能用于UFS文件系統(tǒng)。如果你的程序需要檢測這些類型的事件,那最好使用kqueue接口,我們將在稍后介紹。
- #define POLLEXTEND 0x0200
復制代碼
如果文件已經(jīng)被執(zhí)行,則設置POLLEXTEND事件。
- #define POLLATTRIB 0x0400
復制代碼
如果有任一文件屬性發(fā)生改變,則設置POLLATTIB事件。
如果文件被重命名、刪除或解除鏈接,則設置POLLNLINK事件。
如果文件內(nèi)容被修改,則設置POLLWRITE事件。
下面的事件并不是pollfd events成員的有效標志,poll也將忽略它們。它們是在pollfd revents中返回的,用于表明發(fā)生了某個事件。
POLLERR事件表明有錯誤發(fā)生。
POLLHUP表明在對應的STREAMS上發(fā)生了掛起事件。POLLHUP和POLLOUT是互斥事件,因為一個發(fā)生了掛起的STREAMS就不再是可寫的了。
POLLNVAL表明對poll的請求是無效的。
poll的最后一個參數(shù)是超時值?梢酝ㄟ^這個參數(shù)告訴poll一個以微秒為單位的超時值。如果把超時值設置為-1,poll就會阻塞,直至所請求的事件發(fā)生為止。如果超時值設置為0,則poll將立即返回。
如果對poll的調(diào)用成功,則返回一個正整數(shù)。這個正整數(shù)的值表示有多少個描述符發(fā)生了事件。如果超時,poll將返回0。如果有錯誤發(fā)生,poll則會返回-1。
6.4 kqueue
到目前為止,poll和select已經(jīng)是相當不錯的復用文件描述符的方法了。但為了使用這兩個函數(shù),你需要創(chuàng)建一個描述符的鏈表,然后把它們發(fā)送給內(nèi)核,在返回的時候又要再次查看這個鏈表。這看上去有點效率低下。一個更好一些的模型是把描述符鏈表交給內(nèi)核,然后就等待。一旦有某個或多個事件發(fā)生,內(nèi)核就把一個只包含有發(fā)生了事件的描述符的鏈表通知給進程,由此避免了每次函數(shù)返回的時候都要去遍歷整個鏈表。盡管對于只打開了幾個描述符的進程而言這點改進算不得什么,但對于那些打開了幾千個文件描述符的程序來說,這種性能改進就相當顯著了。這就是kqueue誕生背后的主要目的。同時,設計者還希望進程能夠檢測更多類型的事件,比如文件修改、文件刪除、信號交付或者子進程退出,并提供一個包含了其它任務的靈活的函數(shù)調(diào)用。處理信號、復用文件描述符、以及等待子進程等操作都可以封裝到這個單一的kqueue接口中,因為它們都是在等待某個事件的發(fā)生。
另一個設計考慮就是如何讓一個進程毫無干擾地使用多個kqueue實例。如你所見,進程可以設置一個信號處理器,但是,當代碼中的其它部分也想捕獲那個指定信號的時候該怎么辦?或者考慮更壞的情況,比如一個庫函數(shù)對你的程序想要捕獲的信號設置了信號處理器的時候?要想通過調(diào)試來找出你的程序為什么沒有執(zhí)行你所設置的信號處理器可能要花費幾個小時的時間。不過一般說來,這些情況并不會經(jīng)常發(fā)生。好的程序員應該避免在庫函數(shù)中設置信號處理器。對于大型的、復雜的程序來說,這些情況就很難避免了,所以為了更完美一點,我們應當能夠檢測這些事件,而kqueue就可以。
kqueue API由兩個函數(shù)調(diào)用和一個輔助設置事件的宏組成。這些函數(shù)將在下面進行簡要介紹。
kqueue函數(shù)啟動一個新的kqueue。如果調(diào)用成功,返回值將是一個用來和新創(chuàng)建的kqueue交互的描述符。每個kqueue都有一個與之關(guān)聯(lián)的唯一的描述符。因此,一個程序可以同時打開多個kqueue。kqueue描述符的行為和常規(guī)文件描述符類似:它們也可以被復用。
最后一點,這些描述符是不能被fork創(chuàng)建的子進程繼承的。如果子進程是通過rfork調(diào)用創(chuàng)建的,那就需要設置RFFDG標志,以免這些描述符被子進程共享。如果kqueue函數(shù)失敗,將返回-1,同時相應的設置errno。
- int kevent(int kq, const struct kevent *changelist, int nchanges, struct kevent *eventlist, int nevents, const struct timespec *timeout);
復制代碼
kevent函數(shù)用于和kqueue的交互。第一個參數(shù)是kqueue返回的描述符。changelist參數(shù)是一個大小為nchanges的kevent結(jié)構(gòu)體數(shù)組。changelist參數(shù)用于注冊或修改事件,并且將在從kqueue讀出事件之前得到處理。
eventlist參數(shù)是一個大小為nevents的kevent結(jié)構(gòu)體數(shù)組。kevent通過把事件放在eventlist參數(shù)中來向調(diào)用進程返回事件。如果需要的話,eventlist和changelist參數(shù)可以指向同一個數(shù)組。最后一個參數(shù)是kevent所期待的超時時間。如果超時參數(shù)被指定為NULL,kevent將阻塞,直至有事件發(fā)生為止。如果超時參數(shù)不為NULL,則kevent將阻塞到超時為止。如果超時參數(shù)指定的是一個內(nèi)容為0的結(jié)構(gòu)體,kevent將立即返回所有當前尚未處理的事件。
kevent的返回值指定了放在eventlist數(shù)組中的事件的數(shù)目。如果事件數(shù)目超過了eventlist的大小,可以通過后續(xù)的kevent調(diào)用來獲得它們。在處理事件的過程中發(fā)生的錯誤也會在還有空間的前提下被放到eventlist參數(shù)中。帶有錯誤的事件會設置EV_ERROR位,系統(tǒng)錯誤也會被放到data成員中。對于其它的所有錯誤都將返回-1,并相應地設置errno。
kevent結(jié)構(gòu)體用于和kqueue的通信。FreeBSD上的頭文件位于/usr/include/sys/event.h。在這個文件中有對kevent結(jié)構(gòu)體的聲明,以及其它的一些選項和標志。和select和poll比起來,kqueue還相當?shù)哪贻p,所以它一直都在發(fā)展和添加新的特性。請查看你的系統(tǒng)頭文件以確定任何新的或者特定于系統(tǒng)的選項。
原始的kevent結(jié)構(gòu)體的聲明如下:
- struct kevent {
- uintptr_t ident;
- short filter;
- u_short flags;
- u_int fflags;
- intptr_t data;
- void *udata;
- };
復制代碼
現(xiàn)在,讓我們來看看各個成員:
ident
ident成員用于存儲kqueue的唯一標識。換句話說,如果你想給一個事件添加一個文件描述符的話,ident成員就應當被設置成目標描述符的值。
filter
filter成員用于指定你希望內(nèi)核用于ident成員的過濾器。
flags
flags成員將告訴內(nèi)核應當對該事件完成哪些操作和處理哪些必要的標志。在返回的時候,flags成員可用于保存錯誤條件。
fflags
fflags成員用于指定你想讓內(nèi)核使用的特定于過濾器的標志。在返回的時候,fflags成員可用于保存特定于過濾器的返回值。
data
data成員用于保存任何特定于過濾器的數(shù)據(jù)。
udata
udata成員并不由kqueue使用,kqueue會把它的值不加修改地透傳。這個成員可被進程用來發(fā)送信息甚至是一個函數(shù)給它自己,用于一些依賴于事件檢測的場合。
kqueue 過濾器
下面列出的是kqueue使用的過濾器。某些過濾器會有專用于它的標志。這些標志是在kevent結(jié)構(gòu)體的fflags成員中設置的。
EVFILT_READ過濾器用于檢測什么時候數(shù)據(jù)可讀。kevent的ident成員應當被設成一個有效的描述符。盡管這個過濾器的行為和select或這poll很像,但它返回的事件將是特定于所使用的描述符的類型的。
如果描述符引用的打開文件是一個vnode,該事件就表明讀取偏移量尚未到達文件末尾。data成員保存的是當前距文件末尾的偏移量,這可以是負值。如果描述符引用的是一個pipe或者fifo,那么過濾器將在有實際數(shù)據(jù)可讀時返回。data成員保存的是可供讀取的字節(jié)數(shù)目。EV_EOF bit用于表示是哪個寫入者關(guān)閉了連接。(關(guān)于使用socket時EVFILT_READ的行為細節(jié)請參見kqueue的手冊頁。)
- #define EVFILT_WRITE (-2)
復制代碼
EVFILT_WRITE過濾器用于檢測是否可以對描述符執(zhí)行寫操作。如果描述符引用的是一個pipe、fifo或者socket,則data成員將存有寫緩沖區(qū)中可用的字節(jié)數(shù)目。EV_EOF bit表示讀取方已經(jīng)關(guān)閉了連接。這個標志對于打開的文件描述符無效。
EVFILT_AIO用于異步I/O操作,用于檢測和aio_error系統(tǒng)調(diào)用相似的條件。
- #define EVFILT_VNODE (-4)
復制代碼
EVFILT_VNODE過濾器用于檢測對文件系統(tǒng)上一個文件的某種改動。把ident成員設置成一個有效的打開文件描述符,用fflags成員指定所關(guān)心的事件。返回時,fflags成員將含有所發(fā)生事件的比特掩碼。這些事件如下:
- #define NOTE_DELETE 0x0001
復制代碼
NOTE_DELETE fflag表示進程想知道該文件何時被刪。
- #define NOTE_WRITE 0x0002
復制代碼
NOTE_WRITE fflag表示進程想知道該文件內(nèi)容何時被改變。
- #define NOTE_EXTEND 0x0004
復制代碼
NOTE_EXTEND fflag表示進程想知道該文件何時被擴展。
- #define NOTE_ATTRIB 0x0008
復制代碼
NOTE_ATTRIB fflag表示進程想知道該文件屬性何時被改變。
NOTE_LINK fflag表示進程想知道該文件的鏈接計數(shù)何時被改變。當文件通過link函數(shù)調(diào)用進行硬鏈接的時候,它的鏈接計數(shù)就會改變。(詳情請參見man(2) link。)
- #define NOTE_RENAME 0x0020
復制代碼
NOTE_RENAME fflag表示進程想知道該文件是否被重新命名了。
- #define NOTE_REVOKE 0x0040
復制代碼
NOTE_REVOKE fflag表示對文件的訪問被revoke了。詳情請見man(2) revoke。
- #define EVFILT_PROC (-5) /* attached to struct proc */
復制代碼
EVLILT_PROC過濾器被進程用來檢測發(fā)生在另外一個進程里的事件。所關(guān)心進程的PID存儲在ident成員中,fflags成員則被設成所關(guān)心的事件。返回時,事件將被放在fflags成員中。這些事件由下列事件按比特OR的方式設置:
- #define NOTE_EXIT 0x80000000
復制代碼
NOTE_EXIT fflag用于檢測該進程何時退出。
- #define NOTE_FORK 0x40000000
復制代碼
NOTE_FORK fflag用于檢測該進程何時調(diào)用fork。
- #define NOTE_EXEC 0x20000000
復制代碼
NOTE_EXEC fflag用于檢測該進程何時調(diào)用exec函數(shù)。
- #define NOTE_TRACK 0x00000001
復制代碼
NOTE_TRACK fflag讓kqueue去跟蹤一個跨越fork調(diào)用的進程。子進程返回時將設置fflags中的NOTE_CHILD標志,父進程的PID將放在data成員中。
- #define NOTE_TRACKERR 0x00000002
復制代碼
當在跟蹤子進程的過程中有錯誤發(fā)生時,就會設置NOTE_TRACKERR fflag。這是一個僅用于返回的fflag。
- #define NOTE_CHILD 0x00000004
復制代碼
NOTE_CHILD fflag在子進程內(nèi)設置。這是一個僅用于返回的fflag。
- #define EVFILT_SIGNAL (-6)
復制代碼
EVFILT_SIGNAL過濾器用于檢測是否有信號發(fā)送給該進程。每當有信號發(fā)送時這個過濾器就會檢測到,并把計數(shù)值放在data成員中。這包括設置了SIG_IGN標志的信號。事件將在執(zhí)行完常規(guī)的信號處理過程之后放到kqueue上。注意,這個過濾器將在內(nèi)部設置EV_CLEAR標志。
- #define EVFILT_TIMER (-7)
復制代碼
EVFILT_TIMER過濾器會給kqueue創(chuàng)建一個定時器,用于記錄消逝的事件。如果需要一個一次性的定時器,可以設置EV_ONESHOT標志。這個定時器是在ident成員中指定的,data成員用來指定以毫秒為單位的超時時間。返回值放在data成員中。注意,這個過濾器將在內(nèi)部設置EV_CLEAR標志。
kqueue操作
kqueue操作由所需的操作和標志以比特OR的方式進行設置。
EV_ADD操作向kqueue添加事件。由于kqueue中不允許出現(xiàn)重復,所以如果你想添加一個已經(jīng)存在的事件的話,現(xiàn)有事件將被新的添加操作覆蓋。注意,在添加事件的時候,它們已經(jīng)被默認激活了,除非你設置了EV_DISABLE標志。
EV_DELETE操作從kqueue中刪除事件。
EV_ENABLE用于激活kqueue中的事件。注意,新添加的事件默認就是激活的。
- #define EV_DISABLE 0x0008
復制代碼
EV_DISABLE禁止kqueue返回某個事件的信息。注意,kqueue并不會刪除過濾器。
kqueue操作標志
kqueue的操作標志定義如下。它們和上面列出的操作結(jié)合使用。它們是通過和所需操作進行比特OR來設置的。
- #define EV_ONESHOT 0x0010
復制代碼
EV_ONESHOT標志用于通知kqueue只返回第一個。
EV_CLEAR標志用于通知kqueue,一旦進程從kqueue中獲取到了該事件就將該事件的狀態(tài)復位。
kqueue返回值
僅用于返回的值是放在kevent結(jié)構(gòu)體的flags成員中的。這些值的定義如下:
EV_EOF用于表示文件結(jié)束的情況。
EV_ERROR用于表示有錯誤發(fā)生了。系統(tǒng)錯誤將被放到data成員中。
6.5 結(jié)論
本章研究了BSD中的描述符復用。作為一個程序員,你可以選擇三個接口:select、poll和kqueue。對于小數(shù)量的描述符來說,這三者的性能差不多,但是當描述符數(shù)量很大時,kqueue則是最好的選擇。除此之外,kqueue還可以檢測比I/O事件更為豐富的條件。它可以檢測信號、文件修改以及子進程相關(guān)的事件。在下一章中,我們將針對FreeBSD 5.x中的新特性,研究其它的獲取子進程信息和當前進程統(tǒng)計信息的方法。
[ 本帖最后由 雨絲風片 于 2006-2-27 12:15 編輯 ] |
|