select() - Unix,Linux 系統呼叫
廣告
名稱select、pselect、FD_CLR、FD_ISSET、FD_SET、FD_ZERO - 同步 I/O 多路複用語法
/* According to POSIX.1-2001 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
#define _XOPEN_SOURCE 600
#include <sys/select.h>
int pselect(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, const struct timespec *timeout,
const sigset_t *sigmask);
|
描述
select() 和 pselect() 允許程式監視多個檔案描述符,等待直到一個或多個檔案描述符準備好進行某種型別的 I/O 操作(例如,輸入可能)。如果可以執行相應的 I/O 操作(例如,read(2))而不會阻塞,則檔案描述符被認為已準備好。select() 和 pselect() 的操作相同,但有三個區別
| 標籤 | 描述 |
| (i) |
select() 使用一個 struct timeval 型別的超時(以秒和微秒為單位),而 pselect() 使用一個 struct timespec 型別的超時(以秒和納秒為單位)。 |
| (ii) |
select() 可能會更新 timeout 引數以指示剩餘時間。pselect() 不會更改此引數。 |
| (iii) |
select() 沒有 sigmask 引數,並且行為類似於使用 NULL sigmask 呼叫 pselect()。 |
監視三組獨立的檔案描述符。列在 readfds 中的檔案描述符將被監視以檢視是否有字元可供讀取(更準確地說,是檢視讀取是否不會阻塞;特別是,檔案描述符在檔案結束時也已準備好),列在 writefds 中的檔案描述符將被監視以檢視寫入是否不會阻塞,列在 exceptfds 中的檔案描述符將被監視以檢視異常。在退出時,這些集合會在適當的位置進行修改以指示哪些檔案描述符的狀態發生了變化。如果不需要監視任何檔案描述符以獲取相應的事件類別,則這三組檔案描述符中的每一組都可以指定為 NULL。提供四個宏來操作這些集合。FD_ZERO() 清空一個集合。FD_SET() 和 FD_CLR() 分別將給定的檔案描述符新增到集合中和從集合中移除。FD_ISSET() 測試檔案描述符是否屬於該集合的一部分;這在 select() 返回後很有用。
nfds 是這三組集合中編號最高的的檔案描述符加 1。
timeout 是 select() 返回之前經過時間的上限。它可以為零,導致 select() 立即返回。(這對於輪詢很有用。)如果 timeout 為 NULL(無超時),select() 可以無限期地阻塞。
sigmask 是指向訊號掩碼的指標(參見 sigprocmask(2));如果它不為 NULL,則 pselect() 首先用 sigmask 指向的訊號掩碼替換當前訊號掩碼,然後執行“select”功能,然後恢復原始訊號掩碼。 除了 timeout 引數精度上的差異外,以下 pselect() 呼叫
ready = pselect(nfds, &readfds, &writefds, &exceptfds,
timeout, &sigmask);
|
等效於原子地執行以下呼叫
sigset_t origmask;
sigprocmask(SIG_SETMASK, &sigmask, &origmask);
ready = select(nfds, &readfds, &writefds, &exceptfds, timeout);
sigprocmask(SIG_SETMASK, &origmask, NULL);
|
需要 pselect() 的原因是,如果要等待訊號或檔案描述符準備好,則需要進行原子測試以防止出現競爭條件。(假設訊號處理程式設定一個全域性標誌並返回。然後,如果訊號恰好在測試之後但就在呼叫之前到達,則對該全域性標誌的測試後跟對 select() 的呼叫可能會無限期地掛起。相比之下,pselect() 允許首先阻塞訊號,處理已到達的訊號,然後使用所需的 sigmask 呼叫 pselect(),從而避免競爭。) 超時所涉及的時間結構在 <sys/time.h> 中定義,如下所示
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
|
和
struct timespec {
long tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
|
(但是,請參見下面的 POSIX.1-2001 版本。) 某些程式碼使用所有三個集合為空、n 為零以及非 NULL timeout 呼叫 select() 作為一種相當便攜的方式來以亞秒級精度休眠。 在 Linux 上,select() 修改 timeout 以反映未休眠的時間;大多數其他實現都不會這樣做。(POSIX.1-2001 允許這兩種行為。)這會導致 Linux 程式碼讀取 timeout 時移植到其他作業系統時出現問題,以及當代碼移植到 Linux 時重用 struct timeval 在迴圈中進行多次 select() 呼叫而無需重新初始化它時出現問題。在 select() 返回後,認為 timeout 未定義。 返回值如果成功,select() 和 pselect() 將返回三個返回的描述符集中包含的檔案描述符的數量(即,在 readfds、writefds、exceptfds 中設定的位的總數),如果在發生任何有趣的事情之前超時到期,則該數量可能為零。如果出錯,則返回 -1,並且 errno 將被相應地設定;集合和 timeout 將變得未定義,因此在發生錯誤後不要依賴它們的內容。錯誤
| 標籤 | 描述 |
|
EBADF | 在一組中給出了無效的檔案描述符。(可能是已經關閉的檔案描述符,或者是在其中發生錯誤的檔案描述符。) |
|
EINTR | 捕獲到訊號。 |
|
EINVAL |
nfds 為負,或者 timeout 中包含的值無效。 |
|
ENOMEM | 無法為內部表分配記憶體。 |
示例
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int
main(void) {
fd_set rfds;
struct timeval tv;
int retval;
/* Watch stdin (fd 0) to see when it has input. */
FD_ZERO(&rfds);
FD_SET(0, &rfds);
/* Wait up to five seconds. */
tv.tv_sec = 5;
tv.tv_usec = 0;
retval = select(1, &rfds, NULL, NULL, &tv);
/* Don’t rely on the value of tv now! */
if (retval == -1)
perror("select()");
else if (retval)
printf("Data is available now.\n");
/* FD_ISSET(0, &rfds) will be true. */
else
printf("No data within five seconds.\n");
return 0;
}
|
符合標準
select() 符合 POSIX.1-2001 和 4.4BSD(select() 最初出現在 4.2BSD 中)。通常可移植到/來自支援 BSD 套接字層克隆的非 BSD 系統(包括 System V 變體)。但是,請注意,System V 變體通常在退出之前設定超時變數,但 BSD 變體則不設定。
pselect() 在 POSIX.1g 和 POSIX.1-2001 中定義。 備註fd_set 是一個固定大小的緩衝區。使用負值或等於或大於 FD_SETSIZE 的 fd 值執行 FD_CLR() 或 FD_SET() 將導致未定義的行為。此外,POSIX 要求 fd 是一個有效的檔案描述符。關於所涉及的型別,經典情況是 timeval 結構的兩個欄位是長整型(如上所示),並且該結構在 <sys/time.h> 中定義。POSIX.1-2001 的情況是
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
|
其中該結構在 <sys/select.h> 中定義,資料型別 time_t 和 suseconds_t 在 <sys/types.h> 中定義。 關於原型,經典情況是應該為 select() 包含 <time.h>。POSIX.1-2001 的情況是應該為 select() 和 pselect() 包含 <sys/select.h>。Libc4 和 libc5 沒有 <sys/select.h> 標頭檔案;在 glibc 2.0 及更高版本中,此標頭檔案存在。在 glibc 2.0 中,它無條件地為 pselect() 提供錯誤的原型,在 glibc 2.1-2.2.1 中,它在定義 _GNU_SOURCE 時提供 pselect(),在 glibc 2.2.2-2.2.4 中,它在定義 _XOPEN_SOURCE 且其值為 600 或更大時提供它。毫無疑問,自從 POSIX.1-2001 以來,它應該預設提供該原型。 版本
pselect() 在核心 2.6.16 中新增到 Linux。在此之前,pselect() 在 glibc 中被模擬(但請參見 BUG)。Linux 備註Linux pselect() 系統呼叫會修改其 timeout 引數。但是,glibc 包裝器函式透過使用傳遞給系統呼叫的超時引數的區域性變數來隱藏此行為。因此,glibc pselect() 函式不會修改其超時引數;這是 POSIX.1-2001 所需的行為。錯誤Glibc 2.0 提供了一個不帶 sigmask 引數的 pselect() 版本。從 2.1 版開始,glibc 提供了一個使用 sigprocmask(2) 和 select() 實現的 pselect() 模擬。此實現仍然容易受到 pselect() 旨在防止的競爭條件的影響。在缺少 pselect() 的系統上,可以使用自管道技巧實現可靠的(更具可移植性的)訊號捕獲(其中訊號處理程式向管道的另一端寫入一個位元組,而管道的另一端由主程式中的 select() 監視)。 在 Linux 下,select() 可能會將套接字檔案描述符報告為“準備好讀取”,而後續讀取仍然會阻塞。例如,當資料已到達但檢查後發現校驗和錯誤並被丟棄時,可能會發生這種情況。在其他情況下,檔案描述符也可能被錯誤地報告為已準備好。因此,在不應阻塞的套接字上使用 O_NONBLOCK 可能更安全。 參見
select_tut(2).
有關模糊相關的內容,請參見 accept(2)、connect(2)、poll(2)、read(2)、recv(2)、send(2)、sigprocmask(2)、write(2)、epoll(7)、feature_test_macros(7)
廣告
|