Khai thác cơ chế Epoll trong tiến trình second-stage init của Android

Trong Android, tiến trình init đóng vai trò quan trọng trong việc khởi tạo hệ thống. Cụ thể, giai đoạn second-stage init sử dụng một cơ chế hiệu quả để quản lý và xử lý các sự kiện xảy ra trong quá trình khởi động. Cơ chế này được hiện thực hóa thông qua lớp Epoll, là một lớp bao bọc (wrapper) cho API epoll gốc của Linux. Lớp Epoll này giúp init theo dõi các sự kiện, phân phối các phương thức xử lý tương ứng một cách linh hoạt.

Lớp Epoll cung cấp bốn phương thức chính, cho phép đăng ký, hủy đăng ký, theo dõi và thu thập các phương thức xử lý sự kiện. Sau khi đăng ký, các phương thức xử lý này sẽ được gọi tự động khi có sự kiện tương ứng xảy ra.

Cấu trúc sử dụng cơ bản

Trong second-stage init, lớp Epoll được sử dụng để theo dõi và xử lý các sự kiện theo một quy trình nhất định:


// system/core/init/init.cpp
SecondStageMain() {
    // Bước 1: Khởi tạo đối tượng Epoll
    Epoll epoll_monitor;
    epoll_monitor.Open();

    // Bước 2: Đăng ký các đối tượng lắng nghe và phương thức xử lý
    InstallEventHandlers(&epoll_monitor);

    // Bước 3: Vòng lặp chờ và xử lý sự kiện
    while (true) {
        auto handlers_to_run = epoll_monitor.Wait(epoll_timeout);
        for (const auto& handler_func : *handlers_to_run) {
            (*handler_func)(); // Thực thi phương thức xử lý sự kiện
        }
    }
}

1. Khởi tạo Epoll (Bước 1)

Phương thức khởi tạo (constructor) của Epoll không thực hiện hành động gì đặc biệt. Phương thức Open() sẽ tạo một bộ mô tả tệp (file descriptor) cho epoll và lưu trữ nó trong biến thành viên epoll_fd_.


// system/core/init/epoll.cpp
Result Epoll::Open() {
    if (epoll_fd_.IsValid()) return {}; // Đã mở trước đó
    epoll_fd_ = epoll_create1(EPOLL_CLOEXEC); // Tạo epoll fd

    if (epoll_fd_ == -1) {
        return Error("epoll_create1 failed"); // Xử lý lỗi
    }
    return Success();
}

2. Đăng ký Lắng nghe và Xử lý (Bước 2)

Epoll chỉ có thể lắng nghe các bộ mô tả tệp (file descriptor). Các phương thức xử lý sự kiện được định nghĩa với kiểu std::function<void()>.

Sau khi chuẩn bị đối tượng lắng nghe (fd) và phương thức xử lý tương ứng, chúng ta gọi RegisterHandler() để thêm vào danh sách theo dõi của Epoll.


// system/core/init/epoll.cpp
Result Epoll::RegisterHandler(int fd_to_watch, Handler handler_func, uint32_t watch_events) {
    // a) Lưu trữ thông tin sự kiện và hàm xử lý
    EventInfo event_data;
    event_data.trigger_events = watch_events;
    event_data.callback = std::make_shared<Handler>(std::move(handler_func));
    auto [iterator, inserted] = registered_events_.emplace(fd_to_watch, std::move(event_data));

    // b) Chuẩn bị cấu trúc epoll_event cho kernel
    epoll_event kernel_event_config;
    kernel_event_config.events = watch_events;
    kernel_event_config.data.ptr = &iterator->second; // Liên kết với EventInfo đã lưu

    // c) Thêm fd vào epoll instance
    if (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd_to_watch, &kernel_event_config) == -1) {
        return Error("epoll_ctl ADD failed"); // Xử lý lỗi
    }
    return Success();
}

