4. 设备节点权限模型:Linux文件权限(rwx)、设备节点默认权限、权限对HAL层访问的影响

设备节点是用户空间与内核驱动交互的“门把手”。Linux 内核通过标准的文件权限模型(rwx)控制对设备节点的访问。HAL 层作为用户空间守护进程,必须拥有正确的权限才能打开设备节点并执行 IOCTL、read、write 等操作。本章深入剖析设备节点权限的构成、默认规则以及权限不足对 HAL 层造成的典型故障。

4.1 Linux 文件权限模型(rwx)回顾

Linux 使用 9 位权限位(Owner / Group / Others)控制文件或设备节点的访问能力,外加 3 位特殊权限位(SUID、SGID、Sticky)。对于设备节点,核心关注三类操作:

权限位 数值 对设备节点的含义
r (读) 4 允许 read() 系统调用;部分驱动在 open() 时检查读权限
w (写) 2 允许 write() 系统调用;多数字符设备需要写权限才能发送命令
x (执行) 1 对设备节点无意义(目录才使用);通常置为 0
关键认知: 设备节点的 open() 系统调用会同时检查权限位和 fmode 标志。即使进程具有 root 权限,如果驱动实现的 open() 回调中做了额外的权限校验(如 capabilities 检查),仍可能拒绝访问。

4.2 设备节点默认权限的生成规则

设备节点的默认权限由 udev(或 mdev/devtmpfs)在设备创建时根据内核提供的 devnode 回调、udev 规则文件以及 umask 共同决定。默认权限通常为 0660(rw-rw----),即所有者和所属组可读写,其他用户无任何权限。

默认权限生成流程:
  1. 内核驱动调用 device_create() 或 misc_register() 创建设备。
  2. 内核 devtmpfs 在 /dev 下创建设备节点,初始权限由 devnode 回调返回的 umode_t 决定。
  3. udev 监听到 uevent,根据 /etc/udev/rules.d/ 中的规则文件修改权限、所有者和组。
  4. 最终生效的权限 = udev 规则覆盖后的结果。
设备节点默认权限生成流程 内核驱动注册 devtmpfs 创建节点 udev 监听 uevent udev 规则匹配 权限/属主修改 最终节点权限生效 ⚠️ 默认权限通常为 0660 所有者和组可读写 其他用户无权限

4.3 udev 规则对设备节点权限的定制

实际产品中,HAL 层通常以非 root 用户(如 system、bluetooth、cameraserver)运行。必须通过 udev 规则将设备节点的组设置为 HAL 进程所属的组,并赋予 0660 或 0666 权限。典型的 udev 规则文件位于 /etc/udev/rules.d/99-custom-device.rules:

# 示例:为 GPIO 设备节点设置权限
KERNEL=="gpio*", SUBSYSTEM=="gpio", MODE="0660", GROUP="gpio"

# 示例:为 I2C 设备节点设置权限
KERNEL=="i2c-[0-9]*", SUBSYSTEM=="i2c", MODE="0660", GROUP="i2c"

# 示例:为传感器设备节点设置权限
KERNEL=="sensor*", SUBSYSTEM=="misc", MODE="0664", GROUP="sensors"
常见陷阱: 如果 udev 规则中 MODE 设置为 0660 但 GROUP 写错或缺失,HAL 进程将无法打开设备节点。open() 返回 EACCES(Permission denied),导致 HAL 服务初始化失败。

4.4 权限对 HAL 层访问的影响分析

HAL 层访问设备节点时,权限不足会引发以下典型故障:

权限问题 HAL 层表现 内核日志 解决方案
节点不存在 open() 返回 -1,errno = ENOENT 无 检查驱动是否加载,udev 是否创建节点
节点权限为 0600,HAL 非 owner open() 返回 -1,errno = EACCES 无(或 audit 日志) 修改 udev 规则,设置 MODE="0660" 并指定 GROUP
节点组正确但 MODE 为 0640 HAL 可读但不可写,IOCTL 失败 无 将 MODE 改为 0660 或 0666
SELinux 上下文错误 open() 返回 -1,errno = EACCES avc: denied { open } 添加 SELinux policy 允许 HAL 域访问设备节点
权限不足对 HAL 层访问的影响链路 HAL 进程 open() 调用 内核权限检查 通过 → 正常 拒绝 → EACCES 🔍 排查命令: ls -l /dev/<device> id <hal_user> dmesg | grep -i "denied"

4.5 实战:权限排查与修复步骤

当 HAL 层报告设备节点访问失败时,按以下步骤快速定位权限问题:

  1. 确认节点存在: ls -l /dev/<device_name>,检查主次设备号是否正确。
  2. 检查节点权限和属主: 确认 MODE、OWNER、GROUP 是否符合预期。
  3. 确认 HAL 进程身份: ps -A | grep hal 或 cat /proc/<pid>/status | grep Uid。
  4. 验证进程是否在目标组中: id <hal_user> 查看补充组列表。
  5. 检查 SELinux 策略: audit2allow -a 或 dmesg | grep avc。
  6. 临时修复验证: chmod 0666 /dev/<device_name> 或 chgrp <hal_group> /dev/<device_name>(仅调试用)。
最佳实践: 在系统启动早期(init.rc 或 udev 规则)完成设备节点权限设置,避免 HAL 层在运行时动态请求权限。Android 系统中,使用 devices 或 file_contexts 配置设备节点的 SELinux 标签和权限。

4.6 权限模型与 HAL 层架构的协同

在典型的 HAL 层架构中,权限模型的分层职责如下:

层级 职责 权限相关操作
Linux 内核 提供设备节点,执行 DAC 检查 通过 devnode 回调设置初始权限
udev / devtmpfs 动态创建设备节点,应用规则 修改 MODE、OWNER、GROUP
SELinux / AppArmor 提供 MAC 强制访问控制 定义域转换和访问向量
HAL 守护进程 打开设备节点,执行 IOCTL 以特定用户/组身份运行,继承权限
HAL 客户端(Framework) 通过 Binder/HIDL 调用 HAL 不直接访问设备节点,依赖 HAL 进程权限

理解设备节点权限模型是 HAL 层驱动开发的基石。权限配置错误是 HAL 层“设备打开失败”故障的首要原因。掌握 rwx 权限位、udev 规则定制以及 SELinux 策略编写,能够帮助开发者快速定位并修复访问控制问题,确保 HAL 层与内核驱动的稳定通信。