2. 驱动移植基础:Linux内核驱动与HAL层的关系、设备节点(/dev)与HAL模块的绑定机制

在Android系统中,硬件访问从底层到上层依次经过:Linux内核驱动 → 设备节点 → HAL层 → Framework层。理解内核驱动与HAL层的关系,以及设备节点与HAL模块的绑定机制,是进行驱动移植的首要前提。本章将拆解这一链路,明确各层职责与交互方式。

2.1 Linux内核驱动与HAL层的分层职责

Android系统并非直接在内核空间实现所有硬件逻辑,而是通过HAL层将内核驱动细节与上层框架解耦。下表对比了内核驱动与HAL层的核心差异:

对比维度 Linux内核驱动 Android HAL层
运行空间 内核空间(Kernel Space) 用户空间(User Space)
主要语言 C语言 C / C++
核心职责 硬件寄存器操作、中断处理、DMA、内存映射 封装内核接口,提供稳定上层API
对外接口 设备节点(/dev/xxx)、sysfs、procfs HAL Module 结构体(hw_module_t / hw_device_t)
移植关注点 设备树配置、内核编译、驱动 probe 流程 设备节点路径、HAL stub 实现、权限配置
关键原则:HAL层不直接操作硬件寄存器,而是通过读写设备节点(/dev/xxx)或调用ioctl与内核驱动通信。内核驱动负责硬件底层控制,HAL层负责将硬件能力抽象为Android框架可调用的标准接口。

2.2 设备节点(/dev)的创建与访问

设备节点是用户空间访问内核驱动的“文件接口”。内核驱动在初始化时,通过以下步骤创建设备节点:

  1. 注册设备号:使用 alloc_chrdev_region() 或 register_chrdev_region() 分配主设备号和次设备号。
  2. 初始化字符设备:使用 cdev_init() 和 cdev_add() 将设备添加到内核。
  3. 创建设备类:使用 class_create() 创建设备类。
  4. 创建设备节点:使用 device_create() 在 /dev 目录下生成节点文件。

以下是一个典型的内核驱动创建设备节点的代码片段:

#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>

static dev_t dev_num;
static struct cdev my_cdev;
static struct class *my_class;

static int __init my_driver_init(void) {
    // 1. 分配设备号
    alloc_chrdev_region(&dev_num, 0, 1, "my_device");
    
    // 2. 初始化字符设备
    cdev_init(&my_cdev, &my_fops);
    cdev_add(&my_cdev, dev_num, 1);
    
    // 3. 创建设备类
    my_class = class_create(THIS_MODULE, "my_class");
    
    // 4. 创建设备节点 /dev/my_device
    device_create(my_class, NULL, dev_num, NULL, "my_device");
    
    printk(KERN_INFO "my_driver: device node created\n");
    return 0;
}

static void __exit my_driver_exit(void) {
    device_destroy(my_class, dev_num);
    class_destroy(my_class);
    cdev_del(&my_cdev);
    unregister_chrdev_region(dev_num, 1);
}

module_init(my_driver_init);
module_exit(my_driver_exit);
移植提示:在驱动移植时,务必确认设备节点路径与HAL层代码中定义的路径一致。常见错误是内核驱动创建了 /dev/my_device_v1,而HAL层却尝试打开 /dev/my_device,导致 open() 失败返回 -1。

2.3 HAL模块与设备节点的绑定机制

HAL模块通过标准结构体 hw_module_t 和 hw_device_t 与内核设备节点绑定。绑定流程分为以下步骤:

  • 模块加载:Android Framework 通过 hw_get_module() 加载指定ID的HAL模块(如 GRALLOC_HARDWARE_MODULE_ID)。
  • 设备打开:HAL模块实现 hw_module_methods_t.open() 回调,在该回调中执行 open("/dev/xxx", O_RDWR)。
  • 设备句柄封装:将 open() 返回的文件描述符(fd)存入 hw_device_t 的自定义结构体中,供上层调用。

下图展示了HAL模块与设备节点的绑定关系:

