经过前面几天的学习,我们已经知道了,一块磁盘设备被发现后,内核完成注册可以使用之后,会以uevent的形式通知用户空间,使得用户空间也能做好准备,正确地使用设备。
在Android系统中,Android kernel发出的uevent将在AOSP中被处理,我们今天来分析这个过程。
uevent处理相关文件、头文件均在这个路径:
system/core/init/
在ueventd.cpp这个文件中,包含了一大段注释,详细地介绍了这个文件的一些要点:
在一个较高的等级,ueventd监听在内核中生成通过socket(Android中使用socket而不是user_helper的通信方式)的uevent消息,当ueventd接收到对应的消息时它将通过执行适当的动作来处理它,例如,创建一个设备节点到/dev,设置文件权限,设置selinux标签等。
ueventd也用来处理加载内核需要的硬件,创建连接到块设备或字符设备的链接等。
当ueventd启动,它将为当前已经成功注册的设备重新生成uevents消息,通过遍历/sys目录,向所有被发现的uevent属性文件中写入'add'强制内核重新生成uevent消息并重新发送。之所以要这样做是因为ueventd无法做到在设备完成注册之前已经启动监听(众所周知,内核先启动了),所以在内核启动时发送的uevent无法得到处理,需要重新发送(对uevent而言可以视作所有的设备都是热插拔的),这个过程叫做冷启动过程。
init进程在冷启动进程完成之前都需要进行等待,这要求冷启动过程应该越快越好,一个方法来达到这个加速的目的就是并行化地处理这些占据了冷启动大部分时间的uevent消息。
不过冷启动过程并不是我们需要关心的重点,我们需要关心的重点是收到uevent消息后,是如何进行处理的。
为了找到是如何处理的,我们先去找到uevent的main函数,通常它将会包含初始化、冷启动,以及启动一个监听内核发送的uevent消息的listener,往往是我们切入的第一步。
//system/core/init/ueventd.cpp:323
int ueventd_main(int argc, char** argv) {
/*
* init sets the umask to 077 for forked processes. We need to
* create files with exact permissions, without modification by
* the umask.
*/
umask(000);
android::base::InitLogging(argv, &android::base::KernelLogger);
LOG(INFO) << "ueventd started!";
SelinuxSetupKernelLogging();
SelabelInitialize();
std::vector<std::unique_ptr<UeventHandler>> uevent_handlers;
auto ueventd_configuration = GetConfiguration();
uevent_handlers.emplace_back(std::make_unique<DeviceHandler>(
std::move(ueventd_configuration.dev_permissions),
std::move(ueventd_configuration.sysfs_permissions),
std::move(ueventd_configuration.subsystems), android::fs_mgr::GetBootDevices(), true));
uevent_handlers.emplace_back(std::make_unique<FirmwareHandler>(
std::move(ueventd_configuration.firmware_directories),
std::move(ueventd_configuration.external_firmware_handlers)));
if (ueventd_configuration.enable_modalias_handling) {
std::vector<std::string> base_paths = {"/odm/lib/modules", "/vendor/lib/modules"};
uevent_handlers.emplace_back(std::make_unique<ModaliasHandler>(base_paths));
}
// 定义listener
UeventListener uevent_listener(ueventd_configuration.uevent_socket_rcvbuf_size);
// 冷启动过程
if (!android::base::GetBoolProperty(kColdBootDoneProp, false)) {
ColdBoot cold_boot(uevent_listener, uevent_handlers,
ueventd_configuration.enable_parallel_restorecon,
ueventd_configuration.parallel_restorecon_dirs);
cold_boot.Run();
}
for (auto& uevent_handler : uevent_handlers) {
uevent_handler->ColdbootDone();
}
// 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) {
}
// Restore prio before main loop
setpriority(PRIO_PROCESS, 0, 0);
uevent_listener.Poll([&uevent_handlers](const Uevent& uevent) {
for (auto& uevent_handler : uevent_handlers) {
// 调用了这个函数去处理uevent
uevent_handler->HandleUevent(uevent);
}
return ListenerAction::kContinue;
});
return 0;
}
我们可以发现,在59行中,调用了HandleUevent函数去处理Uevent,我们再看看这个函数是怎么实现的。
//system/core/init/uevent_handler.h:24
class UeventHandler {
public:
virtual ~UeventHandler() = default;
virtual void HandleUevent(const Uevent& uevent) = 0;
virtual void ColdbootDone() {}
};
这个函数是一个纯虚函数,它是需要被其子类实现的,推测devices相关函数中一定存在这个函数的实现已经对这个类的继承。
果然,有一个叫做DeviceHandler的函数继承了这个父类。
//system/core/init/devices.h:110
class DeviceHandler : public UeventHandler {
public:
friend class DeviceHandlerTester;
DeviceHandler();
DeviceHandler(std::vector<Permissions> dev_permissions,
std::vector<SysfsPermissions> sysfs_permissions, std::vector<Subsystem> subsystems,
std::set<std::string> boot_devices, bool skip_restorecon);
virtual ~DeviceHandler() = default;
void HandleUevent(const Uevent& uevent) override;
void ColdbootDone() override;
std::vector<std::string> GetBlockDeviceSymlinks(const Uevent& uevent) const;
// `androidboot.partition_map` allows associating a partition name for a raw block device
// through a comma separated and semicolon deliminated list. For example,
// `androidboot.partition_map=vdb,metadata;vdc,userdata` maps `vdb` to `metadata` and `vdc` to
// `userdata`.
static std::string GetPartitionNameForDevice(const std::string& device);
private:
bool FindPlatformDevice(std::string path, std::string* platform_device_path) const;
std::tuple<mode_t, uid_t, gid_t> GetDevicePermissions(
const std::string& path, const std::vector<std::string>& links) const;
void MakeDevice(const std::string& path, bool block, int major, int minor,
const std::vector<std::string>& links) const;
void HandleDevice(const std::string& action, const std::string& devpath, bool block, int major,
int minor, const std::vector<std::string>& links) const;
void FixupSysPermissions(const std::string& upath, const std::string& subsystem) const;
void HandleAshmemUevent(const Uevent& uevent);
std::vector<Permissions> dev_permissions_;
std::vector<SysfsPermissions> sysfs_permissions_;
std::vector<Subsystem> subsystems_;
std::set<std::string> boot_devices_;
bool skip_restorecon_;
std::string sysfs_mount_point_;
};
这就可以很轻松地找到最终处理Uevent的函数:
void DeviceHandler::HandleUevent(const Uevent& uevent) {
if (uevent.action == "add" || uevent.action == "change" ||
uevent.action == "bind" || uevent.action == "online") {
FixupSysPermissions(uevent.path, uevent.subsystem);
}
// if it's not a /dev device, nothing to do
if (uevent.major < 0 || uevent.minor < 0) return;
std::string devpath;
std::vector<std::string> links;
bool block = false;
// 如果是块io子系统添加的消息,添加到/dev/block路径下
if (uevent.subsystem == "block") {
block = true;
devpath = "/dev/block/" + Basename(uevent.path);
if (StartsWith(uevent.path, "/devices")) {
links = GetBlockDeviceSymlinks(uevent);
}
} else if (const auto subsystem =
std::find(subsystems_.cbegin(), subsystems_.cend(), uevent.subsystem);
subsystem != subsystems_.cend()) {
devpath = subsystem->ParseDevPath(uevent);
} else if (uevent.subsystem == "usb") {
if (!uevent.device_name.empty()) {
devpath = "/dev/" + uevent.device_name;
} else {
// This imitates the file system that would be created
// if we were using devfs instead.
// Minors are broken up into groups of 128, starting at "001"
int bus_id = uevent.minor / 128 + 1;
int device_id = uevent.minor % 128 + 1;
devpath = StringPrintf("/dev/bus/usb/%03d/%03d", bus_id, device_id);
}
} else if (StartsWith(uevent.subsystem, "usb")) {
// ignore other USB events
return;
} else if (uevent.subsystem == "misc" && StartsWith(uevent.device_name, "dm-user/")) {
devpath = "/dev/dm-user/" + uevent.device_name.substr(8);
} else {
devpath = "/dev/" + Basename(uevent.path);
}
// 创建对应的路径
mkdir_recursive(Dirname(devpath), 0755);
HandleDevice(uevent.action, devpath, block, uevent.major, uevent.minor, links);
// Duplicate /dev/ashmem device and name it /dev/ashmem<boot_id>.
// TODO(b/111903542): remove once all users of /dev/ashmem are migrated to libcutils API.
HandleAshmemUevent(uevent);
}
然后47行调用了HandleDevice函数:
//system/core/init/devices.cpp:462
void DeviceHandler::HandleDevice(const std::string& action, const std::string& devpath, bool block,
int major, int minor, const std::vector<std::string>& links) const {
if (action == "add") {
// 该函数中执行了mknod命令,创建了设备节点
MakeDevice(devpath, block, major, minor, links);
}
// We don't have full device-mapper information until a change event is fired.
if (action == "add" || (action == "change" && StartsWith(devpath, "/dev/block/dm-"))) {
for (const auto& link : links) {
if (!mkdir_recursive(Dirname(link), 0755)) {
PLOG(ERROR) << "Failed to create directory " << Dirname(link);
}
if (symlink(devpath.c_str(), link.c_str())) {
if (errno != EEXIST) {
PLOG(ERROR) << "Failed to symlink " << devpath << " to " << link;
} else if (std::string link_path;
Readlink(link, &link_path) && link_path != devpath) {
PLOG(ERROR) << "Failed to symlink " << devpath << " to " << link
<< ", which already links to: " << link_path;
}
}
}
}
if (action == "remove") {
if (StartsWith(devpath, "/dev/block/dm-")) {
RemoveDeviceMapperLinks(devpath);
}
for (const auto& link : links) {
std::string link_path;
if (Readlink(link, &link_path) && link_path == devpath) {
unlink(link.c_str());
}
}
unlink(devpath.c_str());
}
}
创建映射关系就是创建by-name目录到刚才执行mknod创建的设备的链接,移除映射关系就是移除链接,如果是dm设备,则会去调用dm的处理函数处理。创建后的链接如下,普通方式创建的在by-name目录下。
emulator_x86_64:/dev/block/by-name $ ls -l
total 0
lrwxrwxrwx 1 root root 15 2022-10-15 18:24 super -> /dev/block/vda2
lrwxrwxrwx 1 root root 15 2022-10-15 18:24 vbmeta -> /dev/block/vda1
lrwxrwxrwx 1 root root 14 2022-10-15 18:24 vda -> /dev/block/vd
dm方式创建的软连接在mapper目录下:
emulator_x86_64:/dev/block/mapper $ ls -l
total 0
drwxr-xr-x 2 root root 480 2022-10-15 18:24 by-uuid
lrwxrwxrwx 1 root root 15 2022-10-15 18:24 product -> /dev/block/dm-2
lrwxrwxrwx 1 root root 15 2022-10-15 18:24 system -> /dev/block/dm-0
lrwxrwxrwx 1 root root 15 2022-10-15 18:24 system-verity -> /dev/block/dm-4
lrwxrwxrwx 1 root root 15 2022-10-15 18:24 system_ext -> /dev/block/dm-1
lrwxrwxrwx 1 root root 16 2022-10-15 18:24 userdata -> /dev/block/dm-32
lrwxrwxrwx 1 root root 15 2022-10-15 18:24 vendor -> /dev/block/dm-3
好,峰回路转,现在转回来看一下ueventd在何时被启动,我们可以看到init.rc:
//system/core/rootdir/init.rc:15
on early-init
# Disable sysrq from keyboard
write /proc/sys/kernel/sysrq 0
# Android doesn't need kernel module autoloading, and it causes SELinux
# denials. So disable it by setting modprobe to the empty string. Note: to
# explicitly set a sysctl to an empty string, a trailing newline is needed.
write /proc/sys/kernel/modprobe \n
# Set the security context of /adb_keys if present.
restorecon /adb_keys
# Set the security context of /postinstall if present.
restorecon /postinstall
mkdir /acct/uid
# memory.pressure_level used by lmkd
chown root system /dev/memcg/memory.pressure_level
chmod 0040 /dev/memcg/memory.pressure_level
# app mem cgroups, used by activity manager, lmkd and zygote
mkdir /dev/memcg/apps/ 0755 system system
# cgroup for system_server and surfaceflinger
mkdir /dev/memcg/system 0550 system system
# symlink the Android specific /dev/tun to Linux expected /dev/net/tun
mkdir /dev/net 0755 root root
symlink ../tun /dev/net/tun
# set RLIMIT_NICE to allow priorities from 19 to -20
setrlimit nice 40 40
# Allow up to 32K FDs per process
setrlimit nofile 32768 32768
# Set up linker config subdirectories based on mount namespaces
mkdir /linkerconfig/bootstrap 0755
mkdir /linkerconfig/default 0755
# Disable dm-verity hash prefetching, since it doesn't help performance
# Read more in b/136247322
write /sys/module/dm_verity/parameters/prefetch_cluster 0
# Generate ld.config.txt for early executed processes
exec -- /system/bin/bootstrap/linkerconfig --target /linkerconfig/bootstrap
chmod 644 /linkerconfig/bootstrap/ld.config.txt
copy /linkerconfig/bootstrap/ld.config.txt /linkerconfig/default/ld.config.txt
chmod 644 /linkerconfig/default/ld.config.txt
# Mount bootstrap linker configuration as current
mount none /linkerconfig/bootstrap /linkerconfig bind rec
# 启动ueventd服务
start ueventd
//system/core/rootdir/init.rc:1247
service ueventd /system/bin/ueventd
class core
critical
seclabel u:r:ueventd:s0
shutdown critical
init.rc定义service的语法:
class core 表示启动core类,如果core类中有多个service,将会同时启动
critical 表示服务如果在4分钟内存在多于4次,则重启系统到恢复模式
seclabel u:r:ueventd:s0 socket相关的SELinux安全上下文
shutdown critical 进程的关闭行为,critical的service在shutdown超时前不会被关闭
可以看到ueventd服务在启动过程中很早就启动了,几乎是最优先启动的一个进程,它将完成冷启动相关的功能,之后的启动脚本还有很多在/dev目录下的,所以启动ueventd的优先级是相当高的。