嵌入式安卓快速入门,谢双元最新笔记,(三天学会安卓底层开发课程)
一个人想学一样技术的最好的方式是有人带着实战,实战的思路是先明确自己要做什么,步聚是什么,这样才学得快,而不是找出一概念在那儿没有目的学
以下这个例子,就是我们先搞了一个灯,然后编写这个灯的驱动,到HAL 层 到,用户层调用的完整示例。
Android HAL 层 LED 灯控制学习文档,从设备树 → 内核驱动 → HAL 层,逐层打通。按照你的讲课习惯:先讲业务逻辑和目标 → 中文伪代码 → 完整带注释代码 → 关键总结。
以下这个例子,就是我们先搞了一个灯,然后编写这个灯的驱动,到HAL 层 到,用户层调用的完整示例。
Android HAL 层 LED 灯控制学习文档,从设备树 → 内核驱动 → HAL 层,逐层打通。按照你的讲课习惯:先讲业务逻辑和目标 → 中文伪代码 → 完整带注释代码 → 关键总结。
Android HAL 层完整学习示例 —— LED 灯控制
一、整体业务逻辑(先搞清楚我们要干什么)
plain
┌─────────────────────────────────────────────────────────────┐
│ 应用层 (App / SystemUI) │
│ Java代码:调用系统服务控制LED │
├─────────────────────────────────────────────────────────────┤
│ Framework层 (Java + JNI) │
│ SystemServer中的LED服务,通过JNI调用HAL │
├─────────────────────────────────────────────────────────────┤
│ HAL层 (C/C++) ←── 我们今天重点写这层 │
│ 封装内核sysfs接口,暴露标准硬件模块给Framework │
├─────────────────────────────────────────────────────────────┤
│ 内核驱动层 (C) ←── 也要写 │
│ 操作GPIO寄存器,提供 /sys/class/leds/my_led/brightness │
├─────────────────────────────────────────────────────────────┤
│ 设备树 (DTS) ←── 描述硬件 │
│ 告诉内核:LED接在哪个GPIO上 │
├─────────────────────────────────────────────────────────────┤
│ 硬件层 │
│ LED灯 ── 限流电阻 ── GPIO引脚 │
└─────────────────────────────────────────────────────────────┘
每一步的目标
表格
| 步骤 | 目标 | 通俗解释 |
|---|---|---|
| Step 1 设备树 | 告诉内核:LED接在哪个GPIO上 | 给新来的电工一张电路图,告诉他灯开关在哪 |
| Step 2 内核驱动 | 把GPIO操作封装成文件(sysfs) | 电工装个智能开关,你可以用手机APP控制 |
| Step 3 HAL层 | 把文件操作封装成Android标准模块 | 给Android系统做一个统一的"开关面板" |
| Step 4 构建配置 | 编译安装,让系统能找到这个模块 | 把面板装到墙上,贴上标签 |
二、中文伪代码(理解思路,不用记语法)
Step 1: 设备树伪代码
plain
设备树节点 "my_led" {
兼容驱动 = "mycompany,led-gpio"; // 驱动靠这个名字找到这个设备
标签 = "status_led"; // LED的名字
GPIO引脚 = <&gpio1 15 GPIO_ACTIVE_HIGH>; // 接在gpio1的第15脚,高电平亮
默认状态 = "off"; // 默认关灯
}
Step 2: 内核驱动伪代码
plain
驱动初始化() {
从设备树读取GPIO引脚号
申请GPIO使用权
设置GPIO方向为输出
注册LED设备到sysfs(创建 /sys/class/leds/my_led/brightness)
}
LED亮度设置(亮度值) {
如果 亮度值 > 0 {
GPIO输出高电平 // 灯亮
} 否则 {
GPIO输出低电平 // 灯灭
}
}
Step 3: HAL层伪代码
plain
定义LED硬件模块 {
模块ID = "led"
模块方法 = { 打开设备: led设备打开 }
}
LED设备打开() {
分配设备结构体内存
填充设备接口 = { 开灯: 开灯操作, 关灯: 关灯操作 }
返回设备指针给Framework
}
开灯操作() {
打开文件 "/sys/class/leds/my_led/brightness"
写入 "1"
关闭文件
}
关灯操作() {
打开文件 "/sys/class/leds/my_led/brightness"
写入 "0"
关闭文件
}
三、完整示例代码(带详细注释)
Step 1: 设备树(Device Tree)
文件路径:
arch/arm64/boot/dts/rockchip/rk3568-myboard.dtsi(根据你的平台调整)dts
/ {
// LED设备节点,放在根节点下或合适的父节点下
leds {
compatible = "gpio-leds"; // 使用内核自带的gpio-leds驱动框架
// 我们的LED灯
my_led: led-status {
label = "myboard:status"; // LED的名字,会出现在/sys/class/leds/下
gpios = <&gpio1 RK_PA7 GPIO_ACTIVE_HIGH>; // GPIO1_A7,高电平亮
linux,default-trigger = "none"; // 默认不绑定任何触发器
default-state = "off"; // 默认状态:关闭
// 自定义属性,我们自己的驱动可以用
compatible = "mycompany,led-gpio";
};
};
};
注释说明:
-
compatible = "gpio-leds":使用Linux内核自带的GPIO LED驱动框架,省得自己写完整驱动 -
gpios = <&gpio1 RK_PA7 GPIO_ACTIVE_HIGH>:指定GPIO引脚,RK平台用RK_PA7这种宏 -
label:决定sysfs路径名,这里是/sys/class/leds/myboard:status/brightness
如果你要自己写驱动(不用gpio-leds框架),compatible写你自己的名字,如"mycompany,led-gpio"
Step 2: 内核驱动(Kernel Driver)
这里演示自己写驱动的方式(不用gpio-leds框架),更能理解原理。
文件路径:
drivers/leds/led-mygpio.cc
/*
* 自定义LED GPIO驱动
* 功能:通过sysfs文件控制LED亮灭
* 路径:/sys/class/leds/my_led/brightness
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/of.h> // 设备树解析
#include <linux/of_gpio.h> // GPIO相关
#include <linux/gpio.h> // GPIO操作
#include <linux/leds.h> // LED子系统
// 设备私有数据结构
struct myled_data {
struct led_classdev cdev; // LED子系统标准结构体
int gpio; // GPIO引脚号
};
// ========== 核心操作:设置LED亮度 ==========
static void myled_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
// 通过led_cdev反向找到我们的私有数据
struct myled_data *priv = container_of(led_cdev, struct myled_data, cdev);
// value: 0=灭, 1~255=亮(我们简单处理,非0就亮)
if (value == LED_OFF) {
gpio_set_value(priv->gpio, 0); // GPIO输出低电平,灯灭
pr_info("MYLED: LED OFF\n");
} else {
gpio_set_value(priv->gpio, 1); // GPIO输出高电平,灯亮
pr_info("MYLED: LED ON, brightness=%d\n", value);
}
}
// ========== 驱动探测函数(设备树匹配后调用)==========
static int myled_probe(struct platform_device *pdev)
{
struct myled_data *priv;
struct device_node *np = pdev->dev.of_node;
int ret;
pr_info("MYLED: Driver probe start\n");
// 1. 分配私有数据结构体内存
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
// 2. 从设备树获取GPIO("led-gpios"属性或gpios属性)
priv->gpio = of_get_named_gpio(np, "led-gpios", 0);
if (priv->gpio < 0) {
// 尝试"gpios"属性
priv->gpio = of_get_named_gpio(np, "gpios", 0);
}
if (priv->gpio < 0) {
dev_err(&pdev->dev, "Failed to get GPIO from device tree\n");
return priv->gpio;
}
// 3. 申请GPIO使用权
ret = devm_gpio_request(&pdev->dev, priv->gpio, "my_led");
if (ret) {
dev_err(&pdev->dev, "Failed to request GPIO %d\n", priv->gpio);
return ret;
}
// 4. 设置GPIO方向为输出,默认低电平(灯灭)
ret = gpio_direction_output(priv->gpio, 0);
if (ret) {
dev_err(&pdev->dev, "Failed to set GPIO as output\n");
return ret;
}
// 5. 初始化LED子系统结构体
priv->cdev.name = "my_led"; // sysfs中显示的名字
priv->cdev.brightness_set = myled_set; // 亮度设置回调函数
priv->cdev.max_brightness = 1; // 最大亮度值(0或1,开关型LED)
priv->cdev.brightness = LED_OFF; // 初始状态:关闭
// 6. 注册到LED子系统(自动创建/sys/class/leds/my_led/brightness)
ret = led_classdev_register(&pdev->dev, &priv->cdev);
if (ret) {
dev_err(&pdev->dev, "Failed to register LED classdev\n");
return ret;
}
// 7. 把私有数据保存到platform_device,方便后面使用
platform_set_drvdata(pdev, priv);
pr_info("MYLED: LED driver registered, GPIO=%d\n", priv->gpio);
return 0;
}
// ========== 驱动移除函数 ==========
static int myled_remove(struct platform_device *pdev)
{
struct myled_data *priv = platform_get_drvdata(pdev);
// 注销LED设备(自动删除sysfs节点)
led_classdev_unregister(&priv->cdev);
// GPIO在devm_gpio_request时已标记为自动释放,无需手动释放
pr_info("MYLED: LED driver removed\n");
return 0;
}
// ========== 设备树匹配表 ==========
static const struct of_device_id myled_of_match[] = {
{ .compatible = "mycompany,led-gpio", }, // 和设备树中的compatible对应
{ },
};
MODULE_DEVICE_TABLE(of, myled_of_match);
// ========== platform驱动结构体 ==========
static struct platform_driver myled_driver = {
.probe = myled_probe, // 设备匹配成功时调用
.remove = myled_remove, // 设备移除时调用
.driver = {
.name = "my-led-gpio", // 驱动名字
.of_match_table = myled_of_match, // 设备树匹配表
},
};
// ========== 模块入口 ==========
module_platform_driver(myled_driver);
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Simple LED GPIO Driver");
MODULE_LICENSE("GPL");
Kconfig 和 Makefile 配置:
drivers/leds/Kconfig 中添加:kconfig
config LEDS_MYGPIO
tristate "LED support for My GPIO LED"
depends on LEDS_CLASS
depends on GPIOLIB
help
This option enables support for simple GPIO-connected LEDs
using custom driver.
drivers/leds/Makefile 中添加:makefile
obj-$(CONFIG_LEDS_MYGPIO) += led-mygpio.o
驱动层关键总结:
-
led_classdev_register()会自动创建/sys/class/leds/my_led/brightness -
用户空间写
1到 brightness → 调用myled_set()→gpio_set_value(1)→ 灯亮 -
用户空间写
0到 brightness → 调用myled_set()→gpio_set_value(0)→ 灯灭
Step 3: HAL层代码(传统 libhardware 方式)
Android HAL传统方式使用
hw_module_t 和 hw_device_t 结构体,编译成 .so 库。3.1 HAL 头文件(定义接口)
文件路径:
hardware/libhardware/include/hardware/led_hal.hc
/*
* LED HAL 接口头文件
* 定义了Framework和HAL之间的标准接口
*/
#ifndef ANDROID_LED_HAL_H
#define ANDROID_LED_HAL_H
#include <hardware/hardware.h>
__BEGIN_DECLS
// ========== 模块ID ==========
// Android通过这个名字找到对应的.so库
// 系统会在 /system/lib/hw/led.default.so 或 /vendor/lib/hw/led.xxx.so 中查找
#define LED_HARDWARE_MODULE_ID "led"
// ========== 设备结构体 ==========
// 继承自 hw_device_t,添加我们自己的LED操作接口
struct led_device_t {
struct hw_device_t common; // 必须放在第一个!HAL标准结构体
// LED操作接口(函数指针)
int (*set_on)(struct led_device_t* dev); // 开灯
int (*set_off)(struct led_device_t* dev); // 关灯
int (*get_status)(struct led_device_t* dev); // 获取状态(返回0=关,1=开)
};
// ========== 模块结构体 ==========
// 继承自 hw_module_t
struct led_module_t {
struct hw_module_t common; // HAL标准模块结构体
};
__END_DECLS
#endif // ANDROID_LED_HAL_H
3.2 HAL 实现文件
文件路径:
hardware/libhardware/modules/led/led.cc
/*
* LED HAL 实现
* 功能:封装内核sysfs接口,提供标准HAL模块
* 编译后生成:led.default.so
*/
#define LOG_TAG "LedHAL"
#include <hardware/hardware.h>
#include <hardware/led_hal.h>
#include <cutils/log.h> // Android日志:ALOGI, ALOGE等
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
// ========== 内核sysfs节点路径 ==========
// 对应驱动创建的 /sys/class/leds/my_led/brightness
#define LED_SYSFS_PATH "/sys/class/leds/my_led/brightness"
// ========== 内部辅助函数:写sysfs ==========
static int write_sysfs(const char *path, const char *value)
{
int fd;
int ret;
// 以只写方式打开sysfs文件
fd = open(path, O_WRONLY);
if (fd < 0) {
ALOGE("LED HAL: Failed to open %s, errno=%d", path, errno);
return -1;
}
// 写入数据("0"或"1")
ret = write(fd, value, strlen(value));
if (ret < 0) {
ALOGE("LED HAL: Failed to write %s to %s, errno=%d", value, path, errno);
close(fd);
return -1;
}
close(fd);
ALOGI("LED HAL: Write %s to %s success", value, path);
return 0;
}
// ========== 内部辅助函数:读sysfs ==========
static int read_sysfs(const char *path, char *buf, size_t size)
{
int fd;
int ret;
fd = open(path, O_RDONLY);
if (fd < 0) {
ALOGE("LED HAL: Failed to open %s", path);
return -1;
}
ret = read(fd, buf, size - 1);
if (ret < 0) {
ALOGE("LED HAL: Failed to read %s", path);
close(fd);
return -1;
}
buf[ret] = '\0'; // 确保字符串结束
close(fd);
return 0;
}
// ========== HAL接口实现:开灯 ==========
static int led_set_on(struct led_device_t* dev)
{
(void)dev; // 暂时没用到dev参数,消除编译警告
ALOGI("LED HAL: set_on() called");
return write_sysfs(LED_SYSFS_PATH, "1");
}
// ========== HAL接口实现:关灯 ==========
static int led_set_off(struct led_device_t* dev)
{
(void)dev;
ALOGI("LED HAL: set_off() called");
return write_sysfs(LED_SYSFS_PATH, "0");
}
// ========== HAL接口实现:获取状态 ==========
static int led_get_status(struct led_device_t* dev)
{
char buf[4];
int status = 0;
(void)dev;
if (read_sysfs(LED_SYSFS_PATH, buf, sizeof(buf)) == 0) {
status = (buf[0] == '1') ? 1 : 0;
}
ALOGI("LED HAL: get_status() returned %d", status);
return status;
}
// ========== 设备关闭函数 ==========
static int led_close(struct hw_device_t* device)
{
struct led_device_t* led_dev = (struct led_device_t*)device;
if (led_dev) {
free(led_dev);
ALOGI("LED HAL: device closed");
}
return 0;
}
// ========== 模块打开函数(核心!Framework通过它获取设备)==========
static int led_device_open(const struct hw_module_t* module,
const char* id,
struct hw_device_t** device)
{
struct led_device_t *dev;
ALOGI("LED HAL: led_device_open() called, id=%s", id);
// 1. 分配设备结构体内存
dev = (struct led_device_t*)malloc(sizeof(*dev));
if (!dev) {
ALOGE("LED HAL: Failed to allocate memory");
return -ENOMEM;
}
memset(dev, 0, sizeof(*dev));
// 2. 填充 hw_device_t 标准字段(必须!)
dev->common.tag = HARDWARE_DEVICE_TAG; // 设备标签
dev->common.version = 0; // 版本号
dev->common.module = (struct hw_module_t*)module; // 指向所属模块
dev->common.close = led_close; // 关闭回调
// 3. 填充我们自己的LED操作接口
dev->set_on = led_set_on;
dev->set_off = led_set_off;
dev->get_status = led_get_status;
// 4. 通过出参返回设备指针给Framework
*device = &dev->common;
ALOGI("LED HAL: Device opened successfully");
return 0;
}
// ========== 模块方法结构体 ==========
// 只有一个方法:open,用于打开设备
static struct hw_module_methods_t led_module_methods = {
.open = led_device_open,
};
// ========== 模块信息结构体(重要!必须叫 HAL_MODULE_INFO_SYM)==========
// 这是HAL模块的"入口",Android通过这个名字找到模块
const struct led_module_t HAL_MODULE_INFO_SYM = {
.common = {
.tag = HARDWARE_MODULE_TAG, // 模块标签
.module_api_version = HARDWARE_MODULE_API_VERSION(1, 0), // API版本
.hal_api_version = HARDWARE_HAL_API_VERSION, // HAL版本
.id = LED_HARDWARE_MODULE_ID, // 模块ID,必须和头文件一致
.name = "My LED HAL", // 模块名字(给人看的)
.author = "Your Company", // 作者
.methods = &led_module_methods, // 模块方法表
},
};
3.3 HAL 构建配置(Android.bp)
文件路径:
hardware/libhardware/modules/led/Android.bpbp
// 编译LED HAL模块为动态库
cc_library_shared {
name: "led.default", // 编译生成 led.default.so
// Android查找规则:led.<variant>.so
// 默认variant是"default"
relative_install_path: "hw", // 安装到 /system/lib/hw/ 或 /vendor/lib/hw/
proprietary: true, // 标记为厂商专有(放vendor分区)
srcs: ["led.c"], // 源文件
shared_libs: [
"liblog", // Android日志库(ALOGI等)
],
// 头文件搜索路径
include_dirs: [
"hardware/libhardware/include",
],
}
注意:如果是AOSP 8.0+,HAL通常放在vendor分区,路径为vendor/<company>/hardware/interfaces/...,使用HIDL或AIDL。上面的传统方式适用于学习和理解HAL原理。
Step 4: Framework层调用示例(JNI + Java)
为了让整个链路完整,这里给出Framework层如何调用HAL。
4.1 JNI 层(C++)
文件路径:
frameworks/base/services/core/jni/com_android_server_led_LedService.cppcpp
/*
* LED Service JNI
* 连接Java层和HAL层
*/
#define LOG_TAG "LedServiceJNI"
#include <jni.h>
#include <nativehelper/JNIHelp.h>
#include <utils/Log.h>
#include <hardware/hardware.h>
#include <hardware/led_hal.h>
// 全局HAL设备指针
static struct led_device_t* sLedDevice = NULL;
// ========== 初始化:加载HAL模块 ==========
static jint led_init(JNIEnv* env, jobject clazz)
{
(void)clazz;
const hw_module_t* module;
int ret;
// 1. 通过hw_get_module加载HAL模块(系统会自动找led.default.so)
ret = hw_get_module(LED_HARDWARE_MODULE_ID, &module);
if (ret != 0) {
ALOGE("LedServiceJNI: hw_get_module failed, ret=%d", ret);
return -1;
}
ALOGI("LedServiceJNI: HAL module loaded, name=%s", module->name);
// 2. 调用模块的open方法打开设备
ret = module->methods->open(module, LED_HARDWARE_MODULE_ID,
(struct hw_device_t**)&sLedDevice);
if (ret != 0) {
ALOGE("LedServiceJNI: Failed to open LED device");
return -1;
}
ALOGI("LedServiceJNI: LED device opened successfully");
return 0;
}
// ========== JNI方法:开灯 ==========
static void led_setOn(JNIEnv* env, jobject clazz)
{
(void)env;
(void)clazz;
if (sLedDevice && sLedDevice->set_on) {
sLedDevice->set_on(sLedDevice);
}
}
// ========== JNI方法:关灯 ==========
static void led_setOff(JNIEnv* env, jobject clazz)
{
(void)env;
(void)clazz;
if (sLedDevice && sLedDevice->set_off) {
sLedDevice->set_off(sLedDevice);
}
}
// ========== JNI方法:获取状态 ==========
static jint led_getStatus(JNIEnv* env, jobject clazz)
{
(void)env;
(void)clazz;
if (sLedDevice && sLedDevice->get_status) {
return sLedDevice->get_status(sLedDevice);
}
return -1;
}
// ========== JNI方法注册表 ==========
static const JNINativeMethod ledMethods[] = {
{"native_init", "()I", (void*)led_init},
{"native_setOn", "()V", (void*)led_setOn},
{"native_setOff", "()V", (void*)led_setOff},
{"native_getStatus", "()I", (void*)led_getStatus},
};
// ========== 注册JNI方法 ==========
int register_android_server_led_LedService(JNIEnv* env)
{
return jniRegisterNativeMethods(env, "com/android/server/led/LedService",
ledMethods, NELEM(ledMethods));
}
4.2 Java Service
java
package com.android.server.led;
public class LedService {
static {
System.loadLibrary("android_servers"); // 加载JNI库
}
// Native方法声明(对应JNI中的实现)
private native int native_init();
private native void native_setOn();
private native void native_setOff();
private native int native_getStatus();
public LedService() {
int ret = native_init();
if (ret != 0) {
throw new RuntimeException("Failed to init LED HAL");
}
}
public void turnOn() {
native_setOn();
}
public void turnOff() {
native_setOff();
}
public boolean isOn() {
return native_getStatus() == 1;
}
}
四、关键代码总结(背这几句就够了)
表格
| 层级 | 关键代码 | 作用 |
|---|---|---|
| 设备树 | compatible = "mycompany,led-gpio" |
驱动匹配钥匙 |
| 设备树 | gpios = <&gpio1 RK_PA7 GPIO_ACTIVE_HIGH> |
告诉内核GPIO在哪 |
| 驱动 | led_classdev_register() |
自动创建sysfs节点 |
| 驱动 | gpio_set_value(priv->gpio, 1) |
控制GPIO高低电平 |
| HAL | HAL_MODULE_INFO_SYM |
HAL模块入口,必须这个名字 |
| HAL | hw_get_module(LED_HARDWARE_MODULE_ID, &module) |
Framework加载HAL |
| HAL | module->methods->open(module, ..., &device) |
打开HAL设备 |
| HAL | write(fd, "1", 1) → /sys/class/leds/my_led/brightness |
操作内核接口 |
五、调试方法
1. 检查内核驱动是否加载
bash
# 查看内核日志
adb shell dmesg | grep MYLED
# 查看sysfs节点是否存在
adb shell ls /sys/class/leds/my_led/
# 应该看到 brightness, max_brightness 等文件
# 手动测试
adb shell "echo 1 > /sys/class/leds/my_led/brightness" # 开灯
adb shell "echo 0 > /sys/class/leds/my_led/brightness" # 关灯
2. 检查HAL模块是否编译成功
bash
# 查看vendor分区是否有so文件
adb shell ls -l /vendor/lib/hw/led.default.so
# 或 /system/lib/hw/led.default.so
# 查看日志
adb logcat -s LedHAL:D
3. 检查Framework调用
bash
adb logcat -s LedServiceJNI:D
六、补充:新版AIDL HAL方式简介
Android 11+ 推荐使用 AIDL HAL,主要区别:
表格
| 对比项 | 传统HAL (libhardware) | AIDL HAL |
|---|---|---|
| 接口定义 | C头文件 | .aidl 文件 |
| 通信方式 | 直接函数调用 | Binder IPC |
| 代码生成 | 无 | aidl 工具自动生成 |
| 位置 | /vendor/lib/hw/ |
/vendor/bin/hw/(服务进程) |
| 启动方式 | 动态加载.so | init.rc启动服务 |
| 适用版本 | Android 8.0之前 | Android 11+ |
AIDL HAL核心流程:
-
写
ISslight.aidl定义接口 -
aidl工具生成 C++/Java 桩代码 -
实现
BnSslight(服务端) -
写
.rc文件让init启动服务 -
配置
vintf清单
如果你是做Android 11+的新项目,建议学习AIDL HAL;如果是学习原理,传统HAL更容易理解,如果觉得看文档看累,可以加入元歌fgxwan 抖音,注定我定期分享实操
浙公网安备 33010602011771号