2.7字符设备驱动之阻塞式IO和非阻塞式IO

非阻塞式IO

当设备资源不可用时驱动立即返回,错误码通常是 -EAGAIN(表示资源不可用,错误码类型没有规定),当驱动文件采用非阻塞式方式打开后 structfile 的 f_flags 成员的 O_NONBLOCK 标志会置位。

阻塞式IO

当设备资源不可用时驱动程序将当前进程挂起,直到资源可用,通常采用等待队列来实现挂起操作,也可采用信号量阻塞,但是为了方便多路复用IO的开发,通常选择等待队列,系统默认采用阻塞式打开文件。

等待队列

下列是常用的等待队列函数

	//等待队列
	struct wait_queue_head
	//初始化等待队列
	init_waitqueue_head(q)
	//定义并初始化等待队列
	DECLARE_WAIT_QUEUE_HEAD(name)
	
	//条件condition不成立则将当前进程加入等待队列并休眠,不可被信号唤醒
	//timeout表示具有超时属性
	//interruptible表示可以被信号唤醒
	//exclusive表示具有排他性,默认唤醒队列中的使用进程,但是采用排他性休眠的在唤醒它后不会继续唤醒其他进程
	//locked表示操作队列前先获得内部的锁
	//irq表示在操作队列时要关闭中断
	wait_event(wq, condition)
	wait_event_timeout(wq, condition, timeout)
	wait_event_interruptible(wq, condition)
	wait_event_interruptible_timeout(wq, condition, timeout)
	wait_event_interruptible_exclusive(wq, condition)
	wait_event_interruptible_locked(wq, condition)
	wait_event_interruptible_locked_irq(wq, condition)
	wait_event_interruptible_exclusive_locked(wq, condition)
	wait_event_interruptible_exclusive_locked_irq(wq, condition)
	
	//从等待队列中唤醒
	//与wait_event或wait_event_timeout配合使用
	wake_up(x)
	//与wait_event_interruptible、wait_event_interruptible_timeout和
	//wait_event_interruptible_exclusive配合使用
	wake_up_interruptible(x)
	//与wait_event_interruptible_locked、wait_event_interruptible_locked_irq、
	//wait_event_interruptible_exclusive_locked和wait_event_interruptible_exclusive_locked_irq配合使用
	wake_up_locked(x)

驱动程序实现

驱动程序实现一个全局FIFO,当采用阻塞式打开时进行写操作如果FIFO满则进行等待直到有数据被读取,进行读操作如果FIFO空也进行等待直到有新的数据写入,当采用非阻塞式打开时,如给写时FIFO满则返回EAGAIN错误码,如果读取时FIFO空也返回EAGAIN错误码,如下时驱动程序源代码:


#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/kfifo.h>

//次设备号,为MISC_DYNAMIC_MINOR表示自动分配
#define BLOCKIO_MINOR	MISC_DYNAMIC_MINOR
//设备文件名
#define BLOCKIO_NAME	"blockio"

static DEFINE_KFIFO(gfifo, char, 16);

//定义并初始化等待队列
static DECLARE_WAIT_QUEUE_HEAD(read_queue);
static DECLARE_WAIT_QUEUE_HEAD(write_queue);

//打开设备,对应应用层中的open
static int blockio_open(struct inode *inode, struct file *file)
{
	return 0;
}

//释放设备,在应用层最后因此调用close时调用
static int blockio_release(struct inode *inode, struct file *file)
{
	return 0;
}

//读数据,对应应用层中的read
static ssize_t blockio_read(struct file *file, char __user *buf, size_t len, loff_t *pos)
{
	unsigned int copied;

	if(kfifo_is_empty(&gfifo))
	{
//		printk("fifo is empty\r\n");
		//以非阻塞式打开,直接返回-EAGAIN
		if(file->f_flags & O_NONBLOCK)
			return -EAGAIN;

		//挂起当前进程到等待队列,能被信号唤醒
		if(wait_event_interruptible_exclusive(read_queue, !kfifo_is_empty(&gfifo)))
		{
			//返回-ERESTARTSYS,表示被信号唤醒
			return -ERESTARTSYS;
		}
	}

	//将FIFO中的数据读取到应用层
	if(kfifo_to_user(&gfifo, buf, len, &copied) != 0)
	{
		printk("Bad address\r\n");
		return -EFAULT;
	}

	//如果可以写入则唤醒写进程
	if(!kfifo_is_full(&gfifo))
		wake_up_interruptible(&write_queue);

	return copied;
}

