从应用层到内核 poll()函数的分析

【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) 
参考资料: 韦东山的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;
}
  1. do_poll()函数在 count为零/超时/有信号等待处理 时退出;
  2. 当do_pollfd(pfd, pt)调用成功时返回非零值, 表示do_pollfd至少有一个成功;
  3. __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()函数

实现步骤分两步:

  1. 构造file_operation结构时, 定义poll函数;
  2. 通过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. 整个流程的结论

  1. 执行到驱动程序的poll_wait函数时, 进程并没有休眠, 我们的驱动程序里实现的poll函数是不会引起休眠的. 让进程进入休眠, 是在do_pollfd(pfd, pt)函数后面的schedule_timeout(__timeout);函数
  2. poll_wait只是把本进程挂入某个队列, 应用程序调用poll > sys_poll > do_sys_poll > poll_initwait, do_poll > do_pollfd > 我们自己写的poll函数后, 再调用schedule_timeout进入休眠.如果我们的驱动程序发现情况就绪, 可以把这个队列上挂着的进程唤醒.
  3. 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");
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
应用层中,`poll()`函数是用于监视多个文件描述符的函数,并等待其中的一个或多个文件描述符上发生事件。它的原型如下: ```c #include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout); ``` 其中,参数说明如下: - `fds`:一个指向`struct pollfd`结构体数组的指针,每个结构体描述一个文件描述符以及该文件描述符上感兴趣的事件。 - `nfds`:`fds`数组中元素的个数。 - `timeout`:等待的超时时间,以毫秒为单位。传递负值表示无限等待,传递0表示立即返回。 `struct pollfd`结构体定义如下: ```c struct pollfd { int fd; // 文件描述符 short events; // 需要监视的事件(可使用POLLIN、POLLOUT等常量组合) short revents; // 实际发生的事件(由内核填充) }; ``` `events`字段用于指定需要监视的事件,可以使用以下常量进行组合: - `POLLIN`:可读事件 - `POLLOUT`:可写事件 - `POLLERR`:错误事件 - `POLLHUP`:挂起事件 - `POLLNVAL`:无效事件 `revents`字段由内核在返回时填充,表示实际发生的事件。 `poll()`函数的返回值表示发生事件的文件描述符数量,返回值为负数表示出错,0表示超时,正数表示发生事件的文件描述符数量。 注意,在使用`poll()`函数前,需要包含头文件`<poll.h>`。另外,还可以使用`pselect()`函数和`ppoll()`函数来替代`poll()`函数,它们在一些特定的场景下提供了更多的功能和选项。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

安河桥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值