应用层和内核层数据传输-Linux驱动学习(3)

应用层和内核层的数据传输

【学习笔记】

1、应用层和内核层数据传输常用的函数

在Linux中,文件对应的操作有:打开、关闭、读写,同样与文件类似,设备节点对应的操作有:打开、关闭、读写

如果我们在应用层使用系统IO对设备节点进行打开、关闭、读写等操作会发生什么?

【注】:
下面这些函数都定义在linux内核文件夹,比如:linux-4.9.268/include/linux/fs.h中的 struct file_operations {};结构体中

(1)当我们在应用层对设备节点进行read操作时,就会触发驱动里边的read这个函数。

ssize_t(*read)(struct file*, char __user*, size_t, loff_t*);#这里(*read)是函数名字,可以自定义,下面的都是如此

(2)当我们在应用层对设备节点进行write操作时,就会触发驱动里边的write这个函数。

ssize_t(*write)(struct file*, const char __user*, size_t, loff_t*);

(3)当我们在应用层对设备节点进行poll/select操作时,就会触发驱动里边的poll这个函数。

unsigned_t(*poll)(struct file*, struct poll_table_struct*);

(4)当我们在应用层对设备节点进行ioctl操作时,就会触发驱动里边的ioctl这个函数。

long(*unlocked_ioctl)(struct file*, unsigned int, unsigned long);

(5)当我们在应用层对设备节点进行open操作时,就会触发驱动里边的open这个函数。

int(*open)(struct inode*, struct file*);

(6)当我们在应用层对设备节点进行close操作时,就会触发驱动里边的release这个函数。

int(*release)(struct inode*, struct file*);

【注意】read和write参数中的__user*前面是两个下划线,写成一个编译时会报错

2、应用层调用驱动的例子

驱动层:驱动file_operations.c

#include <linux/init.h>
#include <linux/module.h>
//杂项设备驱动需要增加两个头文件
#include <linux/miscdevice.h>
#include <linux/fs.h>

int misc_open(struct inode *inode, struct file *file){//(*open)函数实现
	printk("hello misc_open\n");
	return 0;
}

int misc_release(struct inode *inode, struct file *file){//(*release)函数实现
	printk("bye bye\n");
	return 0;
}

ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t){
	printk("hello read\n");
	return 0;
}

ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){
	printk("hello write\n");
	return 0;

}


//第2步:填充文件操作集
struct file_operations misc_fops = {
	.owner = THIS_MODULE,	//这里简单的填充一个owner
	.open = misc_open,	//根据我们自定义的函数名来填充
	.release = misc_release,
	.read = misc_read,
	.write = misc_write
};

//第1步:填充杂项设备结构体
struct miscdevice misc_dev = {
	.minor = MISC_DYNAMIC_MINOR,	//次设备号,动态分配
	.name = "hello_misc",			//设备节点的名字
	.fops = &misc_fops				//填充文件操作集
};
//第3步;注册到内核
static int misc_init(void){
	int ret;
	ret = misc_register(&misc_dev);//存储注册的地址
	//判断是否注册成功
	if(ret < 0){
		printk("misc registe is error\n");
		return -1;
	}
	printk("misc registe is successful\n");  //内核里不能使用c语言库,所以不能用printf
	return 0;
}
//卸载驱动
static void misc_exit(void){
	misc_deregister(&misc_dev);
	printk("misc bye bye\n");
}

//入口和出口
module_init(misc_init);
module_exit(misc_exit);

//声明许可证
MODULE_LICENSE("GPL");

应用层:app.c上层函数

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

int main(int argc, char *argv[]){//如果打开设备节点成功,这会调用驱动里边的misc_open()函数

	int fd;
	
	char buf[64] = {0};
	
	fd = open("/dev/hello_misc",O_RDWR);//open the device node

	if(fd < 0){		//determine whether the opening is successful
	
		perror("open error\n");//和printf用法相似
		
		return fd;
	}

	read(fd,buf,sizeof(buf));//读取设备节点
	//write(fd,buf,sizeof(buf));//写设备节点
	close(fd);//关闭节点
	
	return 0;
}

