驱动-IO模型

IO模型

在这里插入图片描述

阻塞IO

在linux系统重,驱动的阻塞IO一般使用等待队列实现
等待队列使用方法:

1.初始化等待队列头,并将条件置成假(condition=0)
2.在需要阻塞的地方调用wait_ event(),使进程进入休眠。
3.当条件满足时,需要解除休眠,先将条件置成真(condition=1), 然后 调用wake_ up函 数唤醒等待
队列中的休眠进程。

具体实现

struct device_test{
	dev_t dev_num;
	int major;
	int minor;
	struct cdev cdev_test;
	struct class *class;
	struct device *device;
	char kbuf[32];
	int flag;  //设置条件变量
};
struct device_test dev1;

DECLARE_WAIT_QUEUE_HEAD(read_wq); //定义队列头

//在驱动初始化中设置条件变量初始值
static int modulecdev_init(void){
	...
	dev1.flag = 0;
}

//在read中调用查询阻塞函数
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off){
	struct device_test *test_dev = (struct device_test *)file->file->private_data;
	wait_event_interruptible(read_wq, test_dev.flag);
	...
}
//在write中写数据,唤醒阻塞函数
static ssize_ t cdev_ test_write(struct file *file, const char __user *buf, size_ t size, loff t *off){
	struct device_test *test_dev = (struct device_test *)file->file->private_data;
	...
	test_dev->flag =1;
	wake_up_interruptible(&read_wq);
}

非阻塞访问

应用程序可以使用如下所示示例代码来实现阻塞访问:

fd = open(" /dev/xxx_ dev”,0_ RDWR); /*阻塞方式打开*/
ret = read(fd, &data, sizeof(data));/* 读取数据*/

可以看出对于设备驱动文件的默认读取方式就是阻塞式的。
如果应用程序要采用非阻塞的方式来访问驱动设备文件,可以使用 如下所示代码:

fd = open(" /dev/xxx_ dev",0_ _RDWR| O_NONBL0CK); /*非阻塞方式打开*/
ret = read (fd, &data, sizeof (data));/*读取数据*/

使用open 函数打开“/dev/xxx_ dev”设备文件的时候添加了参数“0_NONBLOCK”,表示以非
阻塞方式打开设备,这样从设备中读取数据的时候就是非阻塞方式的了
在fs.h头文件中,file结构体中定义了f_flags用于读写模式的判断
在这里插入图片描述

//在阻塞IO的基础上加上非阻塞标志位
//在read中调用查询阻塞函数
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off){
	struct device_test *test_dev = (struct device_test *)file->file->private_data;
	if(file->f_flags & O_NONBLOCK){
		if(test_dev->flag!=1){
			return -EAGAIN;//try again
		}
	}
	wait_event_interruptible(read_wq, test_dev.flag);
	...
}
//应用程序需要配合IO查询
int main(int argc, char *argv[]){
	...
	int fd = open("/dev/test", O_RDWR | O_NONBLOCK);
	if(fd<0){
		perrot("open error");
		return fd;
	}
	//在应用程序中不断查询IO
	while(1){
		read(fd, buf1,32);
		printf("buf is %s\n", buf1);
		sleep(1);
	}
	...
}

IO多路复用

I0多路复用可以实现-一个进程监视多个文件描述符,一-旦其中-一个文件描述符准备就绪。就通知应用程序进行相应的操作。在应用层Linux提供了三种I0复用的API函数,分别是select, poll, epoll。在驱动中,需要实现file_operations结 构体的poll函数。
poll和select基本一样,都可以监听多个文件描述符,通过轮询文件描述符来获取已经准备好的文件描述符。
epoll是将主动轮询变成了被动通知,当事件发生时,被动的接收通知。

使用select和pol1等系统调用会触发设备驱动中的poll()函数被执行。所以需要完善驱动中的poll函数。驱动中的poll函数原型:
unsigned int (*poll) (struct file *filp, struct poll_ _table *wait) ;
驱动中po11函数要进行两项工作。第一项工作:对可能引起设备文件状态变化的等待队列调用poll_ wait, 将对应的等待队列头添加到poll_ table。 第二项工作:返回表示是否能对设备进行无阻塞读写访问的掩码。
poll_ wait原型:
void poll_ wait(struct file *filp, wait_ queue_ head_ _t *queue, poll_ _table *wait) ;
注意: poll_ wait这个函数是不会引起阻塞的
struct pollfd{
int fd;//被监视的文件描述符
short events; //等待的事 件
short revents; //实际发生的事件
}

在这里插入图片描述
polI函数介绍

