linux驱动开发:IO模型

目录

1.非阻塞型I/O

2.阻塞型IO(等待队列)

3.IO多路复用poll

4.异步IO

5.异步通知(信号驱动IO)


1.非阻塞型I/O

设备不一定随时都能给用户提供服务,资源也就有了可用和不可用两种状态。举个例子,当用户想要读取虚拟串口的数据时,如果FIFO中没有数据,那么对读进程来说就是资源不可用,但对于写进程,此时资源是可用的。当FIFO为满时,对写进程来说资源是不可用的。当资源不可用时,应用程序和驱动一起的各种配合就组成了多种IO模型。

如果应用程序以非阻塞方式来打开设备文件,当资源不可用时,驱动就应该立即返回,并用一个错误码来通知应用程序此时资源不可用,对于这样的方式驱动程序的读写接口代码应该做一些修改。

static ssize_t my_read (struct file *pf, char __user *ubuf, size_t size, loff_t *loff)
{
    int ret;
    unsigned int copied=0;
    if(kfifo_is_empty(&myfifo))//判断fifo中是否有数据
        if(file->f_flags & O_NONBLOCK)//O_NONBLOCK表示以非阻塞打开设备文件
            return -EAGAIN;//没有数据可读立即返回错误码
    ret=kfifo_to_user(&myfifo,ubuf,size,&copied);//从管道中读数据到ubuf中
    return ret==0?copied:ret;
}
static ssize_t my_write (struct file *pf, char __user *ubuf, size_t size, loff_t *loff)
{
    int ret;
    unsigned int copied=0;
    if(kfifo_is_full(&myfifo))//判断fifo中是否满了
        if(file->f_flags & O_NONBLOCK)//O_NONBLOCK表示以非阻塞打开设备文件
            return -EAGAIN;//没有空间可写立即返回错误码
    ret=kfifo_from_user(&myfifo,ubuf,size,&copied);//给管道写数据
    return ret==0?copied:ret;
}

kfifo_is_empty:判断管道是否为空的宏,参数是管道的地址

kfifo_is_full:判断管道是否为满的宏,参数是管道的地址

O_NONBLOCK:表示以非阻塞方式打开设备文件

2.阻塞型IO(等待队列)

当进程以阻塞的方式打开设备文件时(默认方式),如果资源不可用,那么进程阻塞,也就是进程休眠,具体讲就是将在自己的状态主动设置为TASK_INTERRUPTIBLE,然后将自己加入一个驱动所维护的等待队列中,相较于非阻塞IO,最大的优点就是资源不可用时,不占用CPU时间,而非阻塞型IO必须定期尝试,看资源是否可以获得,但缺点就是进程在休眠期间不能做其他的事。

当然我们也会有相应的唤醒操作,驱动程序在资源可用时负责执行唤醒操作,所以在我们实现阻塞操作,最重要的数据结构就是等待队列:根本性的工作就是构造并初始化等待头队列,构造等待队列节点,设置进程状体,将节点加入到等待队列,放弃CPU,调度其他进程执行,在资源可用时唤醒队列上的进程

等待队列头的数据类型:wait_queue_head_t

队列节点的数据类型:wait_queue_t

下面介绍一些围绕等待队列的宏和函数

头文件

linux/wait.h
linux/sched.h

DECLARE_WAIT_QUEUE_HEAD(name)//静态定义一个等待队列头

wait_queue_head_t my_wq; //定义等待队列

init_waitqueue_head(&my_wq);  //初始化等待队列

wait_event(queue, condition); //等待事件不可中断

wait_event_timeout(queue, condition,timeout);

wait_event_interruptible(queue, condition); //等待事件可被中断

wait_event_interruptible_timeout(queue, condition,timeout);

//等待,直到条件为真

//queue:等待队列对象

//condition:等待条件

//timeout:有超时检测

//interruptible:为可中断,否则为不可中断

//返回值:被信号打断,返回-ERESTARTSYS

//带超时检测:返回0表示超时,返回大于0表示成功唤醒

