Linux驱动学习记录-15.非阻塞(轮询)及其程序

上一节介绍了阻塞,这次是非阻塞IO。

一、非阻塞介绍

应用程序使用非阻塞访问设备时,当设备不可用,就会返回一个错误码,表示数据读取失败,应用程序会重新读取数据,一直循环,直达读到数据。
简单示例:

char buf;
fd = open("/dev/led", O_RDWR | O_NONBLOCK);
...
while(read(fd, &buf, 1) != 1)
printf("%c",buf);

二、轮询

应用程序需要非阻塞的方式,那么驱动程序就要提供非阻塞的处理方式:轮询。poll、epoll、select三个函数。

1.select

函数原型:

int select(int nfds,          //所监视三类文件描述符,加一。
		   fd_set *readfds,   //监视描述符集的读变化
		   fd_set *writefds,  //监视描述符集的写变化
		   fd_set *exceptfds, //监视文件异常
		   struct timeval *timeout) //超时时间

struct timeval{long tv_sec; //秒
			   long tv_usec; //微秒
			   };

三个参数是fd_set类型的,以readfds为例,如果文件可读,则返回一个大于0的值表示可以读取,如果没有可读的,会判断是否超时。可将read设置为NULL,表示不关心读变化。
几个宏操作:

void FD_ZERO(fd_set *set)  //所有位清零
void FD_SET(int fd, fd_set *set) //某个位置置一
void FD_CLR(int fd, fd_set *set) //某个位清零
int FD_ISSET(int fd, fd_set *set) //一个文件是否属于某个集合

示例如下:

void main (void)
{
	int fd;
	fd_set readfds;
	struct timeval timeout;
	fd = open("/dev/xxx", O_RDWR | O_NONBLOCK);
	FD_ZERO(&readfds);
	FD_SET(fd, &readfds);
	timeout.tv_sec = 0;
	timeout_tv_usec = 500000;
	ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
	switch(ret){
	}
}

2.poll函数

在单个线程中,select函数能够监视的最大描述符有上限1024,poll函数没有限制,本质和select没差别。原型如下

int poll(struct pollfd *fds,
		 nfds_t nfds, //要监视的文件描述符数量
		 int timeout) //超时时间
		 
struct pollfd{
	int fd;        //文件描述符
	short events;  //请求的事件
	short revents; //返回的事件
	}; 

events监视的事件类型如下几种:

  • POLLIN :有数据可以读取
  • POLLPRI:紧急数据需要读取
  • POLLOUT:可以写数据
  • POLLERR:指定描述符发生错误
  • POLLHUP:文件描述符挂起
  • POLLNVAL:无效请求
  • POLLRDNORM:等同于POLLIN
    示例代码如下:
void main(void)
{
	int ret fd;
	struct pollfd fds;
	fd = open(filename, O_RDWR | O_NONBLOCK);
	fds.fd = fd;
	fds.events = POLLIN;
	ret = poll(&fds, 1, 500);
	switch(ret){
	}
}

3.epoll函数

select和poll会随着监听的fd数量增加,出现效率低下的问题,而且poll函数每次要遍历所有描述符来检查,为此,epoll应运而生,epoll为处理大并发而准备的,一般用于网络编程。
创建如下:

int epoll_create(int size) //创建一个epoll句柄(int类型),size随便填入大于0的数

int epoll_ctl(int epfd,  //上面的句柄
			  int op,    //对句柄进行的操作
			  int fd,    //文件描述符
			  struct epoll_event *event) //监视的类型
			  
struct epoll_event{
	uint32_t events;   //epoll事件
	epoll_data_t data; //用户数据
}

op可设置为

  • EPOLL_CTL_ADD:向句柄(epfd)添加文件参数fd表示的描述符
  • EPOLL_CTL_MOD:修改参数fd 的event事件
  • EPOLL_CTL_DEL:从epfd中删除fd描述符
    结构体epoll_event的events成员变量表示监视的事件
  • EPOLLIN:有数据可读
  • EPOLLOUT:可以写数据
  • EPOLLPRI:有紧急数据要读
  • EPOLLERR:文件描述符发生错误
  • EPOLLHUP:文件描述符挂起
  • EPOLLET:设置为边沿触发,默认水平触发
  • EPOLLONESHOT:一次性监视,监视完后需要重新添加
    设置完毕后,可以通过epoll_wait函数来等待事件发生
int epoll_wait(int epfd, //要等待的epoll
			   struct epoll_event *event, //事件结构体
			   int maxevents, //events数组大小
			   int timeout) //超时时间

4.驱动程序里的poll操作

