驱动开发--阻塞与非阻塞

发布时间:2024年01月18日

一、五种IO模型------读写外设数据的方式

  1. 阻塞: 不能操作就睡觉

  2. 非阻塞:不能操作就返回错误

  3. 多路复用:委托中介监控

  4. 信号驱动:让内核如果能操作时发信号,在信号处理函数中操作

  5. 异步IO:向内核注册操作请求,内核完成操作后发通知信号

二、阻塞与非阻塞

应用层:

open时由O_NONBLOCK指示read、write时是否阻塞

open以后可以由fcntl函数来改变是否阻塞:

flags = fcntl(fd,F_GETFL,0);
flags |= O_NONBLOCK;
fcntl(fd, F_SETFL, flags);

驱动层:通过等待队列

wait_queue_head_t //等待队列头数据类型
?
init_waitqueue_head(wait_queue_head_t *pwq) //初始化等待队列头
 ? ?
wait_event_interruptible(wq,condition)//浅度睡眠,条件不成立,则进入等待队列
/*
功能:条件不成立则让任务进入浅度睡眠,直到条件成立醒来
 ? ?wq:等待队列头
 ? ?condition:C语言表达式
返回:正常唤醒返回0,信号唤醒返回非0(此时读写操作函数应返回-ERESTARTSYS)
*/
 ? ? ? ?
wait_event(wq,condition) //深度睡眠

?
wake_up_interruptible(wait_queue_head_t *pwq)//唤醒浅度睡眠
 ? ? ? ?
wake_up(wait_queue_head_t *pwq)//唤醒深度睡眠
 ?
 ? ?
/*
1. 读、写用不同的等待队列头rq、wq
2. 无数据可读、可写时调用wait_event_interruptible(rq、wq,条件)
3. 写入数据成功时唤醒rq,读出数据成功唤醒
*/



?

?

三、以读函数队列为例

?1.添加2个等待队列 (读、写)

2.在init函数里面,初始化这两个等待队列?

?3.判断是否为非阻塞

4.write函数写数据的话,唤醒读等待队列

1. shell命令

linux@linux:~/fs4412/mydrivercode$ lsmod | grep char
linux@linux:~/fs4412/mydrivercode$ sudo insmod ./mychar.ko
[sudo] password for linux: 
linux@linux:~/fs4412/mydrivercode$ cat /proc/devices |grep cahr
linux@linux:~/fs4412/mydrivercode$ cat /proc/devices |grep char
 11 mychar
linux@linux:~/fs4412/mydrivercode$ sudo mknod /dev/mydev c 11 0
linux@linux:~/fs4412/mydrivercode$ ls /dev/mydev* -l
crw-r--r-- 1 root root 11, 0 Jan 18 00:19 /dev/mydev
linux@linux:~/fs4412/mydrivercode$ sudo chmod a+w /dev/mydev
linux@linux:~/fs4412/mydrivercode$ ls /dev/mydev* -l
crw-rw-rw- 1 root root 11, 0 Jan 18 00:19 /dev/mydev
linux@linux:~/fs4412/mydrivercode$ vi testmychar_app.c 
linux@linux:~/fs4412/mydrivercode$ cp testmychar_app.c  testmychar_nonblockread.c
linux@linux:~/fs4412/mydrivercode$ vi testmychar_nonblockread.c 
linux@linux:~/fs4412/mydrivercode$ gcc testmychar_nonblockread.c -o trn -Wall
linux@linux:~/fs4412/mydrivercode$ ./trn /dev/mydev
read data failed

2.mychar.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#include "mychar.h"

#define BUF_LEN 100
int major = 11;//主设备号
int minor =0;//次设备号
int mychar_num = 1;//次设备数量
//ioctrl
struct mychar_dev
{
	struct cdev mydev;
	char mydev_buf[BUF_LEN];
	int curlen;//100个字节中已经存有的数据
	wait_queue_head_t rq;
	wait_queue_head_t wq;
};

struct mychar_dev gmydev;

int mychar_open(struct inode *pnode,struct file *pfile)
{//inode * pnode 中有一个成员 i_rdev存放这mydev的地址,现在用struct成员的地址求出结构体的地址
	//求出gmydev的地址
	pfile->private_data = (void *)container_of(pnode->i_cdev,struct mychar_dev,mydev);
	printk("mychar_open is called\n");
	return 0;
}

int mychar_close(struct inode *pnode,struct file *pfile)
{
	printk("mychar_close is called\n");
	return 0;
}
ssize_t mychar_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos)
{//内核空间mydev_buf  向   用户空间puser  进行copy
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	//定义一个指针pmydev
	int size = 0;
	int ret = 0;

	if(pmydev->curlen<=0)//如果无数据可读
	{
		if(pfile->f_flags & O_NONBLOCK)//如果为真,fd描述符为非阻塞

		{//非阻塞,且无数据可读,返回错误
			printk("O_NONBLOCK no data to read\n");
			return -1;
		}
		else
		{//阻塞,且无数据可读,加入到等待队列中去,进入浅度睡眠中		   
			ret = wait_event_interruptible(pmydev->rq,pmydev->curlen > 0);
			if(ret)//if ret==0,条件成立,正常唤醒,下面语句不执行
			{
				printk("wake up by signal\n");
				return -ERESTART;
			}
		}
	}

	if(count >pmydev->curlen)//如果期望读取的数据大小大于了原本数据大小
	{
		size = pmydev->curlen;//读取的数据为被读取数据的大小
	}
	else
	{
		size = count;//被读取的数据大小为期望读取的数据大小

	}
	ret = copy_to_user(puser,pmydev->mydev_buf,size);
	if(ret)//
	{
		printk("copy_to_user failed\n");
		return -1;
	}
	//将没被读取的数据拷贝到初始读取的位置.
	memcpy(pmydev->mydev_buf,pmydev->mydev_buf + size,pmydev->curlen - size);
	pmydev->curlen =pmydev-> curlen -size;//被读取后剩余这么多字节

	
	//一旦读取一些数据后,curlen一定小于BUF_LEN,直接唤醒write函数中的写队列
	wake_up_interruptible(&pmydev->wq);

	return size;

}

