uevent拔插事件分析--基於imx8

 

1.uevent介紹

Uevent是內核通知android有狀態變化的一種方法,比如USB線插入、拔出,電池電量變化等等。其本質是內核發送(可以通過socket)一個字符串,應用層(android)接收並解釋該字符串,獲取相應信息。

 

路徑:

system/core/init/ueventd.cpp

 

imx8 uevent說明:

At a high level, ueventd listens for uevent messages generated by the kernel through a netlink
socket.  When ueventd receives such a message it handles it by taking appropriate actions,
which can typically be creating a device node in /dev, setting file permissions, setting selinux
labels, etc.
Ueventd also handles loading of firmware that the kernel requests, and creates symlinks for block
and character devices.

When ueventd starts, it regenerates uevents for all currently registered devices by traversing
/sys and writing 'add' to each 'uevent' file that it finds.  This causes the kernel to generate
and resend uevent messages for all of the currently registered devices.  This is done, because
ueventd would not have been running when these devices were registered and therefore was unable
to receive their uevent messages and handle them appropriately.  This process is known as
'cold boot'.
'init' currently waits synchronously on the cold boot process of ueventd before it continues
its boot process.  For this reason, cold boot should be as quick as possible.  One way to achieve
a speed up here is to parallelize the handling of ueventd messages, which consume the bulk of the
time during cold boot.

Handling of uevent messages has two unique properties:
1) It can be done in isolation; it doesn't need to read or write any status once it is started.
2) It uses setegid() and setfscreatecon() so either care (aka locking) must be taken to ensure
   that no file system operations are done while the uevent process has an abnormal egid or
   fscreatecon or this handling must happen in a separate process.
Given the above two properties, it is best to fork() subprocesses to handle the uevents.  This
reduces the overhead and complexity that would be required in a solution with threads and locks.
In testing, a racy multithreaded solution has the same performance as the fork() solution, so
there is no reason to deal with the complexity of the former.

One other important caveat during the boot process is the handling of SELinux restorecon.
Since many devices have child devices, calling selinux_android_restorecon() recursively for each
device when its uevent is handled, results in multiple restorecon operations being done on a
given file.  It is more efficient to simply do restorecon recursively on /sys during cold boot,
than to do restorecon on each device as its uevent is handled.  This only applies to cold boot;
once that has completed, restorecon is done for each device as its uevent is handled.

With all of the above considered, the cold boot process has the below steps:
1) ueventd regenerates uevents by doing the /sys traversal and listens to the netlink socket for
   the generated uevents.  It writes these uevents into a queue represented by a vector.

2) ueventd forks 'n' separate uevent handler subprocesses and has each of them to handle the
   uevents in the queue based on a starting offset (their process number) and a stride (the total
   number of processes).  Note that no IPC happens at this point and only const functions from
   DeviceHandler should be called from this context.

3) In parallel to the subprocesses handling the uevents, the main thread of ueventd calls
   selinux_android_restorecon() recursively on /sys/class, /sys/block, and /sys/devices.

4) Once the restorecon operation finishes, the main thread calls waitpid() to wait for all
   subprocess handlers to complete and exit.  Once this happens, it marks coldboot as having
   completed.

At this point, ueventd is single threaded, poll()'s and then handles any future uevents.

Lastly, it should be noted that uevents that occur during the coldboot process are handled
without issue after the coldboot process completes.  This is because the uevent listener is
paused while the uevent handler and restorecon actions take place.  Once coldboot completes,
the uevent listener resumes in polling mode and will handle the uevents that occurred during
coldboot.

中文翻譯:

在較高的級別上,ueventd通過netlink套接字監聽內核生成的uevent消息。當ueventd收到這樣的消息時,它將通過採取適當的措施來處理它,通常可以是在/ dev中創建
設備節點,設置文件許可權,設置selinux標籤等。Ueventd還處理內核請求的固件加載,併爲塊和字符設備創建符號鏈接。

當ueventd啓動時,它將遍歷/ sys並將'add'寫入找到的每個'uevent'文件中,從而爲所有當前註冊的設備重新生成uevents。這導致內核爲所有當前註冊的設備生成並重
新發送uevent消息。這樣做是因爲在註冊這些設備時ueventd不會運行,因此無法接收其uevent消息並進行適當處理。這個過程稱爲
“冷啓動”。
當前,'init'在ueventd的冷啓動過程中同步等待,然後繼續其啓動過程。因此,冷啓動應儘可能快。一種提高速度的方法是並行處理未舉報消息的處理,這將佔用冷啓動
期間的大部分時間。

