2-驱动开发知识构建
为什么要linux驱动
首先我们需要明白一点,驱动和开发的关系可以理解为是先有鸡还是再先有蛋,驱动是更底层的是会涉及到内核的,而应用是无法直接操控内核,我们在开发过程中先做一个驱动,驱动提供一个接口可以被应用调用。
1.系统移植
我们开发一个产品,确定好主控芯片后我们会对他进行系统移植,那么我们知道一个系统的核心也就是linux内核他是一个系统的主体,他实现了整个操作系统的功能,如果对电脑了解的多会只有电脑主板上有一颗bios芯片,他里面保存的程序是电脑上电后执行的第一个程序就是检测硬件以及其他。在linux中也是如此需要一段bootloader代码用于检查各种外设最后引导启动内核,为什么我们的电脑刚买回来插上鼠标键盘就能用,这就是因为他把这些驱动直接放在bootloader中了上电自动就会启动,为什么有的时候像gpu会显示让你更新驱动,所有的外设都需要所谓的驱动才能让其真正的工作起来,驱动更像是一个代码里面写满了所有这个外设的功能。但一个系统光有上面俩个东西还不能正常使用,还需要根文件系统,就好比uboot将镜像文件进行解压,但需要一个地方去接纳解压后的内容。
|
|-uboot编译 –> 生成可执行文件
|-内核编译 –> zimage镜像文件和设备树
|-构建rootfs –> 进行压缩
|-放在一起烧入
驱动实际案例
helloworld设备
——撰写驱动(有框架)——编写Makefile——加载驱动——创建设备节点——测试应用
知识体系构建
Linux驱动开发
├── 驱动基础
│ ├── 内核模块(加载/卸载)
│ ├── 字符设备(file_operations)
│ └── 设备号管理(主/次设备号)
├── 硬件交互
│ ├── 设备树(语法、绑定)
│ ├── I2C子系统(设备树、驱动)
│ ├── SPI子系统(数据传输)
│ └── GPIO控制(中断、方向)
├── 内核机制
│ ├── 中断处理(顶/底半部)
│ ├── 同步机制(锁、信号量)
│ └── 内存管理(DMA、kmalloc)
├── 高级框架
│ ├── IIO(传感器驱动)
│ ├── Input子系统(事件上报)
│ └── PWM(脉宽调制)
├── 调试与优化
│ ├── 工具链(dmesg, i2c-tools)
│ ├── 性能优化(DMA、中断合并)
│ └── 用户态接口(sysfs、ioctl)
└── 开发流程
├── 硬件分析 → 设备树 → 驱动开发 → 测试验证
└── 扩展知识(电源管理、安全)
开发环境搭建
方案一:clion
比较复杂,Linux驱动前期还是用vscode
方案二:vscode
ssh远程连接本地的虚拟机
知识准备
为什么Linux学起来很难——Linux下全tm是文件,封装封装还是封装
这俩句话是贯穿整个Linux系统的真言,在系统开发中我们会发现我们至始至终都是在写文件操控文件,怎么理解呢编译内核模块是将他编程.ko文件,我们实现驱动的挂载也是通过打开文件读取信息再放在内核中
封装,我认为是Linux学起来困难的最主要原因,由于Linux是一个开源系统,全世界的程序员为它增砖添瓦,不用说我们自己写的代码有时会对其逻辑挠头,更何况这个全世界顶尖程序员智慧产物,各种概念会让人摸不清头脑,但归根结底代码的方向永远是——偷懒,就是提高各种效率
1.文件I/O
1.1linux的内存管理
硬盘最小的存储单位是扇区,每个扇区存储512字节(0.5kb)
文件存取的最小单位是块,8个扇区组成一个块(4kb)
操作系统读取硬盘时不会一个扇区一个扇区读这样效率太慢所以都是以块为单位
磁盘在被分区时会有两个区域,一个是数据区一个是inode区(存放inode表)
inode本质上来说是一个结构体,他记录了文件的各种信息,每个inode都有自己的编号,所有的编号存在表里
快速格式化是只删除了inode表
打开一个文件系统有以下三步:系统找到文件名对应的inode编号->通过编号从表里找到对应的inode结构体->根据信息确认所在的block读数据
文件没被打开时是静态文件安静呆在磁盘中,被打开时系统会申请一段缓冲区读取到内存中缓存,此时文件叫动态文件,打开后对其进行操作是对动态文件进行操作,直到操作结束系统会将动态文件同步到静态文件
为什么这样设计,磁盘等存储设备以块为单位进行读取,所以有一个字节要修改就得把整个块拿出来再修改,在内存是有地址的这样修改更加方便,同时内存读写原要比磁盘读写快
1.2linux的文件类型
使用stat命令可以查看文件类型
‘ - ‘:普通文件 :文本文件、二进制文件
‘ d ‘:目录文件
‘ c ‘:字符设备文件
‘ b ‘:块设备文件
‘ l ‘:符号链接文件
‘ s ‘:套接字文件
‘ p ‘:管道文件
2.进程
2.1PCB进程控制块
linux下程序编译
不同于我们在windows下用ide写c语言,由于ide强大的功能我们编译代码是一站式编译,但在Linux下我们需要清楚了解这其中的过程
Linux中使用的是glibc,C语言的标准库,他提供了<stdio.h>这类头文件
预处理——编译——汇编——链接
注意链接是将依赖链接到一起,将机器码文件(.o)链接成一个可执行的文件,这个文件elf文件格式(通用执行文件)
链接有动态库和静态库之分,静态库可以独立运行而不需要其他依赖,但更占用空间,Linux中默认动态库
文件系统
存储设备文件系统
我们首先想到的通常是Windows 下的FAT32、NTFS、exFAT 以及Linux 下常用的ext2、ext3 和ext4 的类型格式。
这些文件系统都是为了解决如何高效管理存储器空间的问题而诞生的。
伪文件系统
Linux 内核还提供了procfs、sysfs 和devfs 等伪文件系统。
伪文件系统存在于内存中,通常不占用硬盘空间,它以文件的形式,向用户提供了访问系统内核数据的接口。用户和应用程序可以通过访问这些数据接口,得到系统的信息,而且内核允许用户修改内核的某些参数。
虚拟文件系统
Linux 内核包含了文件管理子系统组件,它主要实现了虚拟文件系统(Virtual File System,VFS),虚拟文件系统屏蔽了各种硬件上的差异以及具体实现的细节,为所有的硬件设备提供统一的接口,从而达到设备无关性的目的,同时文件管理系统还为应用层提供统一的APi接口。为了使不同的文件系统共存,Linux 内核在用户层与具体文件系统之前增加了虚拟文件系统中间层,它对复杂的系统进行抽象化,对用户提供了统一的文件操作接口。无论是ext2/3/4、FAT32、NTFS 存储的文件,还是/proc、/sys 提供的信息还是硬件设备,无论内容是在本地还是网络上,都使用一样的open、read、write 来访问,使得“一切皆文件”的理念被实现,这也正是软件中间层的魅力。
是什么?内核模块
内核体系结构有:微内核(Micro Kernel) 、宏内核(Monolithic Kernel) 混合内核(HybridKernel) 等
1.windows和鸿蒙是微内核,他们的驱动是不被包含到内核中的,你去动驱动是不影响核心功能(ipc、进程管理啥的)
2.linux是宏内核,驱动是要被编到内核中,但在开发中每次都重新编译一边内核不太现实,所以引入内核模块的概念,提供一种动态的能力
3.我们在开发内核模块时,是将其先编译成.o文件,再链接到内核中,也就是说他可以被单独编译但不能独立运行
内核模块经过编译最终形成.ko 为后缀的ELF 文件
- 1.ELF格式
可以使用readelf 工具查看elf 文件的头部详细信息。
- 2.内核模块安装和卸载的过程
- 3.内核导出符号的过程
驱动开发思路
- 确定设备分类:
- 硬件总线类型判断:
Linux驱动框架分类体系
一、基础设备类型框架
框架类型 | 内核路径 | 核心结构体 | 典型应用场景 |
---|---|---|---|
字符设备驱动 | drivers/char/ | struct file_operations |
传感器、GPIO控制 |
块设备驱动 | drivers/block/ | struct block_device_ops |
SSD、机械硬盘 |
网络设备驱动 | drivers/net/ | struct net_device_ops |
以太网卡、WiFi模块 |
二、总线型驱动框架
总线类型 | 注册接口 | 匹配机制 | 硬件示例 |
---|---|---|---|
I2C总线 | i2c_register_driver() | of_device_id +设备树 |
EEPROM、温度传感器 |
SPI总线 | spi_register_driver() | SPI设备ID匹配 | Flash存储器、TFT屏幕 |
USB总线 | usb_register_driver() | 接口类/厂商ID | HID设备、USB摄像头 |
PCI/PCIe总线 | pci_register_driver() | PCI厂商/设备ID | 显卡、高速网卡 |
ACPI总线 | acpi_bus_register_driver() | ACPI设备路径匹配 | 电源管理设备 |
三、子系统级驱动框架
子系统名称 | 核心组件 | 关键API | 应用案例 |
---|---|---|---|
输入子系统 | drivers/input/ | input_register_device() |
键盘、触摸屏 |
帧缓冲子系统 | drivers/video/ | fb_ops 结构体 |
LCD控制器驱动 |
声音子系统 | sound/soc/ | snd_soc_component_driver |
音频编解码器 |
DMA引擎框架 | drivers/dma/ | dma_device 结构 |
高速数据传输 |
IIO子系统 | drivers/iio/ | iio_chan_spec 定义 |
加速度计、光传感器 |
四、设备树(DT)关联框架
框架类型 | 设备树节点特征 | 关键函数 | 开发优势 |
---|---|---|---|
Platform框架 | 含compatible 属性 |
of_match_table 匹配 |
硬件抽象与驱动分离 |
GPIO子系统 | gpio-controller 定义 |
gpiod_get() 获取引脚 |
跨平台引脚管理 |
Pinctrl子系统 | pinctrl-0 属性定义 |
pinctrl_lookup_state() |
引脚复用配置自动化 |
Clock框架 | clocks 属性链 |
clk_get() 获取时钟 |
统一时钟树管理 |
Regulator框架 | regulator 节点 |
regulator_get() |
电源管理统一接口 |
五、高级功能框架
框架名称 | 技术特性 | 核心机制 | 典型应用 |
---|---|---|---|
DMA-BUF框架 | 内存共享机制 | dma_buf_export() |
GPU与VPU数据交换 |
V4L2框架 | 视频采集标准 | video_device 注册 |
摄像头驱动开发 |
DRM/KMS框架 | 图形渲染管理 | drm_driver 结构体 |
现代显卡驱动 |
NTB框架 | 跨节点通信 | ntb_register_device() |
服务器多机互联 |
RPMSG框架 | 多核间通信 | rpmsg_send() |
SoC异构核通信 |
六、特殊设备框架
框架类型 | 设备特征 | 实现要点 | 代表驱动 |
---|---|---|---|
MISC驱动 | 主设备号10 | miscdevice 结构体 |
看门狗、随机数生成器 |
UIO框架 | 用户空间I/O | uio_info 注册 |
FPGA加速器控制 |
HWMON框架 | 硬件监控 | hwmon_device_register() |
温度传感器监控 |
LED框架 | 灯光控制 | led_classdev_register() |
LED指示灯控制 |
WATCHDOG框架 | 系统看门狗 | watchdog_device 结构 |
硬件级系统复位 |
七、虚拟化驱动框架
框架名称 | 虚拟化类型 | 核心接口 | 应用场景 |
---|---|---|---|
VFIO框架 | 设备直通 | vfio_pci_core_register() |
云计算GPU直通 |
Virtio框架 | 半虚拟化 | virtio_device_ops |
虚拟网卡、块设备 |
Xen PV驱动 | Xen虚拟化 | xenbus_driver 注册 |
云服务器虚拟设备 |
KVM设备模拟 | 硬件辅助虚拟化 | kvm_ioctl() |
嵌入式虚拟化平台 |
***********
学习课程安排
2 内核模块传参与符号共享
1 | /* |
3 设备号
前两节只是简单框架,但在实际开发中我们用户空间是通过设备文件实现操作
在需要文件时设备号就是必须的
我们可以在/proc/devises查看设备
主设备号对应驱动程序
4驱动基本框架
5设备树
基本语法
从此以后 ARM社区就引入了 PowerPC等架构已经采用的设备树 (Flattened Device Tree),将这些描述板级硬件信息的内容都从 Linux内中分离开来,用一个专属的文件格式来描
述,这个专属的文件就叫做设备树,文件扩展名为 .dts。一个 SOC可以作出很多不同的板子,
这些不同的板子肯定是有共同的信息,将这些共同的信息提取出来作为一个通用的文件,其他
的 .dts文件直接引用这个通用文件即可,这个通用文件就是 .dtsi文件,类似于 C语言中的头文
件。一般 .dts描述板级信息 (也就是开发板上有哪些 IIC设备、 SPI设备等 ),,.dtsi描述 SOC级信
息 (也就是 SOC有几个 CPU、主频是多少、各个外设控制器信息等 )。
这个就是设备树的由来,简而言之就是, Linux内核中 ARM架构下有太多的冗余的垃圾
板级信息文件,导致 linus震怒,然后 ARM社区引入了设备树。
因此在 .dts设
备树文件中,可以通过“ “#include”来引用 .h、 .dtsi和 .dts文件。只是,我们在编写设备树头文
件的时候最好选择 .dtsi后缀。
一般 .dtsi文件用于描述 SOC的内部外设信息,比如 CPU架构、主频、外设寄存器地址范
围,比如 UART、 IIC等等。 比如 rk3568.dtsi就是描述 RK3568芯片本身的外设信息, 内容如
下: