16-非阻塞型IO

从内核中最简单的驱动程序入手,描述Linux驱动开发,主要文章目录如下(持续更新中):
01 - 第一个内核模块程序
02 - 注册字符设备驱动
03 - open & close 函数的应用
04 - read & write 函数的应用
05 - ioctl 的应用
06 - ioctl LED灯硬件分析
07 - ioctl 控制LED软件实现(寄存器操作)
08 - ioctl 控制LED软件实现(库函数操作)
09 - 注册字符设备的另一种方法(常用)
10 - 一个cdev实现对多个设备的支持
11 - 四个cdev控制四个LED设备
12 - 虚拟串口驱动
13 - I2C驱动
14 - SPI协议及驱动讲解
15 - SPI Linux驱动代码实现
16 - 非阻塞型I/O
17 - 阻塞型I/O
18 - I/O多路复用之 select
19 - I/O多路复用之 poll
20 - I/O多路复用之 epoll
21 - 异步通知

1. 非阻塞型IO简介

 设备不一定随时都能给用户提供服务,这就有了资源可用和不可用两种状态,程序默认打开设备是以阻塞方式打开的,当以非阻塞打开设备时需要在open时加上 O_NONBLOCK 标志,非阻塞的访问如下图所示:
在这里插入图片描述
 应用程序使用非阻塞访问方式从设备读取数据,当设备不可用或数据未准备好的时候会立即向内核返回一个错误码,表示数据读取失败。应用程序会再次重新读取数据,这样一直往复循环,直到数据读取成功。

2. 示例代码

 本文以虚拟串口驱动为例来对非阻塞型I/O进行演示(虚拟串口驱动讲解见虚拟串口驱动讲解)。首先打开虚拟串口设备读取里面的数据,如果kfifo里面没有数据则直接返回,然后往里写数据再读,详细的代码实现过程如下:

2.1 demo.c

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/kfifo.h>

#define VSER_CHRDEV_ANME ("vser_chrdev")
#define VSER_CLASS_ANME  ("vser_cls")
#define VSER_DEVICE_ANME ("vser_dev")

struct vser_dev{
	dev_t dev_no;
	int major;
	int minor;
	struct cdev cdev;
	struct class *cls;
	struct device *dev;
};
struct vser_dev test_vser_dev;

DEFINE_KFIFO(vser_fifo, char, 16);	// 声明定义一个虚拟串口

static int vser_open(struct inode *inode, struct file *filp)
{
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	filp->private_data = &test_vser_dev;
	
	return 0;
}

static int vser_release(struct inode *indoe, struct file *filp)
{
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	return 0;
}

static ssize_t vser_read(struct file *filp, char __user *userbuf, size_t size, loff_t *offset)
{
	unsigned int copied_num, ret;

	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	if ( kfifo_is_empty(&vser_fifo) )		// kfifo为空返回真
	{
		printk("kfifo_is_empty.\n");
		if ( filp->f_flags & O_NONBLOCK )	// 判断是否以非阻塞方式打开
		{
			printk("O_NONBLOCK.\n");
			ret = -EAGAIN;	// try again
		}
		goto err0;
	}
	else
	{
		ret = kfifo_to_user(&vser_fifo, userbuf, size, &copied_num);	// kfifo不为空将数据拷贝到用户空间
		if (ret < 0)
		{
			printk("kfifo_to_user failed.\n");
			ret = -EFAULT;	// Bad Address
			goto err0;
		}
		printk("%s copied_num = %d.\n", __FUNCTION__, copied_num);
	}

	return copied_num;
	
err0:
	return ret;
}

static ssize_t vser_write(struct file *filp, const char __user *userbuf, size_t size, loff_t *offset)
{
	unsigned int copied_num = 0;
	unsigned int ret;

	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	if ( kfifo_is_full(&vser_fifo) )		// kfifo为满返回真
	{
		printk("kfifo_is_full.\n");
		if ( filp->f_flags & O_NONBLOCK )	// 判断是否以非阻塞方式打开
		{
		    printk("%s -- O_NONBLOCK.\n", __FUNCTION__);
			ret = -EAGAIN;
			goto err0;
		}
	}

	ret = kfifo_from_user(&vser_fifo, userbuf, size, &copied_num);	// kfifo不为满,则将用户空间数据拷贝到内核空间
	if (ret == -EFAULT)
	{
		printk("kfifo_from_user failed.\n");
		goto err0;
	}
	printk("%s -- copied_num = %d.\n", __FUNCTION__, copied_num);
	
	return copied_num;

err0:
	return ret;
}




struct file_operations vser_fops = 
{
	.open = vser_open,
	.release = vser_release,
	.read = vser_read,
	.write = vser_write,
};


