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àfdcần quan tâm (ví dụ:EPOLLINcho 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ộtstd::mapvới khóa làfd. - Bước b: Khởi tạo cấu trúc
epoll_eventtheo chuẩn củaepoll. Trườngeventschứa các loại sự kiện cần theo dõi, và trườngdata.ptrtrỏ tới cấu trúcEventInfođã tạo ở bước a. - Bước c: Gọi
epoll_ctl()với hành độngEPOLL_CTL_ADDđể yêu cầu kernel bắt đầu giám sátfdnày thông quaepoll_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ênepoll_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
EventInfovà 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.