前言
個人學習過程總結
相關參考資料:
深入剖析 Android 系統_楊長剛/第 9 章 RIL
安卓 4.1 MTK 源碼
整體框架
Rild 框架
RIL(Radio Interface Layer) 是上層程序使用地射頻功能的接口層,它更像一個支持數據格式轉換的
通道。
上層程序的 API 調用最終轉換爲底層射頻能識別出的命令字符串,底層上報的字符串信息被翻譯解釋
後,又能被上層的程序識別,這就是 RIL 層的功能。
RIL 層工作流程:
Phone 進程 【Java】
RILJ
----- socket -------
Rild 【C/C++】
----- serial ------- AT 命令
Modem 【硬件】
主要進程關係:
【rild 進程】
1. 創建 eventLoop 線程
【eventLoop 線程】: 套接字監聽執行回調,執行提交來的定時任務函數
2. 創建 mainLoop 的工作線程
/////////////////////////////////////////////////////////////////////////
// 以下兩個線程都是 reference-ril.c 硬件動態庫中創建的
【mainLoop 線程】:打開 AT 串口,創建一個定時任務給 eventLoop 初始化 Modem
創建工作線程 readerLoop:
【readerLoop 線程】:主要任務就是從 AT 的串口設備讀取數據,並解析處理
相關文件關係:
RILConstants.java // 保存了 Java 層的請求號等信息,要求與【硬件庫的 ril.h 文件使用的請求號保持一致】
ril_commands.h // 定義了請求號與對應分發命令與回覆命令的對應關係,舉例:
// {RIL_REQUEST_DIAL, dispatchDial, responseVoid},
// 請求號 10 對應的分發函數爲 dispatchDial, 回覆函數爲 responseVoid
// dispatchDial()/responseVoid() 實現在 ril.cpp 中
//
// 【RIL_REQUEST_DIAL】: 定義在 Ril.h 中,被 Java 層與 Reference-ril.c 共用,以便請求保存一致
// 【dispatchDial()】: 調用 reference-ril.c 導出的 onRequest() 函數請求 Modem 服務
// 【responseVoid()】: 用於在命令成功返回時,將 AT 回覆的數據寫入 Parcel
ril_unsol_commands.h // 定義了 unsolicited Response 的請求號與命令對應關係,舉例:
// {RIL_UNSOL_CALL_RING, responseCallRing, WAKE_PARTIAL},
// 【RIL_UNSOL_CALL_RING】:請求號,同樣定義在 ril.h 中
// 【responseCallRing】:定義在 ril.cpp 中,將 AT 回覆的數據寫入 Parcel
// 【WAKE_PARTIAL】:回覆時獲得一個 wake lokc 休眠鎖
/////////////////////////////////////////////////////////////////////////
// 上面兩個頭文件都定義在 ril.cpp 中引用
// 【Index == requestNumber】
// static CommandInfo s_commands[] = {
// #include "ril_commands.h"
// };
//
// static UnsolResponseInfo s_unsolResponses[] = {
// #include "ril_unsol_commands.h"
// };
/////////////////////////////////////////////////////////////////////////
ril.cpp // rild 中調用相關函數實現,如 RIL_startEventLoop(), RIL_register 等等函數,是 rild 核心實現
// 這裏定義了會被 reference-ril 廠家庫調用的回調函數
// static struct RIL_Env s_rilEnv =
// {
// RIL_onRequestComplete, // 動態庫完成一個請求後,通過這個函數通知處理結構,其中第一個參數標明是哪個請求的處理結果
// RIL_onUnsolicitedResponse, // 動態庫用於進行 unsolicited Response 通知的函數: 即 BP 主動上報的事件的處理
// RIL_requestTimedCallback // 向 Rild 提交一個超時任務, 即在 eventloop 指定時間後執行的函數
// };
reference-ril.c // modem 相關硬件操作的庫函數,用於與 modem 通信
// 動態庫開放出來的外部操作函數接口,供外部函數請求調用
// static const RIL_RadioFunctions s_callbacks = {
// RIL_VERSION,
// onRequest,
// currentState,
// onSupports,
// onCancel,
// getVersion
// };
rild.c // 是 rild 模塊的相關邏輯程序,引用上面兩個庫函數,控制先做什麼,後做什麼
Rild 與上層握手過程:
rild 監聽套接字 /dev/socket/rild 等待連接
-> 上層發起連接
-> eventloop 進程
-> listenCallback()
-> 檢查連接權限
1. 正確
新套接字 = accept()
創建一個 processWakeupCallback() 函數處理新套接字請求
2. 不正確
重新提交 listenCallback() 函數監聽 /dev/socket/rild
Rild 與上層通信數據流程:
上層通過套接字發命令
----------- eventloop 線程 ----------------------------------------
-> eventloop 線程
-> processCommandsCallback()
-> 調用 ril_commands.h 定義的分發函數發送命令給 AT
-> 等待 AT 設備響應
------------ reader 線程 ----------------------------------
reader 線程
-> AT 串口有數據發送上來
-> processLine
喚醒 eventloop 線程
----------- eventloop 線程 -------------------------------------
-> eventloop 繼續執行
-> sendResponse
-> 調用 ril_commands.h 定義的回覆函數封裝一些消息到 Parcel 對象中
-> 通過套接字將執行結果返回給上層
上層發來的命令格式:
////////////////////////////////////////////////////////////////
// 【上層發來的請求的格式】
// typedef struct RequestInfo {
// int32_t token; //this is not RIL_Token
// CommandInfo *pCI;
// typedef struct {
// int requestNumber; // 請求號
// void (*dispatchFunction) (Parcel &p, struct RequestInfo *pRI);
// int(*responseFunction) (Parcel &p, void *response, size_t responselen);
// } CommandInfo;
// struct RequestInfo *p_next;
// char cancelled;
// char local; // responses to local commands do not go back to command process
// } RequestInfo;
Rild 初始化流程總結:
Rild.c (hardware\ril\rild)
// 1. 讀取 system.prop 中的系統屬性:
// rild.libpath=/system/lib/libreference-ril.so // 與 modem 通信的具體廠家實現的動態庫
// rild.libargs=-d /dev/ttyS0 // 與 modem 通信使用的串口
// 通過 dlopen 系統加載廠家實現的動態庫
//
// 2. 啓動 EventLoop 線程, 在這裏進行事件處理,監聽管道套接字,收到數據後調用回調處理
// 同時也可以註冊定時函數讓他到期時調用回調處理
// -------------------
// eventLoop 線程
// 初始化 readFds, 看來 Ril 會使用 select 來做多路 IO 複用
// 創建匿名管道 【用於被喚醒使用】
// 進入事件等待循環中,等待外界觸發事件並做出對應的處理
// 1. 定時任務,由 ril_timer_add() 函數添加,到期執行
// 2. 非定時任務,這些任務的 FD 加入到 select 中監控,有數據可讀時喚醒執行
// 3. 遍歷 pending_list,執行任務函數
//
// 3. 得到 RefRil 庫中的 RIL_Init 函數的地址
// 【注意】此函數是在硬件動態庫中實現的,裏面所有函數都是動態庫內部的,除了傳入的接口
// 調用 RefRil 庫輸出的 RIL_Init 函數,注意此函數傳輸的第一個參數和它的返回值
// 參考動態庫的源碼位於: Reference-ril.c
// 此函數主要完成工作爲:
// 1. 創建一個 mainLoop 工作線程,它的任務是初始化 AT 模塊,並監控 AT 模塊,一旦
// AT 模塊被關閉,則會重新初始化 AT 模塊
// 2. AT 模塊內部會創建一個工作線程 readerLoop,該線程的作用是從串口設備中讀取信息
// ,也就是直接和 BP 打交道
// 3. mainLoop 通過向 Rild 提交超時任務,完成了對 Modem 的初始化工作
//
//
// 4. 註冊上面 rilInit 函數的返回值(一個 RIL_RadioFunctions 類型的結構體)到 Rild 中,用於 AP 發送命令給 BP
// // RIL_register() 將創建兩個監聽端 socket:
// // 1. rild : 用於與 Java 層的應用通信
// // 2. rild-debug: 用來接收測試程序的命令
// RIL_register(funcs);
//
// 5. 主線程 sleep, 具體工作交給工作線程完成
// while(1) {
// // sleep(UINT32_MAX) seems to return immediately on bionic
// sleep(0x00ffffff);
// }
Rilj 框架
與 rild 守護進程交互的 Java 部分是 com.android.internal.telephony.RIL 類(簡稱爲 RILJ),
它是通過 UNIX 套接字(/dev/socket/rild)進行通信。進程 com.android.phone 通過使用 Teleohony
框架來使用系統的各種 Teleohony 功能。
具體框架類似:
------ phone 進程 -------- 【Java】
android Telephony
framework
-------------
RILJ
--------socket ----------
/\
||
\/
------- rild 模塊 ----- 【C/C++】
--------- AT 串口 -------
硬件設備
RILJ 中各類功能:
// RILJ 負責與 rild 守護進程交互,提供 API 用於執行 RIL 請求,供 Telephony Framework 調用
// 它還提供底層對 RIL 請求的響應回覆的處理,它將以消息的形式發送給調用者。
RILJ {
RILSender: 是 Handler 的子類,將在發送線程中處理髮送消息等事件
RILReceiver: 實現了 Runnable 接口類的 Run 接口函數,作爲線程的執行體運行在 RILJ 創建
接收線程(見 RIL 類的構造函數)中,負責消息的接收
}
相關類介紹:
RILConstants:定義了 RIL 的各種請求號和 unsolicited 號,它們必須與 ril.h 中定義保持一致,
用於標識請求號和 unsolicited 消息號,這些標誌用在硬件動態庫與 rild 中使用。
CommandsInterface: 接口類,定義了 RILJ 和 rild 交互的接口
BaseCommands implements CommandsInterface: 實現了 CommandsInterface 的部分接口,用於通知手機各種內部
狀態的變化,它裏面包含了很多註冊者 Registrant 和註冊者列表 RegistrantList, 它們
代表着對某些事件感興趣希望接收事件變化通知的接收者。
當對某種狀態變化感興趣時,就可以調用 registerXXX 函數將自己註冊爲一個對某種
狀態感 興趣的通知接收者。
註冊時,在 BaseCommands 內部創建一個對應的 registrant 將 registrant 添加到
列表 registrantList 中。
調用 registerXXX() 時,調用者將 message 和 handler 傳遞給 Registrant 對象,
當有狀態變化(processUnsolicited 處理主動上報的信息則往往意味着狀態的改變)時,則
通過 Registrant.NotifyXXX() 調用 hander 將消息發到消息隊列上,Handler 所在線程將
處理這些消息,這也保證了儘可能的實時通知調用者的目的。
RILRequest: 代表着一個即將發送出去的 RIL 請求,它裏面包含了 Request 請求號、序列號(自 0 開始累加)和保存
請求結果的 Message,Java 部分的 Request 請求號就是上述的 RILConstants 中的常量(與 C/C++
部分的請求號保持一致)。當需要執行某種 RIL 請求時,則需創建一個新的 RILRequest 使用
RILRequest 的 obtain 函數
RILJ 類的初始化:
///////////////////////////////////////////////////////////////////////////////////////////
// 1. 外部創建一個對象,使用時應該是直接將此類當作一個處理線程運行,用來與 rild 通信發命令
// 是一個內部類
RIL::RILSender extends Handler implements Runnable // 繼承 Runnable 多線程類,
// Handler: 給消息隊列發消息並調用回調處理
{
// 構造函數傳入的參數是一個消息隊列
// 表示與一個消息隊列綁定,給此消息隊列發消息,
// 當目標進程處理消息隊列中此條消息時,會調用發送時傳入的回調處理,即本類的 handleMessage() 處理
RILSender(Looper looper)
{
super(looper);
}
// 外部調用 start 時將此函數當線程啓動
run(){}
// 當在構造函數中綁定的消息隊列有消息時,調用此函數進行解析處理
handleMessage(Message msg)
{
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// 2. 創建一個接收,接收 rild 回覆的消息
// 內部類
RIL::RILReceiver implements Runnable // 繼承 Runnable ,多線程類
{
// 外部 start 時將此函數當線程啓動
run() {
for (;;) {
// 連接上 rild 內部的套接字 /dev/socket/rild
s = new LocalSocket();
l = new LocalSocketAddress(socketRil, LocalSocketAddress.Namespace.RESERVED);
s.connect(l);
InputStream is = mSocket.getInputStream();
for (;;) {
// 從套接字讀數據
length = readRilMessage(is, buffer);
// 沒數據退出循環
if (length < 0) {
// End-of-stream reached
break;
}
// 獲得套接字傳入的數據
p = Parcel.obtain();
p.unmarshall(buffer, 0, length);
p.setDataPosition(0);
///////////////////////////////////////
// 進行數據處理
processResponse(p);
}
// 從套接讀數據返回卻沒讀到數據?復位 AT 串口
setRadioState (RadioState.RADIO_UNAVAILABLE);
mSocket.close();
RILRequest.resetSerial(mySimId);
}
}
}
【發送消息的流程:】
//////////////////////////////////////////////////////////////////////////////////////////
// 調用相應的 RIL 請求對應的成員函數發給命令,比如: 向 SIM/USIM 卡提供 PIN 碼對應的函數
// 所有的接口位於 CommandsInterface 接口類中定義中:
RIL::supplyIccPinForApp(String pin, String aid, Message result)
{
//Note: This RIL request has not been renamed to ICC,
// but this request is also valid for SIM and RUIM
///////////////////////////////////////////////////////////////////////////////////
// 1. 獲得一個請求對象,他保存在一個緩衝池中,當命令回覆時需要釋放
RILRequest rr = RILRequest.obtain(RIL_REQUEST_ENTER_SIM_PIN, result, mySimId);
// 獲得一個 RILRequest 對象
RILRequest::obtain()
// 從內部維護的 RIL_request 池 sPool中取下一個 request, 得到一個 RILRequest 實例
// 它裏面的請求號和請求結構消息來自傳遞的實參, 傳遞的參數:
// 1. RIL 請求號
// 2. 請求結果保存位置
// 3. 返回結果的處理者(Message 中的 Handler),【當有結果時通過此給目標消息隊列發消息】
if (sPool != null) {
rr = sPool;
sPool = rr.mNext;
rr.mNext = null;
sPoolSize--;
}
if (rr == null) {
rr = new RILRequest();
}
//////////////////////////////////////////////////////////////////////
// 待寫入 socket 中的字符串個數
rr.mParcel.writeInt(2);
// 寫入 pin 碼字符串
rr.mParcel.writeString(pin);
// 寫入其他附屬信息
rr.mParcel.writeString(aid);
////////////////////////////////////////////////////////////////////
// 發送請求,由 sender 線程完成向 socket 寫入數據
send(rr)
send(RILRequest rr) {
Message msg;
if (mSocket == null) {
rr.onError(RADIO_NOT_AVAILABLE, null);
rr.release();
return;
}
// 獲得一個消息對象,
// 消息類型爲:EVENT_SEND,
// 消息數據爲: 要寫入套接字發給底層 rild 的數據
msg = mSender.obtainMessage(EVENT_SEND, rr);
// 獲得休眠鎖?
acquireWakeLock();
//////////////////////////////////////////////////////////////////////////////
// 將消息添加到目標消息隊列中,如果有需要,喚醒目標消息隊列所在線程
msg.sendToTarget();
Messgae::sendToTarget()
target.sendMessage(this);
//////////////////////////////
// 調用 Handler 類發送消息
Handler::sendMessage()
sendMessageDelayed(msg, 0);
sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
enqueueMessage(queue, msg, uptimeMillis);
queue.enqueueMessage(msg, uptimeMillis);
/////////////////////////////////////
// 調用 MessageQueue 類發送消息
MessageQueue::enqueueMessage()
// 這裏僅僅當消息插入目標消息隊列,然後判斷是否需要喚醒目標線程
// 如果需要,則調用 nativeWake() 喚醒目標線程,【流程見情景分析】
//【注意】目標消息隊列是初始化 Hander 對象時指定的。
}
}
//////////////////////////////////////////////////////////////////
// RIL 初始化時構造的 RILSender 線程接收到此上面要發送的數據的消息
// 調用其 handlerMessage() 函數處理消息
RILSender::handleMessage(Message msg)
{
// 獲得發來的消息類型
RILRequest rr = (RILRequest)(msg.obj);
// 根據不同的消息類型處理
switch (msg.what) {
///////////////////////////////
// 通過套接字發送命令給 rild
case EVENT_SEND:
// parcel 中是待寫入數據的緩衝區,經過 marshall 賦給 data
data = rr.mParcel.marshall();
// 往 socket 寫入數據長度
s.getOutputStream().write(dataLength);
////////////////////////////////////////
// 寫入數據給 rild 【底層 rild 怎麼接收處理數據,見 rild 分析】
s.getOutputStream().write(data);
}
}
【接收消息處理流程:】
以 RILReceiver 初始化已經有介紹怎麼收到數據,這裏只介紹消息的處理:
// RILReceiver 接收到消息後,最終調用 processResponse() 函數處理
RIL::processResponse(Parcel p)
{
////////////////////////////////////////
// 1. 處理主動上報的消息
if (type == RESPONSE_UNSOLICITED)
processUnsolicited (p);
// 讀取回復請求號,位於 ril_unsol_commands.h 中的
// 獲得所有回覆請求號, 應該與 ril_unsol_commands.h 文件中保持一致?
// cat libs/telephony/ril_unsol_commands.h \
// | egrep "^ *{RIL_" \
// | sed -re 's/\{([^,]+),[^,]+,([^}]+).+/case \1: \2(rr, p); break;/'
//
response = p.readInt();
//////////////////////////////////////////////////////////////
// 1. 根據不同的回覆請求,來調用不同處理函數, 以獲得上報的數據
switch(response){
case RIL_UNSOL_ON_USSD: ret = responseStrings(p); break;
// 僅僅是從回覆的數據中提取出要返回的字符串
responseStrings(Parcel p) {
int num;
String response[];
response = p.readStringArray();
return response;
}
。。。
}
//////////////////////////////////////////////////////////////
// 2. 對相應結果作進一步處理,如通知感興趣的註冊者
switch(response){
case RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED:
// 從回覆的消息中獲得狀態
RadioState newState = getRadioStateFromInt(p.readInt());
// 更新狀態
switchToRadioState(newState);
}
/////////////////////////////////////////
// 2. 處理 RIL 請求的執行回覆結果
else if (type == RESPONSE_SOLICITED)
processSolicited (p);
// 讀取序列號
serial = p.readInt();
// 讀取執行成功與否標誌
error = p.readInt();
// 在請求列表查找並摘下 RILRequest 對應項,表示命令發送完成
// 【這表明在發送時分配的 RILRequest 對就項應該會添加此鏈表中管理,but 沒找到...】
rr = findAndRemoveRequestFromList(serial);
for (int i = 0, s = mRequestList.size() ; i < s ; i++)
RILRequest rr = mRequestList.get(i);
if (rr.mSerial == serial)
mRequestList.remove(i);
///////////////////////////////////////////////////////////////////////////////////
// 1. 命令發送成功,且有了返回的結果
if (error == 0 || p.dataAvail() > 0)
// 根據回覆的請求號,調用不同的函數處理上傳的數據,得到要返回的數據
try{
switch (rr.mRequest){
case RIL_REQUEST_GET_SIM_STATUS: ret = responseIccCardStatus(p); break;
// 表示要返回一個 IccCardStatus 的對象,此函數就是根據
// 上傳來的數據構造 IccCardStatus 對象
RIL::responseIccCardStatus(Parcel p)
// 創建一個 IccCardStatus 對象
IccCardStatus cardStatus = new IccCardStatus();
// 處理上傳數據,填充 IccCardStatus 對應項
cardStatus.setCardState(p.readInt());
cardStatus.setUniversalPinState(p.readInt());
。。。
// 返回 IccCardStatus 對象
return cardStatus;
// 接收到不能識別的命令,拋出異常
default:
throw new RuntimeException("Unrecognized solicited response: " + rr.mRequest);
}
}catch (Throwable tr) {
// 捕捉異常
// AsyncResult.forMessage() 將三個實參封裝到一個 AsyncResult 實例中
// 然後將該實例賦值給 Message.obj 成員
AsyncResult.forMessage(rr.mResult, null, tr);
//////////////////////////////////////////////////////////////////////
// 調用 Message.sendToTarget() 將消息發送到初始化時調用的 Handler
// 隊列中,由相應的線程去處理,這裏是將結果送給調用者處理
// 這個 Message 對象是在調用 RILRequest::obtain() 設置的,見上調用處
rr.mResult.sendToTarget();
// 釋放 RILRequest 回池中
rr.release();
return;
}
////////////////////////////////////////////////////////////////////////
// 2. 有執行結果,但出錯了,發消息給調用者處理
if (error != 0)
//////////////////////////////////////////////////////////
// 調用 RILRequest 的錯誤處理函數,發送 msg 消息給調用者
rr.onError(error, ret);
RILRequest::onError(int error, Object ret)
ex = CommandException.fromRilErrno(error);
if (mResult != null)
AsyncResult.forMessage(mResult, ret, ex);
//////////////////////////////////////////////////////////
// mResult 是在 RILRequest::obtain() 時設置的 Message 對應
// 所以是發給調用者處理
mResult.sendToTarget();
// 釋放 RILRequest 回池中
rr.release();
return;
///////////////////////////////////////////////////////////////////////////
// 3. 有執行結果,發消息給調用處理
if (rr.mResult != null) {
AsyncResult.forMessage(rr.mResult, ret, null);
rr.mResult.sendToTarget();
}
// 釋放 RILRequest 回池中
rr.release();
}