2020.11.25
韦东山视频学习笔记
字符设备驱动程序框架
总体来说,分为++APP应用层、驱动层、硬件层++这三个层面
应该和STM32的开发方式可能比较接近吧。硬件小哥画原理图画PCB焊板子(硬件层),企业帮我们做好了函数的封装,让我们刻意调用标准库或者HAL库来开发,而不需要直接操作寄存器(驱动层),我们通过调用需要的函数来初始化等等(应用层)
暂时这样理解吧…
- APP层面的应用开发,有各种的函数调用,这些函数会在驱动程序中编写完善。所以APP不直接和硬件打交道,没有办法做一个光荣的 “点灯工程师”
- Driver:驱动层,对具体的函数来进行编写,通过和硬件打交道,控制寄存器等等来达到我们想要的效果
- 硬件层:老本行就不记笔记了
驱动程序的编写流程
-
确定主设备号,也可以让内核来分配
-
定义一个file_operations结构体,这个结构体相当于把我们这个驱动要完成的事件给抽象出来
-
实现对应的drv_open/drv_read/drv_write函数,这些函数和应用层的函数都有所对应。把这些函数填入file_operations结构体里。
-
用register_chrdev来把file_operations结构体告诉内核,把他注册进内核
-
入口函数:在安装驱动时会调用这个函数,由他来注册驱动函数
-
出口函数:unregister_chrdev,出口函数会调用这个函数
-
其他的完善:提供设备信息,自动创建设备节点:class_create,device_create
小细节:
-
驱动程序根据主设备号来知道我们要驱动什么类型的设备:LED、OLED
-
驱动程序根据次设备号来知道我们要驱动哪一个设备:LED_1 or LED_2
驱动程序的分层:
个人拙见:还是和STM32的开发做对比,分层就相当于同一个模块的代码,可以给不同型号的芯片使用,只需要修改他的引脚或者状态就可以完美移植,这就是分层。XX小车之家的MPU6050的代码封装就很好,只需要做设定上的修改,就可以实现我想要的功能,嘿嘿嘿
这里我的理解是,将驱动程序进一步分层,暂时分为上下两层:
- 上层是让硬件实现具体效果的代码,它的核心对象是file_operations结构体
static struct file_operations led_drv = {
.owner = THIS_MODULE,
.open = led_drv_open,
.read = led_drv_read,
.write = led_drv_write,
.release = led_drv_close,
};
2.下层是具体到哪一个硬件,作出什么动作的指令式的代码,它的核心对象是led_operations
struct led_operations
{
int (*init) (int which); /* 初始化 LED, which-哪个 LED */
int (*ctl) (int which, char status); /* 控制 LED, which-哪个 LED, status:1-亮,0-灭*/
};
struct led_operations *get_board_led_opr(void);
/*************分割线***************************/
static struct led_operations board_demo_led_opr = {
.init = board_demo_led_init,
.ctl = board_demo_led_ctl,
};
//这里对这个结构体做定义
下面是韦老师的驱动程序代码:
/* 1. 确定主设备号 */
static int major = 0;
static struct class *led_class;
struct led_operations *p_led_opr;
#define MIN(a, b) (a < b ? a : b)
/* 3. 实现对应的open/read/write等函数,填入file_operations结构体 */
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{......}
/* write(fd, &val, 1); */
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{......}
static int led_drv_open (struct inode *node, struct file *file)
{......}
static int led_drv_close (struct inode *node, struct file *file)
{......}
/*2. 定义自己的file_operations结构体将我们需要的函数定义成我们想要的*/
static struct file_operations led_drv = {
.owner = THIS_MODULE,
.open = led_drv_open,
.read = led_drv_read,
.write = led_drv_write,
.release = led_drv_close,
};
/* 4. 把file_operations结构体告诉内核:注册驱动程序 */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
static int __init led_init(void)
{......}
/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数 */
static void __exit led_exit(void)
{......}
/* 7. 其他完善:提供设备信息,自动创建设备节点 */
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
心得:大概的了解了驱动程序和应用程序之间的关系,以及驱动程序的大概框架。
零碎的知识点:
1、C语言的函数指针
2、C语言的这种面向对象的特性和C++的面向对象特性有何不同,有待总结。
3、还有一点是 MODULE_LICENSE(“GPL”); 这一句代码,这个是模块的许可声明,没加会报错:
从2.4.10版本内核开始,模块必须通过MODULE_LICENSE宏声明此模块的许可
证,否则在加载此模块时,会收到内核被污染 “kernel tainted”
的警告。从linux/module.h文件中可以看到,被内核接受的有意义的许可证有
“GPL”,“GPL v2”,“GPL and additional rights”,“Dual BSD/GPL”,“Dual
MPL/GPL”,“Proprietary”。