void wake_up(wait_queue_head_t *queue);

void wake_up_interruptible(wait_queue_head_t *queue);

//唤醒队列

//queue:等待队列对象

//分别唤醒对应的函数

wait_event是在条件condition不成立的情况下将当前进程放入到等待队列并休眠的基本操作

下面来看一段修改为阻塞IO的代码:

#include <linux/wait.h>
#include <linux/sched.h>

wait_queue_head_t rwqh;   //定义读等待队列头
wait_queue_head_t wwqh;   //定义写等待队列头

static ssize_t my_read (struct file *pf, char __user *ubuf, size_t size, loff_t *loff)
{
    int ret;
    unsigned int copied=0;
    if(kfifo_is_empty(&myfifo))//判断fifo中是否有数据
    {
        if(file->f_flags & O_NONBLOCK)//O_NONBLOCK表示以非阻塞打开设备文件
            return -EAGAIN;//没有数据可读立即返回错误码

        //在管道为空的情况下进行休眠
        if(wait_event_interruptible(rwqh,!kfifo_is_empty(&myfifo)))
            return -ERESTARTSYS;
    }
    ret=kfifo_to_user(&myfifo,ubuf,size,&copied);//从管道中读数据到ubuf中
    if(!kfifo_is_full(&myfifo))
        wake_up_interruptible(&wwqh);
    return ret==0?copied:ret;
}

static ssize_t my_write (struct file *pf, char __user *ubuf, size_t size, loff_t *loff)
{
    int ret;
    unsigned int copied=0;
    if(kfifo_is_full(&myfifo))//判断fifo中是否满了
    {
        if(file->f_flags & O_NONBLOCK)//O_NONBLOCK表示以非阻塞打开设备文件
            return -EAGAIN;//不可写立即返回错误码
       //在管道内容为满的情况下,进入等待休眠
        if(wait_event_interruptible(wwqh,!kfifo_is_full(&myfifo)))
            return -ERESTARTSYS;
    }
    ret=kfifo_from_user(&myfifo,ubuf,size,&copied);//向管道写数据

    if(!kfifo_is_empty(&myfifo))//如果管道不为空,唤醒读等待头
        wake_up_interruptible(&rwqh);
    return ret==0?copied:ret;
}

static int __init myuser_init(void)
{
    init_waitqueue_head(&rwqh);  //初始化读等待队列
    init_waitqueue_head(&wwqh);  //初始化写等待队列
}

3.IO多路复用poll

阻塞型IO相较于非阻塞型IO来说,最大的优点就是在设备的资源不可用时,进程主动放弃CPU,让其他的的进程运行,而不用不停轮询,但进程阻塞后无法做其他的操作,这在一个进程中要同时对多个设备进行操作时显得非常不方便,有几种解决办法,比如多进程,多线程和IO多路复用,这里我们主要讨论IO多路复用,应用层调用select或poll都会触发驱动层的poll执行。这里以poll为例来说明:基本思路,在驱动中实现poll操作,并通过poll_wait可以向驱动向poll_table结构添加一个等待队列

poll系统调用的原型及相关数据类型回顾:

int poll(struct pollfd *fds,nfds_t nfds,int timeout)

//参数1.文件描述符集合

//参数2.监听的文件描述符个数

//参数3.超时检测:-1表示一直2监听

struct pollfd{

        int fd;//文件描述符

        short events;//需要监听的事件

        short revents;//返回的事件

};

POLLIN:读

POLLOUT:写

来看看驱动层的示例代码:

static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p); //用于实现poll

//filp:文件指针

//wait_address:等待队列

//poll_table:poll_table对象

static struct file_operations myfops={
	.open = my_open,
	.release = my_close,
	.read = my_read,
	.write = my_write,
	.poll = my_poll,
};
unsigned int my_poll (struct file *pf, struct poll_table_struct *ptab)
{
	unsigned int mask = 0;
	printk("my_poll\n");
	poll_wait(pf,&rwqh,ptab);
    poll_wait(pf,&wwqh,ptab);
	if(!kfifo_is_full(&myfifo)){   //可以写入数据
		mask |= POLLOUT;
	}
	if(!kfifo_is_empty(&myfifo))
    {                               //可以读数据
		mask |= POLLIN;
	}

	return mask;

}

