Uevent是内核通知android有状态变化的一种方法,比如USB线插入、拔出,电池电量变化等等。其本质是内核发送(可以通过socket)一个字符串,应用层(android)接收并解释该字符串,获取相应信息。
路径:
system/core/init/ueventd.cpp
imx8 uevent说明:
At a high level, ueventd listens for uevent messages generated by the kernel through a netlink
socket. When ueventd receives such a message it handles it by taking appropriate actions,
which can typically be creating a device node in /dev, setting file permissions, setting selinux
labels, etc.
Ueventd also handles loading of firmware that the kernel requests, and creates symlinks for block
and character devices.
When ueventd starts, it regenerates uevents for all currently registered devices by traversing
/sys and writing 'add' to each 'uevent' file that it finds. This causes the kernel to generate
and resend uevent messages for all of the currently registered devices. This is done, because
ueventd would not have been running when these devices were registered and therefore was unable
to receive their uevent messages and handle them appropriately. This process is known as
'cold boot'.
'init' currently waits synchronously on the cold boot process of ueventd before it continues
its boot process. For this reason, cold boot should be as quick as possible. One way to achieve
a speed up here is to parallelize the handling of ueventd messages, which consume the bulk of the
time during cold boot.
Handling of uevent messages has two unique properties:
1) It can be done in isolation; it doesn't need to read or write any status once it is started.
2) It uses setegid() and setfscreatecon() so either care (aka locking) must be taken to ensure
that no file system operations are done while the uevent process has an abnormal egid or
fscreatecon or this handling must happen in a separate process.
Given the above two properties, it is best to fork() subprocesses to handle the uevents. This
reduces the overhead and complexity that would be required in a solution with threads and locks.
In testing, a racy multithreaded solution has the same performance as the fork() solution, so
there is no reason to deal with the complexity of the former.
One other important caveat during the boot process is the handling of SELinux restorecon.
Since many devices have child devices, calling selinux_android_restorecon() recursively for each
device when its uevent is handled, results in multiple restorecon operations being done on a
given file. It is more efficient to simply do restorecon recursively on /sys during cold boot,
than to do restorecon on each device as its uevent is handled. This only applies to cold boot;
once that has completed, restorecon is done for each device as its uevent is handled.
With all of the above considered, the cold boot process has the below steps:
1) ueventd regenerates uevents by doing the /sys traversal and listens to the netlink socket for
the generated uevents. It writes these uevents into a queue represented by a vector.
2) ueventd forks 'n' separate uevent handler subprocesses and has each of them to handle the
uevents in the queue based on a starting offset (their process number) and a stride (the total
number of processes). Note that no IPC happens at this point and only const functions from
DeviceHandler should be called from this context.
3) In parallel to the subprocesses handling the uevents, the main thread of ueventd calls
selinux_android_restorecon() recursively on /sys/class, /sys/block, and /sys/devices.
4) Once the restorecon operation finishes, the main thread calls waitpid() to wait for all
subprocess handlers to complete and exit. Once this happens, it marks coldboot as having
completed.
At this point, ueventd is single threaded, poll()'s and then handles any future uevents.
Lastly, it should be noted that uevents that occur during the coldboot process are handled
without issue after the coldboot process completes. This is because the uevent listener is
paused while the uevent handler and restorecon actions take place. Once coldboot completes,
the uevent listener resumes in polling mode and will handle the uevents that occurred during
coldboot.
中文翻译:
在较高的级别上,ueventd通过netlink套接字监听内核生成的uevent消息。当ueventd收到这样的消息时,它将通过采取适当的措施来处理它,通常可以是在/ dev中创建
设备节点,设置文件许可权,设置selinux标签等。Ueventd还处理内核请求的固件加载,并为块和字符设备创建符号链接。
当ueventd启动时,它将遍历/ sys并将'add'写入找到的每个'uevent'文件中,从而为所有当前注册的设备重新生成uevents。这导致内核为所有当前注册的设备生成并重
新发送uevent消息。这样做是因为在注册这些设备时ueventd不会运行,因此无法接收其uevent消息并进行适当处理。这个过程称为
“冷启动”。
当前,'init'在ueventd的冷启动过程中同步等待,然后继续其启动过程。因此,冷启动应尽可能快。一种提高速度的方法是并行处理未举报消息的处理,这将占用冷启动
期间的大部分时间。
uevent消息的处理具有两个独特的属性:
1)可以独立完成;启动后,无需读取或写入任何状态。
2)它使用setegid()和setfscreatecon(),因此在uevent进程具有异常egid或fscreatecon时,必须小心(aka锁定)以确保不执行任何文件系统操作,
否则此处理必须在单独的进程中进行。给定以上两个属性,最好使用fork()子进程来处理uevent。这样可以减少使用线程和锁的解决方案所需的开销和复杂性。
在测试中,通用的多线程解决方案具有与fork()解决方案相同的性能,因此没有理由处理前者的复杂性。
引导过程中的另一个重要警告是对SELinux restorecon的处理。
由于许多设备都有子设备,因此对每个设备递归调用selinux_android_restorecon()
设备在处理其事件后会导致在给定文件上执行多个restorecon操作。在冷启动期间,简单地在/ sys上递归地执行restorecon,比处理每个设备的uevent时在每个设备上
执行restorecon更为有效。这仅适用于冷启动。
完成后,将在处理每个设备的事件时为每个设备执行restorecon。
考虑到以上所有因素,冷启动过程具有以下步骤:
1)ueventd通过执行/ sys遍历来重新生成uevent,并监听netlink套接字
生成的事件。它将这些事件写入向量表示的队列中。
2)ueventd派生n个独立的uevent处理程序子进程,并让每个子进程根据起始偏移量(其进程号)和跨度(进程总数)来处理队列中的uevents。请注意,
此时没有发生IPC,仅应从该上下文中调用DeviceHandler中的const函数。
3)与处理uevent的子进程并行,ueventd的主线程在/ sys / class,/ sys / block和/ sys / devices上递归调用selinux_android_restorecon()。
4)一旦restorecon操作完成,主线程将调用waitpid()等待所有子进程处理程序完成并退出。一旦发生这种情况,它会将Coldboot标记为已完成。
在这一点上,ueventd是单线程的poll(),然后处理将来的任何uevents。
最后,应该注意的是,在冷启动过程完成后,可以毫无问题地处理在冷启动过程中发生的事件。这是因为在执行uevent处理程序和restorecon操作时,
会暂停uevent侦听器。冷启动完成后,uevent侦听器将以轮询模式恢复,并将处理冷启动期间发生的uevent。
uevent启动log:
[ 2.181045] ueventd: ueventd started!
[ 2.182890] selinux: SELinux: Loaded file_contexts
[ 2.183043] ueventd: Parsing file /ueventd.rc...
[ 2.184831] ueventd: Parsing file /vendor/ueventd.rc...
[ 2.185772] ueventd: Parsing file /odm/ueventd.rc...
[ 2.185816] ueventd: Unable to read config file '/odm/ueventd.rc': open() failed: No such file or directory
[ 2.185918] ueventd: Parsing file /ueventd.freescale.rc...
[ 2.185954] ueventd: Unable to read config file '/ueventd.freescale.rc': open() failed: No such file or directory
[ 2.278689] ueventd: Coldboot took 0.091 seconds
ueventd 通过两种方式创建设备节点文件
第一种方式对应 "冷插拔"(Cold Plug)
即以预先定义的设备信息为基础,当 ueventd 启动后,统一创建设备节点文件。
这一类设备节点文件也被称为静态节点文件。
第二种方式对应 "热插拔"(Hot Plug))
即在系统运行中,当有设备插入 USB 端口时,ueventd 就会接收到这一事件,为 插入的设备动态创建设备节点文件。
这一类设备节点文件也被称为动态节点文件
文件路径:source/system/core/init/init.cpp
int main(int argc, char** argv) {
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
文件路径:source/system/core/init/ueventd.cpp
int ueventd_main(int argc, char** argv) {
// 创建新建文件的权限默认值
// 与 chmod 相反,这里相当于新建文件后权限为 666
umask(000);
// 初始化日志输出
InitKernelLogging(argv);
LOG(INFO) << "ueventd started!";
// 注册 selinux 相关的用于打印 log 的回调函数
selinux_callback cb;
cb.func_log = selinux_klog_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
DeviceHandler device_handler = CreateDeviceHandler();
// 创建 socket,用于监听 uevent 事件
UeventListener uevent_listener;
// 通过 access 判断文件 /dev/.coldboot_done 是否存在
// 若已经存在则表明已经进行过冷插拔了
if (access(COLDBOOT_DONE, F_OK) != 0) {
ColdBoot cold_boot(uevent_listener, device_handler);
cold_boot.Run();
}
// We use waitpid() in ColdBoot, so we can't ignore SIGCHLD until now.
signal(SIGCHLD, SIG_IGN);
// Reap and pending children that exited between the last call to waitpid() and setting SIG_IGN
// for SIGCHLD above.
while (waitpid(-1, nullptr, WNOHANG) > 0) {
}
// 监听事件,进行热插拔处理
uevent_listener.Poll([&device_handler](const Uevent& uevent) {
HandleFirmwareEvent(uevent);
device_handler.HandleDeviceEvent(uevent);
return ListenerAction::kContinue;
});
return 0;
}