2. uevent机制原理:Linux内核uevent机制、netlink通信、uevent事件格式与类型
uevent(用户空间事件)是Linux内核向用户空间传递设备状态变化的核心机制。在AAOS系统中,uevent是设备节点监听的基础,理解其原理对于开发可靠的设备驱动至关重要。
2.1 Linux内核uevent机制
uevent机制是内核设备模型(kobject)的一部分,当设备状态发生变化(如插入、移除、属性变更)时,内核通过uevent向用户空间发送通知。整个流程由kobject_uevent函数触发,经过以下关键阶段:
- 事件产生:设备核心层调用
kobject_uevent()或kobject_uevent_env(),传入kobject指针和事件类型。 - 事件过滤:通过
uevent_helper和uevent_suppress标志判断是否发送。 - 事件分发:内核通过netlink套接字和用户空间helper程序两种路径分发事件。
- 用户空间接收:udev、mdev或AAOS的监听服务接收并处理事件。
关键路径:在AAOS中,uevent主要通过netlink套接字传递,因为这种方式效率高、无需fork子进程,适合嵌入式场景。
2.2 netlink通信机制
netlink是一种特殊的套接字(socket),用于内核与用户空间的双向通信。uevent使用NETLINK_KOBJECT_UEVENT协议族,具有以下特点:
| 特性 | 说明 | AAOS中的优势 |
|---|---|---|
| 协议族 | NETLINK_KOBJECT_UEVENT (15) | 专用通道,避免与其他netlink消息混淆 |
| 通信模式 | 多播(multicast) | 多个监听者可同时接收同一事件 |
| 消息边界 | 数据报(datagram) | 每条uevent独立,无需自行组帧 |
| 缓冲区 | 内核环形缓冲区,可配置 | 高负载下不丢失事件(可调) |
| 阻塞/非阻塞 | 支持两种模式 | AAOS使用阻塞模式,减少CPU占用 |
编程要点:创建uevent netlink套接字时,必须使用
socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT),并绑定到pid = 0(监听所有多播组)。
2.3 uevent事件格式
uevent事件以环境变量(key-value对)的形式传递,每条消息以'\0'分隔多个字段,以'\0\0'结束。标准格式如下:
ACTION=add
DEVPATH=/devices/platform/soc/a6000000.i2c/i2c-0/0-0038
SUBSYSTEM=i2c
DEVTYPE=i2c_device
DRIVER=max77818
DEVNAME=i2c-0-0038
SEQNUM=12345
MAJOR=89
MINOR=0
关键字段说明:
| 字段名 | 含义 | 示例值 |
|---|---|---|
| ACTION | 事件动作类型 | add / remove / change / move / online / offline / bind / unbind |
| DEVPATH | 设备在sysfs中的路径 | /devices/platform/soc/xxx |
| SUBSYSTEM | 设备所属子系统 | i2c / spi / input / usb / gpio / pwm |
| DEVTYPE | 设备类型(可选) | i2c_device / usb_interface / scsi_device |
| DRIVER | 绑定的驱动名称 | max77818 / i2c_hid |
| SEQNUM | 事件序列号(单调递增) | 12345 |
| MAJOR / MINOR | 设备号(仅块/字符设备) | 89 / 0 |
2.4 uevent事件类型
内核定义的uevent动作类型共8种,AAOS驱动开发中常用前4种:
| ACTION | 触发时机 | AAOS典型场景 |
|---|---|---|
| add | 设备注册到内核时 | 外设插入 → 创建设备节点 → 驱动绑定 |
| remove | 设备从内核移除时 | 外设拔出 → 清理设备节点 → 释放资源 |
| change | 设备属性或状态变化 | GPIO电平变化、电源状态切换 |
| move | 设备在sysfs中重命名或移动 | USB设备重新枚举、接口切换 |
| online / offline | 设备上线/下线(通常用于CPU、内存) | CPU热插拔、内存热插拔 |
| bind / unbind | 驱动绑定/解绑设备 | 驱动加载/卸载时的通知 |
注意:在AAOS中,
bind和unbind事件通常与add/remove成对出现。监听时建议以add作为设备就绪信号,而非bind,因为bind可能在设备节点创建之前触发。
2.5 内核uevent发送源码分析
内核中uevent的发送入口是lib/kobject_uevent.c,核心函数流程如下:
// 内核uevent发送核心路径(简化)
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
char *envp_ext[])
{
// 1. 检查是否抑制uevent
if (kobj->uevent_suppress)
return -EINVAL;
// 2. 构建环境变量数组
const char *action_string = kobject_actions[action];
// 添加标准字段:ACTION, DEVPATH, SUBSYSTEM等
// 3. 通过netlink广播
ret = uevent_net_broadcast(uevent_sock, state, buf, count, ns);
// 4. 调用用户空间helper(如果配置)
if (uevent_helper[0] && !kobj->uevent_suppress_test)
ret = call_usermodehelper(uevent_helper, argv, env, UMH_WAIT_EXEC);
return ret;
}
AAOS优化点:在AAOS中,通常禁用
uevent_helper(设置为空字符串),仅使用netlink路径,以减少fork子进程的开销。通过echo "" > /sys/kernel/uevent_helper可关闭helper。
2.6 AAOS中的uevent监听实践
AAOS使用Java层的UeventListener类(位于frameworks/base/services/core/java/com/android/server/input/)监听uevent。其核心实现要点:
- Native层:通过JNI调用C++代码,创建netlink套接字并阻塞读取。
- 事件解析:按
'\0'分割消息,解析为key-value映射。 - 过滤策略:只关注特定SUBSYSTEM(如input、i2c、spi)的add/remove事件。
- 线程模型:独立线程循环读取,通过Handler向主线程分发事件。
驱动开发建议:在编写AAOS设备驱动时,确保驱动在
probe()成功后立即创建devfs设备节点,这样uevent的add事件才能正确携带DEVNAME字段,便于用户空间监听程序识别。