目录
在应用层调用open来打开这个系统文件,在向这个设备文件使用read、write等即可调用驱动的函数去工作。
设备文件
设备文件连接着应用层与驱动。对于应用:通过打开设备文件与驱动挂钩;对于驱动,驱动把自己打包装成设备文件拿给应用层去操作。
我们的每一个驱动其实就是一个个file_operations结构体(最多255个)。比如,我这个设备有20个驱动,也就有20个file_operations结构体,这20个结构体组成一个数组
使用ls/dev/来查看目前的设备文件,显示的每一个文件都代表1个驱动(硬件设备),如fb0就是显示屏lcd
[主设备号可以理解为一类设备(比如:四个led其实是一个主设备号,由四个次设备号区分)]
使用mknod创建设备文件:
mknod /dev/xxx c 主设备号 次设备号
设备号 = 主设备号 + 次设备号,使用ls -l去查看设备文件,就可以得到这个设备文件对应的主次设备号。
测试驱动:读写回环测试
应用这边调用open函数,则驱动那边就会运行 .open 函数。应用调用write,则驱动运行 .write函数,其余同理。
步骤:
/*如果驱动改了,需要重新卸载安装驱动*/
rmmod xxxtest.ko //卸载模块
lsmod //确认一下还在不在
cat /proc/devices //再次确认
ls /dev/xxx //xxx是自己命名的设备文件,我们看看有没有,有的话就删掉
rm /dev/xxx //删掉后也还可以通过重启开发板来保证删掉
/*装驱动*/
insmod xxxtest.ko
lsmod //看一下是否成功
cat /proc/devices //再次确认
/*建立设备文件;主设备号250,次设备号0*/
mknod /dev/test c 250 0
ls /dev/test -l //看一下没问题
./app //执行应用程序
源文件:详细的讲解看注释即可
app.c(应用层代码):
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FILE "/dev/test" // 刚才mknod创建的设备文件名
char buf[100];
int main(void)
{
int fd = -1;
fd = open(FILE, O_RDWR);
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 MYMAJOR 200
#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");
ret = copy_to_user(ubuf, kbuf, count);
if (ret)
{
printk(KERN_ERR "copy_to_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_to_user success..\n");
return 0;
}
// 写函数的本质就是将应用层传递过来的数据先复制到内核中,然后将之以正确的方式写入硬件完成操作。
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf,
size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_write\n");
// 使用该函数将应用层传过来的ubuf中的内容拷贝到驱动空间中的一个buf中
//memcpy(kbuf, ubuf); // 不能用memcpy,因为2个不在一个地址空间中
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");
// 真正的驱动中,数据从应用层复制到驱动中后,我们就要根据这个数据
// 去写硬件完成硬件的操作。所以这下面就应该是操作硬件的代码
return 0;
}
// 自定义一个file_operations结构体变量,并且去填充
static const struct file_operations test_fops = {
.owner = THIS_MODULE, // 惯例,直接写即可
.open = test_chrdev_open, // 将来应用open打开这个设备时实际调用的
.release = test_chrdev_release, // 就是这个.open对应的函数
.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"); // 描述模块的别名信息
应用和驱动之间的数据交换
(1)copy_from_user,将数据从用户空间复制到内核空间。
和常规有点不同。返回值如果成功复制则返回0,如果 不成功复制则返回尚未成功复制剩下的字节数。
(2)copy_to_user,将数据从内核空间复制到用户空间。
比如:我们在应用层调用write(fd, "helloworld", 10);这个helloworld写进驱动层的char __user *buf,在这个函数的实现里,会去调用copy_from_user将数据从用户空间复制到内核空间。
注意:复制是和map的映射相对应去区分的。mmap是同一个物理内存地址直接去映射的,而上边那两个是从一个地址复制到另外一个地址的。
另外,因为copy_from_user与copy_to_user是复制,不适合大量数据的传输(速度太慢),如果遇到大量数据的传输,用map(以后讲)