基於Android6.0的RIL底層模塊分析

看代碼的時候不要看到細節裏面,先構建模塊的運行框架,後續有需要再深入細節。必要的時候需要拿一個本子將主要流程畫出來或者寫出來。

我們先看看,從系統剛開機是如何啓動RIL功能的。首先先查看一下init.rc(這個文件包含一些初始化的服務或者功能,在開機階段佔有很重要的地位)。

service ril-daemon /system/bin/rild
    class main
    socket rild stream 660 root radio
    socket sap_uim_socket1 stream 660 bluetooth bluetooth
    socket rild-debug stream 660 radio system
    user root
    group radio cache inet misc audio log qcom_diag

可以看到啓動了三個socket,其中sap_uim_socket1和rild-debug這兩個我們不需要關注,sap是跟藍牙接聽有關的,大家如果有興趣可以另外在瞭解。debug從命名上看應該是跟調試功能有關的,我們現在也不關注,所以我們只看:

socket rild stream 660 root radio

Android系統解析這句的時候,會創建一個名爲rild的socket,這個socket就是上層跟底層進行通信的socket了。可以看看RIL.java裏面的RILReceiver和RILSender類,裏面都是針對這個socket進行的讀寫操作,可見該socket確實就是上下層進行通信的途徑了。

而這個service的對應的可執行文件地址爲:

/system/bin/rild 對應的邏輯代碼爲:hardware\ril\rild\Rild.c

去掉不相干的代碼,只研究最核心的代碼如下:


int main(int argc, char **argv) {
    const RIL_RadioFunctions *(*rilInit)(const struct RIL_Env *, int, char **);
    const RIL_RadioFunctions *funcs;
    dlHandle = dlopen(rilLibPath, RTLD_NOW);
    RIL_startEventLoop();// 1

    rilInit = (const RIL_RadioFunctions *(*)(const struct RIL_Env *, int, char **))dlsym(dlHandle, "RIL_Init");

funcs = rilInit(&s_rilEnv, argc, rilArgv);// 2

    RIL_register(funcs);// 3
}

這個main函數裏面主要就是我上面標明的1/2/3三個函數調用,而這三個函數調用就建立了rild進程的整個事件循環機制,與底層modem通信機制,與上層java層的framework裏面的RIL.java的通信機制。其實上層的撥打電話,發短信什麼的都是通過rild來實現。rild類似一箇中間層,將java層的請求(打電話,發短信等)轉發到modem端,將底層modem的來電或者來短信通知到上層(就是程序中經常看到的Unsolicited,意爲不請自來。也就是來電,來短信之類的事件)。

在程序裏面先是載入了libreference-ril.so庫文件,這個庫的實現是在“hardware\ril\reference-ril\Reference-ril.c”裏面,所以第二步裏面執行的rilInit函數可以在這個源碼文件裏面去找。後續再詳細分析這個初始化函數。

  1. 列表內容

RIL_startEventLoop-啓動一個線程,並將事件監聽機制建立並啓動

我們先看看RIL_startEventLoop的源碼:


//Ril.cpp
extern "C" void RIL_startEventLoop(void) {
    int result = pthread_create(&s_tid_dispatch, &attr, eventLoop, NULL);
}

static void *eventLoop(void *param) {
    int filedes[2];
    ril_event_init();
    ret = pipe(filedes);
    s_fdWakeupRead = filedes[0];
    s_fdWakeupWrite = filedes[1];
    ril_event_set (&s_wakeupfd_event, s_fdWakeupRead, true, processWakeupCallback, NULL);
    rilEventAddWakeup (&s_wakeupfd_event);
    // Only returns on error
    ril_event_loop();
    kill(0, SIGKILL);
    return NULL;
}

總結一下,實際上另外啓動了一個線程s_tid_dispatch(從名字dispatch上看,應該是一個分發事件的線程),這個線程的函數主體是eventLoop。在eventLoop裏面首先調用的ril_event_init函數,這個函數裏面對fd_set readFds,timer_list,pending_list,watch_table進行了初始化(或者說清零)。從這個重要的初始化函數,大家應該可以大概猜到,這個線程主要是操作這幾個數據結構的,至於後續如何操作可以再深入瞭解,但是第一遍我們只需要知道大概流程。裏面用到了fd_set,大家應該去搜索一下Linux的select機制和FD_SET這兩個關鍵字。這裏簡單描述一下,Linux會對fd_set集合裏面的一組socket或者文件句柄進行監聽,如果有部分socket有數據變化,那麼就會返回這部分socket的數組。這樣就可以不用一直輪詢所有的socket或者文件句柄,節省了大量的cpu資源。