//写设备,对应应用层中的write
static ssize_t blockio_write(struct file *file, const char __user *buf, size_t len, loff_t *pos)
{
	unsigned int copied;

	if(kfifo_is_full(&gfifo))
	{
//		printk("fifo is full\r\n");
		//以非阻塞式打开,直接返回-EAGAIN
		if(file->f_flags & O_NONBLOCK)
			return -EAGAIN;

		//挂起当前进程到等待队列,能被信号唤醒
		if(wait_event_interruptible_exclusive(write_queue, !kfifo_is_full(&gfifo)))
		{
			//返回-ERESTARTSYS,表示被信号唤醒
			return -ERESTARTSYS;
		}
	}

	//将应用层的数据写入FIFO
	if(kfifo_from_user(&gfifo, buf, len, &copied) != 0)
	{
		printk("Bad address\r\n");
		return -EFAULT;
	}

	//如果有数据可读则唤醒读进程
	if(!kfifo_is_empty(&gfifo))
		wake_up_interruptible(&read_queue);

	return copied;
}

static struct file_operations ops = {
	.owner = THIS_MODULE,
	.write = blockio_write,
	.read = blockio_read,
	.open = blockio_open,
	.release = blockio_release,
};

static struct miscdevice blockio_misc = {
	.name = BLOCKIO_NAME,
	.minor = BLOCKIO_MINOR,
	.fops = &ops,
};
static int __init blockio_init(void)
{
	int err = 0;

	printk("blockio memory init,1\r\n");

	//注册混杂设备
	err = misc_register(&blockio_misc);
	if(err != 0)
	{
		printk("register misc failed\r\n");
		return err;
	}

	return 0;
}

static void __exit blockio_exit(void)
{
	printk("blockio memory exit\r\n");

	//注销混杂设备
	misc_deregister(&blockio_misc);
}

module_init(blockio_init);
module_exit(blockio_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("lf");
MODULE_DESCRIPTION("blockio");

驱动测试代码

驱动测试代码分为两个,一个是阻塞式测试代码,一个是非阻塞式测试代码,两个代码的流程一样,仅仅再打开设备时传入的打开参数不一样,如下是阻塞式测试代码:

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

int main(int argc, char *argv[])
{
	int fd;
	int i;
	int temp;
	int result;
	int total;
	char rbuf[100];

	fd = open("/dev/blockio", O_RDWR/*|O_NONBLOCK*/);
	if(fd == -1)
	{
		perror("error");
		return -1;
	}

	if(argc >= 2)
	{
		for(i=1; i<argc; i++)
		{
			temp = strlen(argv[i]);
			for(total = 0; total < temp; )
			{
				//将输入的命令行参数写入设备,因为时阻塞式写入,所以这里会等待FIFO有可写入的空间
				result = write(fd, &argv[i][total], temp - total);
				if(result > 0)
					total += result;
				if(result == -1)
				{
					//如果休眠时被信号唤起可能会返回ERESTARTSYS错误
					perror("error");
					break;
				}
			}
			if(total < temp)
					break;
			//写入空格,用于分割输入的参数
			if(write(fd, " ", 1) == -1)
			{
				perror("error");
				break;
			}
		}
	}
	else
	{
		//从文件读取数据并显示,因为时阻塞式读取,所以这里会等待FIFO有可读取的数据
		for(; ; )
		{
			memset(rbuf, 0, sizeof(rbuf));
			if(read(fd, rbuf, sizeof(rbuf)) == -1)
			{
				//如果休眠时被信号唤起可能会返回ERESTARTSYS错误
				perror("error");
				break;
			}
			printf("%s", rbuf);
			fflush(stdout);
		}
	}
	close(fd);

	return 0;
}

上机测试

  1. 这里下载代码并进行编译,然后拷贝到目标板的/root目录中
  2. 执行命令insmod blockio.ko加载驱动
    在这里插入图片描述
  3. 阻塞式读写测试
    1. 再目标板的终端A中执行./block_app.out 123456789 qwertyuiop,因为驱动只有16字节FIFO,所以无法将数据全部写入,此时写入进程挂起
    在这里插入图片描述
    2. 此时打开另一个终端,执行./block_app.out命令,将驱动中的数据读取出来,这个程序也不会自动退出,因为驱动FIFO为空后会将读取进程挂起
    在这里插入图片描述
    3. 此时可见./block_app.out 123456789 qwertyuiop命令成功结束
    在这里插入图片描述
  4. 非阻塞式读写测试
    1. 在终端中执行命令./noblock_app.out 123456789 qwertyuiop,可见程序立即退出,并输出error: Resource temporarily unavailable,这是因为驱动FIFO满后若采用非阻塞式打开写操作返回EAGAIN错误码造成的
    在这里插入图片描述
    2. 执行./noblock_app.out命令读取刚才写入的数据,可将程序读取到”123456789 qwerty“字符,并报error: Resource temporarily unavailable错误后退出,这是因为驱动FIFO空后若采用非阻塞式打开读操作返回EAGAIN错误码造成的
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值