4.异步IO

调用者只是发起IO操作的请求,然后立即返回,程序可以去做别的事情。具体的IO操作在驱动中完成,驱动中可能会被阻塞,也可能不会。当驱动的IO操作完成后,调用者将会得到通知,通常是内核向调用者发送信号,或者自动调用调用者注册的回调函数,通知操作是由内核完成的,而不是驱动本身。

5.异步通知(信号驱动IO)

异步通知类似前面的异步IO,只是当设备资源可用时它是向应用层发信号,而不能直接调用应用层注册的回调函数,并且发信号的操作也是驱动程序自身来完成的。相对与阻塞,非阻塞以及轮询,信号驱动io提供了一种类似与中断的解决机制。注册好后,一旦设备就绪,便主动通知到应用程序。

驱动代码需要完成下面几个操作:

1.构造struct fasync_struct队列的头 
struct fasync_struct *fasyncqueue=NULL; 

2.初始化fasync_struct
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp); 

3.信号的释放(发信号)
void kill_fasync(struct fasync_struct **fp, int sig, int band);
//当设备可以访问的时候,驱动程序需要向应用程序发出信号,相当于产生”中断“。kill_fastync函数负责发送指定的信号。
//fp :fasync_struct对象
//sig:信号,例如SIGIO
//可读时设置为POLL_IN,可写时设置为POLL_OUT

4.注意模块释放时,删除异步通知
xxx_fasync(-1,filp,0);

应用层编程流程如下:

signal(SIGIO, signal_handler);    
fcntl(fd, F_SETOWN, getpid());
flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC);

//1.通过signal()函数连接信号和信号处理函数,相当于注册中断处理函数
//2.通过F_SETOWN IO控制命令设置设备文件的拥有者为本进程,这样从设备驱动发出的信号才能被本进程接收到
//3.通过fcntl设置文件以支持FASYNC,即异步通知模式

/***************驱动层部分代码示例*****************/
wait_queue_head_t my_wq;   //定义等待队列

struct fasync_struct *fasyncqueue=NULL;   //定义异步通知队列

int my_open (struct inode *pi, struct file *pf);
int my_close(struct inode *pi, struct file *pf);
ssize_t my_read (struct file *pf, char __user *ubuf, size_t size, loff_t *loff);
ssize_t my_write (struct file *pf, const char __user *ubuf, size_t size, loff_t *loff);

unsigned int my_poll (struct file *pf, struct poll_table_struct *ptab);
int my_fasync (int fd, struct file * pf, int mode);

static struct file_operations myfops={
	.open = my_open,
	.release = my_close,
	.read = my_read,
	.write = my_write,
	.poll = my_poll,
	.fasync = my_fasync,
};
int my_fasync (int fd, struct file * pf, int mode)
{
	//1.通过fasync_helper,开启异步通知功能
	return fasync_helper(fd,pf,mode,&fasyncqueue);
}
unsigned int my_poll (struct file *pf, struct poll_table_struct *ptab)
{
	unsigned int mask = 0;
	printk("my_poll\n");
	poll_wait(pf,&my_wq,ptab);
	if(len==0){   //可以写入数据
		mask |= POLLOUT;
	}
	else{       //可以读数据
		mask |= POLLIN;
	}

	return mask;

}
ssize_t my_write (struct file *pf, const char __user *ubuf, size_t size, loff_t *loff)
{
	int ret;
	ret = copy_from_user(kbuf,ubuf,size);
	if(ret!=0){
		return ret;
	}
	len = size;
	kill_fasync(&fasyncqueue,SIGIO,POLL_IN);  //2.发出SIGIO信号,通知到应用层
	return size;
}
int my_close(struct inode *pi, struct file *pf)
{
	//3.关闭文件的时候, 删除异步通知
	my_fasync(-1,pf,0);
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值