HAL模块与设备节点绑定机制 Android Framework HAL Module (hw_module_t) hw_module_methods_t.open() /dev/my_device Linux Kernel Driver file_operations: open/read/write/ioctl 绑定关键点 1. open() 设备节点 2. 获取文件描述符 fd 3. fd 存入 hw_device_t

2.4 绑定过程中的关键数据结构

HAL层与设备节点绑定时,核心数据结构定义在 hardware/libhardware/include/hardware/hardware.h 中。以下是最关键的两个结构体:

// 硬件模块结构体(每个HAL库只有一个)
typedef struct hw_module_t {
    uint32_t tag;                  // 必须为 HARDWARE_MODULE_TAG
    uint16_t module_api_version;
    uint16_t hal_api_version;
    const char *id;                // 模块ID,如 "gralloc"
    const char *name;
    const char *author;
    struct hw_module_methods_t* methods; // 指向方法表
    void* dso;
    uint32_t reserved[32];
} hw_module_t;

// 硬件设备结构体(每个设备实例一个)
typedef struct hw_device_t {
    uint32_t tag;                  // 必须为 HARDWARE_DEVICE_TAG
    uint32_t version;
    struct hw_module_t* module;    // 指向所属模块
    int (*close)(struct hw_device_t* device);
    int fd;                        // 设备节点文件描述符(自定义扩展)
} hw_device_t;
常见陷阱:许多移植新手在自定义 hw_device_t 的扩展结构体时,忘记将 fd 初始化为 -1,导致在 HAL 模块卸载时执行 close(fd) 关闭了一个无效的文件描述符,引发进程崩溃。建议在 open() 失败时显式将 fd 置为 -1。

2.5 绑定流程的完整时序

从Framework调用到内核驱动响应的完整时序如下:

  1. hw_get_module() 根据模块ID查找并加载 /vendor/lib/hw/xxx.default.so 或 /system/lib/hw/xxx.$(ro.board.platform).so。
  2. 加载成功后,获取 hw_module_t 指针,并调用 module->methods->open()。
  3. open() 回调 内部执行 open("/dev/xxx", O_RDWR),返回文件描述符。
  4. 将文件描述符存入自定义设备结构体,并通过 hw_device_t** 返回给Framework。
  5. Framework 通过 hw_device_t 中的函数指针(如 device->read())间接调用 ioctl() 或 read()/write() 与内核驱动交互。

以下时序图展示了这一完整过程:

HAL模块与设备节点绑定完整时序 Framework HAL Module 设备节点 内核驱动 hw_get_module(id) 返回 hw_module_t* module->methods->open() open("/dev/xxx", O_RDWR) 驱动 file_operations.open 返回 fd 返回 fd 返回 hw_device_t* (含fd) device->ioctl(CMD, args) ioctl(fd, CMD, args) 返回结果 返回结果

2.6 移植检查清单

在进行驱动移植时,请逐项核对以下清单,确保内核驱动与HAL层的绑定正确:

检查项 检查内容 常见问题
设备节点路径 HAL层 open() 路径与内核 device_create() 名称一致 路径写死为 /dev/xxx,但内核创建了 /dev/xxx_v2
设备节点权限 /dev/xxx 的权限为 0660 或 0666,所属组为 system 或 root 权限为 0600 导致 HAL 进程无权限打开
设备节点存在性 内核驱动加载后,ls -l /dev/xxx 能正常显示 驱动 probe 失败,设备节点未创建
HAL模块ID hw_module_t.id 与 hw_get_module() 传入的ID一致 ID 大小写不匹配或拼写错误
文件描述符管理 open() 失败时 fd 置为 -1,close() 前检查 fd 有效性 重复 close 或 close 无效 fd 导致 crash
ioctl 命令码 HAL层与内核驱动的 ioctl 命令码(cmd)定义一致 命令码使用 _IOR/_IOW 宏时方向或大小不匹配
调试技巧:在HAL层的 open() 回调中加入 ALOGD 日志,打印 open() 的返回值以及 errno。如果 open() 返回 -1,通过 strerror(errno) 可以快速定位是权限问题(EACCES)、设备不存在(ENOENT)还是驱动未响应(EIO)。