RIL.java裏request流程

Android GSM驅動模塊(rild)詳細分析(二)request流程
熊貓哥哥 發表於IT168和Opendroid 轉載請註明

1. 多路複用I/O機制的運轉
上文說到request是接收,是通過ril_event_loop中的多路複用I/O,也對初始化做了分析.現在我們來仔細看看這個機制如何運轉.
ril_event_set負責配置一個event,主要有兩種event:
ril_event_add添加使用多路I/O的event,它負責將其掛到隊列,同時將event的通道句柄fd加入到watch_table,然後通過select等待.
ril_timer_add添加timer event,它將其掛在隊列,同時重新計算最短超時時間.
無論哪種add,最後都會調用triggerEvLoop來刷新隊列,更新超時值或等待對象.

刷新之後, ril_event_loop從阻塞的位置,select返回,只有兩種可能,一是超時,二是等待到了某I/O操作.
超時的處理在processTimeouts中,摘下超時的event,加入pending_list.
檢查有I/O操作的通道的處理在processReadReadies中,將超時的event加入pending_list.
最後在firePending中,檢索pending_list的event並依次執行event->func.
這些操作完之後,計算新超時時間,並重新select阻塞於多路I/O.

前面的初始化流程已分析得知,初始化完成以後,隊列上掛了3個event對象,分別是:
s_listen_event: 名爲rild的socket,主要requeset & response通道
s_debug_event: 名爲rild-debug的socket,調試用requeset & response通道(流程與s_listen_event基本相同,後面僅分析s_listen_event)
s_wakeupfd_event: 無名管道,用於隊列主動喚醒(前面提到的隊列刷新,就用它來實現,請參考使用它的相關地方)

2. request的傳入和dispatch
明白了event隊列的基本運行流程,我們可以來看看request是怎麼傳入和dispatch的了.
上 層的部分,核心代碼在frameworks/base/telephony/java/com/android/internal/telephony /gsm/RIL.java,這是android java框架處理radio(gsm)的核心組件.本文因爲主要關注rild,也就是驅動部分,所以這裏只作簡單介紹.
我們看一個具體的例子,RIL.java中的dial函數:
    public void
    dial (String address, int clirMode, Message result)
    {
        RILRequest rr = RILRequest.obtain(RIL_REQUEST_DIAL, result);

        rr.mp.writeString(address);
        rr.mp.writeInt(clirMode);

        if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));

        send(rr);
    }
rr是以RIL_REQUEST_DIAL爲request號而申請的一個RILRequest對象.這個request號在java框架和rild庫中共享(參考RILConstants.java中這些值的由來:))
RILRequest初始化的時候,會連接名爲rild的socket(也就是rild中s_listen_event綁定的socket),初始化數據傳輸的通道.
rr.mp 是Parcel對象,Parcel是一套簡單的序列化協議,用於將對象(或對象的成員)序列化成字節流,以供傳遞參數之用.這裏可以看到String address和int clirMode都是將依次序列化的成員.在這之前,rr初始化的時候,request號跟request的序列號(自動生成的遞增數),已經成爲頭兩個 將被序列化的成員.這爲後面的request解析打下了基礎.
接下來是send到handleMessage的流程,send將rr直接傳遞給另一個線程的handleMessage,handleMessage執行data = rr.mp.marshall()執行序列化操作, 並將data字節流寫入到rild socket.

接下來回到我們的rild,select發現rild socket有了請求鏈接的信號,導致s_listen_event被掛入pending_list,執行event->func,即
static void listenCallback (int fd, short flags, void *param);
接下來,s_fdCommand = accept(s_fdListen, (sockaddr *) &peeraddr, &socklen),獲取傳入的socket描述符,也就是上層的java RIL傳入的連接.
然 後,通過record_stream_new建立起一個record_stream, 將其與s_fdCommand綁定, 這裏我們不關注record_stream 的具體流程, 我們來關注command event的回調, processCommandsCallback函數, 從前面的event機制分析, 一旦s_fdCommand上有數據, 此回調函數就會被調用. (略過onNewCommandConnect的分析)
processCommandsCallback通過 record_stream_get_next阻塞讀取s_fdCommand上發來的 數據, 直到收到一完整的request(request包的完整性由record_stream的機制保證), 然後將其送達processCommandBuffer.
進入processCommandBuffer以後,我們就正式進入了命令的解析部分. 每個命令將以RequestInfo的形式存在.
typedef struct RequestInfo {
int32_t token; //this is not RIL_Token
CommandInfo *pCI;
struct RequestInfo *p_next;
char cancelled;
char local; // responses to local commands do not go back to command process
} RequestInfo;
這 裏的pRI就是一個RequestInfo結構指針, 從socket過來的數據流, 前面提到是Parcel處理過的序列化字節流, 這裏會通過反序列化的方法提取出來. 最前面的是request號, 以及token域(request的遞增序列號). 我們更關注這個request號, 前面提到, 上層和rild之間, 這個號是統一的. 它的定義是一個包含ril_commands.h的枚舉, 在ril.cpp中
static CommandInfo s_commands[] = {
#include "ril_commands.h"
};
pRI直接訪問這個數組, 來獲取自己的pCI.
這是一個CommandInfo結構:
typedef struct {
int requestNumber;
void (*dispatchFunction) (Parcel &p, struct RequestInfo *pRI);
int(*responseFunction) (Parcel &p, void *response, size_t responselen);
} CommandInfo;
基本解析到這裏就完成了, 接下來, pRI被掛入pending的request隊列, 執行具體的pCI->dispatchFunction, 進行詳細解析.

3. request的詳細解析
對dial而言, CommandInfo結構是這樣初始化的:
{RIL_REQUEST_DIAL, dispatchDial, responseVoid},
這 裏執行dispatchFunction, 也就是dispatchDial這一函數.我們可以看到其實有很多種類的dispatch function, 比如dispatchVoid, dispatchStrings, dispatchSIM_IO等等, 這些函數的區別, 在於Parcel傳入的參數形式,Void就是不帶參數的,Strings是以string[]做參數,又如Dial等,有自己的參數解析方式,以此類 推.
request號和參數現在都有了,那麼可以進行具體的request函數調用了.
s_callbacks.onRequest(pRI->pCI->requestNumber, xxx, len, pRI)完成這一操作.
s_callbacks 是上篇文章中提到的獲取自libreference-ril的RIL_RadioFunctions結構指針,request請求在這裏轉入底層的 libreference-ril處理,handler是reference-ril.c中的onRequest.
onRequest進行一個簡單的switch分發,我們依然來看RIL_REQUEST_DIAL
流程是 onRequest-->requestDial-->at_send_command-->at_send_command_full-->at_send_command_full_nolock-->writeline
requestDial中將命令和參數轉換成對應的AT命令,調用公共send command接口at_send_command.
除 了這個接口之外,還有 at_send_command_singleline,at_send_command_sms,at_send_command_multiline 等,這是根據at返回值,以及發命令流程的類型來區別的.比如at+csq這類,需要at_send_command_singleline,而發送短 信,因爲有prompt提示符">",傳裸數據,結束符等一系列操作,需要專門用at_send_command_sms來實現.
然後執行at_send_command_full,前面幾個接口都會最終到這裏,再通過一個互斥的at_send_command_full_nolock調用,然後完成最終的寫出操作,在writeline中,寫出到初始化時打開的設備中.
writeline返回之後,還有一些操作,如保存type等信息,供response回來時候使用,以及一些超時處理. 不再詳述.

到這裏,request的詳細流程,就分析完畢了.

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