如下图,设备节点是内核层和应用层的桥梁

image

如果驱动文件中相应的操作集没有填充,而应用层又调用了对应的函数,那么将什么都不会发生,也不会报错。

应用层和数据层不能直接进行数据传输,需要使用其他函数

下面两个函数定义在linux-4.9.268/include/linux/uaccess.h中

//用户层向内核层传输数据
static inline long copy_from_user(void *to, const void _user *from, unsigned long n);
//内核层向用户层传输数据
static inline long copy_to_user(void *to, const void *from, unsigned long n);

注意这两个函数只能在驱动里边用,不能在应用层使用。

加入数据传输函数后的代码

file_operation.c

#include <linux/init.h>
#include <linux/module.h>
//杂项设备驱动需要增加两个头文件
#include <linux/miscdevice.h>
#include <linux/fs.h>
//增加传输函数所在的头文件
#include <linux/uaccess.h>

int misc_open(struct inode *inode, struct file *file){//(*open)函数实现
	printk("hello misc_open\n");
	return 0;
}

int misc_release(struct inode *inode, struct file *file){//(*release)函数实现
	printk("bye bye\n");
	return 0;
}

ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t){

	char kbuf[64] = "mydate";//定义字符串,能够在应用层读取到

	if(copy_to_user(ubuf, kbuf, strlen(kbuf)) != 0){//判断是否成功向应用层传输数据

		printk("copy_to_user error\n");
		return -1;
	
	}
	
	return 0;
}

ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){

	char kbuf[64] = {0};
	
	if(copy_from_user(kbuf, ubuf, size) != 0){//判断是否成功向应用层传输数据

		printk("copy_from_user error\n");

		return -1;
	
	}

	printk("kbuf is %s\n",kbuf);
	return 0;

}


//第2步:填充文件操作集
struct file_operations misc_fops = {
	.owner = THIS_MODULE,	//这里简单的填充一个owner
	.open = misc_open,	//根据我们自定义的函数名来填充
	.release = misc_release,
	.read = misc_read,
	.write = misc_write
};

//第1步:填充杂项设备结构体
struct miscdevice misc_dev = {
	.minor = MISC_DYNAMIC_MINOR,	//次设备号,动态分配
	.name = "hello_misc",			//设备节点的名字
	.fops = &misc_fops				//填充文件操作集
};
//第3步;注册到内核
static int misc_init(void){
	int ret;
	ret = misc_register(&misc_dev);//存储注册的地址
	//判断是否注册成功
	if(ret < 0){
		printk("misc registe is error\n");
		return -1;
	}
	printk("misc registe is successful\n");  //内核里不能使用c语言库,所以不能用printf
	return 0;
}
//卸载驱动
static void misc_exit(void){
	misc_deregister(&misc_dev);
	printk("misc bye bye\n");
}

//入口和出口
module_init(misc_init);
module_exit(misc_exit);

//声明许可证
MODULE_LICENSE("GPL");


app.c

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

int main(int argc, char *argv[]){//如果打开设备节点成功,这会调用驱动里边的misc_open()函数

	int fd;
	
	char read_buf[64] = {0};
	char write_buf[64] = "write date"//
	
	fd = open("/dev/hello_misc",O_RDWR);//open the device node

	if(fd < 0){		//determine whether the opening is successful
	
		perror("open error\n");//和printf用法相似
		
		return fd;
	}

	read(fd,read_buf,sizeof(read_buf));//读设备节点

	printf("read_buf is %s\n",read_buf);//打印从内核层读取的内容
	
	write(fd,write_buf,sizeof(write_buf));//将数据写入设备节点,将应用层数据传入的内核层
	

	close(fd);//关闭节点
	
	return 0;
}

整理自嵌入式学习之Linux驱动篇

posted @ 2021-05-19 11:24  水鸽  阅读(638)  评论(0编辑  收藏  举报