除此之外還建立了一個管道,並且將這個管道的讀端綁定到了s_wakedupfd_event上面。注意看這個event設置二人組,在程序裏面很多地方都出現過:

ril_event_set (&s_wakeupfd_event, s_fdWakeupRead, true,
                processWakeupCallback, NULL);
rilEventAddWakeup (&s_wakeupfd_event);

先用一些參數設置一個event,然後將這個event添加到watch_table和readFds裏面,我們上面在ril_event_init函數裏面也提到過過這兩個變量。其實翻譯成人類理解的語言就是,如果在s_fdWakeupRead這個fd上有數據變化,那麼就發送給processWakeupCallback進行處理。將這兩個值包裝成一個ril_event,並且另起一個線程建立起eventloop循環來處理ril_event事件。

最後調用ril_event_loop,核心代碼如下:

//Ril_event.cpp
void ril_event_loop()
{
    for (;;) {
        // make local copy of read fd_set
        memcpy(&rfds, &readFds, sizeof(fd_set));
        n = select(nfds, &rfds, NULL, NULL, ptv);
        // Check for timeouts
        processTimeouts();
        // Check for read-ready
        processReadReadies(&rfds, n);
        // Fire away
        firePending();
    }
}

基本上啓動了一個無限循環,不停的查看readFds集合裏面是否有一些文件句柄fd有數據變化,並將這些有變化的句柄保存到nfds裏面。processTimeouts進行超時處理,processReadReadies將nfds裏面的ril_event事件取出,進行一定判斷,並將其放到pending_list裏面(注意這個列表我們在ril_event_init裏面也提到過)。firePending,看名字就知道是處理Pending列表了,它的代碼裏面確實對ril_event事件進行了處理。

static void firePending()
{
    struct ril_event * ev = pending_list.next;
    while (ev != &pending_list) {
        struct ril_event * next = ev->next;
        removeFromList(ev);
        ev->func(ev->fd, 0, ev->param);
        ev = next;
    }
}

基本上是一個遍歷鏈表,並且處理每個節點的過程。注意,ril_event結構裏面本身就包含了一個func函數指針,這個函數的作用就是在這個時候處理自己的。所以其實這個事件本身就包含了如何處理自身數據的功能,這樣也算是一種程度上的解耦吧。類似一種自治的結構,不需要外部模塊提供處理函數,同時提高了封裝性。

再從前面的代碼:

ril_event_set (&s_wakeupfd_event, s_fdWakeupRead, true,
                processWakeupCallback, NULL);

通體再讀一遍,會發現其實這個事件的處理函數processWakeupCallback,其實只是起到清空wakeup socket的作用。

階段總結

RIL_startEventLoop會另起一個線程,監聽readFds這個集合裏面的socket(或者文件或者管道)句柄,並且對監聽到的變化進行處理。而readFds裏面的內容是通過二人組進行添加的。你看這樣分析之後就很簡單了吧。

  1. rilInit(&s_rilEnv, argc, rilArgv)
    這個函數實際調用的是libreference-ril.so庫文件裏面的RIL_Init函數:
//Reference-ril.c
const RIL_RadioFunctions *RIL_Init(const struct RIL_Env *env, int argc, char **argv)
{
    ret = pthread_create(&s_tid_mainloop, &attr, mainLoop, NULL);
}
static void *mainLoop(void *param __unused)
{
    for (;;) {
        fd = -1;
        while  (fd < 0) {
            if (s_port > 0) {
                fd = socket_loopback_client(s_port, SOCK_STREAM);
            } else if (s_device_path != NULL) {
                fd = open (s_device_path, O_RDWR);
                if ( fd >= 0 && !memcmp( s_device_path, "/dev/ttyS", 9 ) ) {
                }
            }
        }
        ret = at_open(fd, onUnsolicited);
        RIL_requestTimedCallback(initializeCallback, NULL, &TIMEVAL_0);
    }
}

其實就是啓動一個s_tid_mainloop線程,主題函數爲mainLoop。在這個函數裏面會用at_open打開文件/dev/ttyS。其實這個就是modem在linux系統裏面抽象出來的文件。而在at_open裏面會另外再起一個線程:

pthread_create(&s_tid_reader, &attr, readerLoop, &attr);

這個readerLoop函數需要自己去看了,我只簡單說一下(因爲其實我也沒有深入去看啊,不過只是瞭解流程的話並無必要,除非碰到實際問題需要解決)。這個readerLoop從AT channel讀取內容,並且解析該內容,並且根據內容調用不同的函數進行處理。看看下面的處理函數:

