写在前头
在阅读本文之前需要对 uevent 有一个基础的概念,更有助于理解 ueventd
可参考我此前写的文章Linux 设备模型之 Uevent
ueventd概述
Android 中的 ueventd 是一个守护进程,它通过netlink scoket监听内核生成的uevent消息,当ueventd收到这样的消息时,它通过采取适当的行动来处理它,通常是在/dev中创建设备节点,设置文件权限,设置selinux标签等。ueventd还可以处理内核请求的固件加载、创建块设备符号链接以及创建字符设备。
ueventd启动流程
从本质上讲,ueventd也是一个 init 进程,启动 ueventd也是执行了 init 中的代码。我们先来看下 ueventd是如何启动的。
从下面这段 init.rc 中的代码我们可以看到ueventd是在 early-init 阶段启动:
/*system/core/rootdir/init.rc*/
on early-init
...
start ueventd
## Daemon processes to be run by init.
service ueventd /sbin/ueventd
class core
critical
seclabel u:r:ueventd:s0
shutdown critical
从 service 的定义可以知道ueventd即/sbin/ueventd,那么为什么前面说 ueventd也是一个 init 进程呢?我们来看下 init Android.mk 的定义:
/*system/core/init/Android.mk*/
# Create symlinks.
LOCAL_POST_INSTALL_CMD := $(hide) mkdir -p $(TARGET_ROOT_OUT)/sbin; \
ln -sf ../init $(TARGET_ROOT_OUT)/sbin/ueventd;
这样就很清楚了,ueventd只是 init 的一个软连接。
看过 init 代码的朋友都清楚,在 init main 函数开头有三大条件判断,每一个判断都对应着一个独立的进程处理,首当其冲的就是本文中的主角 ueventd。
int main(int argc, char** argv) {
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
……
}
所以start ueventd也会执行init的main函数,从而执行ueventd的相关代码。
ueventd 的主要组成部分
本小节主要涉及文件有:
devices.cpp
devices.h
uevent.h
uevent_listener.cpp
uevent_listener.h
ueventd.cpp
ueventd.h
ueventd_parser.cpp
ueventd_parser.h
通过上文我们知道,ueventd_main()函数就是ueventd进程的主体,接下来我们就来看下ueventd_main函数,ueventd_main主要实现了以下几个功能:
- 解析ueventd相关rc文件,管理设备节点权限;
- Cold boot,递归扫描/sys目录,根据uevent文件,静态创建设备节点;
- Hot plug,获取内核uevent事件,动态创建设备节点。
ueventd_main()代码如下:
int ueventd_main(int argc, char** argv) {
……
LOG(INFO) << "ueventd started!";
DeviceHandler device_handler = CreateDeviceHandler(); // 解析ueventd相关rc文件,管理设备节点权限;
UeventListener uevent_listener;
if (access(COLDBOOT_DONE, F_OK) != 0) {
ColdBoot cold_boot(uevent_listener, device_handler);
cold_boot.Run(); // 递归扫描/sys目录,根据uevent文件,静态创建设备节点;
}
// 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) { //获取内核uevent事件,动态创建设备节点。
HandleFirmwareEvent(uevent);
device_handler.HandleDeviceEvent(uevent);
return ListenerAction::kContinue;
});
return 0;
}
接下来从这三个主要功能来介绍ueventd。
CreateDeviceHandler
先来看下CreateDeviceHandler()具体实现:
DeviceHandler CreateDeviceHandler() {
Parser parser;
std::vector subsystems;
parser.AddSectionParser("subsystem", std::make_unique(&subsystems));
using namespace std::placeholders;
std::vector sysfs_permissions;
std::vector dev_permissions;
parser.AddSingleLineParser("/sys/",
std::bind(ParsePermissionsLine, _1, &sysfs_permissions, nullptr));
parser.AddSingleLineParser("/dev/",
std::bind(ParsePermissionsLine, _1, nullptr, &dev_permissions));
parser.ParseConfig("/ueventd.rc");
parser.ParseConfig("/vendor/ueventd.rc");
parser.ParseConfig("/odm/ueventd.rc");
/*
* keep the current product name base configuration so
* we remain backwards compatible and allow it to override
* everything
* TODO: cleanup platform ueventd.rc to remove vendor specific
* device node entries (b/34968103)
*/
std::string hardware = android::base::GetProperty("ro.hardware", "");
parser.ParseConfig("/ueventd." + hardware + ".rc");
auto boot_devices = fs_mgr_get_boot_devices();
return DeviceHandler(std::move(dev_permissions), std::move(sysfs_permissions),
std::move(subsystems), std::move(boot_devices), true);
}
从代码上看,这里的实现很“直接”,主要是完成了对ueventd相关的rc解析以及对DeviceHandler的初始化。
Coldboot
当ueventd启动时,它会遍历所有在/sys注册的设备并将'add'写入它找到的每个'uevent'文件,这会导致内核生成并重新发送所有当前注册设备的uevent消息。这样做是因为当这些设备注册到sysfs时,ueventd根本还没有开始运行,没能接收他们的uevent消息并妥善处理,这样Android系统也是无法正常运行的,所以需要重新生成其uevent以便ueventd对其做处理。这个过程被称为
'冷启动',即cold_boot,cold_boot也是ueventd在开机过程中的最主要的工作。
Cold_boot的主体run函数:
if (access(COLDBOOT_DONE, F_OK) != 0) {
ColdBoot cold_boot(uevent_listener, device_handler); // 创建netlink socket,监听uevent
cold_boot.Run();
}
void ColdBoot::Run() {
android::base::Timer cold_boot_timer;
RegenerateUevents(); // 递归扫/sys目录,对uevent写入add,重新生成uevent
ForkSubProcesses(); // 对应生成子线程处理每一个uevent
DoRestoreCon(); // 重新生成selinux context
WaitForSubProcesses(); // 等待子线程处理完成
// 处理完成后生成"/dev/.coldboot_done"
close(open(COLDBOOT_DONE, O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
LOG(INFO) << "Coldboot took " << cold_boot_timer.duration().count() / 1000.0f << " seconds";
}
RegenerateUevents
RegenerateUevents会递归的往/sys目录下已经注册的设备的uevent文件写入“add”,kernel会为此重新生成uevent。
void ColdBoot::RegenerateUevents() {
uevent_listener_.RegenerateUevents([this](const Uevent& uevent) {
HandleFirmwareEvent(uevent);
uevent_queue_.emplace_back(std::move(uevent));
return ListenerAction::kContinue;
});
}
static const char* kRegenerationPaths[] = {"/sys/class", "/sys/block", "/sys/devices"};
void UeventListener::RegenerateUevents(const ListenerCallback& callback) const {
for (const auto path : kRegenerationPaths) {
if (RegenerateUeventsForPath(path, callback) == ListenerAction::kStop) return;
}
}
ListenerAction UeventListener::RegenerateUeventsForDir(DIR* d, const ListenerCallback& callback) const {
int dfd = dirfd(d);
int fd = openat(dfd, "uevent", O_WRONLY); // 打开uevent文件
if (fd >= 0) {
write(fd, "add\n", 4); // 往uevent文件写入“add”
close(fd);
Uevent uevent;
while (ReadUevent(&uevent)) { //从socket中获取uevent事件
if (callback(uevent) == ListenerAction::kStop) return ListenerAction::kStop;
}
}
......
}
在ueventd做coldboot的时候,init进程也在同步等待cold_boot的完成。
static Result wait_for_coldboot_done_action(const BuiltinArguments& args) {
Timer t;
LOG(VERBOSE) << "Waiting for " COLDBOOT_DONE "...";
// Historically we had a 1s timeout here because we weren't otherwise
// tracking boot time, and many OEMs made their sepolicy regular
// expressions too expensive (http://b/19899875).
// Now we're tracking boot time, just log the time taken to a system
// property. We still panic if it takes more than a minute though,
// because any build that slow isn't likely to boot at all, and we'd
// rather any test lab devices fail back to the bootloader.
if (wait_for_file(COLDBOOT_DONE, 60s) < 0) {
LOG(FATAL) << "Timed out waiting for " COLDBOOT_DONE;
}
property_set("ro.boottime.init.cold_boot_wait", std::to_string(t.duration().count()));
return Success();
}
从代码中我们可以看到,init会等待60s,若ueventd在60s内不能完成cold_boot的处理,那么系统将会reboot to bootloader。
ForkSubProcesses
ForkSubProcesses主要工作就是并行处理ueventd消息,并在对应目录生成device文件,
void ColdBoot::ForkSubProcesses() {
for (unsigned int i = 0; i < num_handler_subprocesses_; ++i) {
auto pid = fork();
if (pid < 0) {
PLOG(FATAL) << "fork() failed!";
}
if (pid == 0) {
UeventHandlerMain(i, num_handler_subprocesses_);//处理uevent信息,生成node
}
subprocess_pids_.emplace(pid);
}
}
void ColdBoot::UeventHandlerMain(unsigned int process_num, unsigned int total_processes) {
for (unsigned int i = process_num; i < uevent_queue_.size(); i += total_processes) {
auto& uevent = uevent_queue_[i];
//处理uevent信息,生成node,最终调用mknod处理
device_handler_.HandleDeviceEvent(uevent);
}
_exit(EXIT_SUCCESS);
}
DoRestoreCon
void ColdBoot::DoRestoreCon() {
selinux_android_restorecon("/sys", SELINUX_ANDROID_RESTORECON_RECURSE);
device_handler_.set_skip_restorecon(false);
}
启动过程中过程中一个重要的部分是SELinux restorecon的处理。由于ueventd创建了许多设备文件,而这些文件都有子设备文件,因此都需针对每个设备递归调用selinux_android_restorecon(...)。在cold_boot期间简单地在/ sys上递归执行restorecon更有效率,而不是在处理它的uevent时在每个设备上执行restorecon。
WaitForSubProcesses
若子线程出现crash时,crash信息反馈到ueventd中,init中会接收到此信号并重启ueventd,若出现多次,ueventd作为critical service,将会reboot to bootloader。
若子线程出现block导致没有及时退出,在init等待60s后也将reboot to bootloader。
void ColdBoot::WaitForSubProcesses() {
while (!subprocess_pids_.empty()) {
int status;
pid_t pid = TEMP_FAILURE_RETRY(waitpid(-1, &status, 0)); //断尝试进行操作。 结果:操作成功。或则发生错误返回-1,并把错误类型保存'errno'
if (pid == -1) {
PLOG(ERROR) << "waitpid() failed";
continue;
}
auto it = std::find(subprocess_pids_.begin(), subprocess_pids_.end(), pid);
if (it == subprocess_pids_.end()) continue;
if (WIFEXITED(status)) { //子进程是否为正常退出的,如果是,它会返回一个非零值
if (WEXITSTATUS(status) == EXIT_SUCCESS) { // 当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值
subprocess_pids_.erase(it);
} else {
LOG(FATAL) << "subprocess exited with status " << WEXITSTATUS(status);
}
} else if (WIFSIGNALED(status)) {
LOG(FATAL) << "subprocess killed by signal " << WTERMSIG(status);
}
}
}
小结
综上:cold_boot过程经历了以下过程:
1)ueventd通过监听netlink socketuevent事件,将这些将这些uevents写入event队列中,执行/ sys遍历重新生成uevent,
2)ueventd 为每一个uevent单独fork子进程处理器uevent事件,创建device node。
3)与处理uevents的子进程并行,ueventd的主线程调用
selinux_android_restorecon()递归地在/ sys / class,/ sys / block和/ sys / devices上处理。
4)一旦restorecon操作完成,主线程调用waitpid()等待all子进程处理程序完成并退出。
当处理完上面这些事件,ueventd就会将cold_boot标记为完成,即创建"/dev/.coldboot_done"。
hotplug
在完成cold_boot之后,ueventd开始轮询处理热插拔事件,并将处理在cold_boot期间发生的事件。
uevent_listener.Poll([&device_handler](const Uevent& uevent) {
HandleFirmwareEvent(uevent);
device_handler.HandleDeviceEvent(uevent);
return ListenerAction::kContinue;
});
ueventd对uevent的处理
前面已简略的提到,ueventd接收kernel传来的uevent所用的netlink socket是在初始化coldboot时所创建的,处理uevent是在ForSubProcesses中实现的,本小节将就代码铺开这些部分,包括netlink socket的创建,uevent的监听,uevent的处理三个部分。
netlink socket的创建
直接从代码看创建流程:
system/core/init/ueventd.cpp
int ueventd_main(int argc, char** argv) {
......
DeviceHandler device_handler = CreateDeviceHandler();
UeventListener uevent_listener;
if (access(COLDBOOT_DONE, F_OK) != 0) {
ColdBoot cold_boot(uevent_listener, device_handler); // 初始化coldboot
cold_boot.Run();
}
......
}
system/core/init/ueventd.cpp
ColdBoot(UeventListener& uevent_listener, DeviceHandler& device_handler)
: uevent_listener_(uevent_listener), // 初始化uevent_listener
device_handler_(device_handler),
num_handler_subprocesses_(std::thread::hardware_concurrency() ?: 4) {}
system/core/init/uevent_listener.cpp
UeventListener::UeventListener() { // 调用默认构造函数
// is 2MB enough? udev uses 128MB!
device_fd_.reset(uevent_open_socket(2 * 1024 * 1024, true)); // 调用创建uevent socket的系统函数,
if (device_fd_ == -1) {
LOG(FATAL) << "Could not open uevent socket";
}
fcntl(device_fd_, F_SETFL, O_NONBLOCK); // 设置socket文件标志为nonblock
}
system/core/libcutils/uevent.cpp
int uevent_open_socket(int buf_sz, bool passcred) {
......
// 创建netlink socket
s = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT);
if (s < 0) return -1;
......
}
对应流程图为:
uevent的监听与接收
从netlink socket的创建中我们知道在调用ueventListener的默认构造函数时UeventListener()将socket fd赋值给了device_fd_,那么ueventd什么时候再去操作这个device_fd_。即何时监听uevent事件。
以下是uevent的监听与接收的流程图:
从流程图我们可以看到,uevent的监听与接收是在cold_boot阶段RegenerateUevents流程中处理的,这里我们重点看下第8和第9步,” 8. uevent_kernel_multicast_recv(device_fd_, msg, UEVENT_MSG_LEN);”对应着去netlink socket获取kernel产生的uevent信息,将信息存至msg返回;” 9. ParseEvent(msg, uevent)”对应着将msg信息解析,将解析出来的msg存至uevent结构体中。
static void ParseEvent(const char* msg, Uevent* uevent) {
uevent->partition_num = -1;
uevent->major = -1;
uevent->minor = -1;
uevent->action.clear();
uevent->path.clear();
uevent->subsystem.clear();
uevent->firmware.clear();
uevent->partition_name.clear();
uevent->device_name.clear();
// currently ignoring SEQNUM
while (*msg) {
if (!strncmp(msg, "ACTION=", 7)) {
msg += 7;
uevent->action = msg;
} else if (!strncmp(msg, "DEVPATH=", 8)) {
msg += 8;
uevent->path = msg;
} else if (!strncmp(msg, "SUBSYSTEM=", 10)) {
msg += 10;
uevent->subsystem = msg;
} else if (!strncmp(msg, "FIRMWARE=", 9)) {
msg += 9;
uevent->firmware = msg;
} else if (!strncmp(msg, "MAJOR=", 6)) {
msg += 6;
uevent->major = atoi(msg);
} else if (!strncmp(msg, "MINOR=", 6)) {
msg += 6;
uevent->minor = atoi(msg);
} else if (!strncmp(msg, "PARTN=", 6)) {
msg += 6;
uevent->partition_num = atoi(msg);
} else if (!strncmp(msg, "PARTNAME=", 9)) {
msg += 9;
uevent->partition_name = msg;
} else if (!strncmp(msg, "DEVNAME=", 8)) {
msg += 8;
uevent->device_name = msg;
}
// advance to after the next \0
while (*msg++)
;
}
if (LOG_UEVENTS) {
LOG(INFO) << "event { '" << uevent->action << "', '" << uevent->path << "', '"
<< uevent->subsystem << "', '" << uevent->firmware << "', " << uevent->major
<< ", " << uevent->minor << " }";
}
}
从ParseEvent的实现可以看到,每一个uevent结构体都是重新创建并从msg中获取相关信息赋值的。
在”7. ReadUevent()”后,说”后”也不太准确,确切的说是没接收生成一个新的uevent之后,都会调用callback函数,而callback函数就是一开始在RegenerateUevents()传进来的,在这个callback中可以看到会将这uevent结构体存至uevent链表中即uevent_queue_,至此uevent的监听与接收处理完成。
void ColdBoot::RegenerateUevents() {
uevent_listener_.RegenerateUevents([this](const Uevent& uevent) {
HandleFirmwareEvent(uevent);
uevent_queue_.emplace_back(std::move(uevent));
return ListenerAction::kContinue;
});
}
ListenerAction UeventListener::RegenerateUeventsForDir(DIR* d,
const ListenerCallback& callback) const {
int dfd = dirfd(d);
int fd = openat(dfd, "uevent", O_WRONLY);
if (fd >= 0) {
write(fd, "add\n", 4);
close(fd);
Uevent uevent;
while (ReadUevent(&uevent)) {
if (callback(uevent) == ListenerAction::kStop) return ListenerAction::kStop;
}
}
.....
}
uevent的处理
当接收到uevent且存储到ueventd中的uevent_queue_后,何时开始对这些uevent进行处理呢?
先来看流程图:
从流程图可以看到最终是通过调用mknod去创建device,我看来看下其中一些主要处理代码:
void ColdBoot::UeventHandlerMain(unsigned int process_num, unsigned int total_processes) {
for (unsigned int i = process_num; i < uevent_queue_.size(); i += total_processes) {
auto& uevent = uevent_queue_[i];
device_handler_.HandleDeviceEvent(uevent); //从uevent_queue_中一一获取uevent,开始创建device
}
_exit(EXIT_SUCCESS);
}
void DeviceHandler::HandleDeviceEvent(const Uevent& uevent) {
......
// 根据设备类型的不同对devpath进行处理
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 {
devpath = "/dev/" + Basename(uevent.path); // 默认情况下,路径前都加上"/dev"
}
mkdir_recursive(Dirname(devpath), 0755);
HandleDevice(uevent.action, devpath, block, uevent.major, uevent.minor, links);
}
从代码中可以看到此前存储至uevent_queue_的uevent是在UeventHandlerMain(i, num_handler_subprocesses_)这个处理里面提取出来的。