嵌入式 Android 系统开发 - 零基础入门指南
本文档专为零基础新手设计,从最基础的概念开始,一步步带你进入嵌入式 Android 系统开发的世界。
目录
- 写在前面:给新手的话
- 什么是嵌入式 Android
- 学习路线总览
- 第零阶段:基础知识准备
- 第一阶段:开发环境搭建
- 第二阶段:Linux 基础
- 第三阶段:C 语言与数据结构
- 第四阶段:Android 系统架构认知
- 第五阶段:Bootloader 与 U-Boot
- 第六阶段:Linux Kernel 驱动开发
- 第七阶段:设备树 (Device Tree)
- 第八阶段:Android Init 系统
- 第九阶段:HAL 硬件抽象层
- 第十阶段:Android Framework
- 实践项目
- 常见问题与解答
- 学习资源推荐
1. 写在前面:给新手的话
1.1 这份指南适合谁?
- 完全没有嵌入式开发经验的新手
- 想从手机/应用开发转向系统开发的程序员
- 对智能电视、机顶盒等设备开发感兴趣的人
- 想深入理解 Android 系统底层的开发者
1.2 学习这个需要多久?
诚实地说:嵌入式 Android 开发是一个庞大的领域,完全掌握需要较长时间。但你可以:
入门阶段(能看懂代码、做简单修改): 3-6 个月
熟练阶段(能独立解决问题): 6-12 个月
精通阶段(能设计架构、移植系统): 2-3 年不要被吓到! 你不需要学完所有内容才能开始工作。学完前几个阶段,你就能参与实际项目了。
1.3 学习建议
┌───────────────────────────────────────────────────────────────────────────┐
│ 新手学习黄金法则 │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 【动手第一】 看 10 遍不如自己敲 1 遍 │
│ 每个代码示例都要亲手输入运行 │
│ │
│ 2. 【不求甚解】 第一遍学习时,遇到不懂的可以先跳过 │
│ 等学完后面内容再回头看,往往豁然开朗 │
│ │
│ 3. 【善用搜索】 遇到错误先 Google/百度 │
│ 90% 的问题别人都遇到过 │
│ │
│ 4. 【记录笔记】 建立自己的知识库 │
│ 今天解决的问题,明天可能还会遇到 │
│ │
│ 5. 【循序渐进】 不要跳跃学习 │
│ 每个阶段都是下一阶段的基础 │
│ │
└───────────────────────────────────────────────────────────────────────────┘2. 什么是嵌入式 Android
2.1 用大白话解释
普通 Android 开发:你写一个 App(比如微信),用户下载安装后使用。你只需要关心 App 本身。
嵌入式 Android 开发:你要让整个 Android 系统在一块硬件板子上跑起来。从开机第一行代码,到用户看到桌面,都是你的工作范围。
2.2 嵌入式 Android 用在哪里?
┌───────────────────────────────────────────────────────────────────────────┐
│ 嵌入式 Android 应用场景 │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ 🖥️ 智能电视 - 小米电视、创维电视、海信电视... │
│ │
│ 📺 机顶盒 - 运营商送的盒子、小米盒子、天猫魔盒... │
│ │
│ 🚗 车载系统 - 比亚迪、蔚来、小鹏的中控屏幕 │
│ │
│ 🎮 游戏机 - 一些安卓游戏掌机 │
│ │
│ 🏭 工业设备 - 工业平板、点餐机、广告机 │
│ │
│ 🏠 智能家居 - 智能音箱带屏幕的、智能冰箱 │
│ │
│ 📱 定制手机 - 一些特殊用途的手机(如老人机、儿童手机) │
│ │
└───────────────────────────────────────────────────────────────────────────┘2.3 我们学习的平台
本指南基于 Amlogic S905X5M 芯片平台,运行 Android 14 系统。
为什么选这个平台?
- Amlogic(晶晨半导体)是国内最大的机顶盒/智能电视芯片厂商之一
- 市面上大量设备使用 Amlogic 芯片
- 学会这个平台,很容易迁移到其他类似平台
2.4 嵌入式 Android vs 手机 Android
| 对比项 | 手机 Android | 嵌入式 Android (如电视盒子) |
|---|---|---|
| 触摸屏 | 必须有 | 通常没有,用遥控器 |
| 电池 | 有电池,需要省电 | 插电使用,不太考虑省电 |
| 摄像头 | 必须有 | 通常没有 |
| 通话功能 | 有 | 没有 |
| 视频输出 | 手机屏幕 | HDMI 输出到电视 |
| 主要交互 | 触摸 | 遥控器、语音(有的项目有) |
| 应用场景 | 移动便携 | 固定位置使用 |
3. 学习路线总览
3.1 完整系统架构图(先混个眼熟)
┌───────────────────────────────────────────────────────────────────────────┐
│ Android 系统架构全景图 │
│ (从上到下,从软件到硬件) │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ 第10阶段 应用层 (Applications) │ │
│ │ 你平时用的 App:设置、视频播放器、浏览器等 │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ ▲ │
│ │ 调用 │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ 第10阶段 Framework 层 (Android 框架) │ │
│ │ Java/Kotlin 写的系统服务,管理 App 的生命周期等 │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ ▲ │
│ │ 调用 │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ 第9阶段 HAL 层 (硬件抽象层) │ │
│ │ C/C++ 写的,连接 Android 和硬件驱动的桥梁 │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ ▲ │
│ │ 调用 │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ 第6-8阶段 Linux Kernel (Linux 内核) │ │
│ │ 操作系统核心 + 硬件驱动程序 │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ ▲ │
│ │ 启动 │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ 第5阶段 Bootloader (引导程序) │ │
│ │ 开机后第一个运行的程序,负责加载 Linux 内核 │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ ▲ │
│ │ 上电 │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ Hardware (硬件) │ │
│ │ CPU、内存、存储、HDMI、USB、网口等 │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────────────────────┘3.2 学习路线图
学习路线图
════════════════════════════════════════════════════════════════════
基础准备 核心技能 进阶技能
──────── ──────── ────────
┌────────┐
│ 第0阶段│ 基础知识准备
│ 计算机 │ (1-2周)
│ 基础 │ 了解计算机、操作系统基本概念
└───┬────┘
│
▼
┌────────┐
│ 第1阶段│ 环境搭建
│ 环境 │ (1周)
│ 搭建 │ 安装 Linux、配置开发环境
└───┬────┘
│
▼
┌────────┐
│ 第2阶段│ Linux 基础
│ Linux │ (2-3周)
│ 基础 │ 命令行、文件系统、Shell
└───┬────┘
│
▼
┌────────┐
│ 第3阶段│ C 语言
│ C 语言 │ (3-4周)
│ │ 指针、结构体、内存管理
└───┬────┘
│
▼
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│ 第4阶段│ │ 第5阶段│ │ 第6阶段│ │ 第7阶段│
│ Android│──▶│ Boot- │──▶│ Kernel │──▶│ 设备树 │
│ 架构 │ │ loader │ │ 驱动 │ │ DTS │
│ (1周) │ │ (2周) │ │ (3-4周)│ │ (2周) │
└───┬────┘ └────────┘ └────────┘ └───┬────┘
│ │
│ ┌────────────────────────────┘
│ │
▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│ 第8阶段│ │ 第9阶段│ │第10阶段│
│ Android│──▶│ HAL │──▶│Frame- │
│ Init │ │ 硬件 │ │ work │
│ (2周) │ │ 抽象层 │ │ │
└────────┘ │ (3周) │ │ (4周+) │
└────────┘ └────────┘3.3 各阶段详细说明
| 阶段 | 名称 | 主要内容 | 学习目标 | 难度 |
|---|---|---|---|---|
| 0 | 基础知识准备 | 计算机组成、操作系统概念 | 理解基本术语 | ★☆☆☆☆ |
| 1 | 环境搭建 | Ubuntu 安装、工具配置 | 能编译 Android 源码 | ★★☆☆☆ |
| 2 | Linux 基础 | 命令行、Shell、文件系统 | 熟练使用 Linux | ★★☆☆☆ |
| 3 | C 语言 | 指针、结构体、内存 | 能读写 C 代码 | ★★★☆☆ |
| 4 | Android 架构 | 系统组成、编译系统 | 理解整体架构 | ★★☆☆☆ |
| 5 | Bootloader | U-Boot、启动流程 | 理解开机过程 | ★★★☆☆ |
| 6 | Kernel 驱动 | 驱动模型、字符设备 | 能写简单驱动 | ★★★★☆ |
| 7 | 设备树 | DTS 语法、硬件描述 | 能修改设备树 | ★★★☆☆ |
| 8 | Android Init | init.rc、属性系统 | 能定制启动流程 | ★★★☆☆ |
| 9 | HAL | HIDL/AIDL 接口 | 能读懂 HAL 代码 | ★★★★☆ |
| 10 | Framework | 系统服务、Binder | 能进行系统定制 | ★★★★★ |
4. 第零阶段:基础知识准备
目标:了解计算机和操作系统的基本概念,为后续学习打好基础
4.1 你需要知道的基本概念
4.1.1 计算机是怎么工作的?
┌───────────────────────────────────────────────────────────────────────────┐
│ 计算机工作原理简图 │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ CPU │ 大脑:执行指令 │
│ │ (处理器) │ │
│ └────────┬────────┘ │
│ │ │
│ ┌──────────────────┼──────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ 内存 │ │ 存储 │ │ 输入输出 │ │
│ │ (RAM) │ │(硬盘/闪存)│ │ 设备 │ │
│ │ │ │ │ │ │ │
│ │ 临时存放 │ │ 永久保存 │ │ 键盘鼠标 │ │
│ │ 运行数据 │ │ 文件数据 │ │ 显示器等 │ │
│ └───────────┘ └───────────┘ └───────────┘ │
│ │
│ 类比: │
│ CPU = 厨师 (负责干活) │
│ 内存 = 案板 (放正在处理的食材,关机就清空) │
│ 存储 = 冰箱 (长期保存食材,关机不丢失) │
│ 输入输出 = 窗口 (和外界交流) │
│ │
└───────────────────────────────────────────────────────────────────────────┘4.1.2 什么是操作系统?
操作系统就像一个"大管家",负责:
1. 管理硬件资源
- 决定谁能用 CPU
- 分配内存给各个程序
- 控制硬盘读写
2. 提供统一接口
- 程序不需要知道硬件细节
- 通过操作系统提供的接口操作硬件
3. 运行应用程序
- 加载程序到内存
- 调度程序运行
常见操作系统:
- Windows(电脑)
- macOS(苹果电脑)
- Linux(服务器、嵌入式设备)
- Android(手机、电视盒子)—— 基于 Linux
- iOS(苹果手机)4.1.3 什么是 Linux?
┌───────────────────────────────────────────────────────────────────────────┐
│ Linux 简介 │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ Linux 是一个开源的操作系统内核,由 Linus Torvalds 在 1991 年创建 │
│ │
│ 特点: │
│ ├── 开源免费:任何人都可以查看、修改、分发源代码 │
│ ├── 稳定可靠:服务器领域占统治地位 │
│ ├── 高度可定制:可以裁剪到很小,适合嵌入式设备 │
│ └── 生态丰富:大量工具和软件支持 │
│ │
│ 常见 Linux 发行版: │
│ ├── Ubuntu:最流行,适合新手(我们用这个) │
│ ├── CentOS/Rocky:服务器常用 │
│ ├── Debian:稳定,Ubuntu 基于它 │
│ └── Arch:高度可定制,适合高手 │
│ │
│ Android 和 Linux 的关系: │
│ ┌─────────────────────────────────┐ │
│ │ Android 系统 │ │
│ │ ┌──────────────────────────┐ │ │
│ │ │ Android Framework │ │ ← Google 开发的 │
│ │ │ (Java/Kotlin 层) │ │ │
│ │ ├──────────────────────────┤ │ │
│ │ │ Native 层 │ │ ← 各种 C/C++ 库 │
│ │ ├──────────────────────────┤ │ │
│ │ │ Linux Kernel │ │ ← Linux 内核(开源) │
│ │ │ (Linux 内核) │ │ │
│ │ └──────────────────────────┘ │ │
│ └─────────────────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────────────────────┘4.1.4 进制转换(必须掌握)
在嵌入式开发中,你会频繁遇到不同进制的数字:
十进制 (我们日常用的): 0, 1, 2, ... 9, 10, 11 ...
二进制 (计算机用的): 0, 1, 10, 11, 100, 101 ...
十六进制 (程序员常用): 0, 1, 2 ... 9, A, B, C, D, E, F, 10, 11 ...
为什么用十六进制?
- 二进制太长:11111111 = 255
- 十六进制简短:FF = 255
- 每个十六进制位对应 4 个二进制位,转换方便
常见表示方法:
- 十进制:255
- 二进制:0b11111111 或 11111111b
- 十六进制:0xFF 或 0xff 或 FFh
常用对照表:
┌────────┬────────┬────────┐
│ 十进制 │ 二进制 │十六进制│
├────────┼────────┼────────┤
│ 0 │ 0000 │ 0 │
│ 1 │ 0001 │ 1 │
│ 2 │ 0010 │ 2 │
│ 3 │ 0011 │ 3 │
│ 4 │ 0100 │ 4 │
│ 5 │ 0101 │ 5 │
│ 6 │ 0110 │ 6 │
│ 7 │ 0111 │ 7 │
│ 8 │ 1000 │ 8 │
│ 9 │ 1001 │ 9 │
│ 10 │ 1010 │ A │
│ 11 │ 1011 │ B │
│ 12 │ 1100 │ C │
│ 13 │ 1101 │ D │
│ 14 │ 1110 │ E │
│ 15 │ 1111 │ F │
│ 255 │11111111│ FF │
└────────┴────────┴────────┘4.1.5 常见存储单位
1 Byte (字节) = 8 bits (位)
1 KB = 1024 Bytes
1 MB = 1024 KB
1 GB = 1024 MB
常见大小参考:
- 一个英文字符:1 Byte
- 一个中文字符:2-3 Bytes (UTF-8)
- 一张手机照片:2-5 MB
- 一部高清电影:1-2 GB
- Android 系统源码:约 100 GB
- 编译后的镜像:约 1-4 GB4.2 第零阶段检验
完成以下问题,确保你理解了基本概念:
5. 第一阶段:开发环境搭建
目标:搭建一个能够编译 Android 源码的开发环境
5.1 硬件要求
┌───────────────────────────────────────────────────────────────────────────┐
│ 开发电脑硬件要求 │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ 最低配置(能用但很慢): │
│ ├── CPU:4 核 │
│ ├── 内存:16 GB │
│ ├── 硬盘:256 GB SSD │
│ └── 网络:能访问 Google(或配置镜像源) │
│ │
│ 推荐配置(流畅开发): │
│ ├── CPU:8 核以上(如 Intel i7/i9 或 AMD Ryzen 7/9) │
│ ├── 内存:32 GB 以上(64 GB 更好) │
│ ├── 硬盘:512 GB SSD 以上(1TB 更好,且建议 NVMe) │
│ └── 网络:稳定的网络连接 │
│ │
│ 说明: │
│ - 编译 Android 源码非常消耗资源 │
│ - 内存不足会导致编译失败或极慢 │
│ - SSD 比机械硬盘快很多,强烈推荐 │
│ - 编译时 CPU 会满载,注意散热 │
│ │
└───────────────────────────────────────────────────────────────────────────┘5.2 安装 Ubuntu 操作系统
5.2.1 为什么用 Ubuntu?
- Android 官方推荐在 Linux 下编译
- Ubuntu 是最流行的 Linux 发行版,资料最多
- 大部分问题都能搜到解决方案
5.2.2 安装方式选择
略
5.2.3 Ubuntu 安装步骤(双系统示例)
略
5.3 配置开发环境
打开终端(Ctrl + Alt + T),依次执行以下命令:
5.3.1 更新系统
# 更新软件包列表
sudo apt update
# 升级已安装的软件包
sudo apt upgrade -y5.3.2 安装编译 Android 所需的依赖
# 安装必要的软件包
sudo apt install -y \
git \
gnupg \
flex \
bison \
build-essential \
zip \
curl \
zlib1g-dev \
libc6-dev-i386 \
libncurses5 \
lib32ncurses5-dev \
x11proto-core-dev \
libx11-dev \
lib32z1-dev \
libgl1-mesa-dev \
libxml2-utils \
xsltproc \
unzip \
fontconfig \
python3 \
python-is-python3 \
libssl-dev \
bc \
rsync \
ccache \
device-tree-compiler \
u-boot-tools \
lzop \
vim \
openssh-server5.3.3 安装 ADB 和 Fastboot
# 安装 Android 平台工具
sudo apt install -y android-tools-adb android-tools-fastboot
# 添加 udev 规则(让普通用户能使用 USB 设备)
sudo usermod -aG plugdev $USER
# 创建 udev 规则文件
sudo tee /etc/udev/rules.d/51-android.rules << 'EOF'
# Amlogic devices
SUBSYSTEM=="usb", ATTR{idVendor}=="1b8e", MODE="0666", GROUP="plugdev"
EOF
# 重新加载 udev 规则
sudo udevadm control --reload-rules5.3.4 配置 Git
# 设置你的名字和邮箱(改成你自己的)
git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"
# 设置默认编辑器为 vim(或改成你喜欢的)
git config --global core.editor vim
# 设置凭证缓存,避免每次输入密码
git config --global credential.helper cache5.3.5 配置 ccache(加速编译)
# 设置 ccache 缓存大小(50GB,根据你的硬盘空间调整)
ccache -M 50G
# 在 ~/.bashrc 中添加环境变量
echo 'export USE_CCACHE=1' >> ~/.bashrc
echo 'export CCACHE_DIR=~/.ccache' >> ~/.bashrc
# 使配置生效
source ~/.bashrc5.3.6 安装 Java (Android 14 需要)
# 安装 OpenJDK 17
sudo apt install -y openjdk-17-jdk
# 验证安装
java -version5.4 获取源代码
5.4.1 创建工作目录
# 创建源码目录
mkdir -p ~/android/s905x5
cd ~/android/s905x55.4.2 源码获取方式
通常有以下几种方式获取源码:
1. 从芯片厂商获取
- Amlogic 会提供 SDK 给客户
- 通过内部 Git 服务器或 FTP 下载
2. 从公司内部服务器获取
- 公司一般会有内部的代码服务器
- 使用 git clone 或 repo sync
3. 从 AOSP 获取(纯净 Android,不含芯片厂商代码)
- 仅用于学习 Android 框架
- 不能直接在 Amlogic 板子上运行5.4.3 源码目录结构概览
当你获取源码后,会看到类似这样的目录结构:
android-source/
├── art/ # Android Runtime(ART 虚拟机)
├── bionic/ # Android 的 C 库
├── bootable/ # 引导程序相关
│ ├── bootloader/ # Bootloader 源码
│ └── recovery/ # Recovery 模式
├── build/ # ★ 编译系统
│ └── make/ # Makefile 和编译脚本
├── common/ # ★ Linux 内核源码
│ └── common14-5.15/ # 5.15 版本内核
├── device/ # ★ 设备配置
│ └── amlogic/ # Amlogic 设备配置
│ └── ross/ # 你的产品配置
├── external/ # 第三方开源项目
├── frameworks/ # ★ Android 框架
│ ├── av/ # 音视频框架
│ ├── base/ # 核心框架
│ └── native/ # Native 服务
├── hardware/ # ★ HAL 硬件抽象层
│ └── amlogic/ # Amlogic HAL
├── kernel/ # 内核相关
├── packages/ # 应用程序
│ └── apps/ # 系统应用
├── prebuilts/ # 预编译工具
├── system/ # 系统核心组件
├── vendor/ # ★ 厂商定制
│ └── amlogic/ # Amlogic 定制
├── Makefile # 顶层 Makefile
└── README.md # 说明文件
带 ★ 的是你最常打交道的目录5.5 第一次编译
5.5.1 初始化编译环境
# 进入源码目录
cd ~/android/s905x5
# 初始化编译环境(每次打开新终端都要执行)
source build/envsetup.sh
# 选择编译目标
lunch
# 会显示类似的菜单:
# 1. ross-userdebug
# 2. ross-user
# 选择 ross-userdebug(带调试功能)5.5.2 开始编译
# 方式一:使用项目提供的编译脚本(推荐)
source build/envsetup.sh && lunch ross-userdebug && make -j16
# 参数说明:
# -a : 编译 Android
# -c : 清理编译
# -k : 编译内核
# -o : 生成 OTA 包
# -j200 : 使用 200 个并行任务(根据 CPU 核心数调整)
# 方式二:使用标准 make 命令
make -j$(nproc)
# $(nproc) 自动获取 CPU 核心数5.5.3 编译时间参考
首次完整编译:
├── 高配机器(32核/64GB):1-2 小时
├── 中配机器(8核/32GB):3-5 小时
└── 低配机器(4核/16GB):6-10 小时
后续增量编译(只改了少量代码):
├── 几分钟到几十分钟不等
└── 开启 ccache 后更快5.5.4 编译输出
# 编译成功后,镜像文件位于:
out/target/product/ross/
# 主要文件:
├── boot.img # 内核镜像
├── system.img # 系统分区镜像
├── vendor.img # 厂商分区镜像
├── userdata.img # 用户数据分区
├── recovery.img # Recovery 镜像
└── update.zip # OTA 升级包5.6 烧录测试
5.6.1 准备工作
# 确保 ADB 工具可用
adb version
# 连接设备(USB 线连接电视盒子和电脑)
# 在盒子上启用"开发者选项" > "USB 调试"
# 检查设备是否连接
adb devices
# 应该显示类似:
# List of devices attached
# 1234567890ABCDEF device5.6.2 进入刷机模式
# 方式一:通过 ADB 命令
adb reboot bootloader
# 方式二:按键组合
# 断电 → 按住复位键 → 上电 → 松开复位键5.6.3 烧录镜像
# 进入编译输出目录
cd out/target/product/ross
# 使用 fastboot 烧录(各分区分别烧录)
fastboot flash boot boot.img
fastboot flash system system.img
fastboot flash vendor vendor.img
fastboot reboot
# 或者使用 Amlogic 提供的烧录工具
# USB_Burning_Tool(Windows)
# aml-flash-tool(Linux)5.7 第一阶段检验
6. 第二阶段:Linux 基础
目标:熟练使用 Linux 命令行,为后续开发打好基础
6.1 为什么要学 Linux 命令行?
在嵌入式 Android 开发中,你会:
1. 在 Linux 电脑上编译代码(每天都用)
2. 通过串口/ADB 登录设备的 Linux Shell(调试必备)
3. 阅读和编写 Shell 脚本(编译脚本、启动脚本)
4. 查看系统日志、调试问题(定位问题)
可以说,不会 Linux 命令行 = 无法进行嵌入式开发6.2 文件系统基础
6.2.1 Linux 目录结构
/ # 根目录(一切的起点)
├── bin/ # 基本命令(ls, cp, mv 等)
├── boot/ # 启动文件(内核、bootloader)
├── dev/ # 设备文件(★ 嵌入式开发常用)
│ ├── null # 空设备
│ ├── ttyS0 # 串口设备
│ └── sda # 硬盘设备
├── etc/ # 配置文件
│ ├── passwd # 用户信息
│ └── fstab # 挂载配置
├── home/ # 用户主目录
│ └── yourname/ # 你的主目录 (~)
├── lib/ # 系统库文件
├── mnt/ # 挂载点
├── opt/ # 可选软件
├── proc/ # ★ 进程和内核信息(虚拟文件系统)
│ ├── cpuinfo # CPU 信息
│ └── meminfo # 内存信息
├── root/ # root 用户主目录
├── sbin/ # 系统管理命令
├── sys/ # ★ 系统和设备信息(虚拟文件系统)
├── tmp/ # 临时文件
├── usr/ # 用户程序
│ ├── bin/ # 用户命令
│ ├── lib/ # 用户库
│ └── share/ # 共享数据
└── var/ # 可变数据(日志等)
└── log/ # 系统日志6.2.2 路径表示
绝对路径:从根目录开始的完整路径
例如:/home/yourname/android/source
相对路径:相对于当前目录的路径
例如:./source 或 ../android/source
特殊目录表示:
~ : 当前用户的主目录,等同于 /home/yourname
. : 当前目录
.. : 上一级目录
- : 上一次所在的目录6.3 基础命令学习
6.3.1 文件和目录操作
# ========== 查看 ==========
ls # 列出当前目录内容
ls -l # 详细信息(权限、大小、时间)
ls -la # 包含隐藏文件(以 . 开头的文件)
ls -lh # 人性化显示文件大小(KB, MB, GB)
pwd # 显示当前目录的绝对路径
cat file.txt # 显示文件内容(适合小文件)
less file.txt # 分页查看(按 q 退出,空格翻页)
head -n 20 file.txt # 查看前 20 行
tail -n 20 file.txt # 查看后 20 行
tail -f log.txt # 实时查看文件更新(看日志常用)
# ========== 切换目录 ==========
cd /path/to/dir # 切换到指定目录
cd ~ # 切换到主目录
cd .. # 切换到上级目录
cd - # 切换到上次的目录
# ========== 创建 ==========
mkdir dirname # 创建目录
mkdir -p a/b/c # 递归创建多级目录
touch file.txt # 创建空文件(或更新时间戳)
# ========== 复制 ==========
cp source dest # 复制文件
cp -r dir1 dir2 # 递归复制目录(-r = recursive)
cp -a dir1 dir2 # 保留所有属性复制(推荐)
# ========== 移动/重命名 ==========
mv old.txt new.txt # 重命名
mv file.txt /path/ # 移动到指定目录
# ========== 删除 ==========
rm file.txt # 删除文件
rm -r dirname # 递归删除目录
rm -rf dirname # 强制递归删除(⚠️ 危险!无确认)
# ⚠️ 警告:rm -rf 非常危险!
# 永远不要执行:rm -rf / 或 rm -rf ~6.3.2 文件查找
# find 命令(功能强大)
find /path -name "*.c" # 按名字查找
find /path -name "*.c" -type f # 只找文件
find /path -name "*.c" -type d # 只找目录
find /path -size +100M # 找大于 100MB 的文件
find /path -mtime -7 # 找 7 天内修改的文件
# locate 命令(更快,但需要更新数据库)
sudo updatedb # 更新数据库
locate filename # 快速查找
# which 命令(查找命令位置)
which python # 显示 python 命令的路径
# grep 命令(搜索文件内容)★ 非常常用
grep "pattern" file.txt # 在文件中搜索
grep -r "pattern" /path # 递归搜索目录
grep -i "pattern" file.txt # 忽略大小写
grep -n "pattern" file.txt # 显示行号
grep -l "pattern" *.c # 只显示包含的文件名
# 组合使用(在代码中搜索)
grep -rn "function_name" --include="*.c" .6.3.3 文件权限
权限格式解读:
-rwxr-xr-x 1 user group 1234 Jan 1 12:00 filename
│└┬┘└┬┘└┬┘
│ │ │ └── 其他人权限
│ │ └───── 组权限
│ └──────── 所有者权限
└────────── 文件类型(- 普通文件,d 目录,l 链接)
权限字母含义:
r (read) : 读权限 = 4
w (write) : 写权限 = 2
x (execute) : 执行权限 = 1
常见权限组合:
755 = rwxr-xr-x (所有者可读写执行,其他人可读执行)
644 = rw-r--r-- (所有者可读写,其他人只读)
777 = rwxrwxrwx (所有人可读写执行,通常不推荐)# 修改权限
chmod 755 script.sh # 数字方式
chmod +x script.sh # 添加执行权限
chmod -w file.txt # 移除写权限
# 修改所有者
chown user:group file.txt
chown -R user:group dir/ # 递归修改
# 以管理员权限执行
sudo command # 临时获取 root 权限
sudo -i # 切换到 root 用户6.3.4 进程管理
# 查看进程
ps aux # 查看所有进程
ps aux | grep process # 搜索特定进程
top # 实时查看进程(按 q 退出)
htop # 更好用的 top(需要安装)
# 杀死进程
kill PID # 发送终止信号
kill -9 PID # 强制杀死
killall process_name # 按名字杀死
# 后台运行
command & # 后台运行命令
nohup command & # 后台运行,断开终端不停止
jobs # 查看后台任务
fg %1 # 将任务 1 调到前台6.3.5 管道和重定向
# 管道 | :将前一个命令的输出作为后一个命令的输入
ls -l | grep ".txt" # 列出文件并筛选 .txt
ps aux | grep java # 查找 java 进程
cat log.txt | head -100 # 查看日志前 100 行
# 重定向:将输出保存到文件
command > file.txt # 输出到文件(覆盖)
command >> file.txt # 输出到文件(追加)
command 2> error.txt # 错误输出到文件
command > all.txt 2>&1 # 标准输出和错误都到文件
# 实用示例
# 编译并保存日志
make 2>&1 | tee build.log
# 搜索代码并统计行数
grep -r "TODO" --include="*.c" . | wc -l6.4 文本编辑器
6.4.1 Vim 编辑器(推荐掌握)
为什么学 Vim?
- 几乎所有 Linux 系统都有 vim
- 在远程服务器、嵌入式设备上常用
- 熟练后效率很高
Vim 三种模式:
┌───────────────────────────────────────────────────────────────────────────┐
│ │
│ ┌─────────────────┐ │
│ │ 普通模式 │ │
│ │ (Normal Mode) │ │
│ │ 浏览、命令操作 │ │
│ └───────┬─────────┘ │
│ │ │
│ ┌──────────────────┼──────────────────┐ │
│ │ 按 i │ │ 按 : │
│ ▼ │ ▼ │
│ ┌───────────────┐ │ ┌───────────────┐ │
│ │ 插入模式 │ │ │ 命令行模式 │ │
│ │ (Insert Mode) │ │ │(Command Mode) │ │
│ │ 输入文字 │ │ │ 保存、退出等 │ │
│ └───────────────┘ │ └───────────────┘ │
│ │ │ │ │
│ │ 按 Esc │ │ 按 Esc/Enter │
│ └──────────────────┴──────────────────┘ │
│ │
└───────────────────────────────────────────────────────────────────────────┘Vim 基础命令速查表:
启动和退出:
vim file.txt 打开文件
:w 保存
:q 退出
:wq 保存并退出
:q! 强制退出(不保存)
:wq! 强制保存并退出
移动光标(普通模式):
h j k l 左下上右(或用方向键)
gg 到文件开头
G 到文件末尾
0 到行首
$ 到行尾
w 下一个单词
b 上一个单词
Ctrl+f 下一页
Ctrl+b 上一页
:123 跳到第 123 行
编辑(普通模式):
i 在光标前插入
a 在光标后插入
o 在下方新建一行并插入
O 在上方新建一行并插入
x 删除光标处字符
dd 删除整行
yy 复制整行
p 粘贴
u 撤销
Ctrl+r 重做
搜索:
/pattern 向下搜索
?pattern 向上搜索
n 下一个匹配
N 上一个匹配
:%s/old/new/g 全局替换6.4.2 VS Code(推荐日常使用)
# 安装 VS Code
sudo snap install code --classic
# 或者下载 .deb 包安装
# https://code.visualstudio.com/download
# 推荐安装的插件:
# - C/C++
# - Remote - SSH(远程开发)
# - GitLens
# - Android AOSP6.5 Shell 脚本基础
6.5.1 什么是 Shell 脚本?
Shell 脚本就是把一系列命令写在一个文件里,一次性执行。
用途:
- 自动化编译流程
- 批量处理文件
- 系统管理任务
- Android 启动脚本6.5.2 第一个 Shell 脚本
#!/bin/bash
# 文件名: hello.sh
# 第一行叫 shebang,指定用什么程序执行这个脚本
# 这是注释
echo "Hello, World!" # echo 用于输出
# 变量
NAME="Android"
echo "Hello, $NAME!"
# 接收参数
# $0 脚本名, $1 第一个参数, $2 第二个参数...
echo "脚本名: $0"
echo "第一个参数: $1"
# 条件判断
if [ -f "file.txt" ]; then
echo "file.txt 存在"
else
echo "file.txt 不存在"
fi
# 循环
for i in 1 2 3 4 5; do
echo "数字: $i"
done
# 遍历文件
for file in *.txt; do
echo "处理文件: $file"
done# 运行脚本
chmod +x hello.sh # 添加执行权限
./hello.sh # 执行
./hello.sh arg1 arg2 # 带参数执行6.5.3 常用 Shell 语法
# ========== 变量 ==========
NAME="value" # 定义变量(= 两边不能有空格!)
echo $NAME # 使用变量
echo ${NAME} # 推荐写法
echo "${NAME}_suffix" # 拼接字符串
# 命令结果赋值给变量
FILES=$(ls)
COUNT=`wc -l < file.txt` # 反引号也可以
# ========== 条件判断 ==========
# 文件测试
[ -f file ] # 文件存在且是普通文件
[ -d dir ] # 目录存在
[ -e path ] # 路径存在
[ -r file ] # 文件可读
[ -w file ] # 文件可写
[ -x file ] # 文件可执行
# 字符串比较
[ "$a" = "$b" ] # 相等
[ "$a" != "$b" ] # 不等
[ -z "$a" ] # 为空
[ -n "$a" ] # 不为空
# 数字比较
[ $a -eq $b ] # 等于
[ $a -ne $b ] # 不等于
[ $a -lt $b ] # 小于
[ $a -gt $b ] # 大于
[ $a -le $b ] # 小于等于
[ $a -ge $b ] # 大于等于
# if-else 语句
if [ condition ]; then
commands
elif [ condition2 ]; then
commands
else
commands
fi
# ========== 循环 ==========
# for 循环
for i in 1 2 3; do
echo $i
done
for file in *.c; do
echo $file
done
for ((i=0; i<10; i++)); do
echo $i
done
# while 循环
while [ condition ]; do
commands
done
# ========== 函数 ==========
my_function() {
echo "参数1: $1"
echo "参数2: $2"
return 0
}
# 调用函数
my_function arg1 arg26.6 第二阶段检验
完成以下任务,检验你的 Linux 基础:
7. 第三阶段:C 语言与数据结构
目标:掌握 C 语言基础,能够阅读和编写内核/驱动代码
7.1 为什么要学 C 语言?
在嵌入式 Android 开发中:
Linux 内核 → 99% 用 C 语言编写
硬件驱动 → 99% 用 C 语言编写
HAL 层 → C/C++ 混合
Bootloader (U-Boot) → C 语言
Native 库 → C/C++ 编写
结论:不会 C 语言 = 无法进行底层开发7.2 C 语言基础语法
7.2.1 第一个 C 程序
// hello.c
#include <stdio.h> // 包含标准输入输出头文件
int main(int argc, char *argv[]) // 程序入口
{
printf("Hello, World!\n"); // 打印输出
return 0; // 返回 0 表示成功
}# 编译
gcc hello.c -o hello
# 运行
./hello7.2.2 数据类型
// ========== 基本数据类型 ==========
// 整数类型
char c = 'A'; // 1 字节,-128 ~ 127
short s = 100; // 2 字节
int i = 1000; // 4 字节(通常)
long l = 100000; // 4 或 8 字节
long long ll = 10000000000LL; // 8 字节
// 无符号整数(只能表示正数)
unsigned char uc = 255; // 0 ~ 255
unsigned int ui = 4294967295;// 0 ~ 4294967295
// 浮点数
float f = 3.14f; // 4 字节
double d = 3.14159265; // 8 字节
// ========== 内核开发常用的类型(需要 #include <stdint.h>)==========
uint8_t // 无符号 8 位 (0 ~ 255)
uint16_t // 无符号 16 位 (0 ~ 65535)
uint32_t // 无符号 32 位
uint64_t // 无符号 64 位
int8_t // 有符号 8 位
int16_t // 有符号 16 位
int32_t // 有符号 32 位
int64_t // 有符号 64 位
// 为什么用这些?
// 因为 int 在不同平台可能大小不同,而 uint32_t 永远是 32 位7.2.3 指针(★★★★★ 最重要)
// 指针是 C 语言的灵魂,必须掌握!
// ========== 基本概念 ==========
// 指针就是"内存地址"
int a = 10; // 变量 a,假设地址是 0x1000
int *p = &a; // 指针 p,存储 a 的地址 0x1000
// & 取地址运算符:获取变量的地址
// * 解引用运算符:获取指针指向的值
printf("a 的值: %d\n", a); // 输出: 10
printf("a 的地址: %p\n", &a); // 输出: 0x1000(具体值不同)
printf("p 的值: %p\n", p); // 输出: 0x1000
printf("p 指向的值: %d\n", *p); // 输出: 10
*p = 20; // 通过指针修改 a 的值
printf("a 的值: %d\n", a); // 输出: 20
// ========== 指针图解 ==========
/*
变量 a: 指针 p:
┌─────────┐ ┌─────────┐
│ 10 │ ◄────────│ 0x1000 │
└─────────┘ └─────────┘
地址: 0x1000 地址: 0x2000
p 存储的是 a 的地址
*p 就是去 0x1000 这个地址取值,得到 10
*/
// ========== 指针和数组 ==========
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // 数组名就是首地址
printf("%d\n", arr[0]); // 输出: 1
printf("%d\n", *p); // 输出: 1
printf("%d\n", p[0]); // 输出: 1
printf("%d\n", *(p+1)); // 输出: 2
printf("%d\n", p[1]); // 输出: 2
// p+1 不是地址+1,而是地址+sizeof(int)
// 如果 int 是 4 字节,p+1 实际是地址+4
// ========== 指针的指针 ==========
int a = 10;
int *p = &a; // p 指向 a
int **pp = &p; // pp 指向 p
printf("%d\n", **pp); // 输出: 10
// ========== 空指针 ==========
int *p = NULL; // 空指针,不指向任何有效地址
// 使用前一定要判断
if (p != NULL) {
*p = 10;
}7.2.4 结构体(★★★★★ 非常重要)
// 结构体:把多个不同类型的数据组合在一起
// ========== 定义结构体 ==========
struct Person {
char name[50];
int age;
float height;
};
// 使用
struct Person p1;
strcpy(p1.name, "张三");
p1.age = 25;
p1.height = 175.5;
// ========== typedef 简化 ==========
typedef struct {
char name[50];
int age;
float height;
} Person;
Person p2; // 不需要 struct 关键字了
// ========== 结构体指针 ==========
Person *pp = &p1;
// 访问成员的两种方式
printf("%s\n", (*pp).name); // 方式1:先解引用
printf("%s\n", pp->name); // 方式2:箭头运算符(推荐)
// ========== 内核中的常见模式 ==========
// 内核代码大量使用结构体来描述硬件、设备、驱动等
// 示例:字符设备结构
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
dev_t dev;
unsigned int count;
};
// 示例:文件操作结构
struct file_operations {
struct module *owner;
ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);
ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *);
int (*open)(struct inode *, struct file *);
int (*release)(struct inode *, struct file *);
// ... 更多操作
};7.2.5 内存管理
#include <stdlib.h>
// ========== 动态内存分配 ==========
// malloc: 分配内存
int *p = (int *)malloc(sizeof(int) * 10); // 分配 10 个 int 的空间
if (p == NULL) {
// 分配失败
printf("内存分配失败!\n");
return -1;
}
// 使用内存
for (int i = 0; i < 10; i++) {
p[i] = i * 10;
}
// free: 释放内存(必须!否则内存泄漏)
free(p);
p = NULL; // 好习惯:释放后置为 NULL
// ========== calloc: 分配并清零 ==========
int *p2 = (int *)calloc(10, sizeof(int)); // 10 个 int,初始化为 0
// ========== realloc: 重新分配 ==========
p = (int *)realloc(p, sizeof(int) * 20); // 扩展到 20 个 int
// ========== 内存泄漏示例(错误!)==========
void bad_function() {
int *p = malloc(100);
// 忘记 free(p)
return; // p 丢失,100 字节永远无法释放
}
// ========== 内核中的内存分配 ==========
// 内核不用 malloc/free,而是用:
// kmalloc / kfree - 小块内存
// vmalloc / vfree - 大块虚拟连续内存
// devm_* - 设备管理的内存(自动释放)7.3 C 语言进阶
7.3.1 函数指针
// 函数指针:指向函数的指针
// 定义一个普通函数
int add(int a, int b) {
return a + b;
}
int sub(int a, int b) {
return a - b;
}
// 定义函数指针
int (*operation)(int, int);
// 使用函数指针
operation = add; // 指向 add 函数
printf("%d\n", operation(3, 2)); // 输出: 5
operation = sub; // 指向 sub 函数
printf("%d\n", operation(3, 2)); // 输出: 1
// ========== 在内核中的应用 ==========
// 内核大量使用函数指针实现多态
struct file_operations {
// 这些都是函数指针
ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);
ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *);
int (*open)(struct inode *, struct file *);
int (*release)(struct inode *, struct file *);
};
// 驱动实现自己的函数
static ssize_t my_read(struct file *file, char __user *buf,
size_t count, loff_t *pos) {
// 实现读取逻辑
return 0;
}
// 注册到结构体
static struct file_operations my_fops = {
.read = my_read,
.write = my_write,
.open = my_open,
.release = my_release,
};7.3.2 预处理器
// ========== 宏定义 ==========
#define PI 3.14159
#define MAX(a, b) ((a) > (b) ? (a) : (b))
// 使用
float area = PI * r * r;
int max = MAX(10, 20);
// ⚠️ 宏是文本替换,注意加括号
#define SQUARE(x) ((x) * (x))
// 如果不加括号:#define SQUARE(x) x * x
// SQUARE(1+2) 变成 1+2 * 1+2 = 5,而不是 9
// ========== 条件编译 ==========
#ifdef DEBUG
printf("调试信息: x = %d\n", x);
#endif
#ifndef HEADER_H
#define HEADER_H
// 头文件内容,防止重复包含
#endif
#if defined(PLATFORM_ARM)
// ARM 平台代码
#elif defined(PLATFORM_X86)
// X86 平台代码
#else
// 默认代码
#endif
// ========== 内核中常见的宏 ==========
#define BIT(x) (1 << (x)) // BIT(3) = 0b1000 = 8
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
#define container_of(ptr, type, member) // 通过成员指针获取结构体指针7.3.3 位操作
// 嵌入式开发大量使用位操作来控制寄存器
// ========== 位运算符 ==========
& (与) : 1 & 1 = 1, 其他都是 0
| (或) : 0 | 0 = 0, 其他都是 1
^ (异或) : 相同为 0,不同为 1
~ (取反) : ~0 = 1, ~1 = 0
<< (左移) : 1 << 3 = 0b1000 = 8
>> (右移) : 8 >> 2 = 0b10 = 2
// ========== 常用位操作 ==========
// 设置某一位为 1
reg |= (1 << bit);
// 清除某一位为 0
reg &= ~(1 << bit);
// 切换某一位
reg ^= (1 << bit);
// 检查某一位是否为 1
if (reg & (1 << bit)) {
// 该位为 1
}
// ========== 实际例子 ==========
// 假设有一个 8 位寄存器控制 LED
// bit 0: LED1, bit 1: LED2, bit 2: LED3 ...
uint8_t led_reg = 0;
// 打开 LED1 (bit 0)
led_reg |= (1 << 0); // led_reg = 0b00000001
// 打开 LED3 (bit 2)
led_reg |= (1 << 2); // led_reg = 0b00000101
// 关闭 LED1
led_reg &= ~(1 << 0); // led_reg = 0b00000100
// 检查 LED3 是否亮
if (led_reg & (1 << 2)) {
printf("LED3 is ON\n");
}
// ========== 位域(内核常用)==========
struct {
uint8_t led1 : 1; // 占 1 位
uint8_t led2 : 1; // 占 1 位
uint8_t led3 : 1; // 占 1 位
uint8_t reserved : 5;// 保留 5 位
} led_status;
led_status.led1 = 1; // 打开 LED17.4 阅读内核代码的技巧
1. 不要试图一次读懂所有代码
- 先了解整体架构
- 再深入感兴趣的部分
2. 善用搜索
- grep -r "关键字" .
- 找函数定义:grep -rn "^int function_name"
3. 理解常见模式
- 结构体 + 函数指针 = 面向对象
- probe/remove = 设备发现和移除
- init/exit = 模块加载和卸载
4. 善用内核文档
- Documentation/ 目录下有很多文档
- 函数注释通常很详细
5. 借助工具
- VS Code + C/C++ 插件
- Source Insight(付费)
- cscope + ctags(命令行)7.5 第三阶段检验
8. 第四阶段:Android 系统架构认知
目标:理解 Android 系统的整体架构,知道各层的作用
8.1 Android 系统架构详解
┌─────────────────────────────────────────────────────────────────────────┐
│ Android 系统架构详解 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 应用层 (Applications) │ │
│ │ │ │
│ │ 这是用户直接接触的层,包括: │ │
│ │ - 系统应用:设置、电话、短信、相机(电视上是设置、视频播放器)│ │
│ │ - 第三方应用:微信、抖音、爱奇艺等 │ │
│ │ - 你开发的应用 │ │
│ │ │ │
│ │ 使用语言:Java / Kotlin │ │
│ │ 对应目录:packages/apps/ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 调用 Android API │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Framework 层 (Android 框架) │ │
│ │ │ │
│ │ Android 的核心,提供 API 给应用使用: │ │
│ │ - Activity Manager:管理应用生命周期 │ │
│ │ - Window Manager:管理窗口显示 │ │
│ │ - Content Provider:应用间数据共享 │ │
│ │ - Package Manager:应用安装管理 │ │
│ │ - Notification Manager:通知管理 │ │
│ │ - Resource Manager:资源管理 │ │
│ │ │ │
│ │ 使用语言:Java │ │
│ │ 对应目录:frameworks/base/ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ JNI 调用 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Native 层 (C/C++ 库) │ │
│ │ │ │
│ │ 高性能的底层库: │ │
│ │ - libc (Bionic):Android 定制的 C 库 │ │
│ │ - OpenGL ES:图形渲染 │ │
│ │ - Media Framework:音视频播放 │ │
│ │ - SQLite:数据库 │ │
│ │ - WebKit:网页渲染 │ │
│ │ - SurfaceFlinger:图形合成 │ │
│ │ - AudioFlinger:音频混合 │ │
│ │ │ │
│ │ 使用语言:C/C++ │ │
│ │ 对应目录:frameworks/native/, external/ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 调用 HAL 接口 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ HAL 层 (硬件抽象层) │ │
│ │ │ │
│ │ 连接 Android 和硬件驱动的桥梁: │ │
│ │ - 定义统一的硬件接口 │ │
│ │ - 不同硬件实现相同接口 │ │
│ │ - 隔离 Android 和具体硬件 │ │
│ │ │ │
│ │ 常见 HAL: │ │
│ │ - Audio HAL:音频 │ │
│ │ - Camera HAL:摄像头 │ │
│ │ - Graphics HAL (Gralloc, HWComposer):显示 │ │
│ │ - Sensors HAL:传感器 │ │
│ │ │ │
│ │ 使用语言:C/C++ │ │
│ │ 对应目录:hardware/ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 系统调用 / ioctl │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Linux Kernel (Linux 内核) │ │
│ │ │ │
│ │ 操作系统核心 + 硬件驱动: │ │
│ │ - 进程管理 │ │
│ │ - 内存管理 │ │
│ │ - 文件系统 │ │
│ │ - 网络协议栈 │ │
│ │ - 设备驱动:显示、音频、USB、WiFi、蓝牙等 │ │
│ │ │ │
│ │ 使用语言:C(少量汇编) │ │
│ │ 对应目录:common/common14-5.15/ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘8.2 Android 启动流程概览
┌─────────────────────────────────────────────────────────────────────────┐
│ Android 完整启动流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 上电 │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 1. BootROM(固化在芯片中的代码) │ │
│ │ - 芯片上电后执行的第一段代码 │ │
│ │ - 初始化最基本的硬件 │ │
│ │ - 加载 Bootloader 到内存 │ │
│ └─────────────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 2. Bootloader (U-Boot) │ │
│ │ - 初始化 CPU、内存、存储等硬件 │ │
│ │ - 显示开机 Logo │ │
│ │ - 加载 Linux 内核到内存 │ │
│ │ - 传递启动参数给内核 │ │
│ │ - 跳转到内核执行 │ │
│ └─────────────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 3. Linux Kernel │ │
│ │ - 初始化内存管理、进程调度 │ │
│ │ - 加载设备驱动 │ │
│ │ - 挂载文件系统 │ │
│ │ - 启动第一个用户进程:/init │ │
│ └─────────────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 4. init 进程(Android 第一个进程,PID=1) │ │
│ │ - 解析 init.rc 配置文件 │ │
│ │ - 设置属性系统 │ │
│ │ - 启动各种系统服务 │ │
│ │ - 启动 Zygote 进程 │ │
│ └─────────────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 5. Zygote 进程(所有 Java 进程的父进程) │ │
│ │ - 启动 Android Runtime (ART) │ │
│ │ - 预加载 Java 类和资源 │ │
│ │ - 启动 System Server │ │
│ │ - 等待 fork 请求创建新进程 │ │
│ └─────────────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 6. System Server(系统服务进程) │ │
│ │ - 启动所有核心系统服务: │ │
│ │ Activity Manager Service │ │
│ │ Window Manager Service │ │
│ │ Package Manager Service │ │
│ │ Power Manager Service │ │
│ │ ... │ │
│ │ - 发送 ACTION_BOOT_COMPLETED 广播 │ │
│ └─────────────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 7. Launcher(桌面) │ │
│ │ - 显示应用图标 │ │
│ │ - 等待用户操作 │ │
│ │ - 启动完成! │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 整个过程大约需要 10-60 秒,取决于硬件和系统复杂度 │
│ │
└─────────────────────────────────────────────────────────────────────────┘8.3 Android 编译系统
8.3.1 编译系统简介
Android 使用两套编译系统:
1. Make(传统,正在逐步淘汰)
- 使用 Makefile 和 Android.mk
- 历史悠久,很多老代码还在用
2. Soong(现代,Android 7.0+ 推荐)
- 使用 Android.bp(Blueprint 语法)
- 更简洁、更快速
- 现代 Android 代码应该用这个8.3.2 编译命令详解
# 进入源码目录
cd ~/android/s905x5
# 1. 初始化编译环境(每次新开终端都要执行)
source build/envsetup.sh
# 2. 选择编译目标
lunch ross-userdebug
# lunch 目标格式:<product>-<variant>
# product: 产品名称(如 ross)
# variant: 编译类型
# - user: 正式发布版本,没有 root 权限
# - userdebug: 调试版本,有 root 权限(开发常用)
# - eng: 工程版本,最多调试信息
# 3. 开始编译
make -j$(nproc)
# -j$(nproc) 表示使用所有 CPU 核心并行编译
# 或者使用项目脚本
make -j16
# 4. 只编译特定模块
make Settings # 编译设置应用
make framework # 编译 framework
make bootimage # 编译内核镜像8.3.3 Android.bp 示例
# Android.bp 基本语法
# 编译一个 C 可执行程序
cc_binary {
name: "my_tool",
srcs: ["main.c", "utils.c"],
cflags: ["-Wall", "-Werror"],
}
# 编译一个共享库
cc_library_shared {
name: "libmy_utils",
srcs: ["utils.c"],
export_include_dirs: ["include"],
}
# 编译一个 Java 库
java_library {
name: "my_java_lib",
srcs: ["src/**/*.java"],
sdk_version: "current",
}
# 编译一个 APK
android_app {
name: "MyApp",
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
manifest: "AndroidManifest.xml",
platform_apis: true,
certificate: "platform",
}8.4 重要目录说明
android-source/
│
├── build/ # 编译系统
│ ├── make/ # Make 编译规则
│ └── soong/ # Soong 编译规则
│
├── device/amlogic/ross/ # ★ 产品配置(你最常改的地方)
│ ├── ross.mk # 产品 Makefile
│ ├── BoardConfig.mk # 板级配置
│ ├── init.amlogic.rc # init 配置
│ └── ...
│
├── common/common14-5.15/ # ★ Linux 内核
│ ├── arch/arm64/boot/dts/ # 设备树
│ ├── drivers/amlogic/ # Amlogic 驱动
│ └── ...
│
├── hardware/amlogic/ # ★ Amlogic HAL
│ ├── audio/ # 音频 HAL
│ ├── gralloc/ # 图形内存 HAL
│ └── ...
│
├── frameworks/ # Android 框架
│ ├── base/ # 核心框架
│ │ ├── core/java/android/ # Android API
│ │ └── services/ # 系统服务
│ └── native/ # Native 服务
│
├── packages/apps/ # 系统应用
│ ├── Settings/ # 设置
│ ├── Launcher3/ # 启动器
│ └── ...
│
└── out/target/product/ross/ # ★ 编译输出
├── system/ # system 分区内容
├── vendor/ # vendor 分区内容
├── boot.img # 内核镜像
└── ...8.5 第四阶段检验
9. 第五阶段:Bootloader 与 U-Boot
📚 详细文档: Bootloader 与 U-Boot 开发指南
目标:理解设备开机的第一步,掌握 U-Boot 基本操作
9.1 什么是 Bootloader?
┌───────────────────────────────────────────────────────────────────────────┐
│ Bootloader 是什么? │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ Bootloader = 引导加载程序 │
│ │
│ 类比: │
│ 想象你的电脑是一辆汽车: │
│ - Bootloader 就像启动车辆的钥匙和点火系统 │
│ - 它负责唤醒"沉睡"的系统,做好准备工作 │
│ - 然后把控制权交给"驾驶员"(操作系统) │
│ │
│ Bootloader 的职责: │
│ 1. 初始化硬件(CPU、内存、存储) │
│ 2. 显示开机 Logo │
│ 3. 加载操作系统内核 │
│ 4. 传递启动参数 │
│ 5. 提供刷机模式(fastboot) │
│ │
└───────────────────────────────────────────────────────────────────────────┘9.2 Amlogic 启动流程
上电
│
▼
┌───────────────────────────────────────────────────────────────────────────┐
│ BL1 (BootROM) │
│ - 固化在芯片内部,无法修改 │
│ - 初始化最基本的硬件 │
│ - 从存储设备加载 BL2 │
└───────────────────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────────────────┐
│ BL2 │
│ - 初始化 DDR 内存 │
│ - 安全启动验证 │
│ - 加载 BL2E、BL2X │
└───────────────────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────────────────┐
│ BL31 (ARM Trusted Firmware) │
│ - 安全监控程序 │
│ - 运行在最高权限级别 │
│ - 处理安全相关的功能 │
└───────────────────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────────────────┐
│ BL32 (可选, Secure OS) │
│ - 安全操作系统(如 OP-TEE) │
│ - DRM、安全支付等功能 │
└───────────────────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────────────────┐
│ BL33 (U-Boot) ★ 这是我们主要关注的 │
│ - 功能丰富的引导程序 │
│ - 加载 Linux 内核 │
│ - 提供命令行交互 │
│ - 支持 fastboot 刷机 │
└───────────────────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────────────────┐
│ Linux Kernel │
│ - 操作系统内核开始运行 │
└───────────────────────────────────────────────────────────────────────────┘9.3 U-Boot 基础
9.3.1 进入 U-Boot 命令行
方法1:串口连接
1. 用 USB 转 TTL 线连接板子的调试串口
2. 打开串口终端(如 minicom, screen, putty)
3. 波特率通常是 115200
4. 开机时快速按回车键(在看到 "Hit any key to stop autoboot" 时)
方法2:ADB 命令
adb reboot bootloader
# 然后通过串口查看
串口终端设置示例(Linux):
sudo minicom -D /dev/ttyUSB0 -b 115200
# 或
sudo screen /dev/ttyUSB0 1152009.3.2 常用 U-Boot 命令
# ========== 信息查看 ==========
help # 查看所有命令
help <cmd> # 查看某个命令的帮助
version # 查看 U-Boot 版本
bdinfo # 查看板级信息
printenv # 打印所有环境变量
printenv <var> # 打印某个环境变量
# ========== 环境变量 ==========
setenv var value # 设置环境变量
saveenv # 保存环境变量到存储
run <var> # 执行环境变量中的命令
# ========== 内存操作 ==========
md 0x01000000 0x100 # 显示内存内容
mw 0x01000000 0x12345678 # 写入内存
# ========== 存储操作 ==========
mmc list # 列出 MMC 设备
mmc dev 0 # 选择设备 0
mmc info # 显示 MMC 信息
mmc read addr blk cnt # 读取数据到内存
# ========== 启动相关 ==========
boot # 执行默认启动命令
booti addr # 启动 Image 格式内核
reset # 重启9.3.3 重要环境变量
# 查看启动命令
printenv bootcmd
# 通常类似:run storeboot
# 查看/修改启动参数
printenv bootargs
# 包含传递给内核的参数,如:
# root=/dev/mmcblk0p18 rootfstype=ext4 init=/init ...
# 设置启动延迟(秒)
setenv bootdelay 3
# 保存修改
saveenv9.4 Fastboot 模式
# Fastboot 是 Android 的刷机协议
# 进入 fastboot 模式
# 方法1:U-Boot 命令
fastboot # 或 run fastboot
# 方法2:ADB 命令
adb reboot fastboot
# 方法3:按键组合(不同设备不同)
# PC 端 fastboot 命令
fastboot devices # 列出连接的设备
fastboot flash boot boot.img # 烧录 boot 分区
fastboot flash system system.img
fastboot reboot # 重启
fastboot oem unlock # 解锁 bootloader(如果支持)9.5 第五阶段检验
10. 第六阶段:Linux Kernel 驱动开发
📚 详细文档: Linux Kernel 驱动开发指南
目标:掌握 Linux 驱动开发基础,能编写简单的驱动
10.1 驱动开发基础概念
10.1.1 什么是驱动?
┌───────────────────────────────────────────────────────────────────────────┐
│ 驱动是什么? │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ 驱动 = 操作系统和硬件之间的"翻译官" │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ 应用程序 │ │ 硬件设备 │ │
│ │ │ │ │ │
│ │ printf() │ │ 串口芯片 │ │
│ │ read() │ │ GPIO │ │
│ │ write() │ │ I2C 设备 │ │
│ └──────┬──────┘ └──────▲──────┘ │
│ │ │ │
│ │ 统一的接口 │ 硬件操作 │
│ │ (系统调用) │ (读写寄存器) │
│ ▼ │ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 驱动程序 │ │
│ │ │ │
│ │ 把"read 文件"翻译成"读取硬件寄存器" │ │
│ │ 把"write 文件"翻译成"写入硬件寄存器" │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 为什么需要驱动? │
│ - 应用程序只需要 read/write,不需要知道硬件细节 │
│ - 同样的程序可以在不同硬件上运行(只要有对应驱动) │
│ - 硬件变化时只需要改驱动,不需要改应用 │
│ │
└───────────────────────────────────────────────────────────────────────────┘10.1.2 Linux 驱动的分类
┌───────────────────────────────────────────────────────────────────────────┐
│ Linux 驱动分类 │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 字符设备 (Character Device) │
│ - 按字节流访问,像文件一样读写 │
│ - 例如:串口、键盘、LED、GPIO │
│ - 设备文件:/dev/ttyS0, /dev/input/event0 │
│ │
│ 2. 块设备 (Block Device) │
│ - 按块(通常512字节或4KB)访问 │
│ - 例如:硬盘、U盘、SD卡 │
│ - 设备文件:/dev/sda, /dev/mmcblk0 │
│ │
│ 3. 网络设备 (Network Device) │
│ - 处理网络数据包 │
│ - 例如:以太网卡、WiFi │
│ - 没有设备文件,通过 socket 访问 │
│ │
│ 对于嵌入式开发,最常接触的是字符设备! │
│ │
└───────────────────────────────────────────────────────────────────────────┘10.2 第一个内核模块
10.2.1 Hello World 模块
/* hello.c - 最简单的内核模块 */
#include <linux/module.h> /* 所有模块都需要 */
#include <linux/kernel.h> /* printk() 函数 */
#include <linux/init.h> /* __init 和 __exit 宏 */
/*
* 模块加载时执行的函数
* __init 表示这个函数只在初始化时使用,之后可以释放内存
*/
static int __init hello_init(void)
{
/*
* printk 是内核的打印函数(不是 printf!)
* KERN_INFO 是日志级别
*/
printk(KERN_INFO "Hello, Kernel World!\n");
return 0; /* 返回 0 表示成功 */
}
/*
* 模块卸载时执行的函数
* __exit 表示只在卸载时使用
*/
static void __exit hello_exit(void)
{
printk(KERN_INFO "Goodbye, Kernel World!\n");
}
/* 注册初始化和退出函数 */
module_init(hello_init);
module_exit(hello_exit);
/* 模块信息 */
MODULE_LICENSE("GPL"); /* 许可证,必须有 */
MODULE_AUTHOR("Your Name"); /* 作者 */
MODULE_DESCRIPTION("A simple hello module"); /* 描述 */
MODULE_VERSION("1.0"); /* 版本 */10.2.2 Makefile
# 内核模块的 Makefile
# 模块名(hello.c -> hello.ko)
obj-m := hello.o
# 内核源码路径(根据你的实际路径修改)
KERNEL_DIR := /home/user/android/s905x5/common/common14-5.15
# 当前目录
PWD := $(shell pwd)
# 交叉编译器前缀
CROSS_COMPILE := aarch64-linux-gnu-
# 目标架构
ARCH := arm64
all:
make ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNEL_DIR) M=$(PWD) modules
clean:
make ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNEL_DIR) M=$(PWD) clean10.2.3 编译和测试
# 编译模块
make
# 生成 hello.ko 文件
# 推送到设备
adb push hello.ko /data/local/tmp/
# 在设备上加载模块
adb shell
su
cd /data/local/tmp
insmod hello.ko # 加载模块
dmesg | tail # 查看内核日志,应该看到 "Hello, Kernel World!"
# 查看已加载的模块
lsmod | grep hello
# 卸载模块
rmmod hello
dmesg | tail # 应该看到 "Goodbye, Kernel World!"10.3 字符设备驱动
10.3.1 字符设备驱动框架
/* mychar.c - 简单的字符设备驱动 */
#include <linux/module.h>
#include <linux/fs.h> /* file_operations */
#include <linux/cdev.h> /* cdev 结构 */
#include <linux/uaccess.h> /* copy_to_user, copy_from_user */
#define DEVICE_NAME "mychar"
#define BUFFER_SIZE 1024
static dev_t dev_num; /* 设备号 */
static struct cdev my_cdev; /* 字符设备结构 */
static struct class *my_class; /* 设备类 */
static char buffer[BUFFER_SIZE];/* 数据缓冲区 */
static int buffer_len = 0; /* 缓冲区数据长度 */
/*
* 打开设备时调用
*/
static int my_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "mychar: 设备被打开\n");
return 0;
}
/*
* 关闭设备时调用
*/
static int my_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "mychar: 设备被关闭\n");
return 0;
}
/*
* 读取设备时调用
* 用户程序调用 read() 时会执行这个函数
*/
static ssize_t my_read(struct file *file, char __user *user_buf,
size_t count, loff_t *offset)
{
int bytes_to_read;
/* 计算要读取的字节数 */
bytes_to_read = min((int)count, buffer_len);
if (bytes_to_read == 0) {
return 0; /* 没有数据可读 */
}
/* 把数据从内核空间拷贝到用户空间 */
if (copy_to_user(user_buf, buffer, bytes_to_read)) {
return -EFAULT; /* 拷贝失败 */
}
printk(KERN_INFO "mychar: 读取了 %d 字节\n", bytes_to_read);
return bytes_to_read;
}
/*
* 写入设备时调用
* 用户程序调用 write() 时会执行这个函数
*/
static ssize_t my_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *offset)
{
int bytes_to_write;
/* 计算要写入的字节数 */
bytes_to_write = min((int)count, BUFFER_SIZE - 1);
/* 把数据从用户空间拷贝到内核空间 */
if (copy_from_user(buffer, user_buf, bytes_to_write)) {
return -EFAULT; /* 拷贝失败 */
}
buffer[bytes_to_write] = '\0';
buffer_len = bytes_to_write;
printk(KERN_INFO "mychar: 写入了 %d 字节: %s\n", bytes_to_write, buffer);
return bytes_to_write;
}
/*
* 文件操作结构体 - 把我们的函数和系统调用关联起来
*/
static struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = my_open,
.release = my_release,
.read = my_read,
.write = my_write,
};
/*
* 模块初始化函数
*/
static int __init mychar_init(void)
{
int ret;
/* 1. 分配设备号 */
ret = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
if (ret < 0) {
printk(KERN_ERR "mychar: 分配设备号失败\n");
return ret;
}
printk(KERN_INFO "mychar: 设备号 %d:%d\n", MAJOR(dev_num), MINOR(dev_num));
/* 2. 初始化 cdev 结构 */
cdev_init(&my_cdev, &my_fops);
my_cdev.owner = THIS_MODULE;
/* 3. 添加 cdev 到系统 */
ret = cdev_add(&my_cdev, dev_num, 1);
if (ret < 0) {
unregister_chrdev_region(dev_num, 1);
return ret;
}
/* 4. 创建设备类(用于自动创建设备节点)*/
my_class = class_create(THIS_MODULE, DEVICE_NAME);
if (IS_ERR(my_class)) {
cdev_del(&my_cdev);
unregister_chrdev_region(dev_num, 1);
return PTR_ERR(my_class);
}
/* 5. 创建设备节点 /dev/mychar */
device_create(my_class, NULL, dev_num, NULL, DEVICE_NAME);
printk(KERN_INFO "mychar: 驱动加载成功\n");
return 0;
}
/*
* 模块退出函数
*/
static void __exit mychar_exit(void)
{
device_destroy(my_class, dev_num);
class_destroy(my_class);
cdev_del(&my_cdev);
unregister_chrdev_region(dev_num, 1);
printk(KERN_INFO "mychar: 驱动卸载成功\n");
}
module_init(mychar_init);
module_exit(mychar_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple char device driver");10.3.2 测试字符设备驱动
# 加载驱动
insmod mychar.ko
# 查看设备节点是否创建
ls -l /dev/mychar
# 写入数据
echo "Hello, Driver!" > /dev/mychar
# 读取数据
cat /dev/mychar
# 使用 C 程序测试/* test_mychar.c - 测试程序 */
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd;
char write_buf[] = "Test message from user space";
char read_buf[100] = {0};
/* 打开设备 */
fd = open("/dev/mychar", O_RDWR);
if (fd < 0) {
perror("打开设备失败");
return -1;
}
/* 写入数据 */
write(fd, write_buf, strlen(write_buf));
printf("写入: %s\n", write_buf);
/* 读取数据 */
read(fd, read_buf, sizeof(read_buf));
printf("读取: %s\n", read_buf);
/* 关闭设备 */
close(fd);
return 0;
}10.4 平台设备驱动(重点)
10.4.1 平台驱动模型简介
┌───────────────────────────────────────────────────────────────────────────┐
│ 平台驱动模型 │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ 为什么需要平台驱动? │
│ │
│ 传统方式:硬件信息硬编码在驱动中 │
│ ┌─────────────────────────────────────┐ │
│ │ #define REG_BASE 0xFE001000 │ 每换一个板子 │
│ │ #define IRQ_NUM 32 │ 都要改代码 │
│ │ #define CLK_RATE 24000000 │ 重新编译 │
│ └─────────────────────────────────────┘ │
│ │
│ 平台驱动:硬件信息从设备树获取 │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ 设备树 (DTS) │ │ 平台驱动 │ │
│ │ │ │ │ │
│ │ my_device { │ │ static struct │ │
│ │ compatible = │◀──▶│ platform_driver = { │ │
│ │ "vendor,my-dev"; │ │ .probe = my_probe, │ │
│ │ reg = <...>; │ │ .driver = { │ │
│ │ interrupts = <...>; │ │ .of_match_table = │ │
│ │ }; │ │ my_of_match, │ │
│ └─────────────────────────┘ │ }, │ │
│ │ }; │ │
│ 换板子只需改设备树 └─────────────────────────┘ │
│ 驱动代码不用变 │
│ │
└───────────────────────────────────────────────────────────────────────────┘10.4.2 平台驱动框架代码
/* my_platform.c - 平台驱动示例 */
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/io.h>
/*
* probe 函数 - 当设备树中的设备与驱动匹配时调用
* 这是初始化设备的地方
*/
static int my_probe(struct platform_device *pdev)
{
struct resource *res;
void __iomem *base;
int irq;
u32 my_property;
printk(KERN_INFO "my_platform: probe 被调用\n");
/* 从设备树获取寄存器资源 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "获取内存资源失败\n");
return -ENODEV;
}
printk(KERN_INFO "寄存器地址: 0x%lx, 大小: 0x%lx\n",
(unsigned long)res->start, (unsigned long)resource_size(res));
/* 映射寄存器地址到虚拟地址 */
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base)) {
return PTR_ERR(base);
}
/* 从设备树获取中断号 */
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_warn(&pdev->dev, "没有找到中断资源\n");
} else {
printk(KERN_INFO "中断号: %d\n", irq);
}
/* 从设备树读取自定义属性 */
if (of_property_read_u32(pdev->dev.of_node, "my-property", &my_property)) {
dev_warn(&pdev->dev, "没有找到 my-property\n");
my_property = 0; /* 默认值 */
} else {
printk(KERN_INFO "my-property = %d\n", my_property);
}
/* TODO: 初始化硬件... */
return 0;
}
/*
* remove 函数 - 设备被移除时调用
*/
static int my_remove(struct platform_device *pdev)
{
printk(KERN_INFO "my_platform: remove 被调用\n");
/* TODO: 清理资源... */
return 0;
}
/*
* 设备树匹配表 - 定义这个驱动支持哪些设备
* compatible 字符串必须和设备树中的一致
*/
static const struct of_device_id my_of_match[] = {
{ .compatible = "myvendor,my-device", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_of_match);
/*
* 平台驱动结构体
*/
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my-platform-driver",
.of_match_table = my_of_match,
},
};
/* 简化的模块注册宏 */
module_platform_driver(my_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple platform driver");10.4.3 对应的设备树节点
/* 在设备树中添加设备节点 */
my_device: my_device@fe001000 {
compatible = "myvendor,my-device"; /* 必须和驱动中的一致 */
reg = <0x0 0xfe001000 0x0 0x100>; /* 寄存器地址和大小 */
interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>; /* 中断 */
my-property = <12345>; /* 自定义属性 */
status = "okay"; /* 启用设备 */
};10.5 常见的内核 API
/* ========== 内存操作 ========== */
/* 动态分配内存(小块) */
void *ptr = kmalloc(size, GFP_KERNEL);
kfree(ptr);
/* 分配并清零 */
void *ptr = kzalloc(size, GFP_KERNEL);
/* 设备管理的内存分配(驱动卸载时自动释放) */
void *ptr = devm_kmalloc(&pdev->dev, size, GFP_KERNEL);
void *ptr = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
/* ========== IO 操作 ========== */
/* 映射物理地址到虚拟地址 */
void __iomem *base = ioremap(phys_addr, size);
iounmap(base);
/* 设备管理版本(推荐) */
void __iomem *base = devm_ioremap_resource(&pdev->dev, res);
/* 读写映射后的地址 */
u32 val = readl(base + offset); /* 读 32 位 */
writel(val, base + offset); /* 写 32 位 */
u16 val = readw(base + offset); /* 读 16 位 */
writew(val, base + offset); /* 写 16 位 */
u8 val = readb(base + offset); /* 读 8 位 */
writeb(val, base + offset); /* 写 8 位 */
/* ========== 延时 ========== */
mdelay(ms); /* 毫秒级忙等待延时 */
udelay(us); /* 微秒级忙等待延时 */
msleep(ms); /* 毫秒级睡眠延时(可被打断) */
usleep_range(min_us, max_us); /* 微秒级睡眠(推荐) */
/* ========== 打印日志 ========== */
printk(KERN_ERR "错误信息\n");
printk(KERN_WARNING "警告信息\n");
printk(KERN_INFO "一般信息\n");
printk(KERN_DEBUG "调试信息\n");
/* 推荐使用 dev_* 系列(会自动加上设备名) */
dev_err(&pdev->dev, "错误: %d\n", err);
dev_warn(&pdev->dev, "警告\n");
dev_info(&pdev->dev, "信息\n");
dev_dbg(&pdev->dev, "调试\n");
/* ========== 中断 ========== */
/* 注册中断处理函数 */
int ret = request_irq(irq, my_handler, IRQF_TRIGGER_RISING, "my_irq", data);
free_irq(irq, data);
/* 设备管理版本(推荐) */
int ret = devm_request_irq(&pdev->dev, irq, my_handler,
IRQF_TRIGGER_RISING, "my_irq", data);
/* 中断处理函数 */
static irqreturn_t my_handler(int irq, void *data)
{
/* 处理中断 */
return IRQ_HANDLED;
}10.6 第六阶段检验
11. 第七阶段:设备树 (Device Tree)
📚 详细文档: 设备树开发指南
目标:掌握设备树语法,能够修改设备树配置硬件
11.1 设备树基础
11.1.1 什么是设备树?
┌───────────────────────────────────────────────────────────────────────────┐
│ 设备树简介 │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ 设备树(Device Tree)是一种描述硬件的数据结构 │
│ │
│ 打个比方: │
│ - 设备树就像是硬件的"配置文件"或"说明书" │
│ - 告诉 Linux 内核这个板子上有什么硬件,地址是多少,怎么连接的 │
│ │
│ 为什么需要设备树? │
│ - 以前:硬件信息写死在代码里,换板子就要改代码、重新编译内核 │
│ - 现在:硬件信息写在设备树里,换板子只需要换设备树文件 │
│ │
│ 设备树文件类型: │
│ - .dts (Device Tree Source):源文件,人可读 │
│ - .dtsi (Device Tree Source Include):被包含的源文件 │
│ - .dtb (Device Tree Blob):编译后的二进制文件,给内核用 │
│ │
│ 工作流程: │
│ ┌─────────┐ 编译 ┌─────────┐ 加载 ┌─────────┐ │
│ │ .dts │ ───────▶ │ .dtb │ ───────▶ │ 内核 │ │
│ │ 源文件 │ (dtc) │ 二进制 │ │ │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
└───────────────────────────────────────────────────────────────────────────┘11.1.2 设备树语法详解
/* 设备树基本语法示例 */
/dts-v1/; // 版本声明(必须在第一行)
// 可以包含其他文件
#include "base.dtsi"
/ { // 根节点,用 / 表示
// ========== 根节点属性 ==========
model = "My Board Name"; // 板子型号(描述性字符串)
compatible = "vendor,board"; // 兼容性字符串
// 地址相关配置(影响子节点的 reg 属性解析)
#address-cells = <2>; // 地址占用 2 个 32 位单元(64位地址)
#size-cells = <2>; // 大小占用 2 个 32 位单元
// ========== 子节点 ==========
// 格式:[标签:] 节点名[@地址] { ... }
// 示例1:简单节点
chosen {
// chosen 节点用于传递参数给内核
bootargs = "console=ttyS0,115200";
};
// 示例2:内存节点
memory@0 {
device_type = "memory";
reg = <0x0 0x00000000 0x0 0x80000000>; // 起始地址 0,大小 2GB
// ^高32位^ ^低32位^ ^高32位^ ^低32位^
};
// 示例3:带标签的设备节点
uart0: serial@fe001000 { // uart0 是标签,serial 是节点名
compatible = "vendor,uart"; // 用于匹配驱动
reg = <0x0 0xfe001000 0x0 0x100>; // 寄存器地址和大小
interrupts = <GIC_SPI 10 IRQ_TYPE_LEVEL_HIGH>; // 中断
clocks = <&clk_uart>; // 引用时钟节点
clock-names = "uart_clk"; // 时钟名称
status = "okay"; // 启用状态
};
// 示例4:GPIO 控制器节点
gpio: gpio@fe004000 {
compatible = "vendor,gpio";
reg = <0x0 0xfe004000 0x0 0x100>;
gpio-controller; // 标记这是 GPIO 控制器
#gpio-cells = <2>; // 引用时需要 2 个参数
};
// 示例5:使用 GPIO 的 LED 节点
leds {
compatible = "gpio-leds";
led1 {
label = "power";
gpios = <&gpio 5 GPIO_ACTIVE_HIGH>; // 引用 gpio 节点的第5个引脚
default-state = "on";
};
};
};
// 在根节点外修改已有节点(Overlay 语法)
&uart0 { // & 表示引用已存在的节点
status = "disabled"; // 禁用这个串口
};11.2 常用属性详解
┌───────────────────────────────────────────────────────────────────────────┐
│ 常用属性参考表 │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ 属性名 说明 示例 │
│ ─────────────────────────────────────────────────────────────────────── │
│ compatible 兼容性字符串,用于 "amlogic,meson-uart" │
│ 匹配驱动 │
│ │
│ reg 寄存器地址和大小 <0x0 0xfe001000 │
│ 格式受 #address-cells 0x0 0x100> │
│ 和 #size-cells 影响 │
│ │
│ interrupts 中断配置 <GIC_SPI 10 │
│ IRQ_TYPE_LEVEL_HIGH> │
│ │
│ clocks 时钟引用 <&clkc CLKID_UART> │
│ │
│ clock-names 时钟名称(对应 clocks) "core", "bus" │
│ │
│ resets 复位信号引用 <&reset RESET_UART> │
│ │
│ pinctrl-0 引脚配置引用 <&uart_pins> │
│ pinctrl-names 引脚配置名称 "default" │
│ │
│ status 设备状态 "okay" 或 "disabled" │
│ okay=启用, disabled=禁用 │
│ │
│ #address-cells 子节点地址占用的 cell 数 <1> 或 <2> │
│ #size-cells 子节点大小占用的 cell 数 <1> 或 <2> │
│ │
│ phandle 节点句柄(自动生成) <0x01> │
│ │
└───────────────────────────────────────────────────────────────────────────┘11.3 S905X5 设备树结构
arch/arm64/boot/dts/amlogic/
│
├── meson-s7d.dtsi # S7D SoC 基础定义(所有 S7D 芯片共用)
│ ├── CPU 配置
│ ├── 中断控制器
│ ├── 时钟配置
│ ├── 各种外设定义(UART, I2C, SPI, USB...)
│ └── ...
│
├── meson-s7d-bm201.dtsi # BM201 参考板的公共定义
│ ├── 内存配置
│ ├── 启用的外设
│ ├── 引脚配置
│ └── ...
│
├── s7d_s905x5m_bm201.dts # ★ 最终产品配置(2GB DDR)
│ ├── #include "meson-s7d-bm201.dtsi"
│ ├── 产品特有配置
│ └── ...
│
├── s7d_s905x5m_bm201-1g.dts # 1GB DDR 版本
└── s7d_s905x5m_bm201-4g.dts # 4GB DDR 版本
文件包含关系(从下到上):
┌───────────────────────────────────────────────────────────────────────────┐
│ s7d_s905x5m_bm201.dts │
│ └── #include "meson-s7d-bm201.dtsi" │
│ └── #include "meson-s7d.dtsi" │
│ └── #include "meson-s7-base.dtsi" │
│ └── (ARM64 和 Amlogic 基础定义) │
└───────────────────────────────────────────────────────────────────────────┘11.4 常见修改场景
11.4.1 启用/禁用设备
/* 禁用设备 - 方式1:在 DTS 文件中直接修改 */
&uart_b {
status = "disabled";
};
/* 启用设备 */
&uart_a {
status = "okay";
};11.4.2 添加 GPIO LED
/* 在 DTS 中添加 LED 节点 */
/ {
leds {
compatible = "gpio-leds";
power_led {
label = "power";
gpios = <&gpio GPIOH_5 GPIO_ACTIVE_HIGH>;
default-state = "on";
};
status_led {
label = "status";
gpios = <&gpio GPIOH_6 GPIO_ACTIVE_LOW>;
linux,default-trigger = "heartbeat";
};
};
};11.4.3 添加 I2C 设备
/* 启用 I2C 总线并添加设备 */
&i2c0 {
status = "okay";
pinctrl-0 = <&i2c0_pins>;
pinctrl-names = "default";
clock-frequency = <400000>; /* 400KHz */
/* 添加 EEPROM */
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>; /* I2C 地址 */
};
/* 添加 RTC */
rtc@68 {
compatible = "dallas,ds1307";
reg = <0x68>;
};
};11.4.4 修改内存配置
/* 修改内存大小 */
/ {
memory@0 {
device_type = "memory";
reg = <0x0 0x00000000 0x0 0x80000000>; /* 2GB */
/* 改为 1GB: */
/* reg = <0x0 0x00000000 0x0 0x40000000>; */
/* 改为 4GB: */
/* reg = <0x0 0x00000000 0x1 0x00000000>; */
};
};11.5 设备树调试
# 编译设备树
cd common/common14-5.15
make ARCH=arm64 dtbs
# 编译后的文件位置
ls arch/arm64/boot/dts/amlogic/*.dtb
# 反编译 DTB 为 DTS(查看最终合并结果)
dtc -I dtb -O dts -o output.dts arch/arm64/boot/dts/amlogic/s7d_s905x5m_bm201.dtb
# 在设备上查看设备树
adb shell ls /proc/device-tree/
adb shell cat /proc/device-tree/compatible
adb shell cat /proc/device-tree/model
# 查看设备树节点
adb shell ls /sys/firmware/devicetree/base/
# 查看某个节点的属性
adb shell xxd /proc/device-tree/serial@fe001000/reg11.6 第七阶段检验
12. 第八阶段:Android Init 系统
📚 详细文档: Android Init 系统开发指南 | Android 启动流程分析
目标:理解 Android 启动流程,掌握 init.rc 配置
12.1 Init 进程简介
┌───────────────────────────────────────────────────────────────────────────┐
│ Android Init 进程 │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ init 是 Android 的第一个用户空间进程(PID = 1) │
│ │
│ 主要职责: │
│ 1. 挂载文件系统(/dev, /proc, /sys 等) │
│ 2. 初始化 SELinux 安全策略 │
│ 3. 解析并执行 init.rc 配置文件 │
│ 4. 启动属性服务(property service) │
│ 5. 启动各种系统服务 │
│ 6. 监控服务状态,必要时重启崩溃的服务 │
│ │
│ init.rc 是什么? │
│ - 类似于"启动脚本" │
│ - 定义开机时要做什么、启动哪些服务 │
│ - 使用特殊的语法(不是 Shell 脚本!) │
│ │
└───────────────────────────────────────────────────────────────────────────┘12.2 init.rc 语法详解
12.2.1 基本结构
# init.rc 由三种语句组成:
# 1. Action(动作)- 在特定触发条件下执行的命令集合
on <trigger>
<command>
<command>
...
# 2. Service(服务)- 定义后台运行的程序
service <name> <pathname> [<argument>]*
<option>
<option>
...
# 3. Import(导入)- 包含其他 rc 文件
import /path/to/other.rc12.2.2 常用触发器 (Trigger)
# ========== 系统阶段触发器 ==========
on early-init # 早期初始化阶段
on init # 基本初始化阶段
on late-init # 后期初始化阶段
on boot # 启动阶段
on post-fs # 文件系统挂载后
on post-fs-data # /data 分区挂载后
# ========== 属性触发器 ==========
on property:ro.boot_completed=1 # 属性值变为指定值时触发
on property:sys.boot_completed=1
# ========== 组合触发器 ==========
on property:sys.usb.config=mtp && property:sys.usb.configfs=112.2.3 常用命令
# ========== 文件/目录操作 ==========
mkdir /data/mydir 0755 system system # 创建目录
chmod 0660 /dev/mydev # 修改权限
chown system system /dev/mydev # 修改所有者
write /sys/class/gpio/export 100 # 写入文件
copy /src/file /dest/file # 复制文件
symlink /data/file /system/link # 创建符号链接
rm /data/temp_file # 删除文件
# ========== 属性操作 ==========
setprop ro.my.property value # 设置属性
# ========== 服务控制 ==========
start my_service # 启动服务
stop my_service # 停止服务
restart my_service # 重启服务
enable my_service # 启用服务
# ========== 内核模块 ==========
insmod /vendor/lib/modules/mymodule.ko # 加载内核模块
# ========== 执行命令 ==========
exec /system/bin/mycommand # 执行命令并等待完成
exec_background /system/bin/mycommand # 后台执行
# ========== 挂载 ==========
mount ext4 /dev/block/xxx /mnt # 挂载分区12.2.4 服务选项
service my_daemon /vendor/bin/my_daemon arg1 arg2
# ========== 身份设置 ==========
user system # 以 system 用户运行
group system audio video # 用户组
# ========== 运行类型 ==========
class main # 服务类别(main, core, hal 等)
oneshot # 只运行一次,不重启
disabled # 默认禁用,需要手动 start
# ========== 重启控制 ==========
critical # 关键服务,崩溃4次就重启设备
onrestart restart other_svc # 本服务重启时,也重启其他服务
# ========== 能力 ==========
capabilities NET_ADMIN # Linux capabilities
# ========== Socket ==========
socket my_socket stream 0660 system system # 创建 socket
# ========== 文件描述符 ==========
writepid /dev/cpuset/system-background/tasks12.3 实际配置示例
12.3.1 添加开机执行的命令
# 在开机完成后执行一些命令
on property:sys.boot_completed=1
# 设置 CPU 性能模式
write /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor performance
# 启动自定义服务
start my_custom_service
# 设置属性
setprop vendor.my.feature.enabled 112.3.2 添加自定义服务
# 定义一个自定义服务
service my_daemon /vendor/bin/my_daemon
class main
user system
group system
disabled # 默认不启动
# 在特定条件下启动
on property:persist.vendor.my.daemon.enable=1
start my_daemon
on property:persist.vendor.my.daemon.enable=0
stop my_daemon12.3.3 开机执行一次性脚本
# 定义一次性执行的服务
service init_once /vendor/bin/init_once.sh
class main
user root
group root
oneshot # 只执行一次
disabled
# 在 /data 分区挂载后执行
on post-fs-data
start init_once12.4 属性系统
12.4.1 属性类型
┌───────────────────────────────────────────────────────────────────────────┐
│ 属性前缀说明 │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ 前缀 特点 示例 │
│ ─────────────────────────────────────────────────────────────────────── │
│ ro.* 只读,只能在启动时设置一次 ro.build.version.sdk │
│ persist.* 持久化,重启后保留 persist.sys.timezone │
│ sys.* 系统属性 sys.boot_completed │
│ ctl.* 控制属性,用于启停服务 ctl.start, ctl.stop │
│ vendor.* 厂商定义的属性 vendor.xxx.xxx │
│ init.* init 进程使用 init.svc.<name> │
│ │
│ service 状态属性:init.svc.<service_name> │
│ - stopped: 服务已停止 │
│ - starting: 服务正在启动 │
│ - running: 服务正在运行 │
│ - restarting: 服务正在重启 │
│ - stopping: 服务正在停止 │
│ │
└───────────────────────────────────────────────────────────────────────────┘12.4.2 属性操作命令
# 获取属性
adb shell getprop ro.build.version.sdk
adb shell getprop # 获取所有属性
# 设置属性(只能设置非 ro.* 属性)
adb shell setprop persist.my.property value
# 监控属性变化
adb shell watchprops
# 通过属性控制服务
adb shell setprop ctl.start my_service
adb shell setprop ctl.stop my_service12.5 Amlogic init.rc 文件结构
device/amlogic/ross/
├── init.amlogic.rc # ★ Amlogic 主配置文件
├── init.amlogic.usb.rc # USB 相关配置
├── init.amlogic.wifi.rc # WiFi 配置
├── init.amlogic.board.rc # 板级配置
├── ueventd.amlogic.rc # 设备节点权限配置
└── fstab.amlogic # 分区挂载表
system/core/rootdir/
├── init.rc # ★ Android 核心 init.rc
└── init.zygote64.rc # Zygote 配置
# 加载顺序
init.rc
└── import init.amlogic.rc
├── import init.amlogic.usb.rc
├── import init.amlogic.wifi.rc
└── import init.amlogic.board.rc12.6 ueventd 配置(设备节点权限)
# ueventd.amlogic.rc
# 格式: <设备路径> <权限> <用户> <用户组>
# 视频设备
/dev/amvideo 0666 system system
/dev/ppmgr 0666 system system
/dev/amstream_* 0666 system system
# 音频设备
/dev/audiodsp0 0660 system audio
/dev/snd/* 0660 system audio
# 串口
/dev/ttyS* 0666 system system
# I2C
/dev/i2c-* 0660 system system
# GPIO
/dev/gpiochip* 0660 system system12.7 第八阶段检验
13. 第九阶段:HAL 硬件抽象层
📚 详细文档: HAL 硬件抽象层开发指南
目标:理解 HAL 的作用,能阅读和修改 HAL 代码
13.1 HAL 简介
┌───────────────────────────────────────────────────────────────────────────┐
│ HAL 是什么? │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ HAL (Hardware Abstraction Layer) = 硬件抽象层 │
│ │
│ 作用: │
│ - 定义统一的硬件接口 │
│ - 隔离 Android 框架和具体硬件 │
│ - 不同硬件只需实现相同接口 │
│ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ Android Framework │ │
│ │ (不需要知道具体硬件是怎么工作的) │ │
│ └─────────────────────────────┬─────────────────────────────────────┘ │
│ │ 统一的 HAL 接口 │
│ ▼ │
│ ┌──────────────┬──────────────┬──────────────┬──────────────┐ │
│ │ Audio HAL │ Camera HAL │ Display HAL │ Sensors HAL │ │
│ │ │ │ │ │ │
│ │ 厂商A实现 │ 厂商A实现 │ 厂商A实现 │ 厂商A实现 │ │
│ │ 或 │ 或 │ 或 │ 或 │ │
│ │ 厂商B实现 │ 厂商B实现 │ 厂商B实现 │ 厂商B实现 │ │
│ └──────────────┴──────────────┴──────────────┴──────────────┘ │
│ │ │
│ ▼ 调用驱动 │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ Linux Kernel 驱动 │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────────────────────┘13.2 HAL 演进历史
┌───────────────────────────────────────────────────────────────────────────┐
│ HAL 演进史 │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ Android 7.x 及更早 Android 8.0 ~ 13 Android 14+ │
│ ───────────────── ────────────────── ────────────── │
│ │
│ Legacy HAL Binderized HAL AIDL HAL │
│ (传统 HAL) (HIDL) │
│ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ Framework │ │ Framework │ │ Framework │ │
│ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │
│ │ │ │ │
│ │ dlopen │ Binder IPC │ Binder IPC │
│ │ (同进程) │ (跨进程) │ (跨进程) │
│ ▼ ▼ ▼ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ HAL .so │ │HAL Service│ │HAL Service│ │
│ │ (动态库) │ │ (独立进程)│ │ (独立进程)│ │
│ └───────────┘ └───────────┘ └───────────┘ │
│ │
│ 问题: 改进: 改进: │
│ HAL崩溃会导致 进程隔离 更现代的接口语言 │
│ Framework崩溃 可独立更新 性能更好 │
│ 更好的安全性 统一到AIDL │
│ │
│ 注意:Android 14 上,Amlogic 的很多 HAL 还是用 HIDL 实现的 │
│ │
└───────────────────────────────────────────────────────────────────────────┘13.3 Amlogic HAL 代码结构
hardware/amlogic/
├── audio/ # ★ 音频 HAL
│ ├── audio_hal/ # 主要实现
│ │ ├── audio_hw.c # 核心代码
│ │ ├── audio_hw.h
│ │ └── Android.bp
│ └── audio_policy/ # 音频策略
│
├── gralloc/ # 图形内存分配 HAL
│
├── hwcomposer/ # ★ 显示合成 HAL
│ ├── hwc2/ # HWC2 实现
│ └── common/
│
├── hdmicec/ # HDMI CEC HAL
│
├── tv_input/ # TV 输入 HAL
│
├── media/ # 媒体相关
│ └── amvdec/ # 视频解码
│
├── thermal/ # 热管理 HAL
│
└── power/ # 电源管理 HAL13.4 Audio HAL 详解
13.4.1 Audio HAL 架构
┌───────────────────────────────────────────────────────────────────────────┐
│ Audio HAL 架构 │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ 应用 (播放音乐/视频) │ │
│ └─────────────────────────────┬─────────────────────────────────────┘ │
│ │ MediaPlayer API │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ AudioFlinger (Framework) │ │
│ │ 负责音频混合、路由 │ │
│ └─────────────────────────────┬─────────────────────────────────────┘ │
│ │ HIDL 接口 │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ Audio HAL │ │
│ │ ┌───────────────────────────────────────────────────────────┐ │ │
│ │ │ audio_hw.c │ │ │
│ │ │ │ │ │
│ │ │ adev_open() - 打开音频设备 │ │ │
│ │ │ adev_open_output_stream() - 打开输出流 │ │ │
│ │ │ out_write() - 写入音频数据 │ │ │
│ │ │ adev_open_input_stream() - 打开输入流(录音) │ │ │
│ │ │ in_read() - 读取音频数据 │ │ │
│ │ │ adev_set_parameters() - 设置参数 │ │ │
│ │ │ │ │ │
│ │ └───────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────┬─────────────────────────────────────┘ │
│ │ 调用驱动 │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ ALSA 驱动 │ │
│ │ │ │
│ │ /dev/snd/pcmC0D0p - 播放设备 │ │
│ │ /dev/snd/pcmC0D0c - 录音设备 │ │
│ │ /dev/snd/controlC0 - 控制设备 │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────────────────────┘13.4.2 Audio HAL 关键代码
/* hardware/amlogic/audio/audio_hal/audio_hw.c */
/* 音频设备结构体 */
struct aml_audio_device {
struct audio_hw_device hw_device; /* 继承标准接口 */
/* Amlogic 特有的成员 */
int hdmi_format;
int speaker_mute;
// ...
};
/* 打开音频设备 */
static int adev_open(const hw_module_t* module, const char* name,
hw_device_t** device)
{
struct aml_audio_device *adev;
/* 分配设备结构 */
adev = calloc(1, sizeof(struct aml_audio_device));
/* 设置标准接口函数 */
adev->hw_device.common.tag = HARDWARE_DEVICE_TAG;
adev->hw_device.common.version = AUDIO_DEVICE_API_VERSION_2_0;
adev->hw_device.common.module = (hw_module_t *)module;
adev->hw_device.common.close = adev_close;
adev->hw_device.init_check = adev_init_check;
adev->hw_device.set_parameters = adev_set_parameters;
adev->hw_device.open_output_stream = adev_open_output_stream;
adev->hw_device.open_input_stream = adev_open_input_stream;
// ...
/* 初始化硬件 */
// ...
*device = &adev->hw_device.common;
return 0;
}
/* 写入音频数据(播放) */
static ssize_t out_write(struct audio_stream_out *stream,
const void *buffer, size_t bytes)
{
struct aml_stream_out *out = (struct aml_stream_out *)stream;
/* 将数据写入 ALSA 设备 */
int ret = pcm_write(out->pcm, buffer, bytes);
if (ret != 0) {
ALOGE("pcm_write failed: %s", pcm_get_error(out->pcm));
return -1;
}
return bytes;
}13.5 Display HAL (HWComposer) 详解
┌───────────────────────────────────────────────────────────────────────────┐
│ HWComposer 工作流程 │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. SurfaceFlinger 收集所有图层 (Layers) │
│ - 状态栏图层 │
│ - 应用窗口图层 │
│ - 导航栏图层 │
│ - 视频图层 │
│ │
│ 2. 调用 HWComposer.validateDisplay() │
│ - HWC 决定每个图层用什么方式合成: │
│ - DEVICE:用硬件合成(性能好) │
│ - CLIENT:用 GPU 合成(通用性好) │
│ │
│ 3. 如果有 CLIENT 图层,SurfaceFlinger 用 GPU 合成 │
│ │
│ 4. 调用 HWComposer.presentDisplay() │
│ - 显示最终合成结果 │
│ │
│ Amlogic 特有: │
│ - OSD 图层:用于 Android UI │
│ - Video 图层:用于视频播放(硬件解码直接输出) │
│ - HDMI TX:输出到电视 │
│ │
└───────────────────────────────────────────────────────────────────────────┘13.6 HAL 调试技巧
# 查看 HAL 服务是否运行
adb shell ps -A | grep android.hardware
# 查看 HAL 日志
adb logcat | grep -i audio_hw
adb logcat | grep -i hwc
# 查看音频设备
adb shell cat /proc/asound/cards
adb shell cat /proc/asound/pcm
# 查看显示状态
adb shell dumpsys SurfaceFlinger
adb shell dumpsys display
# 查看 HAL 接口版本
adb shell lshal13.7 第九阶段检验
14. 第十阶段:Android Framework
📚 详细文档: Android Framework 开发指南 | SELinux 安全策略指南
目标:了解 Android 框架层,能进行基本的系统定制
14.1 Framework 架构
┌───────────────────────────────────────────────────────────────────────────┐
│ Android Framework 层 │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ Framework 层是 Android 的核心,提供: │
│ - 应用程序 API │
│ - 系统服务 │
│ - 进程间通信机制 │
│ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ system_server 进程 │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Activity │ │ Window │ │ Package │ │ │
│ │ │ Manager │ │ Manager │ │ Manager │ │ │
│ │ │ Service │ │ Service │ │ Service │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ 管理Activity │ │ 管理窗口 │ │ 管理应用 │ │ │
│ │ │ 生命周期 │ │ 显示层级 │ │ 安装卸载 │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Power │ │ Input │ │ Display │ │ │
│ │ │ Manager │ │ Manager │ │ Manager │ │ │
│ │ │ Service │ │ Service │ │ Service │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ 管理电源 │ │ 管理输入 │ │ 管理显示器 │ │ │
│ │ │ 休眠唤醒 │ │ 按键触摸 │ │ 分辨率亮度 │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ │ │ │
│ │ ... 还有很多其他服务 ... │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │
│ 其他系统进程: │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ surfaceflinger│ │ audioserver │ │ mediaserver │ │
│ │ │ │ │ │ │ │
│ │ 图形合成 │ │ 音频混合 │ │ 媒体播放 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└───────────────────────────────────────────────────────────────────────────┘14.2 Binder IPC 机制
┌───────────────────────────────────────────────────────────────────────────┐
│ Binder 通信原理 │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ Binder 是 Android 的进程间通信 (IPC) 机制 │
│ │
│ 为什么不用 Linux 原生 IPC? │
│ - Socket:开销大,需要两次数据拷贝 │
│ - 共享内存:难以管理 │
│ - Binder:只需一次数据拷贝,性能好,安全性高 │
│ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ App 进程 │ │ system_server │ │
│ │ │ │ │ │
│ │ ActivityMgr │ │ Activity │ │
│ │ .getService()│ │ ManagerService│ │
│ │ (Proxy) │ │ (实现) │ │
│ └───────┬───────┘ └───────▲───────┘ │
│ │ │ │
│ │ transact() onTransact() │
│ │ │ │
│ ▼ │ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ /dev/binder │ │
│ │ (内核 Binder 驱动) │ │
│ │ │ │
│ │ - 进程间数据拷贝(内存映射,只需一次拷贝) │ │
│ │ - 安全检查(UID/PID 验证) │ │
│ │ - 引用计数和生命周期管理 │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────────────────────┘14.3 常见定制场景
14.3.1 修改系统设置默认值
/* frameworks/base/packages/SettingsProvider/res/values/defaults.xml */
<!-- 修改默认亮度 -->
<integer name="def_screen_brightness">102</integer>
<!-- 修改默认音量 -->
<integer name="def_music_volume">10</integer>
<!-- 修改默认时区 -->
<string name="def_timezone">Asia/Shanghai</string>14.3.2 添加系统属性
/* frameworks/base/core/java/android/os/Build.java */
// 添加自定义属性
public static final String CUSTOM_PROPERTY =
SystemProperties.get("ro.vendor.custom.property", "default");14.3.3 修改系统行为
/* 示例:修改默认 Launcher */
/* frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java */
// 在 resolveIntent 方法中修改默认应用解析逻辑14.4 Framework 代码位置
frameworks/
├── base/ # ★ 核心框架
│ ├── core/
│ │ ├── java/android/ # Android API
│ │ │ ├── app/ # Activity, Service, Application
│ │ │ ├── content/ # ContentProvider, Intent
│ │ │ ├── view/ # View 系统
│ │ │ ├── widget/ # 控件
│ │ │ └── os/ # 系统相关
│ │ ├── jni/ # JNI 层
│ │ └── res/ # 系统资源
│ ├── services/ # ★ 系统服务
│ │ └── core/java/com/android/server/
│ │ ├── am/ # ActivityManager
│ │ ├── wm/ # WindowManager
│ │ ├── pm/ # PackageManager
│ │ └── ...
│ └── packages/
│ └── SettingsProvider/ # 设置默认值
│
├── native/
│ └── services/
│ └── surfaceflinger/ # 图形合成
│
└── av/ # 音视频
├── media/
└── services/
├── audioflinger/ # 音频混合
└── mediaserver/ # 媒体服务14.5 Framework 调试
# 查看系统服务状态
adb shell dumpsys activity # Activity 状态
adb shell dumpsys window # 窗口状态
adb shell dumpsys package # 包管理
adb shell dumpsys power # 电源管理
adb shell dumpsys input # 输入状态
# 查看所有服务
adb shell service list
# 重启 system_server(相当于软重启)
adb shell stop
adb shell start
# 查看 Framework 日志
adb logcat -s ActivityManager
adb logcat -s WindowManager14.6 第十阶段检验
15. 实践项目
15.1 入门项目
| 序号 | 项目 | 涉及知识 | 难度 |
|---|---|---|---|
| 1 | Hello World 内核模块 | 内核模块开发 | ★☆☆☆☆ |
| 2 | 修改开机 Logo | Bootloader | ★☆☆☆☆ |
| 3 | 添加开机动画 | init.rc | ★★☆☆☆ |
| 4 | GPIO LED 控制 | 设备树、驱动 | ★★☆☆☆ |
| 5 | 自定义遥控器键值 | 设备树、KeyLayout | ★★☆☆☆ |
15.2 进阶项目
| 序号 | 项目 | 涉及知识 | 难度 |
|---|---|---|---|
| 1 | I2C 传感器驱动 | 驱动、设备树、HAL | ★★★☆☆ |
| 2 | 自定义系统服务 | Framework | ★★★☆☆ |
| 3 | 预装应用 | 编译系统 | ★★☆☆☆ |
| 4 | 音频通路调试 | Audio HAL | ★★★☆☆ |
| 5 | HDMI CEC 开发 | HAL、驱动 | ★★★☆☆ |
15.3 高级项目
| 序号 | 项目 | 涉及知识 | 难度 |
|---|---|---|---|
| 1 | 自定义 Launcher | Framework、应用开发 | ★★★★☆ |
| 2 | 视频硬解优化 | HAL、Codec | ★★★★☆ |
| 3 | 新板子适配 | 全栈知识 | ★★★★★ |
16. 常见问题与解答
Q1: 编译时内存不足怎么办?
# 减少并行任务数
make -j4 # 改用 4 个任务
# 增加 swap 空间
sudo fallocate -l 8G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# 编辑 /etc/fstab 添加:
/swapfile none swap sw 0 0Q2: adb devices 看不到设备怎么办?
# 1. 检查 USB 连接
lsusb # 应该能看到 Amlogic 设备
# 2. 检查 udev 规则
sudo vim /etc/udev/rules.d/51-android.rules
# 添加:
SUBSYSTEM=="usb", ATTR{idVendor}=="1b8e", MODE="0666"
# 3. 重启 udev
sudo udevadm control --reload-rules
sudo service udev restart
# 4. 重启 adb
adb kill-server
adb start-server
adb devicesQ3: 驱动加载后看不到设备节点?
# 1. 检查模块是否加载成功
lsmod | grep your_module
dmesg | tail -20 # 查看是否有错误
# 2. 检查设备树是否匹配
cat /proc/device-tree/your_device/compatible
# 3. 检查 SELinux
getenforce # 如果是 Enforcing
setenforce 0 # 临时关闭测试Q4: 设备树修改后不生效?
# 1. 确保重新编译了设备树
make dtbs
# 2. 确保烧录了新的 dtb
fastboot flash dtb xxx.dtb
# 或者重新烧录完整镜像
# 3. 检查设备树是否正确加载
adb shell cat /proc/device-tree/your_node/status17. 学习资源推荐
📚 本项目文档索引: S905X5M Android 14 开发文档 - 包含所有子系统和开发指南
17.1 书籍推荐
| 阶段 | 书名 | 说明 |
|---|---|---|
| Linux 基础 | 《鸟哥的 Linux 私房菜》 | Linux 入门经典 |
| C 语言 | 《C Primer Plus》 | C 语言入门 |
| 内核开发 | 《Linux 设备驱动程序》(LDD3) | 驱动开发必读 |
| 内核深入 | 《深入理解 Linux 内核》 | 内核原理 |
| Android | 《深入理解 Android》系列 | Android 系统分析 |
17.2 在线资源
| 资源 | 链接 | 说明 |
|---|---|---|
| Android 官方文档 | source.android.com | 官方权威 |
| 内核文档 | kernel.org/doc | 内核官方文档 |
| eLinux | elinux.org | 嵌入式 Linux Wiki |
| Stack Overflow | stackoverflow.com | 问答社区 |
17.3 开发工具
| 工具 | 用途 |
|---|---|
| VS Code | 代码编辑,推荐插件:C/C++, Remote-SSH |
| Source Insight | 代码阅读(付费) |
| Android Studio | Android 应用开发 |
| Wireshark | 网络抓包分析 |
| Logic Analyzer | 硬件信号分析 |
附录:快速参考
常用路径
| 内容 | 路径 |
|---|---|
| 内核源码 | common/common14-5.15/ |
| 设备树 | common/common14-5.15/arch/arm64/boot/dts/amlogic/ |
| Amlogic 驱动 | common/common14-5.15/drivers/amlogic/ |
| 设备配置 | device/amlogic/ross/ |
| HAL | hardware/amlogic/ |
| Framework | frameworks/base/ |
| 编译输出 | out/target/product/ross/ |
常用命令
# 编译
source build/envsetup.sh && lunch ross-userdebug
make -j$(nproc)
# 调试
adb shell dmesg # 内核日志
adb logcat # Android 日志
adb shell dumpsys # 系统状态
# 文件操作
adb push local remote
adb pull remote local
adb shell
# 模块操作
insmod xxx.ko
rmmod xxx
lsmod
# 属性操作
getprop ro.build.version.sdk
setprop persist.xxx value文档版本: 2.0 (新手友好版)更新日期: 2025-12-12目标平台: Amlogic S905X5M (S7D) Android 14目标读者: 零基础新手