linux驅動編寫之poll機制


一、概念

1、poll情景描述

      以按鍵驅動為例進行說明,用阻塞的方式打開按鍵驅動文件/dev/buttons,應用程序使用read()函數來讀取按鍵的鍵值。這樣做的效果是:如果有按鍵按下了,調用該read()函數的進程,就成功讀取到數據,應用程序得到繼續執行;倘若沒有按鍵按下,則要一直處於休眠狀態,等待這有按鍵按下這樣的事件發生。

      這種功能在一些場合是適用的,但是並不能滿足我們所有的需要,有時我們需要一個時間節點。倘若沒有按鍵按下,那么超過多少時間之后,也要返回超時錯誤信息,進程能夠繼續得到執行,而不是沒有按鍵按下,就永遠休眠。這種例子其實還有很多,比方說兩人相親,男方等待女方給個確定相處的信,男方不可能因為女方不給信,就永遠等待下去,雙方需要一個時間節點。這個時間節點,就是說超過這個時間之后,不能再等了,程序還要繼續運行,需要采取其他的行動來解決問題。

example:  

      單片機編程,等待IIC設備一個事件的發生,如果在允許的時間內發生了就返回1(SUCCESS),否則返回0(ERROR)。

uint8_t I2C_WaitForEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT,int32_t delay)
{    

    while(!I2C_CheckEvent(I2Cx, I2C_EVENT) && (delay-- > 0));
    
    if(delay < 0){
        return 0;
    }
    
    return 1;
}

      此段函數代碼可以這樣來調用,如下:

int8_t I2C_EE_PageWrite(u8* pBuffer, u16 WriteAddr, u8 NumByteToWrite)
{
      .............
      if(I2C_WaitForEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED, 100000) != 1){
            return -1;
      }
      ............
}

      這個例子是STM32單片機寫i2cflash--AT24C02,可見上述的頁寫函數調用的等待字節傳輸完成函數(I2C_EVENT_MASTER_BYTE_TRANSMITTED)

,如果在限定的時間內(CPU將100000減到0),還沒有成功寫入,那么就將返回超時錯誤,頁寫函數也會返回寫入失敗的錯誤信息。之后,任務重新得到了運行。

      對於單片機這樣通常單任務運行的狀況,必須采取這樣的措施。如果沒有超時限制,那么程序將陷入死機,不能再繼續運行。

2、linux應用程序poll的使用

     對於類似的場景,linux系統使用poll功能來解決這樣的問題。而且,與上述單片機等待方式不同,linux系統再調用poll()函數時候,如果沒有發生需要的事件,那么進程進入休眠。如果在限定的時間內得到需要的事件,那么成功返回,如果沒有則返回超時錯誤信息。

     可見,等待期間將進程休眠,利用事件驅動來喚醒進程,將更能提高CPU的效率。下面,以一個應用例程來說明poll的應用程序使用方法:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <poll.h>

int main(int argc, char **argv)
{
    int i;
    int ret;
    int fd;
    unsigned char keys_val;
    struct pollfd fds[1];
    
    fd = open("/dev/buttons", 0);  // 打開設備
    if (fd < 0) {
        printf("Can't open /dev/buttons\n");
        return -1;
    }

    fds[0].fd = fd;
    fds[0].events = POLLIN;
    
    while (1) {
        ret = poll(fds,1, 5000);
        if(ret == 0)
        {
            printf("time out!\n");
        }
        else
        {
            read(fd, &keys_val, sizeof(keys_val));
            printf("keys_val = 0x%x\n",keys_val);
        }
    }
    
    close(fd);
    return 0;    
}

       例程實現的功能是這樣的:用poll()函數監測按鍵按下的事件,如果按下了就將鍵值打印出來;如果超過5S,還沒有按鍵按下,就打印出超時信息。

3、poll()函數

      函數原型

int poll(struct pollfd *fds, nfds_t nfds, int timeout)

     輸入參數

