本文poll機制的內核原理,再說在編程中的使用。
先從內核原理說:
所有的系統調用,基於都可以在它的名字前加上“sys_”前綴,這就是它在內核中對應的函數。比如系統調用open、read、write、poll,與之對應的內核函數爲:sys_open、sys_read、sys_write、sys_poll。
一、內核框架:
對於系統調用poll或select,它們對應的內核函數都是sys_poll。分析sys_poll,即可理解poll機制。
1. sys_poll函數位於fs/select.c文件中,代碼如下:
asmlinkagelong sys_poll(struct pollfd __user *ufds, unsigned int nfds,
long timeout_msecs)
{
s64 timeout_jiffies;
if (timeout_msecs > 0) {
#ifHZ > 1000
/* We can only overflow if HZ >1000 */
if (timeout_msecs / 1000 >(s64)0x7fffffffffffffffULL / (s64)HZ)
timeout_jiffies = -1;
else
#endif
timeout_jiffies =msecs_to_jiffies(timeout_msecs);
} else {
/* Infinite (< 0) or no (0)timeout */
timeout_jiffies = timeout_msecs;
}
return do_sys_poll(ufds,nfds, &timeout_jiffies);
}
它對超時參數稍作處理後,直接調用do_sys_poll。
2. do_sys_poll函數也位於位於fs/select.c文件中,我們忽略其他代碼:
intdo_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout)
{
……
poll_initwait(&table);
……
fdcount = do_poll(nfds, head,&table, timeout);
……
}
poll_initwait函數非常簡單,它初始化一個poll_wqueues變量table:
poll_initwait> init_poll_funcptr(&pwq->pt, __pollwait); > pt->qproc = qproc;
即table->pt->qproc= __pollwait,__pollwait將在驅動的poll函數裏用到。
3. do_sys_poll函數位於fs/select.c文件中,代碼如下:
static int do_poll(unsigned int nfds, struct poll_list *list,
struct poll_wqueues *wait, s64 *timeout)
{
01 ……
02 for (;;){
03 ……
04 if(do_pollfd(pfd, pt)) {
05 count++;
06 pt = NULL;
07 }
08 ……
09 if(count || !*timeout || signal_pending(current))
10 break;
11 count= wait->error;
12 if(count)
13 break;
14
15 if(*timeout < 0) {
16 /*Wait indefinitely */
17 __timeout= MAX_SCHEDULE_TIMEOUT;
18 }else if (unlikely(*timeout >= (s64)MAX_SCHEDULE_TIMEOUT-1)) {
19 /*
20 * Wait for longer than MAX_SCHEDULE_TIMEOUT. Do it in
21 * a loop
22 */
23 __timeout= MAX_SCHEDULE_TIMEOUT - 1;
24 *timeout-= __timeout;
25 }else {
26 __timeout= *timeout;
27 *timeout= 0;
28 }
29
30 __timeout= schedule_timeout(__timeout);
31 if(*timeout >= 0)
32 *timeout+= __timeout;
33 }
34 __set_current_state(TASK_RUNNING);
35 returncount;
36 }
分析其中的代碼,可以發現,它的作用如下:
① 從02行可以知道,這是個循環,它退出的條件爲:
a. 09行的3個條件之一(count非0,超時、有信號等待處理)
count非0表示04行的do_pollfd至少有一個成功。
b. 11、12行:發生錯誤
② 重點在do_pollfd函數,後面再分析
③ 第30行,讓本進程休眠一段時間,注意:應用程序執行poll調用後,如果①②的條件不滿足,進程就會進入休眠。那麼,誰喚醒呢?除了休眠到指定時間被系統喚醒外,還可以被驅動程序喚醒──記住這點,這就是爲什麼驅動的poll裏要調用poll_wait的原因,後面分析。
4. do_pollfd函數位於fs/select.c文件中,代碼如下:
static inline unsigned int do_pollfd(struct pollfd*pollfd, poll_table *pwait)
{
……
if(file->f_op && file->f_op->poll)
mask= file->f_op->poll(file, pwait);
……
}
可見,它就是調用我們的驅動程序裏註冊的poll函數。
二、驅動程序:
驅動程序裏與poll相關的地方有兩處:一是構造file_operation結構時,要定義自己的poll函數。二是通過poll_wait來調用上面說到的__pollwait函數,pollwait的代碼如下:
staticinline void poll_wait(struct file * filp, wait_queue_head_t * wait_address,poll_table *p)
{
if (p && wait_address)
p->qproc(filp, wait_address, p);
}
p->qproc就是__pollwait函數,從它的代碼可知,它只是把當前進程掛入我們驅動程序裏定義的一個隊列裏而已。它的代碼如下:
staticvoid __pollwait(struct file *filp, wait_queue_head_t *wait_address,
poll_table *p)
{
struct poll_table_entry *entry =poll_get_entry(p);
if (!entry)
return;
get_file(filp);
entry->filp = filp;
entry->wait_address = wait_address;
init_waitqueue_entry(&entry->wait,current);
add_wait_queue(wait_address,&entry->wait);
}
執行到驅動程序的poll_wait函數時,進程並沒有休眠,我們的驅動程序裏實現的poll函數是不會引起休眠的。讓進程進入休眠,是前面分析的do_sys_poll函數的30行“__timeout = schedule_timeout(__timeout)”。
poll_wait只是把本進程掛入某個隊列,應用程序調用poll > sys_poll> do_sys_poll > poll_initwait,do_poll > do_pollfd > 我們自己寫的poll函數後,再調用schedule_timeout進入休眠。如果我們的驅動程序發現情況就緒,可以把這個隊列上掛着的進程喚醒。可見,poll_wait的作用,只是爲了讓驅動程序能找到要喚醒的進程。即使不用poll_wait,我們的程序也有機會被喚醒:chedule_timeout(__timeout),只是要休眠__time_out這段時間。
現在來總結一下poll機制:
1. poll > sys_poll > do_sys_poll >poll_initwait,poll_initwait函數註冊一下回調函數__pollwait,它就是我們的驅動程序執行poll_wait時,真正被調用的函數。
2. 接下來執行file->f_op->poll,即我們驅動程序裏自己實現的poll函數
它會調用poll_wait把自己掛入某個隊列,這個隊列也是我們的驅動自己定義的;
它還判斷一下設備是否就緒。
3. 如果設備未就緒,do_sys_poll裏會讓進程休眠一定時間
4. 進程被喚醒的條件有2:一是上面說的“一定時間”到了,二是被驅動程序喚醒。驅動程序發現條件就緒時,就把“某個隊列”上掛着的進程喚醒,這個隊列,就是前面通過poll_wait把本進程掛過去的隊列。
5. 如果驅動程序沒有去喚醒進程,那麼chedule_timeout(__timeou)超時後,會重複2、3動作,直到應用程序的poll調用傳入的時間到達。
上面是內核的機制原理來自於韋東山老師的筆記,
我來說一下應用層和驅動層的具體用法。
在驅動層:
在file_operations 結構體重指出poll成員的函數
static struct file_operations third_fpos= {
.owner = THIS_MODULE,
.open = thirddri_open,
.release = thirddri_close,
.read = thirddri_read,
.poll = thirddri_poll,
};
static unsignedint thirddri_poll(struct file *filp,struct poll_table_struct *pt)
{
poll_wait(filp, &button_waiter, pt);
if (queue)
returnPOLLIN | POLLRDNORM;
return 0;
}
在函數中調用 poll_wait函數,這是內核提供的函數不用我們自己寫,函數裏的三個參數,第一個和第二個參數 由上層調用傳進來的,這裏不用管。第二個參數由我們自己聲明。要 生成一個等待隊列頭,
staticDECLARE_WAIT_QUEUE_HEAD(button_waiter);
注意返回值!上面分析內核源碼已經知道了!返回值爲非零時count++;這樣會跳出上面說到的那個do_poll函數中的死循環;
那麼重點就落在了這個queue數據上!這個的變化就是關鍵了!什麼情況下是非零什麼情況下是零就看你怎麼編程了!我是初始化成0然後在中斷處理函數中將這個數變成1,這樣就使得中斷已發生就可以從do_poll函數中返回!上面已經說過了休眠就在這個do_poll函數中!從這函數返回就回到了應用層(別忘了我們的poll函數是應用層調用的)調用順序是上面已經說了的!
再說應用層:
應用層就是poll函數的使用,可以在系統用用man poll 命名獲取幫助,我還是在下面說一下
# include < sys/ poll. h>
int poll ( struct pollfd * fds, unsigned int nfds, int timeout);
和select()不一樣,poll()沒有使用低效的三個基於位的文件描述符set,而是採用了一個單獨的結構體pollfd數組,由fds指針指向這個組。pollfd結構體定義如下:
# include < sys/ poll. h>
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 等待的事件 */
short revents; /* 實際發生了的事件 */
} ;
每一個pollfd結構體指定了一個被監視的文件描述符,可以傳遞多個結構體,指示poll()監視多個文件描述符。每個結構體的events域是監視該文件描述符的事件掩碼,由用戶來設置這個域。revents域是文件描述符的操作結果事件掩碼。內核在調用返回時設置這個域。events域中請求的任何事件都可能在revents域中返回。
注意events是用戶輸入的事件掩碼,如果它設置爲零,那麼內核返回給我們的revents就永遠是零了!!
合法的事件如下:
POLLIN
有數據可讀。
POLLRDNORM
有普通數據可讀。
POLLRDBAND
有優先數據可讀。
POLLPRI
有緊迫數據可讀。
POLLOUT
寫數據不會導致阻塞。
POLLWRNORM
寫普通數據不會導致阻塞。
POLLWRBAND
寫優先數據不會導致阻塞。
POLLMSG
SIGPOLL 消息可用。
此外,revents域中還可能返回下列事件:
POLLER
指定的文件描述符發生錯誤。
POLLHUP
指定的文件描述符掛起事件。
POLLNVAL
指定的文件描述符非法。
這些事件在events域中無意義,因爲它們在合適的時候總是會從revents中返回。使用poll()和select()不一樣,你不需要顯式地請求異常情況報告。
POLLIN | POLLPRI等價於select()的讀事件,POLLOUT|POLLWRBAND等價於select()的寫事件。POLLIN等價於POLLRDNORM|POLLRDBAND,而POLLOUT則等價於POLLWRNORM。
例如,要同時監視一個文件描述符是否可讀和可寫,我們可以設置events爲POLLIN|POLLOUT。在poll返回時,我們可以檢查revents中的標誌,對應於文件描述符請求的events結構體。如果POLLIN事件被設置,則文件描述符可以被讀取而不阻塞。如果POLLOUT被設置,則文件描述符可以寫入而不導致阻塞。這些標誌並不是互斥的:它們可能被同時設置,表示這個文件描述符的讀取和寫入操作都會正常返回而不阻塞。
timeout參數指定等待的毫秒數,無論I/O是否準備好,poll都會返回。timeout指定爲負數值表示無限超時,使poll()一直掛起直到一個指定事件發生;timeout爲0指示poll調用立即返回並列出準備好I/O的文件描述符,但並不等待其它的事件。這種情況下,poll()就像它的名字那樣,一旦選舉出來,立即返回。
返回值和錯誤代碼
成功時,poll()返回結構體中revents域不爲0的文件描述符個數;如果在超時前沒有任何事件發生,poll()返回0;失敗時,poll()返回-1,並設置errno爲下列值之一:
EBADF
一個或多個結構體中指定的文件描述符無效。
EFAULT
fds指針指向的地址超出進程的地址空間。
EINTR
請求的事件之前產生一個信號,調用可以重新發起。
EINVAL
nfds參數超出PLIMIT_NOFILE值。
ENOMEM
可用內存不足,無法完成請求。
再說一下select
select和poll的工作原理是一樣的 ,都是會調用系統調用 sys_poll 都是讀文件描述符的狀態,
函數原型:
int select (int numfds , fd_set *readfds , fd_set *writefds , fd_set *exceptfds , timeval *timeout );
其中 readfds 、writefds 、 exceptfds 、分別是被select()監視的讀、寫、和異常處理的文件描述符集合,numfds 的值是需要檢查的號碼最高的文件描述符加1.timeout參數是一個指向struct timeval類型的指針,它可以使select()在等待timeout時間後沒有文件描述符準備好則返回,這是一個超時時間。
fd_set的相關操作
清除一個文件描述符集合 : FD_ZERO(fd_set *set)
將一個文件描述符加入到一個文件描述符集合中去: FD_SET(int fd , fd_set *set);
將一個文件描述符從一個文件描述符集合中清除 : FD_CLR(int fd , fd_set *set)
判斷一個文件描述符是否在一個文件描述符集合中: FD_ISSET( int fd , fd_set *set)
使用的時候先將想要監控的文件描述符加入到相應的文件描述符集合中去,比如 想監控fd 的可讀寫狀態 就需要將fd加入到 readfds 和writefds 中去 exceptfds 和 timeout 可以爲NULL , 再調用select(),在用FD_ISSET檢查,readfds 中是否有fd 如果有就是可讀, 檢查 writefds 中是否有fd 如果有 就是可寫,。。。
這樣就可以讀取設備狀態
無論是poll 還是 select 都是一個目的,檢查文件描述符的狀態, 這兩個函數的內核入口都是 sys_poll 。。