当应用程序调用 select 或 poll 函数来对驱动程序进行非阻塞访问的时候,驱动程序
file_operations 操作集中的 poll 函数就会执行。所以驱动程序的编写者需要提供对应的 poll 函
数, poll 函数原型如下所示:

unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait)

返回值状态如下:

  • POLLIN :有数据可以读取
  • POLLPRI:紧急数据需要读取
  • POLLOUT:可以写数据
  • POLLERR:指定描述符发生错误
  • POLLHUP:文件描述符挂起
  • POLLNVAL:无效请求
  • POLLRDNORM:等同于POLLIN
    需要在驱动程序里的poll函数调用poll_wait函数,poll_wait函数不会引起阻塞,只是将应用程序添加到poll_table中,
void poll_wait(struct file *filp, wait_queue_head_t *wait_address, poll_table *p)

参数wait_address是要添加到poll_table的等待队列头,参数p是poll函数的wait。

三、驱动程序

应用程序调用poll函数,不断检查。如果返回的事件为可读,则开始调用read函数,否则一直循环。驱动程序的poll函数:将等待队列头添加至poll_table,不会引起阻塞!下面有按键按下的标志位检测,没按下返回0,直到按下。
撷取主干程序:

static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	int ret = 0;
	unsigned char keyvalue = 0;
	unsigned char releasekey = 0;
	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

	if (filp->f_flags & O_NONBLOCK)	{ /* 非阻塞访问 */
		if(atomic_read(&dev->releasekey) == 0)	/* 没有按键按下,返回-EAGAIN */
			return -EAGAIN;
	} else {							/* 阻塞访问 */
		/* 加入等待队列,等待被唤醒,也就是有按键按下 */
 		ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey)); 
		if (ret) {
			goto wait_error;
		}
	}

	keyvalue = atomic_read(&dev->keyvalue);
	releasekey = atomic_read(&dev->releasekey);

	if (releasekey) { /* 有按键按下 */	
		if (keyvalue & 0x80) {
			keyvalue &= ~0x80;
			ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
		} else {
			goto data_error;
		}
		atomic_set(&dev->releasekey, 0);/* 按下标志清零 */
	} else {
		goto data_error;
	}
	return 0;

wait_error:
	return ret;
data_error:
	return -EINVAL;
}
unsigned int imx6uirq_poll(struct file *filp, struct poll_table_struct *wait)
{
	unsigned int mask = 0;
	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

	poll_wait(filp, &dev->r_wait, wait);	/* 将等待队列头添加到poll_table中 */
	
	if(atomic_read(&dev->releasekey)) {		/* 按键按下 */
		mask = POLLIN | POLLRDNORM;			/* 返回PLLIN */
	}
	return mask;
}
/* 设备操作函数 */
static struct file_operations imx6uirq_fops = {
	.poll = imx6uirq_poll,
};

四、测试App

和前面代码示例相似;

int main(int argc, char *argv[])
{
	int fd;
	int ret = 0;
	char *filename;
	struct pollfd fds;
	fd_set readfds;
	struct timeval timeout;
	unsigned char data;

	if (argc != 2) {
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];
	fd = open(filename, O_RDWR | O_NONBLOCK);	/* 非阻塞访问 */
	if (fd < 0) {
		printf("Can't open file %s\r\n", filename);
		return -1;
	}

#if 0  //poll形式
	/* 构造结构体 */
	fds.fd = fd;
	fds.events = POLLIN;
		
	while (1) {
		ret = poll(&fds, 1, 500);
		if (ret) {	/* 数据有效 */
			ret = read(fd, &data, sizeof(data));
			if(ret < 0) {
				/* 读取错误 */
			} else {
				if(data)
					printf("key value = %d \r\n", data);
			} 	
		} else if (ret == 0) { 	/* 超时 */
			/* 用户自定义超时处理 */
		} else if (ret < 0) {	/* 错误 */
			/* 用户自定义错误处理 */
		}
	}
#endif
  //select形式
	while (1) {	
		FD_ZERO(&readfds);
		FD_SET(fd, &readfds);
		/* 构造超时时间 */
		timeout.tv_sec = 0;
		timeout.tv_usec = 500000; /* 500ms */
		ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
		switch (ret) {
			case 0: 	/* 超时 */
				/* 用户自定义超时处理 */
				break;
			case -1:	/* 错误 */
				/* 用户自定义错误处理 */
				break;
			default:  /* 可以读取数据 */
				if(FD_ISSET(fd, &readfds)) {
					ret = read(fd, &data, sizeof(data));
					if (ret < 0) {
						/* 读取错误 */
					} else {
						if (data)
							printf("key value=%d\r\n", data);
					}
				}
				break;
		}	
	}

	close(fd);
	return ret;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值