static void processLine(const char *line)
{
    pthread_mutex_lock(&s_commandmutex);
    if (sp_response == NULL) {
        /* no command pending */
        handleUnsolicited(line);
    } else if (isFinalResponseSuccess(line)) {
        sp_response->success = 1;
        handleFinalResponse(line);
    } else if (isFinalResponseError(line)) {
        sp_response->success = 0;
        handleFinalResponse(line);
    }
    pthread_mutex_unlock(&s_commandmutex);
}

Unsolicited就是不請自來的來電,來短信等事件了。response就是上層要求的事件比如打電話/發短信等。這些處理函數再深入進去看真的是五花八門,各顯神通了。有直接賦值的handle函數,有使用Linux下面的條件互斥,通知另外一個線程進行處理的機制等。

階段總結

rilInit會啓動一個線程mainLoop(其實後面還啓動了一個readerLoop),專門用AT命令從modem讀取數據,並且進行處理。且最後會返回一個RIL_RadioFunctions到rild.c裏面的main函數裏面。

static const RIL_RadioFunctions s_callbacks = {
    RIL_VERSION,
    onRequest,
    currentState,
    onSupports,
    onCancel,
    getVersion
};
  1. RIL_register(funcs);

這是main裏面的最後一個函數了。

extern "C" void RIL_register (const RIL_RadioFunctions *callbacks) {
    memcpy(&s_callbacks, callbacks, sizeof (RIL_RadioFunctions));
    /* Initialize socket1 parameters */
    s_ril_param_socket = {
         RIL_SOCKET_1,             /* socket_id */
         -1,                       /* fdListen */
         -1,                       /* fdCommand */
        PHONE_PROCESS,            /* processName */
       &s_commands_event,        /* commands_event */
       &s_listen_event,          /* listen_event */
  processCommandsCallback,  /* processCommandsCallback */
NULL                      /* p_rs */
};
    // start listen socket1
    startListen(RIL_SOCKET_1, &s_ril_param_socket);
}

幹了兩件事,一個是初始化了一個s_ril_param_socket,並且將其作爲參數傳遞到了startListen函數。先看看這個結構體SocketListenParam s_ril_param_socket。

typedef struct SocketListenParam {
    RIL_SOCKET_ID socket_id;
    int fdListen;
    int fdCommand;
    char* processName;
    struct ril_event* commands_event;
    struct ril_event* listen_event;
    void (*processCommandsCallback)(int fd, short flags, void *param);
    RecordStream *p_rs;
    RIL_SOCKET_TYPE type;
} SocketListenParam;

用通俗語言解釋就是,在某一張卡(針對的是多卡的手機)上,從listen和command的句柄上獲取listen到的事件和上層發送過來的command事件。並且還包含有處理完命令之後的回調函數。

下面分析一下startListen函數:

static void startListen(RIL_SOCKET_ID socket_id, SocketListenParam* socket_listen_p) {
    fdListen = android_get_control_socket(socket_name);
    ret = listen(fdListen, 4);
    socket_listen_p->fdListen = fdListen;
    /* note: non-persistent so we can accept only one connection at a time */
    ril_event_set (socket_listen_p->listen_event, fdListen, false,listenCallback, socket_listen_p);
    rilEventAddWakeup (socket_listen_p->listen_event);
}

這個android_get_control_socket通過socket_name(一般是”rild”)獲取到socket句柄。用這個句柄名稱在init.rc中查找,並且返回指定句柄。見文章開頭提到的那個句柄。後續會在這個句柄上監聽,該句柄支持最多緩存四個事件,超過的就會被丟棄。並且會把這個句柄用來賦值給socket_listen_p這個變量裏面的fdListen值。後面又看到二人組了,ril_event_set和rilEventAddWakeup。將socket_listen_p->listen_event這個ril_event設置一下(包括監聽哪個fdListen,使用哪個函數處理自己listenCallback),然後把這個ril_event事件添加到watch_table裏面,並且把這個fdListen句柄添加到readFds裏面。前面已經講過,這個readFds上面有個線程在不停監聽,並對有數據的fd進行處理。二人組函數ril_event_set和rilEventAddWakeup的組合,其實就是一個機制:將socket添加到多路IO複用機制中,一旦有上層請求,那麼就能觸發相應的處理函數。

上面就是整個native層的rild模塊的主要工作內容了,相信大家已經比較瞭解了。他對應的Java層的RIL也會對套接字“rild”進行操作,這樣就達到了互相通信的目的了。大家可以看看RIL.java這個函數。Java層的RIL分析後續有時間再奉上。如本文有錯漏之處,請各位朋友指正。

發佈了80 篇原創文章 · 獲贊 119 · 訪問量 51萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章