2.1. 如何管理多個(gè)連接? “我想同時(shí)監控一個(gè)以上的文件描述符(fd)/連接(connection)/流(stream),應該怎么辦?”
使用 select() 或 poll() 函數。
注 意:select() 在BSD中被引入,而poll()是SysV STREAM流控制的產(chǎn)物。因此,這里就有了平臺移植上的考慮:純粹的BSD系統可 能仍然缺少poll(),而早一些的SVR3系統中可能沒(méi)有select(),盡管在SVR4中將其加入。目前兩者都是POSIX. 1g標準,(譯者 注:因此在Linux上兩者都存在)
select()和poll()本質(zhì)上來(lái)講做的是同一件事,只是完成的方法不一樣。兩者都通過(guò)檢驗一組文件描述符來(lái)檢測是否有特定的時(shí)間將在上面發(fā)生并在一定的時(shí)間內等待其發(fā)生。
[重要事項:無(wú)論select()還是poll() 都不對普通文件起很大效用,它們著(zhù)重用于套接口(socket)、管道(pipe)、偽終端(pty)、終端設備(tty)和其他一些字符設備,但是這些操作都是系統相關(guān)(system-dependent)的。]
2.1.1. 我如何使用select()函數? select() 函數的接口主要是建立在一種叫'fd_set'類(lèi)型的基礎上。它('fd_set') 是一組文件描述符(fd)的集合。由于fd_set類(lèi)型的長(cháng)度在不同平臺上不同,因此應該用一組標準的宏定義來(lái)處理此類(lèi)變量:
fd_set set; FD_ZERO(&set); /* 將 set清零 */ FD_SET(fd, &set); /* 將fd加入set */ FD_CLR(fd, &set); /* 將 fd從set中清除 */ FD_ISSET(fd, &set); /* 如果fd在set中則真 */ 在 過(guò)去,一個(gè)fd_set通常只能包含少于等于32個(gè)文件描述符,因為fd_set其實(shí)只用了一個(gè)int的比特矢量來(lái)實(shí)現,在大多數情況下,檢查 fd_set能包括任意值的文件描述符是系統的責任,但確定你的fd_set到底能放多少有時(shí)你應該檢查/修改宏FD_SETSIZE的值。*這個(gè)值是系 統相關(guān)的*,同時(shí)檢查你的系統中的select() 的man手冊。有一些系統對多于1024個(gè)文件描述符的支持有問(wèn)題。[譯者注: Linux就是這樣 的系統!你會(huì )發(fā)現sizeof(fd_set)的結果是128(*8 = FD_SETSIZE=1024) 盡管很少你會(huì )遇到這種情況。]
select 的基本接口十分簡(jiǎn)單:
int select(int nfds, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout); 其中:
nfds 需要檢查的文件描述符個(gè)數,數值應該比是三組fd_set中最大數 更大,而不是實(shí)際文件描述符的總數。 readset 用來(lái)檢查可讀性的一組文件描述符。 writeset 用來(lái)檢查可寫(xiě)性的一組文件描述符。 exceptset 用來(lái)檢查意外狀態(tài)的文件描述符。(注:錯誤并不是意外狀態(tài)) timeout NULL 指針代表無(wú)限等待,否則是指向timeval結構的指針,代表最 長(cháng)等待時(shí)間。(如果其中tv_sec和tv_usec都等于0, 則文件描述符 的狀態(tài)不被影響,但函數并不掛起) 函數將返回響應操作的對應操作文件描述符的總數,且三組數據均在恰當位置被修改,只有響應操作的那一些沒(méi)有修改。接著(zhù)應該用FD_ISSET宏來(lái)查找返回的文件描述符組。
這里是一個(gè)簡(jiǎn)單的測試單個(gè)文件描述符可讀性的例子:
int isready(int fd) { int rc; fd_set fds; struct timeval tv; FD_ZERO(&fds); FD_SET(fd,&fds); tv.tv_sec = tv.tv_usec = 0; rc = select(fd+1, &fds, NULL, NULL, &tv); if (rc < 0) return -1; return FD_ISSET(fd,&fds) ? 1 : 0; } 當然如果我們把NULL指針作為fd_set傳入的話(huà),這就表示我們對這種操作的發(fā)生不感興趣,但select() 還是會(huì )等待直到其發(fā)生或者超過(guò)等待時(shí)間。
[譯 者注:在Linux中,timeout指的是程序在非sleep狀態(tài)中度過(guò)的時(shí)間,而不是實(shí)際上過(guò)去的時(shí)間,這就會(huì )引起和非Linux平臺移植上的時(shí)間不 等問(wèn)題。移植問(wèn)題還包括在System V風(fēng)格中select()在函數退出前會(huì )把timeout設為未定義的 NULL狀態(tài),而在BSD中則不是這樣, Linux在這點(diǎn)上遵從System V,因此在重復利用timeout指針問(wèn)題上也應該注意。]
2.1.2. 我如何使用 poll()? poll ()接受一個(gè)指向結構'struct pollfd'列表的指針,其中包括了你想測試的文件描述符和事件。事件由一個(gè)在結構中事件域的比特掩碼確定。當前 的結構在調用后將被填寫(xiě)并在事件發(fā)生后返回。在SVR4(可能更早的一些版本)中的 "poll.h"文件中包含了用于確定事件的一些宏定義。事件的等待 時(shí)間精確到毫秒 (但令人困惑的是等待時(shí)間的類(lèi)型卻是int),當等待時(shí)間為0時(shí),poll()函數立即返回,-1則使poll()一直掛起直到一個(gè)指定 事件發(fā)生。下面是pollfd的結構。
struct pollfd { int fd; /* 文件描述符 */ short events; /* 等待的事件 */ short revents; /* 實(shí)際發(fā)生了的事件 */ }; 于select()十分相似,當返回正值時(shí),代表滿(mǎn)足響應事件的文件描述符的個(gè)數,如果返回0則代表在規定事件內沒(méi)有事件發(fā)生。如發(fā)現返回為負則應該立即查看 errno,因為這代表有錯誤發(fā)生。
如果沒(méi)有事件發(fā)生,revents會(huì )被清空,所以你不必多此一舉。
這里是一個(gè)例子
/* 檢測兩個(gè)文件描述符,分別為一般數據和高優(yōu)先數據。如果事件發(fā)生 則用相關(guān)描述符和優(yōu)先度調用函數handler(),無(wú)時(shí)間限制等待,直到 錯誤發(fā)生或描述符掛起。*/ #include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <stropts.h> #include <poll.h> #include <unistd.h> #include <errno.h> #include <string.h> #define NORMAL_DATA 1 #define HIPRI_DATA 2 int poll_two_normal(int fd1,int fd2) { struct pollfd poll_list[2]; int retval; poll_list[0].fd = fd1; poll_list[1].fd = fd2; poll_list[0].events = POLLIN|POLLPRI; poll_list[1].events = POLLIN|POLLPRI; while(1) { retval = poll(poll_list,(unsigned long)2,-1); /* retval 總是大于0或為-1,因為我們在阻塞中工作 */ if(retval < 0) { fprintf(stderr,"poll 錯誤: %s\n",strerror(errno)); return -1; } if(((poll_list[0].revents&POLLHUP) == POLLHUP) || ((poll_list[0].revents&POLLERR) == POLLERR) || ((poll_list[0].revents&POLLNVAL) == POLLNVAL) || ((poll_list[1].revents&POLLHUP) == POLLHUP) || ((poll_list[1].revents&POLLERR) == POLLERR) || ((poll_list[1].revents&POLLNVAL) == POLLNVAL)) return 0; if((poll_list[0].revents&POLLIN) == POLLIN) handle(poll_list[0].fd,NORMAL_DATA); if((poll_list[0].revents&POLLPRI) == POLLPRI) handle(poll_list[0].fd,HIPRI_DATA); if((poll_list[1].revents&POLLIN) == POLLIN) handle(poll_list[1].fd,NORMAL_DATA); if((poll_list[1].revents&POLLPRI) == POLLPRI) handle(poll_list[1].fd,HIPRI_DATA); } } 2.1.3. 我是否可以同時(shí)使用SysV IPC和select()/poll()? *不能。* (除非在A(yíng)IX上,因為它用一個(gè)無(wú)比奇怪的方法來(lái)實(shí)現這種組合)
一般來(lái)說(shuō),同時(shí)使用select()或poll()和SysV 消息隊列會(huì )帶來(lái)許多麻煩。SysV IPC的對象并不是用文件描述符來(lái)處理的,所以它們不能被傳遞給select()和 poll()。這里有幾種解決方法,其粗暴程度各不相同:
完全放棄使用 SysV IPC。 :-)
用fork(),然后讓子進(jìn)程來(lái)處理SysV IPC,然后用管道或套接口和父進(jìn)程 說(shuō)話(huà)。父進(jìn)程則使用 select()。
同上,但讓子進(jìn)程用select(),然后和父親用消息隊列交流。
安排進(jìn)程發(fā)送消息給你,在發(fā)送消息后再發(fā)送一個(gè)信號。*警告*:要做好 這個(gè)并不簡(jiǎn)單,非常容易寫(xiě)出會(huì )丟失消息或引起死鎖的程序。
另外還有其他方法。 poll : | | 驅動(dòng)中poll_wait()函數的疑問(wèn)
應用程序的 select()系統調用,調用驅動(dòng)中的poll()方法。 不理解的是在下面的poll()方法實(shí)現中,首先調用poll_wait將等待隊列添加到wait結構中,接下來(lái)是個(gè)判斷語(yǔ)句 if (dev->rp != dev->wp) mask |= POLLIN | POLLRDNORM; /* readable */ 只 考慮可讀情況。如果這個(gè)if語(yǔ)句的條件不滿(mǎn)足,那么就不會(huì )返回可讀,也就是返回0。那么在這里怎么實(shí)現阻塞的呢?也就是說(shuō)如果在應用的select()系 統中,指定一個(gè)等待時(shí)間,在這個(gè)等待時(shí)間里如果沒(méi)有描述符可讀,就一直阻塞。那個(gè)這個(gè)等待時(shí)間是怎么和驅動(dòng)中的poll()方法聯(lián)系起來(lái)的呢?如果要修改 這個(gè)poll()方法怎么修改呢?還有在poll()方法中,怎么指定描述符集中的哪一個(gè)是可讀的呢?簡(jiǎn)單的返回POLLIN | POLLRDNORM,是無(wú)法指定是哪一個(gè)描述符可讀的呀? static unsigned int scull_p_poll(struct file *filp, poll_table *wait) { struct scull_pipe *dev = filp->private_data; unsigned int mask = 0;
/* * The buffer is circular; it is considered full * if "wp" is right behind "rp" and empty if the * two are equal. */ down(&dev->sem); poll_wait(filp, &dev->inq, wait); // poll_wait(filp, &dev->outq, wait); if (dev->rp != dev->wp) mask |= POLLIN | POLLRDNORM; /* readable */ //if (spacefree(dev)) // mask |= POLLOUT | POLLWRNORM; /* writable */ up(&dev->sem); return mask; } | | | | 在調用驅動(dòng)程序的poll之前,實(shí)現調用VFS相關(guān)的poll接口的(比如sys_poll等),阻塞、等待時(shí)間等的實(shí)現是在那個(gè)里面完成的 | | | | 那么驅動(dòng)的這個(gè)poll()方法總是立刻返回的? | | | 是立刻返回的,那么如果有一個(gè)描述符集當前不可讀,也就返回0。但等了一段時(shí)間后可讀,那么怎么返回mask |= POLLIN | POLLRDNORM;。我的意思是怎么指示給應用程序可讀的呢? 那么驅動(dòng)的poll()方法的作用是什么呢? | | | 如果當前不可讀,那么在sys_poll->do_poll中當 前進(jìn)程就會(huì )睡眠在等待隊列上,這個(gè)等待隊列是由驅動(dòng)程序提供的(就是poll_wait中傳入的那個(gè))。當可讀的時(shí)候,驅動(dòng)程序可能有一部分代碼運行了 (比如驅動(dòng)的中斷服務(wù)程序),那么在這部分代碼中,就會(huì )喚醒等待隊列上的進(jìn)程,也就是之前睡眠的那個(gè),當那個(gè)進(jìn)程被喚醒后do_poll會(huì )再一次的調用驅 動(dòng)程序的poll函數,這個(gè)時(shí)候應用程序就知道是可讀的了。
不知道有沒(méi)有解釋清楚啊,呵呵 | | snow_insky | 2006-7-19 04:30 | | | POOL方法就是用來(lái)支持非阻塞式的訪(fǎng)問(wèn),當然是立即返回,但是它會(huì )把這次請求放入一個(gè)等待隊列中,當某個(gè)條件滿(mǎn)足時(shí),內核會(huì )通知應用程序(應用程序的select函數會(huì )感知),然后就會(huì )接著(zhù)select操作 | | |