/* AUTHOR: Pinus
* Creat on : 2018-10-11
* KERNEL : linux-4.4.145
* BOARD : JZ2440(arm9 s3c2440)
* REFS : 韋東山視頻教程第二期
*/
概述
啥子事poll機制呢?直白來說,當你在應用程序中使用poll,程序就會在給定時間內進入沉睡狀態等待某項資源,
只回在兩種情況下返回結束,1.設定的等待時間到了;2.所等待的資源到了。
應用價值:還是說我們的按鍵程序,上一小節,我們雖然使用了中斷的方式,極大的節省了資源,但我們的測試程序一旦啓動,就會在while中循環等待中斷,永遠永遠,程序一直在運行,這顯然不好。所以這節就是引入poll機制,讓測試程序等待指定時間,如果一直沒有中斷產生,就會自動結束程序。
實驗
目標:爲(3.1)一個按鍵所能涉及的:按鍵中斷 添加poll機制處理方式
驅動程序的實現並不複雜,Linux作爲一個成熟的系統,已經爲我們做好了體系,我們往往只要實現具體的功能就可以了。
poll作爲應用中常用的功能,被定義在了file_operation結構體中(這個結構體是驅動的基礎啊)
下文只列出在(3.1)一個按鍵所能涉及的:按鍵中斷 添加poll機制處理方式 基礎上修改的部分
1.在file_operation結構體中添加poll函數
static const struct file_operations jz2440_buttons_fops = {
.owner = THIS_MODULE,
.read = buttons_drv_read,
.open = buttons_drv_open,
.release = buttons_drv_close,
.poll = buttons_drv_poll, /* poll的具體函數 */
};
2. 既然我們採用poll的方式休眠,自然不用再read的時候休眠了
static ssize_t buttons_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
if (1 != size)
return -EINVAL;
/*如果沒有按鍵發生, 休眠
* 如果有,返回
*/
//wait_event_interruptible(button_waitq, ev_press); /* 註釋掉這行 */
ev_press = 0;
copy_to_user(buf, &key_val, 1);
return 1;
}
3. 實現poll函數
unsigned int buttons_drv_poll (struct file *file, struct poll_table_struct *wait)
{
unsigned int mask = 0;
poll_wait(file, &button_waitq, wait); //不會立即休眠,只是將程序掛在隊列上
if(ev_press)
mask |= POLLIN | POLLRDNORM;
return mask;
}
看來關鍵的就是poll_wait(file, &button_waitq, wait); 函數,傳進三個參數
file:是監測的文件
button_waitq:是定義的睡眠隊列
wait:追加到設備驅動上的 poll_table結構體指針參數
4. 喚醒方式不變仍然在中斷處理函數中喚醒程序
ev_press = 1;
wake_up_interruptible(&button_waitq); /*喚醒*/
5. 測試程序
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
/*
* poll 機制
*/
int main(int argc, char **argv)
{
int fd;
char key_val;
struct pollfd fds[1];
int ret;
fd = open("/dev/buttons", O_RDWR);
if (fd < 0){
printf("can't open dev nod !\n");
return -1;
}
fds[0].fd = fd;
fds[0].events = POLLIN;
while(1){
ret = poll(fds, 1, 5000); // 等待5s,關鍵!!
if(ret == 1){
read(fd, &key_val, 1);
printf("key_val = 0x%x\n", key_val);
}
else{
printf("time out \n");
}
}
return 0;
}
原理淺析
驅動中poll原型:unsigned poll(struct file *file, poll_table *wait)
應用中poll原型:int poll(struct pollfd *fds, nfds_t nfds, int timeout);
其中
struct pollfd {
int fd;
short events;
short revents;
};
在linux中執行man poll可以看到events爲期待發生的事件,revent爲真實發生的事件
nfds爲*fds中pollfd的個數
timeout爲睡眠時間,毫秒單位。
1. poll作爲file_operation的一部分,在調用時與open等大致相同
對於系統調用poll或select,經過系統調用層unistd.h
#define __NR_poll 1068
__SYSCALL(__NR_poll, sys_poll)
linux內核通過宏定義調用, 函數有三個參數對應爲fs/select.c中
SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds, int, timeout_msecs)
{
...
ret = do_sys_poll(ufds, nfds, to);
...
}
do_sys_poll函數也位於位於fs/select.c文件中,我們忽略其他不重要的代碼:
int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout)
{
……
poll_initwait(&table);
fdcount = do_poll(nfds, head, &table, timeout);
poll_freewait(&table);
……
}
2. 先來看poll_initwait(&table)
poll_initwait函數非常簡單,它初始化一個poll_wqueues變量table:
poll_initwait >> init_poll_funcptr(&pwq->pt, __pollwait) >> pt->qproc = qproc;
即table->pt->qproc = __pollwait,__pollwait將在驅動的poll函數裏用到。在poll_initwait()中註冊一下回調函數__pollwait,它就是我們的驅動程序執行poll_wait()時,真正被調用的函數。
void poll_initwait(struct poll_wqueues *pwq)
{
init_poll_funcptr(&pwq->pt, __pollwait);
pwq->polling_task = current;
pwq->triggered = 0;
pwq->error = 0;
pwq->table = NULL;
pwq->inline_index = 0;
}
static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
{
pt->_qproc = qproc;
pt->_key = ~0UL; /* all events enabled */
}
3. 再來do_poll函數位於fs/select.c文件中,代碼如下:
static int do_poll(unsigned int nfds, struct poll_list *list,
struct poll_wqueues *wait, struct timespec *end_time)
{
01 ...
02 for (;;)
{
03 struct poll_list *walk;
04 bool can_busy_loop = false;
05 for (walk = list; walk != NULL; walk = walk->next)
{
06 struct pollfd * pfd, * pfd_end;
07 pfd = walk->entries;
08 pfd_end = pfd + walk->len;
//查詢多個驅動程序
09 for (; pfd != pfd_end; pfd++)
{
/*
* Fish for events. If we found one, record it
* and kill poll_table->_qproc, so we don't
* needlessly register any other waiters after
* this. They'll get immediately deregistered
* when we break out and return.
*/
//do_pollfd函數相當於調用驅動裏面的forth_drv_poll函數,下面另外再進行分析,返回值mask非零,count++,記錄等待事件發生的進程數
10 if (do_pollfd(pfd, pt, &can_busy_loop, busy_flag))
{
11 count++;
12 pt->_qproc = NULL;
/* found something, stop busy polling */
13 busy_flag = 0;
14 can_busy_loop = false;
}
}
}
/*
* All waiters have already been registered, so don't provide
* a poll_table->_qproc to them on the next loop iteration.
*/
15 pt->_qproc = NULL;
16 if (!count)
{
17 count = wait->error;
18 if (signal_pending(current))
19 count = -EINTR;
}
20 if (count || timed_out) /* 若count不爲0(有等待的事件發生了)或者timed_out不爲0(有信號發生或超時),則退出休眠 */
break;
/* only if found POLL_BUSY_LOOP sockets && not out of time */
21 if (can_busy_loop && !need_resched())
{
22 if (!busy_end)
{
23 busy_end = busy_loop_end_time();
24 continue;
}
25 if (!busy_loop_timeout(busy_end))
26 continue;
}
27 busy_flag = 0;
/*
* If this is the first loop and we have a timeout
* given, then we convert to ktime_t and set the to
* pointer to the expiry value.
*/
28 if (end_time && !to)
{
29 expire = timespec_to_ktime(*end_time);
30 to = &expire;
}
//上述條件不滿足下面開始進入休眠,若有等待的事件發生了,超時或收到信號則喚醒
31 if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack))
32 timed_out = 1;
}
33 return count;
}
分析其中的代碼,可以發現,它的作用如下:
① 從<02行>可以知道,這是個循環,它退出的條件爲:<20行>的2個條件之一( count非0,超時 )
count非0表示<10行>的do_pollfd至少有一個成功 or <17行>有錯誤產生 or <18行>有信號等待處理.
② 重點在do_pollfd函數,後面再分析
③ 第<31行>,讓本進程休眠一段時間,注意:應用程序執行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) { /* 當fops存在且poll被實際定義時 */
...
mask = file->f_op->poll(file, pwait);
...
}
/* Mask out unneeded events. */
mask &= pollfd->events | POLLERR | POLLHUP; //期望events和實際事件相與;相同有值,不同爲零
pollfd->revents = mask; /* 實際事件 */
return mask;
}
可見,它就是調用我們的驅動程序裏註冊的poll函數。再把驅動中的poll拷貝過來看一下。
unsigned int buttons_drv_poll (struct file *file, struct poll_table_struct *wait)
{
unsigned int mask = 0;
poll_wait(file, &button_waitq, wait); //不會立即休眠,只是將程序掛在隊列上
if(ev_press)
mask |= POLLIN | POLLRDNORM;
return mask;
}
可以預見
1. 當按鍵沒有被按下,也就是ev_press==0時,返回的mask=0;do_pollfd返回值爲0,count爲0,不滿足條件,循環繼續執行。
2. 按鍵按下,在中斷處理函數中,ev_press=1,則在poll函數中返回mask |= POLLIN | POLLRDNORM;,當和預期events(測試程序中)fds[0].events = POLLIN;相與後do_pollfd返回真,count++,退出循環,結束poll函數的等待,應用程序繼續運行。
5. 驅動程序裏與poll相關的地方有兩處:一是構造file_operation結構時,要定義自己的poll函數。二是通過poll_wait來調用上面說到的__pollwait函數,pollwait的代碼如下:
static inline 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函數,從它的代碼可知,它只是把當前進程掛入我們驅動程序裏定義的一個隊列裏而已。它的代碼如下:
static void __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函數時,進程並沒有休眠,只是把進程掛在了一個隊列裏。讓進程進入休眠,是前面分析的do_poll函數的<31行>poll_schedule_timeout()【思考一:這個函數具體是如何實現睡眠延時的?】函數。
poll_wait只是把本進程掛入某個隊列,應用程序調用poll > sys_poll > do_sys_poll > poll_initwait,do_poll > do_pollfd > fops.poll,再調用poll_schedule_timeout進入休眠。如果我們的驅動程序發現情況就緒,可以把這個隊列上掛着的進程喚醒。可見,poll_wait的作用,只是爲了讓驅動程序能找到要喚醒的進程。即使不用poll_wait,當超時,time_out=1,下次循環式程序也會退出。
現在來總結一下poll機制:
1. poll > sys_poll > do_poll > poll_initwait,poll_initwait函數註冊一下回調函數__pollwait,它就是我們的驅動程序執行poll_wait時,真正被調用的函數。
2. 接下來執行file->f_op->poll,即我們驅動程序裏自己實現的poll函數。 它會調用poll_wait把自己掛入某個隊列,這個隊列也是我們的驅動自己定義的,這個隊列的意義是爲了能讓其他程序知道去哪裏喚醒它。
3. 進程被喚醒的條件有2:一是上面說的“一定時間”到了,二是被驅動程序喚醒。驅動程序發現條件就緒時,就把“某個隊列”上掛着的進程喚醒,這個隊列,就是前面通過poll_wait把本進程掛過去的隊列。
4. 如果驅動程序沒有去喚醒進程,那麼poll_schedule_timeout超時後,time_out=1,下次循環式程序也會退出。
應用程序調用poll,經過一系列調用,進入do_poll,首先判斷一下是否有資源,有就直接結束,沒有等待喚醒或者超時,再次判斷是否有資源,有就退出。