2

AndroidP之 Ueventd

 2 years ago
source link: https://www.jianshu.com/p/8a34ba82ac1f
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
0.352019.04.03 15:10:56字数 1,845阅读 6,277

在阅读本文之前需要对 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<Subsystem> subsystems;  
    parser.AddSectionParser("subsystem", std::make_unique<SubsystemParser>(&subsystems));  
  
    using namespace std::placeholders;  
    std::vector<SysfsPermissions> sysfs_permissions;  
    std::vector<Permissions> 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<Success> 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;  
    ......   
}  

对应流程图为:

8a34ba82ac1f
ueventd netlink socket的创建

uevent的监听与接收

从netlink socket的创建中我们知道在调用ueventListener的默认构造函数时UeventListener()将socket fd赋值给了device_fd_,那么ueventd什么时候再去操作这个device_fd_。即何时监听uevent事件。
以下是uevent的监听与接收的流程图:

8a34ba82ac1f
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进行处理呢?
先来看流程图:

8a34ba82ac1f
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_)这个处理里面提取出来的。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK