一.注意
1.区别
裸机驱动:底层,和寄存器打交道,MCU会提供库;驱动和用户程序是杂揉在一起的。
linux驱动:直接操作寄存器不现实,需要根据linux下框架开发,/include/linux/fs.h中有个叫file_operations
的结构体,它是linux内核驱动操作函数集合,框架开发其实就是file_operations
结构体成员变量的实现:open、 close、 write 和 read
等。驱动与用户程序分层开发。file_operations各项成员变量解析.
2.在linux下一切皆文件,驱动注册完成后,驱动表现为/dev/xxx
文件
3.驱动属于内核空间,当用户程序(用户空间)通过open操作打开/dev/xxx
这个驱动,需要使用一个叫做“系统调用”的方法来实现从用户空间“陷入” 到内核空间,这样才能实现对底层驱动的操作。
二.驱动框架
1.实现file_operations
结构体中成员变量函数,chrtest_open
即具体驱动对应的open操作
/* 打开设备 */
static int chrtest_open(struct inode *inode, struct file *filp)
{
/* 用户实现具体功能 */
return 0;
}
/* 从设备读取 */
static ssize_t chrtest_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
{
/* 用户实现具体功能 */
return 0;
}
/* 向设备写数据 */
static ssize_t chrtest_write(struct file *filp,
const char __user *buf,
size_t cnt, loff_t *offt)
{
/* 用户实现具体功能 */
return 0;
}
/* 关闭/释放设备 */
static int chrtest_release(struct inode *inode, struct file *filp)
{
/* 用户实现具体功能 */
return 0;
}
2.实例化file_operations
结构体,并利用以上函数初始化test_fops
static struct file_operations test_fops = {
.owner = THIS_MODULE,
.open = chrtest_open,
.read = chrtest_read,
.write = chrtest_write,
.release = chrtest_release,
};
3.注册驱动/注销驱动
/* 驱动入口函数 */
static int __init xxx_init(void)
{
/* 入口函数具体内容 */
int retvalue = 0;
/* 注册字符设备驱动 */
retvalue = register_chrdev(200, "chrtest", &test_fops);
if(retvalue < 0){
/* 字符设备注册失败,自行处理 */
}
return 0;
}
/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
/* 注销字符设备驱动 */
unregister_chrdev(200, "chrtest");
}
/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(xxx_init);
module_exit(xxx_exit);
三.实验程序编写
1.在vscode中,ctrl+shift+p
打开控制台,搜索"c/c++:编辑配置(json)
",加入linux源码头文件路径
这样就可以使用linux头文件了
但是注意此处写法#include <linux/delay.h>
,头文件开头不需要"/"
,因为路径"/home/xuqiang/work/arm/linux-5.11.11/include/",
末尾已有"/"
;
若路径末尾不加"/"
,而写在头文件中,如:#include </linux/delay.h>
,则会报错.
问题1:头文件虽然被包含,但是跳转过去发现是
/usr/include
这个gcc默认包含路径,并不是我们下载用于开发的内核源码路径
解决:在"c/c++:编辑配置(json)
“中如下位置添加参数”-nostdinc
",强制gcc不读取默认include路径"compilerArgs": [ "-nostdinc" ]
问题2:发现没有generated这个路径以及路径下的头文件,需要编译内核才会生成,见【Linux】交叉编译linux内核到ARM平台
2.于是可以在Vscode下编写驱动如下
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/export.h>
#include <linux/init.h>
#include <linux/delay.h>
#define HELLO_MAJOR 200
#define HELLO_MAME "hello"
static int hello_open(struct inode *inode,struct file *filp)
{
printk("hello world!\n");
return 0;
}
static int hello_release(struct inode *inode, struct file *filp)
{
printk("baibai world!\n");
return 0;
}
struct file_operations hello_fops;
hello_fops = {
.owner = THIS_MODULE,
.open = hello_open,
.release = hello_release,
};
/* 驱动出口函数 */
static void __exit hello_exit(void)
{
/* 注销字符设备驱动 */
unregister_chrdev(HELLO_MAJOR, HELLO_MAME);
}
/* 驱动入口函数 */
static int __init hello_init(void)
{
/* 入口函数具体内容 */
int retvalue = 0;
/* 注册字符设备驱动 */
retvalue = register_chrdev(HELLO_MAJOR, HELLO_MAME,&hello_fops);
if(retvalue < 0){
/* 字符设备注册失败,自行处理 */
}
return 0;
}
/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(hello_init);
module_exit(hello_exit);
3.接下来可以编写相应的APP来测试驱动