【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)
参考资料: 韦东山的poll机制分析.
从应用层到内核 poll()函数的分析
1. 应用层调用poll
struct pollfd {
int fd; /* 需要被监视的文件描述符*/
short events; /* 对文件描述符fd上感兴趣的事件 */
short revents; /* 文件描述符fd上当前实际发生的事件 */
};
/**
* wait for some event on a file descriptor
等待文件描述符上的某些事件, 得到/超时后返回.
*
* @param fds struct pollfd类型的结构体数组, 列出了我们需要poll()监视的文件描述符以及对应的事件;
* @param nfds fds中的数组个数
* @param timeout 超时时间, 以毫秒作为单位.
*
* @return 成功时, 返回一个正数, 表示有多少个fd得到了事件(这个事件不一定是我们想要的);
* 返回0, 表示超时并且没有fd得到事件;
* 返回-1, 表示错误发生.
*/
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
示例代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
int main(int argc, char **argv)
{
int value, ret;
int fd = -1;
struct pollfd fds[1];
fd = open("/dev/yangbkDevice", O_RDWR);
if(fd < 0) {
perror("open() fail:");
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, &value, sizeof(value));
printf("value = %d\n", value);
}
}
close(fd);
return 0;
}
2. Linux kernel中poll()的实现
对于系统调用poll或select, 它们对应的内核函数都是sys_poll(). 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函数也位于位于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_initwait函数非常简单,它初始化一个poll_wqueues变量table:poll_initwait > init_poll_funcptr(&pwq->pt, __pollwait); > pt->qproc = qproc; 即table->pt->qproc = __pollwait,__pollwait将在驱动的poll函数里用到。
static int do_poll(unsigned int nfds, struct poll_list *list,
struct poll_wqueues *wait, s64 *timeout)
{
for (;;) {
if (do_pollfd(pfd, pt)) {
count++;
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_poll()函数在 count为零/超时/有信号等待处理 时退出;
- 当do_pollfd(pfd, pt)调用成功时返回非零值, 表示do_pollfd至少有一个成功;
- __timeout = schedule_timeout(__timeout); 让本进程休眠一段时间;
应用程序执行poll调用后, 如果不能立刻返回, 进程就会进入休眠.
那么, 谁唤醒呢?
除了休眠到指定时间被系统唤醒外, 还可以被驱动程序唤醒(wake_up_interruptible).
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)
mask = file->f_op->poll(file, pwait);
......
}
最终调用到了我们驱动层面上的poll()函数~
3. 驱动层面上的poll()函数
实现步骤分两步:
- 构造file_operation结构时, 定义poll函数;
- 通过poll_wait来调用__pollwait函数;
代码如下所示:
static unsigned int btn_poll(struct file *pfile, struct poll_table_struct *wait)
{
unsigned int mask = 0;
poll_wait(pfile, &button_waitq, wait); // 不会立即休眠
if (ev_press)
mask |= POLLIN | POLLRDNORM;
return mask;
}
static struct file_operations btn_fops = {
.owner = THIS_MODULE,
.open = btn_open,
.read = btn_read,
.release = btn_close,
.poll = btn_poll,
};
4. 整个流程的结论
- 执行到驱动程序的poll_wait函数时, 进程并没有休眠, 我们的驱动程序里实现的poll函数是不会引起休眠的. 让进程进入休眠, 是在do_pollfd(pfd, pt)函数后面的schedule_timeout(__timeout);函数
- poll_wait只是把本进程挂入某个队列, 应用程序调用poll > sys_poll > do_sys_poll > poll_initwait, do_poll > do_pollfd > 我们自己写的poll函数后, 再调用schedule_timeout进入休眠.如果我们的驱动程序发现情况就绪, 可以把这个队列上挂着的进程唤醒.
- poll_wait的作用只是为了让驱动程序能找到要唤醒的进程, 即使不用poll_wait, 我们的程序也有机会被唤醒: chedule_timeout(__timeout), 只是休眠__time_out这段时间.
5. 驱动层的完整代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/poll.h>
static dev_t dev_num = 0;
static struct cdev *cdevice = NULL;
static struct class *sys_class = NULL;
static struct device *class_device = NULL;
struct pin_desc{
volatile unsigned int *gpio_con;
volatile unsigned int *gpio_dat;
int irq;
};
static int gkey_val;
struct pin_desc pins_desc[1] = {
{NULL, NULL},
};
/* 中断事件标志, 中断服务程序将它置1,btn_read将它清0 */
static volatile int ev_press = 0;
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
static irqreturn_t btn_handler(int irq, void *dev_id)
{
struct pin_desc *pindesc = (struct pin_desc *)dev_id;
gkey_val = *(pindesc->gpio_dat);
ev_press = 1; /* 表示中断发生了 */
wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */
return IRQ_RETVAL(IRQ_HANDLED);;
}
int btn_open(struct inode *pinode, struct file *pfile)
{
int ret = -1;
// input mode
*(pins_desc[0].gpio_con + 100) &= 0<<1;
// request irq
ret = request_irq(pins_desc[0].irq, btn_handler, IRQF_TRIGGER_HIGH | IRQF_SHARED,
"ybk_btn", &pins_desc[0]);
printk("ret = %d, pins_desc[0].irq = %d\n", ret, pins_desc[0].irq);
return 0;
}
int btn_close(struct inode *pinode, struct file *pfile)
{
free_irq(pins_desc[0].irq, &pins_desc[0]);
return 0;
}
ssize_t btn_read(struct file *pfile, char __user *userbuf, size_t size, loff_t *loff)
{
/**
* 轮询方式的read()
*
* int value = *(pins_desc[0].gpio_dat);
* copy_to_user(userbuf, &value, sizeof(value));
*/
if(size != sizeof(int))
return -EINVAL;
/* 如果没有按键动作, 休眠 */
wait_event_interruptible(button_waitq, ev_press);
/* 如果有按键动作, 返回键值 */
copy_to_user(userbuf, &gkey_val, sizeof(gkey_val));
ev_press = 0;
return size;
}
static unsigned int btn_poll(struct file *pfile, struct poll_table_struct *wait)
{
unsigned int mask = 0;
poll_wait(pfile, &button_waitq, wait); // 不会立即休眠
if (ev_press)
mask |= POLLIN | POLLRDNORM;
return mask;
}
static struct file_operations btn_fops = {
.owner = THIS_MODULE,
.open = btn_open,
.read = btn_read,
.release = btn_close,
.poll = btn_poll,
};
int btn_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *dp_node = dev->of_node;
struct resource *res = NULL;
int i, reg[2];
for(i=0; i<sizeof(pins_desc)/sizeof(pins_desc[0]); i++) {
res = platform_get_resource(pdev, IORESOURCE_IRQ, i);
if(res) {
pins_desc[i].irq = res->start;
} else {
printk("get irq failed!\n");
return -EINVAL;
}
of_property_read_u32_array(dp_node, "reg", reg, sizeof(reg)/sizeof(reg[0]));
pins_desc[i].gpio_con = (volatile unsigned int *)ioremap(reg[0], reg[1]);
pins_desc[i].gpio_dat = pins_desc[i].gpio_con + 2;
}
printk("btn_probe, found btn\n");
cdevice = cdev_alloc();
cdev_init(cdevice, &btn_fops);
alloc_chrdev_region(&dev_num, 0, 1, "yangbkBtn");
cdev_add(cdevice, dev_num, 1);
sys_class = class_create(THIS_MODULE, "yangbkClass");
class_device = device_create(sys_class, NULL, dev_num, NULL, "yangbkDevice");
return 0;
}
int btn_remove(struct platform_device *pdev)
{
printk("btn_remove, remove btn\n");
device_destroy(sys_class, dev_num);
class_destroy(sys_class);
unregister_chrdev_region(dev_num, 1);
cdev_del(cdevice);
iounmap(pins_desc[0].gpio_con);
return 0;
}
static struct of_device_id of_match_table = {
.compatible = "ybk_btn",
.data = NULL,
};
struct platform_driver btn_drv = {
.probe = btn_probe,
.remove = btn_remove,
.driver = {
.name = "idle",
.of_match_table = &of_match_table,
}
};
static int __init btn_drv_init(void)
{
platform_driver_register(&btn_drv);
return 0;
}
static void __exit btn_drv_exit(void)
{
platform_driver_unregister(&btn_drv);
}
module_init(btn_drv_init);
module_exit(btn_drv_exit);
MODULE_AUTHOR("yangbkGit");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("platform driver model.");
MODULE_ALIAS("model");