uevent消息的處理具有兩個獨特的屬性:
1)可以獨立完成;啓動後,無需讀取或寫入任何狀態。
2)它使用setegid()和setfscreatecon(),因此在uevent進程具有異常egid或fscreatecon時,必須小心(aka鎖定)以確保不執行任何文件系統操作,
否則此處理必須在單獨的進程中進行。給定以上兩個屬性,最好使用fork()子進程來處理uevent。這樣可以減少使用線程和鎖的解決方案所需的開銷和複雜性。
在測試中,通用的多線程解決方案具有與fork()解決方案相同的性能,因此沒有理由處理前者的複雜性。

引導過程中的另一個重要警告是對SELinux restorecon的處理。
由於許多設備都有子設備,因此對每個設備遞歸調用selinux_android_restorecon()
設備在處理其事件後會導致在給定文件上執行多個restorecon操作。在冷啓動期間,簡單地在/ sys上遞歸地執行restorecon,比處理每個設備的uevent時在每個設備上
執行restorecon更爲有效。這僅適用於冷啓動。
完成後,將在處理每個設備的事件時爲每個設備執行restorecon。

考慮到以上所有因素,冷啓動過程具有以下步驟:
1)ueventd通過執行/ sys遍歷來重新生成uevent,並監聽netlink套接字
   生成的事件。它將這些事件寫入向量表示的隊列中。

2)ueventd派生n個獨立的uevent處理程序子進程,並讓每個子進程根據起始偏移量(其進程號)和跨度(進程總數)來處理隊列中的uevents。請注意,
此時沒有發生IPC,僅應從該上下文中調用DeviceHandler中的const函數。

3)與處理uevent的子進程並行,ueventd的主線程在/ sys / class,/ sys / block和/ sys / devices上遞歸調用selinux_android_restorecon()。

4)一旦restorecon操作完成,主線程將調用waitpid()等待所有子進程處理程序完成並退出。一旦發生這種情況,它會將Coldboot標記爲已完成。

在這一點上,ueventd是單線程的poll(),然後處理將來的任何uevents。

最後,應該注意的是,在冷啓動過程完成後,可以毫無問題地處理在冷啓動過程中發生的事件。這是因爲在執行uevent處理程序和restorecon操作時,
會暫停uevent偵聽器。冷啓動完成後,uevent偵聽器將以輪詢模式恢復,並將處理冷啓動期間發生的uevent。

2.代碼分析

uevent啓動log:

[    2.181045] ueventd: ueventd started!
[    2.182890] selinux: SELinux: Loaded file_contexts
[    2.183043] ueventd: Parsing file /ueventd.rc...
[    2.184831] ueventd: Parsing file /vendor/ueventd.rc...
[    2.185772] ueventd: Parsing file /odm/ueventd.rc...
[    2.185816] ueventd: Unable to read config file '/odm/ueventd.rc': open() failed: No such file or directory
[    2.185918] ueventd: Parsing file /ueventd.freescale.rc...
[    2.185954] ueventd: Unable to read config file '/ueventd.freescale.rc': open() failed: No such file or directory
[    2.278689] ueventd: Coldboot took 0.091 seconds

ueventd 通過兩種方式創建設備節點文件

 

第一種方式對應 "冷插拔"(Cold Plug)

即以預先定義的設備信息爲基礎,當 ueventd 啓動後,統一創建設備節點文件。

這一類設備節點文件也被稱爲靜態節點文件。

 

第二種方式對應 "熱插拔"(Hot Plug))

即在系統運行中,當有設備插入 USB 端口時,ueventd 就會接收到這一事件,爲 插入的設備動態創建設備節點文件。

這一類設備節點文件也被稱爲動態節點文件

 

文件路徑:source/system/core/init/init.cpp
int main(int argc, char** argv) {
     if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);
}

文件路徑:source/system/core/init/ueventd.cpp

int ueventd_main(int argc, char** argv) {
    // 創建新建文件的權限默認值
    // 與 chmod 相反,這裏相當於新建文件後權限爲 666 
    umask(000);

    // 初始化日誌輸出
    InitKernelLogging(argv);

    LOG(INFO) << "ueventd started!";

    // 註冊 selinux 相關的用於打印 log 的回調函數
    selinux_callback cb;
    cb.func_log = selinux_klog_callback;
    selinux_set_callback(SELINUX_CB_LOG, cb);


    DeviceHandler device_handler = CreateDeviceHandler();
    // 創建 socket,用於監聽 uevent 事件
    UeventListener uevent_listener;

    // 通過 access 判斷文件 /dev/.coldboot_done 是否存在
    // 若已經存在則表明已經進行過冷插拔了
    if (access(COLDBOOT_DONE, F_OK) != 0) {
        ColdBoot cold_boot(uevent_listener, device_handler);
        cold_boot.Run();
    }

    // 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) {
        HandleFirmwareEvent(uevent);
        device_handler.HandleDeviceEvent(uevent);
        return ListenerAction::kContinue;
    });

    return 0;
}

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章