static int __init vser_init(void)
{
	int ret;
	
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	if (test_vser_dev.major)
	{
		test_vser_dev.dev_no = MKDEV(test_vser_dev.major, 0);
		ret = register_chrdev_region(test_vser_dev.dev_no, 1, VSER_CHRDEV_ANME);
		if (ret < 0)
		{
			printk("register_chrdev_region failed.\n");
			goto register_chrdev_region_err;
		}
	}
	else
	{
		ret = alloc_chrdev_region(&test_vser_dev.dev_no, 0, 1, VSER_CHRDEV_ANME);
		if (ret < 0)
		{
			printk("alloc_chrdev_region failed.\n");
			goto alloc_chrdev_region_err;
		}
	}

	cdev_init(&test_vser_dev.cdev, &vser_fops);
	ret = cdev_add(&test_vser_dev.cdev, test_vser_dev.dev_no, 1);
	if (ret < 0)
	{
		printk("cdev_add failed.\n");
		goto cdev_add_err;
	}

	test_vser_dev.cls = class_create(THIS_MODULE, VSER_CLASS_ANME);
	if ( IS_ERR(test_vser_dev.cls) )
	{
		printk("class_create failed.\n");
		ret = PTR_ERR(test_vser_dev.cls);
		goto class_create_err;
	}
	
	test_vser_dev.dev = device_create(test_vser_dev.cls, NULL, test_vser_dev.dev_no, NULL, VSER_DEVICE_ANME);
	if ( IS_ERR(test_vser_dev.dev) )
	{
		printk("device_create failed.\n");
		ret = PTR_ERR(test_vser_dev.dev);
		goto device_create_err;
	}
	
	return 0;

device_create_err:
	class_destroy(test_vser_dev.cls);
class_create_err:
	cdev_del(&test_vser_dev.cdev);
cdev_add_err:
	unregister_chrdev(test_vser_dev.major, VSER_CHRDEV_ANME);
alloc_chrdev_region_err:
register_chrdev_region_err:
	return ret;
}

static void __exit vser_exit(void)
{
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	device_destroy(test_vser_dev.cls, test_vser_dev.dev_no);
	class_destroy(test_vser_dev.cls);
	cdev_del(&test_vser_dev.cdev);
	unregister_chrdev(test_vser_dev.major, VSER_CHRDEV_ANME);
}

module_init(vser_init);
module_exit(vser_exit);
MODULE_LICENSE("GPL");

2.2 test.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
	int fd, ret;
	const char *dev_pathname = "/dev/vser_dev";
	char read_buf[8]  = "\0";
	char write_buf[8] = "abc"; 
	
	fd = open(dev_pathname, O_RDWR | O_NONBLOCK, 0666);	// 以非阻塞方式打开设备文件
	if (fd < 0)
	{
		perror("open");
		return -1;
	}
	
	while(1)
	{
		ret = read(fd, read_buf, sizeof(read_buf));
		if (ret == -1)	// 读失败
		{
			perror("read");
			printf("\n");
			ret = write(fd, write_buf, strlen(write_buf));
			if (ret == -1)
			{
				perror("write");
				printf("\n");
			}
		}
		else if (ret == 0)
		{
			printf("read end of file\n");
		}
		else
		{
			printf("read_buf = %s\n", read_buf);
			break;
		}
	}

	close(fd);
	
	return 0;
}

2.3 Makefile

KERNELDIR ?= /home/linux/ti-processor-sdk-linux-am335x-evm-04.00.00.04/board-support/linux-4.9.28/
PWD := $(shell pwd)

EXEC = app
OBJS = test.o
CC   = arm-linux-gnueabihf-gcc

$(EXEC):$(OBJS)
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) modules
	$(CC) $^ -o $@
.o:.c
	$(CC) -c $<

install:
	sudo cp *.ko  app /tftpboot
	sudo cp *.ko app /media/linux/rootfs1/home/root/
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
	rm app
clean:
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
	rm app

obj-m += demo.o

2.4 测试结果

root@am335x-evm:~# insmod demo.ko 
[   34.492537] vser_init -- 124.
root@am335x-evm:~# ./app
[   36.315438] vser_open -- 28.
[   36.318426] vser_read -- 46.
[   36.321332] kfifo_is_empty.
[   36.330269] O_NONBLOCK.
read: Resource temporarily unavailable
[   36.338019] vser_write -- 81.
[   36.352124] vser_write -- copied_num = 3.
[   36.356208] vser_read -- 46.
[   36.359118] vser_read copied_num = 3.
read_buf = abc
[   36.374458] vser_release -- 37.
root@am335x-evm:~# rmmod demo.ko 
[   56.308188] vser_exit -- 185.

 test.c中以非阻塞的方式打开虚拟串口设备,最开始kfifo是空的,直接返回,然后往kfifo中写入数据,再次读时可以从kfifo中读出数据并打印出来

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值