在file_operations中有許多的回呼函數,正是這些回呼函數實現了vfs,vfs提供了一個機制,這些回呼函數提供了不同的策略,等於說實現了vfs,照理說這些函數不應該有任何限制,但是唯獨一個poll回呼函數與眾不同,它不能睡眠,這是為何呢?
除了poll以外的回呼函數都擁有直接的語義,比如read就是讀,write就是寫,因此系統調用層可以直接將執行路徑交給vfs,比如在sys_read函數中,幾乎做了簡單的判斷之後就馬上調用了真本文件系統的file_operations的read函數,但是poll函數比較特殊,它並沒有簡單的語義,其實它就是輪詢,可是它不像read,write那樣系統調用層和vfs層那麼統一,poll在vfs層的意思就是“看看這個檔案是否有動作”,但是在系統調用層的意義就是“看看這些檔案中哪個有動作”,這就是不同,為了將系統調用層的語義平滑的過度到vfs層,就必須在系統調用和vfs只見插入機制,這個機制實現了poll,當然還包括select。在poll的實現中,靠的是進程的狀態來同步睡眠/喚醒動作的,它並不是在將進程加入睡眠隊列後馬上睡眠,而是不睡眠等到所有poll的檔案描述符均排入佇列後再睡眠,其實僅僅是左一個調度罷了,總體架構如下:
for (;;)
set_current_state(TASK_INTERRUPTIBLE)
for each fd to poll
ask driver if I/O can happen
add current process to driver wait queue
if one or more fds are ready
break
schedule_timeout_range(...)
注意,這裡是在一開始就將進程的狀態設定為TASK_INTERRUPTIBLE但是不睡眠,在中間的for迴圈中陸續將進程加入到睡眠隊列,到了最後才切換進程,等於說就是睡眠了,看看這個糟糕的實現,在設定了進程TASK_INTERRUPTIBLE狀態後那麼大一會才將進程切換,這很是醜陋,醜陋的本質原因就是vfs實現的poll是poll一個檔案描述符,但是系統調用的語義是poll一大堆的檔案描述符,在沒有必要添加適配機制的情況下,只好用進程狀態來實現了,2.6.29核心實在是看不下去這個局面了,於是提出了poll和別的file_operations的回呼函數一樣,也可以睡眠,並且可以用傳統的睡眠喚醒函數來喚醒進程:
+static int pollwake(wait_queue_t
*wait, unsigned mode, int sync, void *key)
+{
+ struct
poll_wqueues *pwq = wait->private;
+ DECLARE_WAITQUEUE(dummy_wait,
pwq->polling_task);
+
+ set_mb(pwq->triggered,
1);
+
+ /*
perform the default wake up operation */
+ return
default_wake_function(&dummy_wait, mode, sync, key);
+}
static void __pollwait(struct file
*filp, wait_queue_head_t *wait_address, poll_table *p)
{
- struct
poll_table_entry *entry = poll_get_entry(p);
+ struct
poll_wqueues *pwq = container_of(p, struct poll_wqueues, pt);
+ struct
poll_table_entry *entry = poll_get_entry(pwq);
if
(!entry)
return;
get_file(filp);
entry->filp
= filp;
entry->wait_address
= wait_address;
- init_waitqueue_entry(&entry->wait,
current);
+ init_waitqueue_func_entry(&entry->wait,
pollwake);
+ entry->wait.private
= pwq;
add_wait_queue(wait_address,
&entry->wait);
}
+int poll_schedule_timeout(struct
poll_wqueues *pwq, int state,
+ ktime_t *expires, unsigned long slack)
+{
+ int
rc = -EINTR;
+
+ set_current_state(state);
+ if
(!pwq->triggered)
+ rc
= schedule_hrtimeout_range(expires, slack, HRTIMER_MODE_ABS);
+ __set_current_state(TASK_RUNNING);
+
+ /*
clear triggered for the next iteration */
+ pwq->triggered
= 0;
+
+ return
rc;
+}
int do_select(int n, fd_set_bits
*fds, s
for
(;;) {
unsigned
long *rinp, *routp, *rexp, *inp, *outp, *exp;
- set_current_state(TASK_INTERRUPTIBLE);
inp
= fds->in; outp = fds->out; exp = fds->ex;
rinp
= fds->res_in; routp = fds->res_out; rexp = fds->res_ex;
@@ -411,10 +436,10 @@ int
do_select(int n, fd_set_bits *fds, s
to
= &expire;
}
- if
(!schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS))
+ if
(!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,
+ to, slack))
timed_out
= 1;
}
- __set_current_state(TASK_RUNNING);
poll_freewait(&table);
我們可以看到在這個可睡眠的poll的補丁中,去掉了刻意為了適配加入的設定進程狀態的語句,加入了統一的linux的睡眠/喚醒機制,poll_schedule_timeout是個新加入的函數,實際上它就是poll中的睡眠函數,和wait_event沒有本質區別的,這樣的話,加入這些函數,poll的實現和別的回呼函數變得統一起來的。
實際上,我發現在2.6.29核心中,代碼變得更加統一了,核心邏輯變得更加統一了,和前一篇文章我談到的cred從task_struct中分離一樣,poll可睡眠的意義也十分的大,比如將來為了加入新的機制要大改代碼,起碼file_operations中的回呼函數都是統一的,這樣就可以將之獨立成一個模組而不用和別的模組雜糅。