在OpenHarmony6.1源码分析的init模块分析中,已经分析过 ueventd,在 base/startup/init/ueventd/ueventd_main.c 中,今天我们来拆解一下这个进程。ueventd 是 OpenHarmony 启动子系统里的设备节点守护进程,主要职责是监听 Linux 内核发出的 uevent 事件,然后在用户空间创建、删除、修复 /dev 下的设备节点,并设置对应的权限、属主、SELinux 标签。带你装杯带你废,带你霍霍OpenHarmony~ 大家好我是控哥,本期给大家带来的是:文件系统的"物业管家":ueventd。
一、角色定位
内核= 施工队,把硬件设备识别出来,然后对外喊"我发现了 xx 设备,主从设备号是 yy"。ueventd= 物业管家,听到喊声后去 /dev 目录下创建对应的门牌号(设备节点),并配好钥匙权限(mode/uid/gid/SELinux)。init= 总包,第一阶段(init_early)会先把 ueventd 拉起来处理冷启动设备,后续继续完成挂载、服务启动等。base/startup/init/ueventd/ueventd_main.cbase/startup/init/ueventd/ueventd.cbase/startup/init/ueventd/ueventd_device_handler.c
二、核心职责
从 base/startup/init/ueventd/ueventd_main.c 的 main 函数可以看到它有四种工作模式:#defineUEVENTD_MODE_DEAMON 0#defineUEVENTD_MODE_EARLY_BOOT 1#defineUEVENTD_MODE_RETRIGGER 2#defineUEVENTD_MODE_LISTEN 3
模式 | 作用 |
daemon(默认) | 长期守护,监听内核 uevent socket,处理热插拔 |
early boot | 在启动极早期创建少量必须设备节点 |
retrigger | 重新触发并处理所有已产生的 uevent,常用于冷启动补建节点 |
listen | 只监听不 retrigger |
base/startup/init/ueventd/ueventd.c 中的 HandleUevent 是事件分发的核心:STATIC voidHandleUevent(conststruct Uevent *uevent){ if (uevent->action == ACTION_ADD || uevent->action == ACTION_CHANGE || uevent->action == ACTION_ONLINE) { ChangeSysAttributePermissions(uevent->syspath); } SUBSYSTEMTYPE type = GetSubsystemType(uevent->subsystem); switch (type) { case SUBSYSTEM_BLOCK: HandleBlockDeviceEvent(uevent); break; case SUBSYSTEM_FIRMWARE: HandleFimwareDeviceEvent(uevent); break; case SUBSYSTEM_OTHERS: HandleOtherDeviceEvent(uevent); break; default: break; }}
static const char *actions[] = { [ACTION_ADD] = "add", [ACTION_REMOVE] = "remove", [ACTION_CHANGE] = "change", [ACTION_MOVE] = "move", [ACTION_ONLINE] = "online", [ACTION_OFFLINE] = "offline", [ACTION_BIND] = "bind", [ACTION_UNBIND] = "unbind", [ACTION_UNKNOWN] = "unknown",};
三、举个例子:插入一个 U 盘 / 块设备
这是最常见的场景,也是 ueventd 存在的典型意义。当 USB 总线枚举到一个 U 盘时,内核会通过 netlink socket 发送一条 uevent 消息,内容类似:ACTION=addDEVPATH=/devices/platform/soc/xxx/usb1/1-1/1-1:1.0/host0/target0:0:0/0:0:0:0/block/sdaSUBSYSTEM=blockDEVNAME=sdaMAJOR=8MINOR=0PARTN=0
2. ueventd 从 socket 读到这条消息base/startup/init/ueventd/ueventd_main.c 的主循环:staticvoidPollUeventdSocketTimeout(int ueventSockFd, bool ondemand){ struct pollfd pfd = {}; pfd.events = POLLIN; pfd.fd = ueventSockFd; // ... while (1) { ret = poll(&pfd, 1, timeout); if (pfd.revents & (POLLIN | POLLERR)) { ProcessUevent(ueventSockFd, NULL, 0, NULL); } }}
base/startup/init/ueventd/ueventd.c 中的 AddUevent 把字符串解析成结构体:staticvoidAddUevent(struct Uevent *uevent, constchar *event, size_t len){ if (STARTSWITH(event, "DEVPATH=")) { uevent->syspath = event + strlen("DEVPATH="); } else if (STARTSWITH(event, "SUBSYSTEM=")) { uevent->subsystem = event + strlen("SUBSYSTEM="); } else if (STARTSWITH(event, "ACTION=")) { uevent->action = GetUeventAction(event + strlen("ACTION=")); } else if (STARTSWITH(event, "DEVNAME=")) { uevent->deviceName = event + strlen("DEVNAME="); } else if (STARTSWITH(event, "MAJOR=")) { uevent->major = StringToInt(event + strlen("MAJOR="), -1); } else if (STARTSWITH(event, "MINOR=")) { uevent->minor = StringToInt(event + strlen("MINOR="), -1); } // ...}
HandleUevent 判断 SUBSYSTEM=block,调用 HandleBlockDeviceEvent。base/startup/init/ueventd/ueventd_device_handler.c 里最终会调用 mknod 创建 /dev/block/sda,并参考 ueventd.config 设置权限、uid、gid、SELinux 标签。
四、ueventd 处理块设备的流程图
五、启动阶段:init_early 如何拉起 ueventd
在系统启动第一阶段,init_early 会调用 StartUeventd 来处理 ramdisk 阶段的冷启动设备事件。base/startup/init/services/init/standard/init_firststage.cvoidSystemPrepare(longlong upTimeInMicroSecs){ // ... CreateFsAndDeviceNode(); HookMgrExecute(GetBootStageHookMgr(), INIT_FIRST_STAGE, NULL, NULL); if (InUpdaterMode() != 0) { return; } MountRequiredPartitions(); StartSecondStageInit(upTimeInMicroSecs);}
CreateFsAndDeviceNode 内部会启动 ueventd 处理 coldplug,否则 ramdisk 阶段很多块设备节点(比如 /dev/block/by-name/system)还没创建,无法挂载 system 分区。ueventd 在 daemon 模式下,启动后还会检查 UEVENTD_FLAG 文件:if (!listen_only && access(UEVENTD_FLAG, F_OK)) { INIT_LOGI("Ueventd started, trigger uevent"); RetriggerUevent(ueventSockFd, NULL, 0); int fd = open(UEVENTD_FLAG, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); // ...}
如果 flag 文件不存在,说明是冷启动,需要 RetriggerUevent 重新触发并处理所有已产生的 uevent。如果 flag 文件已存在,说明已经处理过,只进入正常监听。
六、ueventd.config 长什么样
ueventd 通过配置文件知道每个设备节点应该用什么权限、属主、SELinux 标签。base\startup\init\ueventd\etc\ueventd.config
base/startup/init/ueventd/ueventd_read_cfg.c 负责解析。/dev/block/sda 0660 system system/dev/ttyUSB* 0660 root root/dev/input/event* 0660 system input
<devicename> <mode> <uid> <gid> <parameter> | 字段 | 说明 | | ----------- | ---------------- | | device name | 设备节点名称 | | mode | 设备节点所给予的权限,遵循UGO模型| | uid | 设备节点的uid | | gid | 设备节点的gid | | parameter | 设备节点参数信息,作用类似于通知,如果设备创建了,会设置一个对应parameter,根据参数信息便于其他进程对该节点进行对应的操作
/dev/null 0666 0 0 device_null 如在dev/null节点下操作: 当设备被创建:系统会设置startup.uevent.device_null为"added" 当设备被卸载:系统会设置startup.uevet.device_null为"removed"
ueventd 在创建节点后,会按匹配规则调用 chown、chmod、setfilecon 等设置属性。
七、总结
问题 | 回答 |
ueventd 是什么 | OpenHarmony 启动子系统中的设备节点守护进程 |
扮演什么角色 | 连接内核热插拔事件与 /dev 文件系统的"物业管家" |
主要职责 | 监听 uevent、创建/删除/修复设备节点、设置权限和 SELinux 标签 |
何时启动 | ramdisk 第一阶段由 init_early 拉起,处理冷启动设备;之后作为守护进程监听热插拔 |
典型例子 | 插入 U 盘后,内核发 ACTION=add SUBSYSTEM=block,ueventd 创建 /dev/block/sda 并配置权限 |
如果没有 ueventd,内核驱动虽然识别了设备,但用户空间没有 /dev 节点,framework 和应用就无法访问硬件。
通俗易懂的ueventd官方文档摘抄:
ueventd服务是按需启动的服务,其行为是当有uevent事件上报时,init监听到socket消息,会将ueventd服务拉起使其处理相应的socket消息,ueventd服务处理完现有的socket消息后,会自己再轮询对应socket句柄30秒,若30秒内又有新消息上报,则继续处理,待处理结束后再次计时轮询30秒;若超过30秒都没有新消息上报,ueventd服务将会退出,并将socket交还给init轮询,此时就会出现日志:poll ueventd socket timeout, ueventd exit
安卓小众宝藏 APP!灵动岛 + 流动文字表白墙,每天零钱可直接提现
灵动岛美化 安卓专属灵动常驻窗口,听歌、电量、网速等自定义样式,手机瞬间变精致,操作便利不卡顿。
全屏流动文字表白墙:三连击灵动岛可进入设置全屏滚动流动文字,不单用于告白,留联系电话、展示标语、心愿短句都适配,字体配色滚动速度随心调,摆摊、告白都好用。
无门槛赚钱提现:日常使用就能攒红包,无最低提现门槛,无提现上限,每日均可微信提现,亲测不到一个月累计微信到账 118.86 元。零碎时间买点零食,yyds
实测零碎时间看广告,每小时红包预计可以拿到手5块,真实靠谱。
填写邀请码 CEAF62A3,双方解锁红包福利,我升级 VIP,你额外多拿收益。 安装包很小,下载无捆绑,感兴趣直接点链接体验:
下载入口:
https://qcnvld5ujs4p.feishu.cn/file/PwlwbwRfSoIPYlxSAfFcaAqMnmd