您的位置:首页 > 其它

字符设备驱动基础篇4——字符设备驱动读写接口的操作实践

2017-07-19 09:23 387 查看
以下内容源于朱有鹏嵌入式课程的学习,如有侵权,请告知删除。

参考资料:http://www.cnblogs.com/biaohc/p/6575074.html

一、细节

1、自动分配主设备号

注册函数传参时,第一个参数填0,表示让内核自动分配主设备号,返回值为主设备号。注销时,注销刚才返回的主设备号。

2、设备文件的创建

设备文件的关键信息是:设备号 = 主设备号 + 次设备号,使用ls -l去查看设备文件,就可以得到设备文件对应的主次设备号。
使用“mknod /dev/xxx c 主设备号 次设备号”来创建设备文件,其中xxx表示设备文件名,c表示字符设备;
xxx不能用vim来打开,可以用ls /dev/xxx -l查看。

二、读写接口的操作实践

(1)应用(用户空间)和驱动(内核空间)之间的数据交换

copy_from_user,将数据从用户空间复制到内核空间;
copy_to_user,将数据从内核空间;
copy_from_user:如果成功复制则返回0,如果不成功复制则返回尚未成功复制的剩下的字节数;
复制机制、mmap的映射两者比较:复制时内核空间和用户空间的地址不一样,效率低。
(2)代码



应用层代码app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//这是应用层

#define FILE	"/dev/test"			// 刚才手工mknod创建的设备文件名,问题是mknod需要输入主设备号,和自动获取不背离吗?

char buf[100];

int main(void)
{
int fd = -1;

fd = open(FILE, O_RDWR);//这里的open,对应的是驱动文件中的.open指定的函数
if (fd < 0)
{
printf("open %s error.\n", FILE);
return -1;
}
printf("open %s success..\n", FILE);

// 读写文件
write(fd, "helloworld2222", 14);
read(fd, buf, 100);
printf("读出来的内容是:%s.\n", buf);

// 关闭文件
close(fd);

return 0;
}


驱动文件module_test.c

#include <linux/module.h>		// module_init  module_exit
#include <linux/init.h>			// __init   __exit
#include <linux/fs.h>
#include <asm/uaccess.h>

#define MYNAME		"testchar"

int mymajor;        //内核自动分配的主设备号
char kbuf[100];		//内核空间(即驱动空间,毕竟内核和驱动属于同一层)的buf

static int test_chrdev_open(struct inode *inode, struct file *file)
{
// 这个函数中真正应该放置的是打开这个设备的硬件操作代码部分
// 但是现在暂时我们写不了这么多,所以用一个printk打印个信息来做代表。
printk(KERN_INFO "test_chrdev_open\n");

return 0;
}

static int test_chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "test_chrdev_release\n");

return 0;
}

//读函数,即从内核空间(驱动空间)读取数据到用户空间
ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
int ret = -1;

printk(KERN_INFO "test_chrdev_read\n");

//将内容从内核空间(驱动空间)读取到用户空间
//返回值为0说明读取成功,读取不成功时返回值是剩余没有读取的字节数
ret = copy_to_user(ubuf, kbuf, count);

if (re
b96b
t)
{
printk(KERN_ERR "copy_to_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_to_user success..\n");

return 0;
}

// 写函数
//1、将应用层的数据先复制到内核中
//2、然后将之以正确的方式写入硬件完成操作。
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{                                  //此函数前三个参数,对应应用层的write函数的三个参数?
int ret = -1;

printk(KERN_INFO "test_chrdev_write\n");

// 1、使用该函数将应用层传过来的ubuf中的内容拷贝到驱动空间中的一个buf中
//memcpy(kbuf, ubuf); // 不行,因为用户空间和内核空间,两者不在一个地址空间中
ret = copy_from_user(kbuf, ubuf, count);
if (ret)
{
printk(KERN_ERR "copy_from_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_from_user success..\n");

// 2、真正的驱动中,数据从应用层复制到驱动中后,我们就要根据这个数据
//去写硬件完成硬件的操作。所以这下面就应该是操作硬件的代码

return 0;
}

// 自定义一个file_operations结构体变量,并且去填充
static const struct file_operations test_fops = {
.owner		= THIS_MODULE,			// 惯例,直接写即可

.open		= test_chrdev_open,		// 将来应用open打开这个设备时实际调用的就是这个.open对应的函数
.release	= test_chrdev_release,
.write 		= test_chrdev_write,
.read		= test_chrdev_read,
};

// 模块安装函数
static int __init chrdev_init(void)
{
printk(KERN_INFO "chrdev_init helloworld init\n");

// 在module_init宏调用的函数中去注册字符设备驱动
// major传0进去表示要让内核帮我们自动分配一个合适的空白的没被使用的主设备号
// 内核如果成功分配就会返回分配的主设备好;如果分配失败会返回负数
mymajor = register_chrdev(0, MYNAME, &test_fops);
if (mymajor < 0)
{
printk(KERN_ERR "register_chrdev fail\n");
return -EINVAL;
}
printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);

return 0;
}

// 模块载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");

// 在module_exit宏调用的函数中去注销字符设备驱动
unregister_chrdev(mymajor, MYNAME);

}

module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");				// 描述模块的许可证
MODULE_AUTHOR("aston");				// 描述模块的作者
MODULE_DESCRIPTION("module test");	// 描述模块的介绍信息
MODULE_ALIAS("alias xxx");			// 描述模块的别名信息
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: