poll 完成的功能和 select 幾乎是一模一樣的,是以在你學會了 select 後,你發現學 poll 會非常容易。在英文中 poll 表示“投票”的意思,這非常形象,有事件發生的描述符,就為其投票。
1. poll 原型
int poll(struct pollfd *fds, nfds_t nfds, int
有幾個參數類型可能我們不認識,實際上 poll 函數的第一個參數是一個類型為 struct pollfd 的數組,第二個參數 nfds (一般是個無符号的整型)是這個數組的大小,第三個參數就是逾時時間了。接下來一個一個說明。
1.1 struct pollfd 類型
struct pollfd {
int fd; /* 檔案描述符 */
short events; /* 監聽的事件,比如可讀事件,可寫事件 */
short revents; /* poll 函數的傳回結果,是可讀還是可寫 */
和 select 不同的是,poll 函數沒有将可讀事件、可寫事件描述符單獨放進兩個不同的集合,而是配置設定了一個結構體,一次性進行監聽。
成員 fd 表示要監聽哪個描述符,如果該值是負數,poll 函數會忽略掉它。
對 events 來說,它使用 bit 位來儲存你要監聽什麼事件,通常用“位或”操作為其指派,在 linux 中,events 有下面的可選值:
- POLLIN: 監聽是描述符是否可讀,相當于 select 中的讀集合參數。
- POLLPRI:監聽是否有緊急資料可讀(比如 TCP 套接字上的 out-of-band(OOB) 資料,這種我們還沒學,是以不考慮監聽它了)
- POLLOUT:監聽是描述符是否可寫。
- POLLRDHUP:這種事件隻有 Linux 2.6.17 核心後才支援,它監聽流式套接字對端是否關閉右半關閉,因為我們還沒學套接字,是以這種的也先不考慮了。
如果有監聽的事件到來,它會将事件類型儲存到 revents 成員中,比如監聽到了有資料可讀,則 revents 的 bit 位中會儲存 POLLIN。如果監聽到了有資料可寫,那 revents 的 bit 位中會儲存 POLLOUT。
還有一些事件,即使你不主動監聽它也會發生,并儲存到 revents 中,這些事件一般都是異常事件:
- POLLERR:這種情況極少見,很抱歉後面的實驗沒示範。一般是硬體上的問題吧,可以參考這個文章傳送門。
- POLLHUP:對端挂斷,比如對于有名管道,其中一端關閉了。
- POLLNVAL:使用了未打開的描述符
1.2 timeout 參數
- timeout = -1,表示永遠等待,直到有事件發生。
- timeout = 0,不等待,立即傳回。
- timeout > 0,等待 timeout 毫秒。
2. poll 與 select
這兩個函數所做的事件是一樣的,但是它們也有差別:
- select 使用 fd_set 來存放描述符,poll 使用結構體數組。
-
select 能夠一次監聽的描述符數量是受 fd_set 集合的限制的,通常這個集合最多隻能放 1024 個描述符。而 poll 一次能夠監聽的描述符個數是根據數組大小來決定的,這要看 nfds_t 被定義成什麼類型了,如果是 unsigned long,4 位元組寬度的情況下,poll 能監聽 232−1
2
32
−
1
3. 實驗
程式 poll.c 隻是對前面的 select.c 做了一點點修改,将 select 替換成了 poll 函數。另外,為了能示範異常事件,程式使用了一個未打開的描述符 fd3。
writepipe 函數做了一點點小小的修改,就是輸入字母 ‘q’ 的時候會主動 close 掉管道描述符,同時退出。
poll 函數同樣會被信号打斷,所有
3.1 代碼
// poll.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <poll.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#define PERR(msg) do { perror(msg); exit(1); } while(0);
void handler(int sig) {
if (sig == SIGINT) {
puts("hello SIGINT");
}
}
int process(char* prompt, int fd) {
int n;
char buf[64];
char line[64];
n = read(fd, buf, 64);
if (n < 0) {
// error
PERR("read");
}
else if (n == 0) {
// peer close
sprintf(line, "%s closed\n", prompt);
puts(line);
return 0;
}
else if (n > 0) {
buf[n] = 0;
sprintf(line, "%s say: %s", prompt, buf);
puts(line);
}
return n;
}
int main () {
int i, n, res;
char buf[64];
struct pollfd fds[4];
if (SIG_ERR == signal(SIGINT, handler)) {
PERR("signal");
}
int fd0 = STDIN_FILENO;
int fd1 = open("a.fifo", O_RDONLY);
printf("open pipe: fd = %d\n", fd1);
int fd2 = open("b.fifo", O_RDONLY);
printf("open pipe: fd = %d\n", fd2);
int fd3 = 100;
fds[0].fd = fd0;
fds[1].fd = fd1;
fds[2].fd = fd2;
fds[3].fd = fd3;
for (i = 0; i < 4; ++i) {
fds[i].events = POLL_IN;
}
while(1) {
res = poll(fds, 4, -1);
if (res < 0) {
// error
if (errno == EINTR) {
perror("poll");
continue;
}
PERR("poll");
}
else if (res == 0) {
// timeout
continue;
}
for (i = 0; i < 4; ++i) {
if (fds[i].revents & POLLIN) {
sprintf(buf, "fd%d", i);
n = process(buf, fds[i].fd);
if (n == 0) fds[i].fd = -1;
}
if (fds[i].revents & POLLERR) {
printf("fd%d Error\n", i);
fds[i].fd = -1;
}
if (fds[i].revents & POLLHUP) {
printf("fd%d Hang up\n", i);
fds[i].fd = -1;
if (fds[i].revents & POLLNVAL) {
printf("fd%d Invalid request\n", i);
fds[i].fd = -1;
}
}
}
}
3.2 編譯與運作
- 編譯
$ gcc poll.c -o poll
- 運作

圖1 運作結果
從圖 1 中可以看到,因為傳入了一個未打開的描述符,導緻 poll 函數監聽到了異常事件,即 POLLNVAL,是以螢幕列印了 fd3 Invalid request.
4. 總結
- 掌握 poll 的用法
- 知道 poll 和 select 的差別