Quá trình đăng ký bao gồm các bước:

  • Bước a: Tạo một cấu trúc EventInfo để lưu trữ các sự kiện mà fd cần quan tâm (ví dụ: EPOLLIN cho biết có dữ liệu để đọc) và con trỏ tới hàm xử lý sự kiện. Cấu trúc này được lưu trữ trong một std::map với khóa là fd.
  • Bước b: Khởi tạo cấu trúc epoll_event theo chuẩn của epoll. Trường events chứa các loại sự kiện cần theo dõi, và trường data.ptr trỏ tới cấu trúc EventInfo đã tạo ở bước a.
  • Bước c: Gọi epoll_ctl() với hành động EPOLL_CTL_ADD để yêu cầu kernel bắt đầu giám sát fd này thông qua epoll_fd_.

3. Chờ Sự kiện và Thu thập Xử lý (Bước 3)

Phương thức Wait() được gọi liên tục trong vòng lặp chính, tương tự như cơ chế của Looper. Nó cho phép một luồng duy nhất quản lý nhiều nguồn sự kiện.


// system/core/init/epoll.cpp
Result<std::vector<std::shared_ptr<Handler>>> Epoll::Wait(std::optional<std::chrono::milliseconds> timeout) {
    // a) Chờ sự kiện trên epoll_fd_
    const auto max_possible_events = registered_events_.size();
    std::vector<epoll_event> current_events(max_possible_events);
    int num_triggered_events = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd_, current_events.data(), max_possible_events, timeout.value_or(-1)));

    if (num_triggered_events == -1) {
        return Error("epoll_wait failed"); // Xử lý lỗi
    }

    // b) Xử lý các sự kiện đã kích hoạt
    std::vector<std::shared_ptr<Handler>> functions_to_execute;
    for (int i = 0; i < num_triggered_events; ++i) {
        auto& event_info_ptr = *reinterpret_cast<EventInfo*>(current_events[i].data.ptr);

        // Kiểm tra sự kiện không mong muốn (nếu có)
        if ((event_info_ptr.trigger_events & (EPOLLIN | EPOLLPRI)) != (EPOLLIN | EPOLLPRI) &&
            (current_events[i].events & EPOLLIN) != current_events[i].events) {
            LOG(ERROR) << "Unexpected epoll event set received: " << current_events[i].events;
        }

        // Thêm hàm xử lý vào danh sách
        functions_to_execute.emplace_back(event_info_ptr.callback);
    }

    // c) Trả về danh sách các hàm xử lý
    return functions_to_execute;
}

Các bước trong phương thức Wait():

  • Bước a: Sử dụng epoll_wait() để đợi cho đến khi có bất kỳ sự kiện nào xảy ra trên epoll_fd_ hoặc hết thời gian chờ (nếu có).
  • Bước b: Lặp qua các sự kiện đã kích hoạt. Với mỗi sự kiện, trích xuất con trỏ tới EventInfo và sau đó lấy ra hàm xử lý tương ứng. Các hàm xử lý này được thu thập vào một danh sách.
  • Bước c: Trả về danh sách các hàm xử lý. Vòng lặp chính sau đó sẽ thực thi từng hàm trong danh sách này.

Sau khi nhận được danh sách các hàm xử lý, chúng sẽ được thực thi tuần tự:


for (const auto& handler_func : *handlers_to_run) {
    (*handler_func)();
}

Kết luận

Lớp Epoll cung cấp một cách tiếp cận đơn giản và hiệu quả để quản lý các sự kiện trong tiến trình init. Nó trừu tượng hóa sự phức tạp của API epoll gốc, cho phép nhà phát triển chỉ cần cung cấp đối tượng cần giám sát và hàm xử lý tương ứng. Tuy nhiên, cần lưu ý rằng đây là một cơ chế đơn luồng. Nếu có quá nhiều đối tượng được đăng ký hoặc một hàm xử lý nào đó mất quá nhiều thời gian để thực thi, nó có thể ảnh hưởng đến khả năng phản hồi của các sự kiện khác.

Thẻ: Android init epoll linux system programming

Đăng vào ngày 17 tháng 7 lúc 16:47