字符设备驱动基础篇4——字符设备驱动读写接口的操作实践
2017-07-19 09:23
387 查看
以下内容源于朱有鹏嵌入式课程的学习,如有侵权,请告知删除。
参考资料:http://www.cnblogs.com/biaohc/p/6575074.html。
使用“mknod /dev/xxx c 主设备号 次设备号”来创建设备文件,其中xxx表示设备文件名,c表示字符设备;
xxx不能用vim来打开,可以用ls /dev/xxx -l查看。
copy_from_user,将数据从用户空间复制到内核空间;
copy_to_user,将数据从内核空间;
copy_from_user:如果成功复制则返回0,如果不成功复制则返回尚未成功复制的剩下的字节数;
复制机制、mmap的映射两者比较:复制时内核空间和用户空间的地址不一样,效率低。
(2)代码
应用层代码app.c
驱动文件module_test.c
参考资料: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"); // 描述模块的别名信息
相关文章推荐
- Linux 字符设备驱动开发--内存读写操作
- 字符设备驱动高级篇6——内核提供的读写寄存器接口
- 支持阻塞操作的字符设备驱动
- s3c2440基于linux的gpio led字符设备驱动实践 [转]
- 字符设备驱动第十四课----IO读写
- 设备驱动学习之字符设备驱动接口
- 设备驱动学习之字符设备驱动内核代码分析(一)——设备号申请接口
- Linux字符设备驱动对IO操作有三种方式
- linux设备驱动开发-高级字符设备操作poll
- Linux字符设备驱动(三)-文件操作函数实现
- 字符设备驱动基础篇0——驱动开发初体验
- Linux设备驱动程式学习(5)-高级字符驱动程式操作[(2)阻塞型I/O和休眠]
- 字符设备驱动基础篇2——用开发板来调试驱动模块的步骤
- Linux驱动程序开发之字符设备驱动——基础篇(二)
- linux内核字符设备驱动之写操作
- linux内核字符设备驱动之发送命令接口
- 设备驱动开发之缓冲区读写操作
- Linux设备驱动程式学习(6)-高级字符驱动程式操作[(3)设备文档的访问控制]
- 字符设备驱动基础篇3——字符设备驱动工作原理
- 字符设备驱动第五课----读写