函数原型: int poll(struct pollfd *fds,nfds_ t nfds,int timeout) ;
功能:监视并等待多个文件描述符的属性变化。
函数参数:
	fds:指向struct pollfd结 构体,用于指定给定的fd条件。
	nfds:指定fds的个数。
	timeout:指定等待的时间,单位是ms。无论I/0是否准好,时间到poll都会返回。如果timeout大于0,等待指定的时间。如果timeout等 于0,立即返回。如果timeout等 于-1,事件发生以后才返回。
	返回值:失败返回-1,成功返回revents不为0的文件描述符个数

应用代码

int main(int argc,char *argv[])
{
	int fd;
	char buf1[32] = {0};
	char buf2[32] = "nihao!";
	struct pollfd fds[1];//定义,监视一个IO
	int ret;

	fd = open(" /dev/test" ,0 RDWR); //打开设备节点
	if(fd < 0){
		perror("open error \n");
		return fd;
	}
	fds[0]. fd=fd;//指定监视的哪个文件描述符
	fds [0] . events=POLLIN;//监视读事件
	printf("read before\n");
	while(1){
		ret  = poll(fds,1,3000);
		if(!ret){
			printf("time out\n");
		}else if(fds[0].revents ==POLLIN){//返回是则读事件
			read(fd,buf1,32);
			printf("buf is %s\n" ,buf1);
			sleep(1) ;
		}
	}
	printf("read after\n");
	close(fd);
	return 0;
}

驱动代码

//实现poll函数
static poll t cdev_ test_ poll(struct file *file, struct poll table_ struct *p){
	struct device test *test dev=(struct device test *)file->private data;
	_poll t mask=0;
	poll wait(file,&read wq,p);
	if(test_ dev->flag==1){
		mask |=POLLIN;|
		return mask; 
}

信号驱动IO

信号驱动I0不需要应用程序去查询设备的状态。一旦设备准备就绪,就触发SIGI0信号,该信号会通知应用程序数据已经到来。
应用程序使用信号驱动I0的步骤:

步骤1:注册信号处理函数。应用程序使用signal函数来注册SIGI0信号的信号处理函数。
步骤2:设置能够接收这个信号的进程。
步骤3:开启信号驱动I0,通常使用fcntl的F_SETFL命令打开FASYNC标志

步骤1:
当应用程序开启信号驱动I0时。会触发驱动中的fasync函数。所以首先在file_ operat ions结构体中实
现fasync函数。函数原型如下:
fasync函数原型:

int (*fasync) (int fd, struct file *filp, int on) 

步骤2:
在驱动中的fasync函数中调用fasync_ helper函数来操作fasync_ struct结 构体,fasync_ helper函数原
型如下:
fasync函数原型:

int fasync_ helper(int fd, struct file * filp,int on,struct fasync_ struct **fapp)

步骤3:
当设备准备好的时候,驱动程序需要调用kill_fasync函数通知应用程序,此时应用程序的SIGIO信号处
理函数就会被执行。kill_ fasync负责发送指定的信号。函数原型如下:

void kill_ fasync (struct fasync_ struct **fp,int sig, int band)
函数参数:
	fp:要操作的fasync_ struct。
	sig: 发送的信号。
	band:可读的时候设置成POLLIN,可写的时候设置成POLLOUT。

读应用程序

int fd;
char buf1[32] = {0};

static void func (int signum)
{
	read(fd,buf1,32) ;
	printf("buf is %s\n" ,buf1) ;
}
int main(int argc,char *argv[])
{
	int ret;
	int flags;
	fd = open(" /dev/test",0 RDWR); //打开设备节点
	if(fd < 0)
	{
		perror("open error \n");
		return fd;
	}
	signal(SIGIO, func);
	fcntl(fd,F_SETOWN ,getpid());
	flags=fcntl(fd,F_GETFD);
	fcntl(fd,F_SETFL, flags | FASYNC);
	
	while(1);
	close(fd) ;
	return 0;
}


驱动程序

struct device_ test{
	dev t dev_ num;
	int major ;
	int minor ;
	struct
	cdev cdev_ test;
	struct class *class;
	struct device *device ;
	char kbuf[32];
	int flag;
	struct fasync_ struct *fasynl;
}

static int cdev_ test_ fasync(int fd, struct file *file, int on ){
	struct device test *test dev=(struct device test *)file->private data;
	return fasync helper(fd, file, on ,&test_ dev->fasync);
}

static ssize_ t cdev_ test_ write(struct file *file, const char_ user *buf, size_ t size, loff t *off){
	struct device_ test *test_ dev=(struct device_ test *)file->private_ data;
	if(copy_ from user(test_ dev->kbuf ,buf ,size)!=0){
		printk("copy_ from user error\n");
		return -1;
	}
	test_ dev->flag=1;
	wake_ up_ interruptible(&read wq);
	kill fasync(&test_dev->fasync, int SIGIO, POLLIN);//通知可读POLLIN
	printk("test_ dev->kbuf is %s\n",test_ dev->kbuf);
	return 0;	
}



  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值