字符设备驱动(六)按键poll机制
字符设备驱动(六)按键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");