ssize_t mychar_write(struct file *pfile,const char __user *puser,size_t count,loff_t *p_pos)
{//用户空间puser  向 内核空间mydev_buf  copy
	int ret=0;
	int size = 0;
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;

	//阻塞与非阻塞
	if(pmydev->curlen>=BUF_LEN)//如果空间可写
	{
		if(pfile->f_flags & O_NONBLOCK)//如果为真,fd描述符为非阻塞

		{//非阻塞,且无数据可读,返回错误
			printk("O_NONBLOCK no space to write\n");
			return -1;
		}
		else
		{//阻塞,且无空间可写,加入到等待队列中去,进入浅度睡眠中		   
			ret = wait_event_interruptible(pmydev->wq,pmydev->curlen<BUF_LEN);
			if(ret)//if ret==0,条件成立,正常唤醒,下面语句不执行
			{
				printk("wake up by signal\n");
				return -ERESTART;
			}
		}
	}



	if(count>BUF_LEN - pmydev->curlen)//如果期望写入的数据大小大于100个字节剩余的空间
	{
		size = BUF_LEN - pmydev->curlen;
	}
	else 
	{
		size = count;
	}
	ret = copy_from_user(pmydev->mydev_buf,puser,size);
	//将用户空间puser中 size大小的数据写入到内核空间mydev_buf为始的地址中去
	if(ret)
	{
		printk("copy_from_user is failed\n");
		return -1;
	}
	pmydev->curlen = pmydev->curlen +size;
	//mydev_buf中存在的数据大小

	//一旦写入数据后,curlen一定大于0,直接唤醒read函数中的睡眠队列
	wake_up_interruptible(&pmydev->rq);

	return size;

}
//实现ioctl
long mychar_ioctl(struct file *pfile,unsigned int cmd,unsigned long arg)
{

	int __user *pret = (int *)arg;
	int maxlen = BUF_LEN;
	int ret = 0;
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	switch(cmd)
	{
	case MYCHAR_IOCTL_GET_MAXLEN:
		//把最大值copy到用户空间去
		ret = copy_to_user(pret,&maxlen,sizeof(int));
		if(ret)
		{
			printk("copy_to_user maxlen failed\n ");
			return -1;
		}
		break;
	case MYCHAR_IOCTL_GET_CURLEN:
		//把当前值copy到用户空间去
		ret = copy_to_user(pret,&pmydev->curlen,sizeof(int));
		if(ret)
		{
			printk("copy_to_user curlen failed\n ");
			return -1;
		}
		break;
	default:
		printk("the cmd is unknow\n");
		return -1;
	}
	return 0;
}
struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mychar_open,
	.release = mychar_close,
	.read = mychar_read,
	.write = mychar_write,
	.unlocked_ioctl = mychar_ioctl,
};

int __init mychar_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major,minor);//组合成完整的设备号
	/*申请设备号*/
	ret = register_chrdev_region(devno,mychar_num,"mychar");
	if(ret)//ret非0,表示失败
	{
		ret = alloc_chrdev_region(&devno,minor,mychar_num,"mychar");
		//此设备号申请后填写到devno地址中去,从minor开始申请mychar_num个
		if(ret)
		{
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);//获取新的设备号,不要遗漏
		//次设备号都是0,所以不用再次提取
	}
	//给struct_cdev对象制定操作函数集
	cdev_init(&gmydev.mydev,&myops);    
	//将struct_cdev对象添加到内核对应的数据结构里
	gmydev.mydev.owner = THIS_MODULE;
	cdev_add(&gmydev.mydev,devno,1);

	init_waitqueue_head(&gmydev.rq);
	init_waitqueue_head(&gmydev.wq);
	return 0;
}

void __exit mychar_exit(void)
{
	dev_t devno = MKDEV(major,minor);
	cdev_del(&gmydev.mydev);

	unregister_chrdev_region(devno,mychar_num);

}


MODULE_LICENSE("GPL");

module_init(mychar_init);
module_exit(mychar_exit);

3.testmychar_nonblockread.c

#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include "mychar.h"



int main(int argc, const char *argv[])
{
	int fd = -1;
	char buf[8] = "";
	int ret = 0;
	

	if(argc<2)
	{
		printf("the argument is too few\n");
		return 1;
	}
	//设为非阻塞,阻塞则去掉 |
	fd = open(argv[1],O_RDWR|O_NONBLOCK);
	if(fd<0)
	{
		printf("open %s failed\n",argv[1]);
		return 2;:
	}
	
ret=	read(fd,buf,8);
if(ret<0)
{
printf("read data failed\n");

}
else
{
	printf("buf = %s\n",buf);

}


	close(fd);
	fd = -1 ;
	return 0;
}

文章来源:https://blog.csdn.net/2302_76785838/article/details/135662216
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。