文章目录
- 一、驱动的外设
- 二、驱动操作文件原理
- 三、编写一个驱动程序
- 3.1 编写驱动程序的步骤
- 3.1.2 确定主设备号以及注册驱动
- 3.1.3 实现对应的函数
- 四、一些错误现象
一、驱动的外设
我们的设备硬件都需要驱动才能工作,没有驱动的硬件可以称之为废铁,没有硬件的驱动也是没有实际意义的。
所以两者是要相辅相成的。
因为我们在ubuntu上进行的一些验证,我们可以外接一个SD卡当作硬件,实际上我们也可以放一个文件当作硬件,这样大家就可以不需要其余的东西了。
二、驱动操作文件原理
我们都知道,在Linux系统中,有种说法叫做一切皆文件,那么我们硬件也可以当作文件,我们用户想要直接操作文件是不可以的,就比如我们直接去open、read、write好像是只有open是可以的,但是read,write这些都是不可以的。
open可以的原因我已经忘了,大家可以去看韦东山的视频复习以下,而read、write这写都是没有权限的,为啥呢,因为内核机制问题,内核为了保证安全,就允许用户空间直接对底层硬件进行操作,只有通过驱动提供接口,然后对文件进行读写操作。
linux通过设备号对相应的文件进行操作
主设备号和次设备号统称为设备号,主设备号代表是的设备的类型,次设备号代表是的具体的设备,随着内核的更新,可以支持的主设备和次设备的数量也越来越多。
三、编写一个驱动程序
3.1 编写驱动程序的步骤
- 确定主设备号
- 定义自己的 file_operations 结构体
- 实现对应的 open/read/write函数,填写入结构体
- 把file_operations 结构体告诉内核,注册驱动程序
- 谁来注册驱动程序?需要一个入口函数;安装驱动程序时,就会调用这个入口函数
- 卸载驱动程序,调用出口函数
- 其他,提供设备信息,创建设备节点
3.1.2 确定主设备号以及注册驱动
为了方便,我这边就和注册一起写了。
source code:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/major.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/kmod.h>static int major = 0; //确定主设备号,0默认系统分配static struct file_operations filectl_ops = {.owner = THIS_MODULE,
};static int __init filectl_init(void)
{// 在初始化的时候进行驱动注册,设备号major = register_chrdev(0,"filectl",&filectl_ops);if(major < 0) {printk("[%s %d] filectl error\n", __FUNCTION__, __LINE__); // 注册失败return -1;}filectl_class = class_create(THIS_MODULE, "filectl"); // class_create 动态创建dev的类// IS_ERR 查看指针是否有错误if(IS_ERR(filectl_class)) {printk("[%s %d] class_create error\n", __FUNCTION__, __LINE__);unregister_chrdev(major,"filectl");return -1;}// 创建字符设备device_create(filectl_class, NULL, MKDEV(major, 0),NULL, "filectl");printk("[%s %d] filectl driver create success\n", __FUNCTION__, __LINE__);return 0;
}static void __exit filectl_exit(void) {device_destroy(filectl_class, MKDEV(major, 0));class_destroy(filectl_class);// 注销字符设备unregister_chrdev(major,"filectl");printk("[%s %d]goodbye filectl driver\n", __FUNCTION__, __LINE__);
}module_init(filectl_init);
module_exit(filectl_exit);
MODULE_LICENSE ("GPL");
MODULE_AUTHOR ("cong.luo");
MODULE_DESCRIPTION ("First file contrl module");
然后编译,加载模块:
然后我们就可以在dev下面看到我们字符设备了 。
3.1.3 实现对应的函数
实现汉函数就是在对应的ops操作集合里。
// 定义自己的驱动的 file_operations
// file_operations是一个函数指针的集合,用于存放我们定义的用于操作设备的函数的指针,如果我们不定义,它默认保留为NULL
static struct file_operations filectl_ops = {.owner = THIS_MODULE,.read = filectl_read,.write = filectl_write,.open = filectl_open,.release = filectl_close, // 好像没有close这个函数
};
这样我们就定义好了,定义好了过后我们就是完善这些函数了,不然会编译不过,
如下
ssize_t filectl_read(struct file *file, char __user *buf, size_t size, loff_t *pops)
{printk("[%s %d]\n", __FUNCTION__, __LINE__);return 1;
}ssize_t filectl_write(struct file *file,const char __user *buf, size_t size, loff_t *pops)
{printk("[%s %d]\n", __FUNCTION__, __LINE__);return 1;
}int filectl_open(struct inode *inode, struct file *file)
{printk("[%s %d]\n", __FUNCTION__, __LINE__);return 0;
}int filectl_close(struct inode *inode, struct file *file)
{printk("[%s %d]\n", __FUNCTION__, __LINE__);return 0;
}
这里对于初学者来说可能比较难的参数的传递,这些参数是怎么来的。
其实根本原因就是,虽然这些filectl_open, filectl_read, filectl_write函数是我们定的,但是实际上我们调用的函数还是底层的 open write这些,所以我们需要满足底层open write这些函数的参数结构。
struct inode *inode
索引, 直白一点就是 文件的存放的地址
struct file *file
操作文件的结构体指针, 描述进程中打开的文件,进程中只要调用了open就有一个该对象。具体描述了打开文件的路径,权限,标志,内部偏移。file结构体是用来维护打开的文件的.
其实比较起来和我们正常的open close这些函数传递的参数是一样的,只不过在内核里面,可能有一些变换, 上面的read和write也都是一样的原理。
完整的code:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/major.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/kmod.h>static int major = 0; //确定主设备号,0默认系统分配
static struct class *filectl_class;ssize_t filectl_read(struct file *file, char __user *buf, size_t size, loff_t *pops)
{printk("[%s %d]\n", __FUNCTION__, __LINE__);return 1;
}ssize_t filectl_write(struct file *file,const char __user *buf, size_t size, loff_t *pops)
{printk("[%s %d]\n", __FUNCTION__, __LINE__);return 1;
}int filectl_open(struct inode *inode, struct file *file)
{printk("[%s %d]\n", __FUNCTION__, __LINE__);return 0;
}int filectl_close(struct inode *inode, struct file *file)
{printk("[%s %d]\n", __FUNCTION__, __LINE__);return 0;
}// 定义自己的驱动的 file_operations
static struct file_operations filectl_ops = {.owner = THIS_MODULE,.read = filectl_read,.write = filectl_write,.open = filectl_open,.release = filectl_close, // 好像没有close这个函数
};static int __init filectl_init(void)
{// 在初始化的时候进行驱动注册,设备号major = register_chrdev(0,"filectl",&filectl_ops);if(major < 0) {printk("[%s %d] filectl error\n", __FUNCTION__, __LINE__); // 注册失败return -1;}filectl_class = class_create(THIS_MODULE, "filectl"); // class_create 动态创建dev的类// IS_ERR 查看指针是否有错误if(IS_ERR(filectl_class)) {printk("[%s %d] class_create error\n", __FUNCTION__, __LINE__);unregister_chrdev(major,"filectl");return -1;}// 创建字符设备device_create(filectl_class, NULL, MKDEV(major, 0),NULL, "filectl");printk("[%s %d] filectl driver create success\n", __FUNCTION__, __LINE__);return 0;
}static void __exit filectl_exit(void) {device_destroy(filectl_class, MKDEV(major, 0));class_destroy(filectl_class);// 注销字符设备unregister_chrdev(major,"filectl");printk("[%s %d]goodbye filectl driver\n", __FUNCTION__, __LINE__);
}module_init(filectl_init);
module_exit(filectl_exit);
MODULE_LICENSE ("GPL");
MODULE_AUTHOR ("cong.luo");
MODULE_DESCRIPTION ("First file contrl module");
这样我们的接口就好了。下一步就是去验证这几个read wrte函数了。
四、一些错误现象
错误现象:
/home/thundersoft/work_test/driver/filectl.c: In function ‘filectl_init’:
/home/thundersoft/work_test/driver/filectl.c:74:20: error: invalid storage class for function ‘filectl_exit’static void __exit filectl_exit(void) {^~~~~~~~~~~~
/home/thundersoft/work_test/driver/filectl.c:74:1: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]static void __exit filectl_exit(void) {^~~~~~
In file included from /home/thundersoft/work_test/driver/filectl.c:1:0:
./include/linux/module.h:128:42: error: invalid storage class for function ‘__inittest’static inline initcall_t __maybe_unused __inittest(void) \^
/home/thundersoft/work_test/driver/filectl.c:81:1: note: in expansion of macro ‘module_init’module_init(filectl_init);
一般有这种的情况都是 多写了一个 } 或者 少写了一个 { 这个需要自己检查下就好