95 lines
5.2 KiB
Markdown
95 lines
5.2 KiB
Markdown
# 8.2 Linux 驱动开发概述
|
||
|
||
## 嵌入式系统的分类
|
||
|
||
SOC:通常内存较大,使用外部磁盘,大容量 Flash 或 EMMC,有 MMU,区分用户态和内核态,运行 Linux/Android/WinCE 等大型系统,应用程序与系统可分开编译,系统可动态安装程序,一般使用远程 GDB 和日志输出方式进行调试。开发分为 BSP、驱动、中间件、应用/UI 等。比如 Intel Atom 系列、ARM Cortex A 系列。
|
||
|
||
MCU:通常内存和 Flash 较小,无 MMU,不区分用户态和内核态,无系统或运行小型实时系统,应用程序与系统一起编译形成镜像进行烧录,通常采用 JTAG 等硬件调试器进行调试。开发不分家或分为系统、驱动、应用开发。比如 ARM Cortex M 系列、Atmel AVR 系列、TI MSP430 系列。
|
||
|
||
## 认识 Linux 系统
|
||
|
||
Linux 可运行于多种平台,比如个人电脑、Power PC 以及 ARM Cortex A 系列 SOC 上。
|
||
|
||
Linux 分为内核态和用户态,内核态为受保护的代码,用户态程序执行异常不会导致系统漰溃,是因为用户态只能执行用户权限指令,特权指令只能在内核态执行 (这是由硬件设计决定的),且由于内核态的内存受到硬件保护,因此用户程序的溢出不会导致内核崩溃。内核态和用户态分离的目的就是为了保证系统的稳定,避免丰富的应用程序导致系统死机,而这种情况在 DOS 时期是很常见的。
|
||
|
||
由于驱动程序往往需要进行特权操作,比如直接操作物理内存,硬件寄存器,使用中断资源等,因此 Linux 系统的硬件驱动程序需要在内核态执行。Linux 的核心代码很小,在源代码中占大多数的为驱动程序或驱动程序框架。
|
||
|
||
为了适应不同类型设备的特征,Linux 内核驱动划分为多个子系统,比如:SPI 子系统、存储子系统、GPU 子系统等。每个子系统都为该类型设备而进行优化设计。同时,许多公司在子系统框架下还会按照自己的产品线构建下一级子系统。
|
||
|
||
## 嵌入式 Linux
|
||
|
||
嵌入式 Linux = SOC + Linux 开发
|
||
|
||
## Linux 内核源代码目录结构
|
||
|
||
* arch:包含和硬件体系结构相关的代码,每种平台占一个相应的目录,如 i386、ARM、PowerPC、MIPS 等。
|
||
* arch/\<arch\>/configs:各种 defconfig 文件。
|
||
* block:块设备驱动程序 I/O 调度。
|
||
* Documentation:内核各部分的通用解释和注释。
|
||
* drivers:设备驱动程序,每个不同的驱动占用一个子目录,如 char、block、net、mtd、i2c 等。
|
||
* include:头文件,与系统相关的头文件被放置在 include/linux 子目录下。
|
||
* kernel:内核的最核心部分,包括进程调度、定时器等,而和平台相关的一部分代码放在 arch/*/kernel 目录下。
|
||
* lib:库文件代码。
|
||
* net:网络相关代码,实现了各种常见的网络协议。
|
||
* scripts:包含用于配置内核的脚本文件。
|
||
* sound:ALSA、OSS 音频设备的驱动核心代码和常用设备驱动。
|
||
|
||
## Linux 内核驱动开发的特点
|
||
|
||
* 由于驱动运行在内核态,任何错误都有可能导致内核崩溃,因此软件自量的要求很高。
|
||
* 开发时需要对驱动有所定位,需要清楚驱动在哪个子系统下,属于总线驱动还是设备驱动等。
|
||
* 内核除为驱动划分了各个子系统外,还将驱动进行了分类,比如:字符设备,块设备,网络设备,杂项设备等。
|
||
* 与用户态一样,内核中提供了很多同步异步,以及通讯机制,学习 Linux 内核驱动开发时应注意各 API 的学习。
|
||
* Linux 的内核版本更新很快,不同版本间架构,API 等会有差异,应注意区分。
|
||
* 内核驱动可以与内核代码编译成一体,称为 Build In,也可以编译成独立模块。
|
||
* 学习时应注意区分驱动程序的入口,加载时机等。
|
||
* 内核源码中会使用大量的高级 C 语言宏操作,有些代码比较绕,通常先知道其用途,再深入学习原理。
|
||
* Linux 内核源码使用 Linux 代码风格进行开发,代码风格应共同遵守。
|
||
* 缺少有效的调试手段,通常是打印输出日志,动态调试和设置断点等需要使用 SOC 专用硬件调试器来调试,授权费用很高。
|
||
|
||
## hellodrv —— 一个简单的 Linux 内核模块
|
||
|
||
```cpp
|
||
#include <linux/init.h>
|
||
#include <linux/module.h>
|
||
MODULE_LICENSE("Dual BSD/GPL");
|
||
|
||
static int hello_init(void)
|
||
{
|
||
printk(KERN_ALERT "hello driver enter\n");
|
||
return 0;
|
||
}
|
||
|
||
static void hello_exit(void)
|
||
{
|
||
printk(KERN_ALERT "hello driver exit\n");
|
||
}
|
||
|
||
module_init(hello_init);
|
||
module_exit(hello_exit);
|
||
MODULE_AUTHOR("rick.chan");
|
||
MODULE_DESCRIPTION("a simple hello world module");
|
||
MODULE_ALIAS("a simplest module");
|
||
```
|
||
|
||
其由 C 程序的 include、函数、变量以及诸多 MODULE_* 宏构成,这个程序里没有用户态程序的标准入口 main() 函数。程序的入口由 module_init() 定义,出口由 module_exit() 定义。
|
||
|
||
Makefile 文件如下:
|
||
|
||
```Makefile
|
||
obj-m:= \
|
||
hellodrv.o
|
||
|
||
hellodrv-objs:= \
|
||
hello_drv.o
|
||
|
||
EXTRA_CFLAGS += \
|
||
-I$(PWD)
|
||
|
||
all:
|
||
$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
|
||
|
||
clean:
|
||
$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
|
||
```
|