Linux 非阻塞IO(轮询—poll机制)原理及架构

一. 非阻塞操作

非阻塞操作的进程在不能进行设备操作时,并不挂起,它或者放弃,或者不停地查询,直至可以进行操作为止。这就是我们常说的“轮询”。这是一种比较浪费CPU的方式。但是可以通过信号等方式以异步的形式提高CPU的利用率。
假设recvfrom函数是一个系统调用:
在这里插入图片描述
使用非阻塞I/O 的应用程序可借助轮询函数来查询设备是否能立即被访问,用户空间调用select()、poll()和epoll()接口,设备驱动提供poll()函数。设备驱动的poll()本身不会阻塞,但是poll()、select()和epoll()系统调用则会阻塞地等待文件描述符集合中的至少一个可访问或超时。

二. 设备驱动中的轮询编程

设备驱动中poll()函数的原型是:

unsigned int (*poll) (struct file *filp, struct poll_table *wait);

第一个参数为file结构体指针,第二个参数为轮询表指针。
该函数进行两项工作:
(1) 对可能引起设备文件状态变化的等待队列调用poll_wait()函数,将对应的等待队列添加到poll_table
(2) 返回表示是否能对设备进行无阻塞读、写访问的掩码。

返回值说明
POLLIN普通或优先级带数据可读
POLLRDNORM普通数据可读
POLLRDBAND优先级带数据可读
POLLPRI高优先级数据可读
POLLOUT普通数据可写
POLLWRNORM写普通数据不会导致阻塞
POLLWRBAND优先级带数据可写
POLLERR发生错误
POLLHUP发生挂起
POLLNVAL描述字不是一个打开的文件

用于向poll_table注册等待队列的poll_wait()函数原型如下:

static inline void poll_wait(struct file *filp, wait_queue_head_t *queue, poll_table *wait)
{
	if(wait && wait->qproc && queue)
		wait->qproc(filp, queue, wait);
}

作用:
将当前进程添加到wait参数指定的等待队列(poll_table)中

设备驱动中poll()函数的典型模板

static unsigned int xxx_poll(struct file *filp, poll_table *wait)
 {
	unsigned int mask = 0;
	struct xxx_dev *dev = filp->private_data; /* 获得设备结构体指针 */
	...
	poll_wait(filp, &dev->r_wait, wait); /* 加入读等待队列 */
	poll_wait(filp, &dev->w_wait, wait); /* 加入写等待队列 */

	if (...) /* 可读 */
		mask |= POLLIN | POLLRDNORM; /* 标示数据可获得(对用户可读) */

	if (...) /* 可写 */
		mask |= POLLOUT | POLLWRNORM; /* 标示数据可写入 */
	...
	return mask;
}

三. 从内核态分析poll机制

从应用程序直接调用poll函数,系统会走以下流程

app:poll() / select()
kernel:
	SYSCALL_DEFINE3(poll, struct pollfd _user*, ufds, unsigned int, nfds, int, timeout_msecs)
    --> do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, struct timespec *end_time)
    	--> poll_initwait(&table);
    		 --> init_poll_funcptr(&pwq->pt, __pollwait);
    			 --> pt->_qproc = __pollwait; ==>定义等待队列,并将该进程添加到等待队列队头中。对应驱动函数中的poll_wait()。

		-->fdcount = do_poll(nfds, head, &table, end_time);
			--> for (; pfd != pfd_end; pfd++) /* 可以监测多个驱动设备所产生的事件 */
				{   
					do_pollfd(pfd, pt) 
					-->mask = file->f_op->poll(file, pwait); > 实际效果:执行我们写的drivers_poll(file,wait)
					     return mask; /* 返回事件类型 */
				} 
				
				if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack)) /* 如果没有事件发生,那么陷入休眠状态 */ 
				--> int poll_sechedule_timeout (struct poll_wqueues* pwq, int state, ktime_t *expires, unsigned long slack)
					{
						...
						set_current_state(state);
						schedule_hrtimeout_rang()	⇒ schedule();//进程调度,使得该进程休眠
						__set_current_state(TASK_RUNNING);//唤醒该进程
						...
					}

		--> poll_freewait(&table)
			--> free_poll_entry()
				--> remove_wait_queue()	//移除等待队列
    	}
       

关于SYSCALL_DEFINEx请参考

https://blog.csdn.net/hxmhyp/article/details/22699669

  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
非阻塞IO是指在进行IO操作时,如果当前没有数据可读或者没有空闲空间可写,不会一直等待,而是立即返回一个错误码,让程序可以继续执行其他任务。 在Linux内核中,实现非阻塞IO的主要方法是使用异步IO轮询机制。 异步IO的实现原理是:应用程序通过对文件描述符进行异步IO设置,当异步IO操作完成时,内核会向应用程序发送一个信号,应用程序可以在信号处理函数中读取数据或者进行其他操作。 轮询机制的实现原理是:应用程序通过对文件描述符进行设置,将其添加到轮询列表中,然后不断地轮询这个列表,检查其中的文件描述符是否可读或可写,如果可读或可写,则进行IO操作,否则继续轮询非阻塞IO的实现步骤如下: 1. 设置文件描述符为非阻塞模式,可以使用fcntl函数进行设置。 2. 在进行IO操作前,使用select或者poll函数对文件描述符进行轮询或者异步IO设置。 3. 在IO操作返回错误码时,根据错误码进行判断,如果是EAGAIN或者EWOULDBLOCK,则说明当前没有数据可读或者没有空闲空间可写,可以进行其他操作;如果是其他错误码,则可能是IO操作出错,需要进行错误处理。 需要注意的是,在使用非阻塞IO时,应用程序需要不断地进行轮询或者异步IO设置,否则可能会出现数据丢失或者延迟等问题。同时,非阻塞IO也会增加CPU的负载,因此需要进行合理的优化和调整。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值