字元裝置驅動(六)按鍵poll機制
title: 字元裝置驅動(六)按鍵poll機制
tags: linux
date: 2018-11-23 18:57:40
toc: true
---
字元裝置驅動(六)按鍵poll機制
引入
在字元裝置驅動(五)按鍵休眠
中的App
中雖然使用了休眠,但是如果Read沒有返回的話會一直死等,類似阻塞,我們期望等待一段時間後自動返回,等待的時候程式依然是睡眠的,這裡引入poll
機制
應用程式的open/close/write/read
都有對應的系統核心的sys_open/sys_close/sys_read/sys_write
,同樣的,poll
對應了系統呼叫sys_poll
程式分析
sys_poll
sys_poll
函式會對超時引數timeout_msecs
作簡單處理後呼叫do_sys_poll
//fs/select.c asmlinkage long sys_poll(struct pollfd __user *ufds, unsigned int nfds, long timeout_msecs) { s64 timeout_jiffies; if (timeout_msecs > 0) { #if HZ > 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
函式do_sys_poll
會呼叫poll_initwait
來初始化一個struct poll_wqueues table
,也就是table->pt->qproc = __pollwait
,__pollwait
將在驅動的poll
函式裡用到,接著呼叫do_poll
//fs/select.c int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout) { struct poll_wqueues table; int fdcount, err; unsigned int i; struct poll_list *head; struct poll_list *walk; /* Allocate small arguments on the stack to save memory and be faster - use long to make sure the buffer is aligned properly on 64 bit archs to avoid unaligned access */ long stack_pps[POLL_STACK_ALLOC/sizeof(long)]; struct poll_list *stack_pp = NULL; /* Do a sanity check on nfds ... */ if (nfds > current->signal->rlim[RLIMIT_NOFILE].rlim_cur) return -EINVAL; poll_initwait(&table); head = NULL; walk = NULL; i = nfds; err = -ENOMEM; while(i!=0) { struct poll_list *pp; int num, size; if (stack_pp == NULL) num = N_STACK_PPS; else num = POLLFD_PER_PAGE; if (num > i) num = i; size = sizeof(struct poll_list) + sizeof(struct pollfd)*num; if (!stack_pp) stack_pp = pp = (struct poll_list *)stack_pps; else { pp = kmalloc(size, GFP_KERNEL); if (!pp) goto out_fds; } pp->next=NULL; pp->len = num; if (head == NULL) head = pp; else walk->next = pp; walk = pp; if (copy_from_user(pp->entries, ufds + nfds-i, sizeof(struct pollfd)*num)) { err = -EFAULT; goto out_fds; } i -= pp->len; } fdcount = do_poll(nfds, head, &table, timeout); /* OK, now copy the revents fields back to user space. */ walk = head; err = -EFAULT; while(walk != NULL) { struct pollfd *fds = walk->entries; int j; for (j=0; j < walk->len; j++, ufds++) { if(__put_user(fds[j].revents, &ufds->revents)) goto out_fds; } walk = walk->next; } err = fdcount; if (!fdcount && signal_pending(current)) err = -EINTR; out_fds: walk = head; while(walk!=NULL) { struct poll_list *pp = walk->next; if (walk != stack_pp) kfree(walk); walk = pp; } poll_freewait(&table); return err; }
poll_initwait
這個初始化函式相當重要,最後賦值table->pt->qproc=__pollwait
poll_initwait(&table);
void poll_initwait(struct poll_wqueues *pwq)
{
init_poll_funcptr(&pwq->pt, __pollwait);
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;
}
也就是最終是
table->pt->qproc=__pollwait
do_poll
static int do_poll(unsigned int nfds, struct poll_list *list,
struct poll_wqueues *wait, s64 *timeout)
{
int count = 0;
poll_table* pt = &wait->pt;
/* Optimise the no-wait case */
if (!(*timeout))
pt = NULL;
for (;;) {
struct poll_list *walk;
long __timeout;
set_current_state(TASK_INTERRUPTIBLE);
for (walk = list; walk != NULL; walk = walk->next) {
struct pollfd * pfd, * pfd_end;
pfd = walk->entries;
pfd_end = pfd + walk->len;
for (; pfd != pfd_end; pfd++) {
/*
* Fish for events. If we found one, record it
* and kill the poll_table, so we don't
* needlessly register any other waiters after
* this. They'll get immediately deregistered
* when we break out and return.
*/
if (do_pollfd(pfd, pt)) {
count++;
pt = NULL;
}
}
}
/*
* All waiters have already been registered, so don't provide
* a poll_table to them on the next loop iteration.
*/
pt = NULL;
if (count || !*timeout || signal_pending(current))
break;
count = wait->error;
if (count)
break;
if (*timeout < 0) {
/* Wait indefinitely */
__timeout = MAX_SCHEDULE_TIMEOUT;
} else if (unlikely(*timeout >= (s64)MAX_SCHEDULE_TIMEOUT-1)) {
/*
* Wait for longer than MAX_SCHEDULE_TIMEOUT. Do it in
* a loop
*/
__timeout = MAX_SCHEDULE_TIMEOUT - 1;
*timeout -= __timeout;
} else {
__timeout = *timeout;
*timeout = 0;
}
__timeout = schedule_timeout(__timeout);
if (*timeout >= 0)
*timeout += __timeout;
}
__set_current_state(TASK_RUNNING);
return count;
}
退出條件:超時,接受到訊號,
do_pollfd
查詢到資料了(count>0)
pt = NULL; if (count || !*timeout || signal_pending(current)) break; count = wait->error; if (count) break;
休眠,如富哦上述的退出條件不滿則則休眠,喚醒的條件是[指定時間超時或應用程式喚醒]
__timeout = schedule_timeout(__timeout);
do_pollfd
這個函式在do_poll
中用來查詢具體的資料,這個會呼叫最終的file->f_op->poll(file, pwait);
,也就是具體到file
結構也就是我們的驅動程式的poll
static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait)
{
unsigned int mask;
int fd;
mask = 0;
fd = pollfd->fd;
if (fd >= 0) {
int fput_needed;
struct file * file;
file = fget_light(fd, &fput_needed);
mask = POLLNVAL;
if (file != NULL) {
mask = DEFAULT_POLLMASK;
if (file->f_op && file->f_op->poll)
mask = file->f_op->poll(file, pwait);
/* Mask out unneeded events. */
mask &= pollfd->events | POLLERR | POLLHUP;
fput_light(file, fput_needed);
}
}
pollfd->revents = mask;
return mask;
}
__pollwait
我們在poll_initwait
中定義了table->pt->qproc=__pollwait
,驅動程式的poll
函式會呼叫poll_wait
來呼叫這個p->qproc
,也就是table
中的__pollwait
,這個函式只是把當前程序掛入我們驅動程式裡定義的一個佇列裡而已,用於查詢到具體的任務以退出.並不是直接休眠,真正的休眠在do_sys_poll
.如果沒有poll_wait
具體的佇列,只能等待超時退出了
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);
}
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
>sys_poll
>do_sys_poll
>poll_initwait
,poll_initwait
函式註冊一下回調函式__pollwait
,它就是我們的驅動程式執行poll_wait
時,真正被呼叫的函式。- 驅動程式的
poll
會執行__pollwait
將自己的程序掛入佇列,判斷是否就緒 - 如果沒有就緒,
do_sys_poll
中進入休眠一段小時間,如果就緒直接退出 一段小時間過後起來再查詢,到步驟3,或者發現超時時間到退出,
程式更改
APP
App
修改使用poll
查詢
flag=poll 掛入查詢機制,超時機制
if(flag) 超時退出
...
else 查詢到有效資料
...read
..read這裡還可以休眠,應該是沒必要休眠的了
poll查詢可以一次查詢多個驅動程式,我們只需要查詢一個就好了.
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
POLLIN There is data to read.
int poll(struct pollfd *fds, nfds_t nfds, int timeout); //返回0表示超時
其中events=POLLIN
表示期待有資料讀取.
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
int main(int argc, char **argv)
{
int fd;
unsigned char key_val;
int ret;
struct pollfd fds[1];
fd = open("/dev/buttons", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}
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, &key_val, 1);
printf("key_val = 0x%x\n", key_val);
}
}
return 0;
}
驅動函式
poll
函式的加入
static unsigned drv_poll(struct file *file, poll_table *wait)
{
unsigned int mask = 0;
poll_wait(file, &button_waitq, wait); // 不會立即休眠
if (ev_press)
mask |= POLLIN | POLLRDNORM;
return mask;
}
測試
載入驅動後可以發現超時後會列印超時,按鍵有輸出
# insmod dri.ko
# ./test /dev/xyz0
time out
irq55
key_val = 0x3
irq55
key_val = 0x83
ps
查詢是休眠狀態,top
查詢cpu
也比較低
#ps
PID Uid VSZ Stat Command
781 0 1312 S ./test /dev/xyz0
#top
PID PPID USER STAT VSZ %MEM %CPU COMMAND
781 770 0 S 1312 2% 0% ./test /dev/xyz0
嘗試刪除read
中的等待休眠,cpu
佔用率依然是低的
刪除中斷的喚醒
//wake_up_interruptible(&button_waitq); /* 喚醒休眠的程序 */
刪除read的休眠
//wait_event_interruptible(button_waitq, flag);
完整的驅動程式
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>
//#include <linux/interrupt.h>
volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;
volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;
static struct class *drv_class;
static struct class_device *drv_class_dev;
// 定義一個名為`button_waitq`的佇列
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
// flag=1 means irq happened and need to update
int flag=0;
struct pin_desc{
unsigned int pin;
unsigned int key_val;
};
/* 鍵值: 按下時, 0x01, 0x02, 0x03, 0x04 */
/* 鍵值: 鬆開時, 0x81, 0x82, 0x83, 0x84 */
static unsigned char key_val;
struct pin_desc pins_desc[4] = {
{S3C2410_GPF0, 0x01},
{S3C2410_GPF2, 0x02},
{S3C2410_GPG3, 0x03},
{S3C2410_GPG11, 0x04},
};
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
printk("irq%d\r\n",irq);
struct pin_desc * pindesc = (struct pin_desc *)dev_id;
unsigned int pinval;
pinval = s3c2410_gpio_getpin(pindesc->pin);
if (pinval)
{
/* 鬆開 */
key_val = 0x80 | pindesc->key_val;
}
else
{
/* 按下 */
key_val = pindesc->key_val;
}
//wake_up_interruptible(&button_waitq); /* 喚醒休眠的程序 */
flag=1;
return IRQ_RETVAL(IRQ_HANDLED);
}
static unsigned drv_poll(struct file *file, poll_table *wait)
{
unsigned int mask = 0;
poll_wait(file, &button_waitq, wait); // 不會立即休眠
if (flag)
mask |= POLLIN | POLLRDNORM;
return mask;
}
static int drv_open(struct inode *inode, struct file *file)
{
/* 配置GPF0,2為輸入引腳 */
/* 配置GPG3,11為輸入引腳 */
request_irq(IRQ_EINT0, buttons_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
request_irq(IRQ_EINT2, buttons_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]);
request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2]);
request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "S5", &pins_desc[3]);
return 0;
}
int drv_close(struct inode *inode, struct file *file)
{
free_irq(IRQ_EINT0, &pins_desc[0]);
free_irq(IRQ_EINT2, &pins_desc[1]);
free_irq(IRQ_EINT11,&pins_desc[2]);
free_irq(IRQ_EINT19,&pins_desc[3]);
return 0;
}
static ssize_t drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
//int minor = MINOR(file->f_dentry->d_inode->i_rdev);
//printk("drv_write=%d\n",minor);
return 0;
}
static ssize_t drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
if (size != 1)
return -EINVAL;
/* 如果沒有按鍵動作, 休眠 */
//wait_event_interruptible(button_waitq, flag);
/* 如果有按鍵動作, 返回鍵值 */
copy_to_user(buf, &key_val, 1);
flag = 0;
return 1;
}
static struct file_operations drv_fops = {
.owner = THIS_MODULE, /* 這是一個巨集,推向編譯模組時自動建立的__this_module變數 */
.open = drv_open,
.write = drv_write,
.read = drv_read,
.release = drv_close,
.poll = drv_poll,
};
static int major;
static int drv_init(void)
{
int minor=0;
major=register_chrdev(0, "drv", &drv_fops); // 註冊, 告訴核心
drv_class = class_create(THIS_MODULE, "drv");
drv_class_dev = class_device_create(drv_class, NULL, MKDEV(major, 0), NULL, "xyz%d", minor);
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
gpfdat = gpfcon + 1;
gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
gpgdat = gpgcon + 1;
return 0;
}
static void drv_exit(void)
{
unregister_chrdev(major, "drv"); // 解除安裝
class_device_unregister(drv_class_dev);
class_destroy(drv_class);
iounmap(gpfcon);
iounmap(gpgcon);
}
module_init(drv_init);
module_exit(drv_exit);
MODULE_AUTHOR("xxx");
MODULE_VERSION("0.1.0");
MODULE_DESCRIPTION("S3C2410/S3C2440 LED Driver");
MODULE_LICENSE("GPL");