天天看點

可以睡眠的poll

在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中的回調函數都是統一的,這樣就可以将之獨立成一個子產品而不用和别的子產品雜糅。

 本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1274186

繼續閱讀