一、引文
作為一個內核初學者,經常容易進入“知其然但不知其所以然”的狀態,在power supply子系統中就是這樣,知道如何去添加一個屬性prop,知道psy可以創建一堆文件節點,也知道上層是通過讀取這些節點來獲取供電信息的,但對於其中的細節,便知之甚少。最近深究其中,才逐步發現內核的奧妙所在。
二、Android供電系統框架
power supply(以下簡稱psy)是Linux中從供電驅動抽象出來的子系統,是Linux電源管理的重要組成部分。psy是一個中間層,在kernel中是屬於設備驅動的一部分,psy的作用主要是向用戶空間匯總各類供電的狀態信息。抽象出來的各類信息稱為property,比如供電設備是否連接就對應着POWER_SUPPLY_PROP_ONLINE。在驅動層,主要是兩大模塊,與電池相關的驅動和與充放電管理相關的驅動(對應圖中的battery.c和charger.c),這兩大模塊主要處理硬件相關的邏輯,在硬件狀態發生變化時,會觸發相關的中斷,驅動層會調用相應的中斷函數,並更新修改相應的psy節點值。驅動負責更新psy節點的狀態,HAL層會去讀這些節點,驅動在檢測硬件、傳感器信息變化會去更新節點值,而HAL層什么時候會去讀取這些節點值呢?以及其中調用的流程是怎樣的?今天就簡單介紹下。
三、power supply子系統簡介
1. 概述
psy子系統的基礎是建立在設備驅動模型之上的,主要運用了其中的class、device、kobject、sysfs、uevent相關知識,也是驅動設備模型的一個具體應用。psy子系統中power_supply_class對應着系統中供電設備類,是一個抽象化的集合,對應着/sys/class/power_supply/目錄,供電設備都在該目錄之下,比如battery設備就對應該目錄下一個子目錄battery,而battery設備的一個屬性則對應battery的一個文件節點,也對應着一個kobject。
2. 相關結構體
psy相關的定義在/include/linux/power_supply.h。
psy_desc是psy子系統中最重要的結構體,該描述符定義了psy的屬性property,以及相關的set/get/is_writeable接口,is_writeable即文件節點的“w”權限,所有節點默認是可讀的。從get函數可以看到調用該函數需要指定某個psy和psp(屬性),結果保存在val中,值得一提的是,val是個union類型,可以傳遞int或char *。
struct power_supply表示一個psy供電設備,比如電池、AC、USB,一般可通過devm_power_supply_register函數注冊成一個psy設備,在注冊設備之前需要定義好該設備的psy_desc。
3. 相關接口函數
相關的函數主要在psy_core.c和psy_sysfs.c中,core主要負責設備狀態變化邏輯,sysfs主要負責文件節點相關邏輯。
最重要的是power_supply_changed,在驅動中檢測到硬件狀態發生變化,會通過該函數調度起psy中的changed_work。該工作隊列負責發送notifier(內核內不同模塊之間)和通過uevent進行change上報。
__power_supply_register負責注冊一個psy設備:
在sysfs中有個power_supply_uevent,該函數在psy class初始化時注冊為設備節點的dev_uevent,在每一個psy目錄下都有一個uevent節點,讀取該節點即調用power_supply_uevent函數。該函數遍歷當前設備下的所有屬性並將結果保存在kobj_uevent_env中,結果以鍵值對的形式進行保存。
- 調用流程
psy子系統主要調度的邏輯都在power_supply_changed_work中。跟蹤這一調用流程可以在驅動中實現的get_property函數增加調用棧打印:
可以看到,在kobject_uevent_env函數中調用對應kset的uevent,會去遍歷每一個屬性節點(dev_uevent),之后,會通過netlink_broadcast函數進行廣播(使用netlink機制),其中廣播的字符串保存在sk_buff->data中,這一字符串以“action_string@devpath”進行拼接,其中action為kobject_action,而devpath則為該psy設備的設備路徑。值得注意的是,使用uevent-netlink機制傳遞的字符串並不會包含psy屬性節點的kobject_uevent_env鍵值對狀態。
四、healthd簡介
由於uevent機制僅將一個簡單的字符串傳遞給了用戶空間,而安卓系統建立在kernel之上,需要思考如何將設備屬性的變化值及時更新到用戶空間,於是就有了healthd服務,healthd目前已經更新到了2.1版本,其主要工作通過epoll_wait來監聽kernel中的uevent事件。具體的函數調用流程圖如下:
相關的代碼路徑主要是在:
/hardware/interfaces/health/;
/system/core/healthd/;
從service.cpp開始理一下調用流程,可以整理出上述的調用流程,黑色線條為初始化流程,紅色線條為當psy-uevent上報后觸發epoll之后的調用流程。與底層節點交互的邏輯都在BatteryMonitor中,在初始化過程中會初始化healthd_config結構體,用於保存psy屬性節點的路徑。
在監聽循環MainLoop函數中,一個while(1)循環,調用epoll_wait()函數來監聽uevent,收到事件之后會調用初始化時注冊好的func(UeventEvent),該函數會通過uevent_kernel_multicast_recv接口去讀取netlink發送的sk_buff->data,通過查找其中的字符串來判斷事件是否為psy子系統發送的,如果不是的話,不會進行處理。進一步的處理流程主要是調用到BatteryMonitor中的updateValues,在該函數中會遍歷讀取psy屬性節點,存儲在HealthInfo結構體中,之后通過BinderHealth中注冊好的回調函數IHealthInfoCallback通知BatterySerice,具體的通知函數為BinderHealth:OnHealthInfoChanged。
Healthd是一個根植於powersupply子系統,並采用了epoll監聽底層節點的uevent事件,之后輪詢底層屬性節點的守護進程。在安卓R版本中,Healthd相關代碼重構為libhealthloop和libhealth2impl,但為了保證向后兼容,可以看到在ScheduleBatteryUpdate()函數中調用了兩次updateValues,這樣會遍歷兩次底層節點造成了冗余。另外在psy-uevent機制中,也有一次屬性節點的遍歷,一共三次遍歷,這就要求底層驅動在更新屬性值時,不能加入耗時的IO操作,否則會影響系統性能。
五、總結
power supply架構的精髓是極大化的發揮了uevent和sysfs的作用,簡單高效地抽象出了與硬件無關的關鍵信息,通過notify機制使得其他內核模塊可以及時獲取相關事件;Healthd通過epoll監聽psy創建的節點uevent,之后再去遍歷讀取結果,這樣是為了避免與kernel的耦合。psy和Healthd比較適合新手學習,能提供一個由外向內的視角去解讀kernel,也能加深對設備驅動模型的理解。熟悉之后可以進行相關的邏輯擴展,也可以進一步學習usb模塊與psy子系統的關系,也可以進一步探究涉及的notify、netlink、epoll等機制。