fds         可以傳遞多個結構體,也就是說可以監測多個驅動設備所產生的事件,只要有一個產生了請求事件,就能立即返回

    struct pollfd {
          int fd;                  /* 文件描述符 */
          short events;        /* 請求的事件類型,監視驅動文件的事件掩碼 */
          short revents;       /* 驅動文件實際返回的事件 */
    } ;

nfds       監測驅動文件的個數

timeout  超時時間,單位為ms 

     事件類型events 可以為下列值:

POLLIN           有數據可讀
POLLRDNORM 有普通數據可讀,等效與POLLIN
POLLPRI         有緊迫數據可讀
POLLOUT        寫數據不會導致阻塞
POLLER          指定的文件描述符發生錯誤
POLLHUP        指定的文件描述符掛起事件
POLLNVAL      無效的請求,打不開指定的文件描述符

     返回值

有事件發生        返回revents域不為0的文件描述符個數(也就是說事件發生,或者錯誤報告)

超時                返回0;

失敗            返回-1,並設置errno為錯誤類型

二、驅動實現方法

/* 定義一個等待隊列,這個等待隊列實際上是由中斷驅動的,當中斷發生時,會令掛接到這個等待隊列的休眠進程喚醒 */
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

static
unsigned drivers_poll(struct file *file, poll_table *wait) { unsigned int mask = 0; poll_wait(file, &button_waitq, wait); /* 將進程掛接到button_waitq等待隊列下 */
/* 根據實際情況,標記事件類型 */
if (ev_press) mask |= POLLIN | POLLRDNORM;
/* 如果mask為0,那么證明沒有請求事件發生;如果非零說明有時間發生 */
return mask; }

      上述代碼展示了一個poll()函數功能,具體對應的底層驅動實現細節。利用這樣的框架,我們可以寫出類似驅動的poll功能。但是,這個框架很難理解,不知道為什么這樣編寫?為此,我們需要了解linux系統poll功能實現的機制。

三、linux內核poll實現機制

     從應用程序調用poll()函數開始,一直到調用drivers_poll函數,期間的過程很復雜,撿主要的內容列出來:

app: poll
      |
drv:sys_poll
      |
      — do_sys_poll(struct pollfd __user * ufds, unsigned int nfds, struct timespec * end_time)
        |   
        - poll_initwait(&table);  >  實際效果:令函數指針 table.pt.qproc = __pollwait,這個函數指針最終會傳遞給poll_wait函數調用中的wait->qproc
        |
        - do_poll(nfds, head, &table, end_time);
        |
_
for ( ; ; ) { for (; pfd != pfd_end; pfd++) { /* 可以監測多個驅動設備所產生的事件 */ if (do_pollfd(pfd, pt)) { |
_
mask = file->f_op->poll(file, pwait); > 實際效果:執行我們寫的drivers_poll(file,pwait)
                                |
_ poll_wait(file,
&button_waitq, wait); > 實際效果:執行__pollwait(file, &button_waitq, wait),也就是將
進程掛接到button_waitq等待隊列下
|
mask賦值 ; return mask; /* 返回事件類型 */
                         pollfd->revents = mask;    /* 將實際事件類型返回 */
count
++; pt = NULL;
}
}
if (count || timed_out) /* 如果有事件發生,或者超時,則跳出poll */
break;
if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack)) /* 如果沒有事件發生,那么陷入休眠狀態 */
timed_out
= 1;
}

      由此可見,我們的drivers_poll()函數,是系統在執行sys_poll()過程中的一個調用,調用的目的是“將進程掛接到等待隊列下”和“返回事件類型mask”。當已經發生了請求事件,那么通過標記mask非0,if (do_pollfd(pfd, pt))判斷為真,令count++,從而可以直接令poll()函數成功返回。如果還沒有發生請求的事件,那么mask被標記為0,進程將通過函數poll_schedule_timeout()陷入休眠狀態。一旦發生了請求的事件,因為之前已經將進程掛接到等待隊列下,所以進程將被喚醒,重新執行drivers_poll(),而顯然此時能夠成功返回。

備注:分析的源碼版本為linux-2.6.30.4。

 

參考資料:韋東山linux教學視頻           

              linux poll函數


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM