1. 程式人生 > > Android7.0 Rild工作流程

Android7.0 Rild工作流程

一、基於Rild的通訊架構

一般智慧手機的硬體架構都是兩個處理器:
一個處理器用來執行作業系統,上面執行應用程式,這個處理器稱作Application Processor,簡稱AP;另一個處理負責和射頻無線通訊相關的工作,叫Baseband Processor,簡稱BP。

在Android系統中,Rild執行在AP上,它是AP和BP在軟體層上通訊的中樞。

目前通過Rild,AP和BP的通訊方式可以分為兩種:
第一種是AP傳送請求給BP,BP響應並回復AP。此時,BP通過Rild回覆的請求被稱為solicited Response。
第二種是BP主動傳送資訊給AP。在這種情況下,BP通過Rild傳送的請求被稱為unsolicited Response。

Android7.0 Rild工作流程

基於Rild程序的整個通訊架構,基本上如上圖所示。
從圖中我們可以看出:
1、Android框架部分,是通過Phone程序與Rild程序通訊的。它們之間的通訊方式採用的是socket。
在前面介紹PhoneApp啟動時,我們知道Phone程序中有兩個Phone物件。每個Phone物件持有一個socket,與對應的Rild程序通訊。因此,我們知道手機中實際上啟動了兩個Rild程序(雙卡手機)。

shell:/ $ ps | grep rildradio 572 1 113732 14792 hrtimer_na 0000000000 S /system/bin/rildradio 869 1 109604 13944 hrtimer_na 0000000000 S /system/bin/rildshell:/ $ ps | grep phoneradio 2621 605 2019984 74424 SyS_epoll_ 0000000000 S com.android.phoneshell:/ $ ps | grep initroot 1 0 9648 1712 SyS_epoll_ 0000000000 S /initshell:/ $ ps | grep zygoteroot 605 1 2195280 70956 poll_sched 0000000000 S zygote64root 606 1 1610708 59144 poll_sched 0000000000 S zygote

我們通過usb連線手機後,通過adb shell進入終端,通過ps和grep命令,可以得到上述結果。
明顯可以看到一個Phone程序對應者兩個Rild程序;同時Rild程序由init程序載入,Phone程序由zygote程序載入。

2、Rild與BP之間並沒有直接通訊,而是引入了廠商的動態庫。
這種設計應該是為了保證靈活性吧。
用面向物件的思想來看,我們可以認為Rild是一個介面,定義了AP、BP雙向通訊時需要使用的最基本的函式。不同的廠商都需要滿足這個介面,以提供手機最基本的通訊功能。
至於具體如何實現,是完全獨立和自由的。

二、Rild的啟動
在hardware/ril/rild/rild.rc中定義了Rild啟動時對應的選項:

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 readproc wakelock

在Android 7.0之前的版本中,該檔案的內容是被定義在init.rc中的。
到了Android7.0 之後,init.rc檔案中的許多內容均被移出,新增到各個程序中。如前面分析Vold程序時,對應的啟動檔案定義於vold.rc中。
個人猜測這些檔案應該會在編譯時,重新整合起來,畢竟在在rild對應的Android.mk中增加了下述欄位:

.......LOCAL_MODULE:= rildLOCAL_MODULE_TAGS := optional//新增欄位LOCAL_INIT_RC := rild.rc.......

目前手邊沒有Android7.0的機器,還不好驗證,以後有機會再做嘗試。

init程序根據rild.rc檔案啟動一個Rild程序,還需要根據廠商定義的rc檔案啟動另一個Rild程序。
廠商定義的rc檔案中,與Rild程序相關的主要內容與rild.rc相似,就是socket名稱不同。對於第二個Rild程序,其socket名應該為rild2。

現在我們看看Rild程序的main函式,定義於rild.c中:

int main(int argc, char **argv) { //rilLibPath用於指定動態庫的位置 const char * rilLibPath = NULL; ........ //Rild規定動態庫必須實現一個叫做Ril_init的函式,這個函式的第一個引數指向結構體RIL_Env //而它的返回值指向結構體RIL_RadioFunctions const RIL_RadioFunctions *(*rilInit)(const struct RIL_Env *, int, char **); ........ const RIL_RadioFunctions *funcs; char libPath[PROPERTY_VALUE_MAX]; //解析引數 ........ if (strncmp(clientId, "0", MAX_CLIENT_ID_LENGTH)) { strlcat(rild, clientId, MAX_SOCKET_NAME_LENGTH); //注意此處呼叫了ril.cpp中的函式,儲存了Rild程序對應socket的名字,後文還會提到 RIL_setRilSocketName(rild); } if (rilLibPath == NULL) { //讀取系統屬性,LIB_PATH_PROPERTY的值為rild.libpath //原生的屬性值定義於build/target/board/generic/system.prop檔案中 //實際的手機中將會使用廠商指定的system.prop檔案 if ( 0 == property_get(LIB_PATH_PROPERTY, libPath, NULL)) { // No lib sepcified on the command line, and nothing set in props. // Assume "no-ril" case. goto done; } else { rilLibPath = libPath; } } .......... //根據動態庫位置,利用dlopen開啟動態庫 dlHandle = dlopen(rilLibPath, RTLD_NOW); .......... //1、啟動EventLoop,事件處理 RIL_startEventLoop() //從動態庫中的到RIL_Init函式的地址 rilInit = (const RIL_RadioFunctions *(*)(const struct RIL_Env *, int, char **)) dlsym(dlHandle, "RIL_Init"); ...... //2、呼叫RIL_init函式 funcs = rilInit(&;s_rilEnv, argc, rilArgv); RLOGD("RIL_Init rilInit completed"); //3、註冊funcs到Rild中 RIL_register(funcs); ........done: RLOGD("RIL_Init starting sleep loop"); while (true) { sleep(UINT32_MAX); }}

根據Rild的main函式,我們可以看出主要就進行了三件事:啟動Event Loop、呼叫RIL_Init函式和註冊庫函式。
接下來我們分別分析一下主要事件對應的流程。

1、 RIL_startEventLoop
RIL_startEventLoop定義於hardware/ril/libril/ril.cpp中:

extern "C" voidRIL_startEventLoop(void) { /* spin up eventLoop thread and wait for it to get started */ s_started = 0; ......... //建立工作執行緒,執行緒ID存入s_tid_dispatch,對應執行函式為eventLoop int result = pthread_create(&;s_tid_dispatch, &;attr, eventLoop, NULL); ......... //工作執行緒eventLoop執行後,會設定s_started為1,並觸發s_startupCond //這裡的等待的目的是保證RIL_startEventLoop返回前,工作執行緒建立並執行成功 while (s_started == 0) { pthread_cond_wait(&;s_startupCond, &;s_startupMutex); } .........}

我們需要跟進eventLoop函式:

static void *eventLoop(void *param) { int ret; int filedes[2]; //1、初始化內部資料結構 ril_event_init(); pthread_mutex_lock(&;s_startupMutex); //通知RIL_startEventLoop本執行緒已經建立併成功運行了 s_started = 1; pthread_cond_broadcast(&;s_startupCond); pthread_mutex_unlock(&;s_startupMutex); //建立匿名管道 ret = pipe(filedes); ........ s_fdWakeupRead = filedes[0]; s_fdWakeupWrite = filedes[1]; //設定讀埠為非阻塞的 fcntl(s_fdWakeupRead, F_SETFL, O_NONBLOCK); //2、建立一個ril_event ril_event_set (&;s_wakeupfd_event, s_fdWakeupRead, true, processWakeupCallback, NULL); //3、將創建出的ril_event加入到event佇列中 rilEventAddWakeup (&;s_wakeupfd_event); //4、進入事件等待迴圈中 ril_event_loop(); ......... }

1.1 初始化內部資料結構
我們先看看ril_event_init函式:

void ril_event_init(){ MUTEX_INIT(); FD_ZERO(&;readFds); //初始化timer_list,任務插入時按時間排序 init_list(&;timer_list); //初始化pending_list,儲存每次需要執行的任務 init_list(&;pending_list); //初始化監控表 memset(watch_table, 0, sizeof(watch_table));}static void init_list(struct ril_event * list){ memset(list, 0, sizeof(struct ril_event)); list->next = list; list->prev = list; list->fd = -1;}//MAX_FD_EVENTS為8//watchtable將用於儲存FD加入到readFDs中的ril_eventstatic struct ril_event * watch_table[MAX_FD_EVENTS];

可以看出ril_event_init就是初始化readFds、timer_list、pending_list和watch_table,其中後三種資料結構均是用來存放ril_event的。

根據前文的程式碼,我們知道Rild的main函式中,通過呼叫RIL_startEventLoop單獨啟動了一個執行緒執行eventLoop,這是一個工作執行緒。
這個工作執行緒就是靠ril_event結構體來描述自己需要執行的任務,並且它將多個任務按時間順序組織起來,儲存在任務佇列中。
ril_event的資料結構如下:

struct ril_event { struct ril_event *next; struct ril_event *prev; int fd; int index; bool persist; struct timeval timeout; ril_event_cb func; void *param;};

如果從設計模式的角度來理解Rild的工作執行緒,易於看出,這其實是比較典型的命令模式。
就如同之前部落格分析vold程序一樣,CommandListener收到資料後,呼叫對應Command的runCommand方法進行處理。
此處,工作執行緒收到ril_event後,加入佇列中,當需要處理時,呼叫ril_event對應的處理函式func。

1.2 建立wakeupfd ril_event
工作執行緒完成資料結構的初始化後,建立了第一個ril_event:

........ril_event_set (&;s_wakeupfd_event, s_fdWakeupRead, true, processWakeupCallback, NULL);........ // Initialize an eventvoid ril_event_set(struct ril_event * ev, int fd, bool persist, ril_event_cb func, void * param){ dlog("~~~~ ril_event_set %x ~~~~", (unsigned int)ev); memset(ev, 0, sizeof(struct ril_event)); ev->fd = fd; ev->index = -1; ev->persist = persist; ev->func = func; ev->param = param; fcntl(fd, F_SETFL, O_NONBLOCK);}

從上面的程式碼可以看出,建立的第一個ril_event的fd為管道的讀端、回撥函式為processWakeupCallback,同時persist屬性為true。

1.3 將創建出的ril_event加入到event佇列中

static void rilEventAddWakeup(struct ril_event *ev) { ril_event_add(ev); triggerEvLoop();}// Add event to watch listvoid ril_event_add(struct ril_event * ev){ dlog("~~~~ +ril_event_add ~~~~"); MUTEX_ACQUIRE(); for (int i = 0; i < MAX_FD_EVENTS; i++) { //找到第一個空閒索引加入 if (watch_table[i] == NULL) { watch_table[i] = ev; //ril ev->index = i; dlog("~~~~ added at %d ~~~~", i); dump_event(ev); //將ril_event對應的fd加入到readFds FD_SET(ev->fd, &;readFds); //select的限制,第一個引數為監聽總數+1 if (ev->fd >= nfds) nfds = ev->fd+1; dlog("~~~~ nfds = %d ~~~~", nfds); break; } } MUTEX_RELEASE(); dlog("~~~~ -ril_event_add ~~~~");}static void triggerEvLoop() { int ret; //pthread_self返回呼叫執行緒的執行緒ID //這裡呼叫triggerEvLoop的就是eventLoop,因此不進入該分支 if (!pthread_equal(pthread_self(), s_tid_dispatch)) { /* trigger event loop to wakeup. No reason to do this, * if we're in the event loop thread */ do { //但看程式碼我們知道,如果其它執行緒呼叫rilEventAddWakeup加入ril_event時,就會向pipe的寫端寫入資料 ret = write (s_fdWakeupWrite, " ", 1); } while (ret < 0 &;&; errno == EINTR); }}

1.4 進入事件等待迴圈中
接下來工作執行緒進入到事件等待迴圈中:

void ril_event_loop() { ........ for (;;) { // make local copy of read fd_set memcpy(&;rfds, &;readFds, sizeof(fd_set)); //根據timer_list來計算select函式等待的時間,timer_list已經按任務的執行時間排序 if (-1 == calcNextTimeout(&;tv)) { // no pending timers; block indefinitely dlog("~~~~ no timers; blocking indefinitely ~~~~"); ptv = NULL; } else { dlog("~~~~ blocking for %ds + %dus ~~~~", (int)tv.tv_sec, (int)tv.tv_usec); ptv = &;tv; } ............ n = select(nfds, &;rfds, NULL, NULL, ptv); //將timer_list中超時的任務加入到pending_list中 processTimeouts(); //將watchtables中收到的任務加入到pending_list中 processReadReadies(&;rfds, n); //處理pendinglist中的任務 firePending(); }}static int calcNextTimeout(struct timeval * tv) { struct ril_event * tev = timer_list.next; struct timeval now; //利用clock_gettime獲取當前時間 getNow(&;now); // Sorted list, so calc based on first node if (tev == &;timer_list) { // no pending timers return -1; } if (timercmp(&;tev->timeout, &;now, >)) { //計算出等待時間 timersub(&;tev->timeout, &;now, tv); } else { // timer already expired. tv->tv_sec = tv->tv_usec = 0; } return 0;}static void processTimeouts(){ ............ struct timeval now; struct ril_event * tev = timer_list.next; struct ril_event * next; getNow(&;now); ............ //目前還沒提及timer_list,實際上呼叫ril_timer_add函式時,可以將對時間有要求的ril_event加入到timer_list中,按照超時時間從小到大排列 while ((tev != &;timer_list) &;&; (timercmp(&;now, &;tev->timeout, >))) { // Timer expired dlog("~~~~ firing timer ~~~~"); next = tev->next; removeFromList(tev); //輪詢timerlist表,將timer_list中的任務加入到pending_list中 addToList(tev, &;pending_list); tev = next; }}static void processReadReadies(fd_set * rfds, int n){ .......... //前面程式碼已提過,當呼叫ril_event_add時,新加入的ril_event將存入watch_table for (int i = 0; (i < MAX_FD_EVENTS) &;&; (n > 0); i++) { struct ril_event * rev = watch_table[i]; if (rev != NULL &;&; FD_ISSET(rev->fd, rfds)) { addToList(rev, &;pending_list); //persist值為false時,才會移除 //記得麼?在eventLoop呼叫ril_event_loop前,加入了一個s_wakeupfd_event,其persist值為true,永不移除 if (rev->persist == false) { removeWatch(rev, i); } n--; } } ..........}static void firePending() { ........... struct ril_event * ev = pending_list.next; while (ev != &;pending_list) { struct ril_event * next = ev->next; removeFromList(ev); //執行對對應的執行函式 //每次迴圈時s_wakeupfd_event的執行函式processWakeupCallback都會被呼叫 ev->func(ev->fd, 0, ev->param); ev = next; } ..........}static void processWakeupCallback(int fd, short flags, void *param) { ....... /* empty our wakeup socket out */ do { //當有其它執行緒呼叫triggerEvLoop時,會向s_fdWakeupWrite中寫入資料 //s_wakeupfd_event被觸發時,負責清空快取 ret = read(s_fdWakeupRead, &;buff, sizeof(buff)); } while (ret > 0 || (ret < 0 &;&; errno == EINTR));}

至此,RIL_startEventLoop的工作介紹完畢,雖然還沒有實際開始工作,但它搭建出了整個事件的處理框架。這裡涉及的程式碼比較繁雜,我們還是藉助於圖形總結一下整個過程:
Android7.0 Rild工作流程

1.4.1 整體架構
如上圖所示,Rild的main函式中呼叫RIL_startEventLoop。在RIL_startEventLoop中創建出工作執行緒,執行eventLoop函式:
Step 1、利用ril_event_init函式初始化資料結構,主要包括readFds、timer_list、pending_list和watch_table;

Step 2、創建出一個pipe物件;

Step 3、建立s_wakeupfd_event,該event的fd指定為pipe的讀端;這個event將被加入到watch_table,同時pipe的讀端將被加入到readFds中;注意這個event的persist屬性為true,於是將永遠存在於watch_table中;

Step 4、呼叫ril_event_loop開始監聽事件的到來。

先在我們結合圖形,舉幾個例子看看,整個事件處理框架是如何工作的。
注意到初始時,timer_list為空,因此ril_event_loop中將不限時地等待readFds。

1.4.2 ril_event加入到timer_list
當其它執行緒呼叫ril_timer_add函式(定義於ril_event.cpp中)填加ril_event事件時:
Step 1、新到來的ril_event將按超時時間,由小到大加入到timer_list中;同時,其它執行緒一般會呼叫triggerEvLoop,該函式將會向pipe的寫端寫入資料。

Step 2、於是,pipe的讀端將會收到資料;由於初始時pipe讀端已經加入到來readFds,因此ril_event_loop將從等待中喚醒。

Step 3、此時,ril_event_loop將執行timer_list和watch_table中儲存的事件。注意到在timer_list中,只有超時的事件才會被處理;在watch_table中,只有對應fd已經存入readFds(此時使用的是拷貝物件)的事件才會被執行。
注意到初始時加入watch_table的s_wakeupfd_event,永遠滿足執行條件;因此,每次ril_event_loop被喚醒時,該事件都被新增到pending_list。
s_wakeupfd_event對應的執行函式,將會清空pipe的buffer。

Step 4、 處理完加入到pending_list中的事件後,ril_event_loop將根據timer_list中事件的超時時間,決定等待readFds的時間。
如果在等待超時之前,沒有其它事件到來,那麼ril_event_loop將在等待超時後處理timer_list中的事件;否則,僅會處理新到來的事件,不會處理timer_list事件。

1.4.3 ril_event加入到watch_table
當其它執行緒呼叫ril_event_add函式(定義於ril_event.cpp中)增加ril_event事件時:
Step 1、當watch_table有空位時,新加入的ril_event將被加入到watch_table中,同時對應的fd被新增到readFds;同時,其它執行緒可能會呼叫triggerEvLoop,以喚醒ril_event_loop。

Step 2、 ril_event_loop被喚醒後,並不會執行新加入到watch_table中的ril_event,因為它們的fd才剛被加入到readFds中。
從程式碼裡我們可以看到,ril_event_loop當次迴圈處理的是readFds的拷貝對應的資料,因此新加入watch_table的ril_event在下次喚醒時才能夠被處理。

Step 3、由於加入ril_event對應的fd被加入到readFds中,因此如果對應的fd寫入資料時,也會喚醒ril_event_loop。

至此,RIL_startEventLoop的主要流程介紹完畢,可以看到它的主要工作就是啟動工作執行緒,然後等待事件的新增
那麼接下來我們可以看看下一個Rild中下一個重要操作,即呼叫RIL_Init函式。

2、 RIL_Init
RIL_Init定義於動態庫中,考慮到廠商的保密性,我們只能分析Android原生的Reference-ril庫。
在Android的原生庫中,RIL_Init定義於hardware/ril/reference-ril/reference-ril.c中。

const RIL_RadioFunctions *RIL_Init(const struct RIL_Env *env, int argc, char **argv){ ............. s_rilenv = env; //引數處理 ........... pthread_attr_init (&;attr); pthread_attr_setdetachstate(&;attr, PTHREAD_CREATE_DETACHED); ret = pthread_create(&;s_tid_mainloop, &;attr, mainLoop, NULL); return &;s_callbacks;}/*** Static Variables ***/static const RIL_RadioFunctions s_callbacks = { RIL_VERSION, //以下皆是函式指標 onRequest, currentState, onSupports, onCancel, getVersion};

從程式碼上來看RIL_Init函式比較簡單,就幹了三件事:儲存Rild傳入的RIL_Env結構體;建立s_tid_mainloop執行緒,執行函式為mainLoop;返回RIL_RadioFunctions結構體。
這裡需要注意的是:RIL_Env和RIL_RadioFunctions結構體,就是Rild架構中用來隔離通用程式碼和廠商相關程式碼的介面。即動態庫通過RIL_Env呼叫Rild中的介面,Rild通過RIL_RadioFunctions呼叫動態庫中的介面。

2.1 通訊介面
我們先看看RIL_RadioFunctions結構體:

//此處略去函式指標的定義typedef struct { int version; /* set to RIL_VERSION */ //用於向BP提交一個請求 RIL_RequestFunc onRequest; //用於查詢BP的狀態 RIL_RadioStateRequest onStateRequest; //用於判斷動態庫是否支援某人requestCode RIL_Supports supports; //用於取消一個提交給BP的請求 RIL_Cancel onCancel; //查詢動態庫版本 RIL_GetVersion getVersion;} RIL_RadioFunctions;

這裡需要重點關注的函式是onRequest,它被Rild用來向動態庫提交一個請求。
Rild架構採用的是非同步請求/處理的通訊方式,Rild通過onRequest向動態庫提交一個請求,然後返回進行自己的工作;動態庫處理這個請求,當處理完請求後,通過回撥的方式將結果通知給Rild。

我們再來看看RIL_Env結構體:

struct RIL_Env { //動態庫完成一個請求後,通過OnRequestComplete通知處理結果,RIL_Token用於標明是哪個請求的處理結果 void (*OnRequestComplete)(RIL_Token t, RIL_Errno e, void *response, size_t responselen); //動態庫主動上報時,呼叫的介面#if defined(ANDROID_MULTI_SIM) void (*OnUnsolicitedResponse)(int unsolResponse, const void *data, size_t datalen, RIL_SOCKET_ID socket_id);#else void (*OnUnsolicitedResponse)(int unsolResponse, const void *data, size_t datalen);#endif //給rild提交一個超時任務 void (*RequestTimedCallback) (RIL_TimedCallback callback, void *param, const struct timeval *relativeTime); //對於同步的請求,傳送應答訊息時,使用該介面,目前沒看到使用 void (*OnRequestAck) (RIL_Token t);}

在RIL_Env的結構體中,主要需要關注的是OnRequestComplete和OnUnsolicitedResponse。

2.2 mainLoop
動態庫的RIL_Init被呼叫後,將會建立一個工作執行緒,其執行函式為mainLoop:

static void *mainLoop(void *param __unused){ ........ //為AT模組設定一些回撥函式。 //對於Reference-Ril庫而言,AT模組就是對串列埠裝置通訊的封裝,用於和BP通訊 at_set_on_reader_closed(onATReaderClosed); at_set_on_timeout(onATTimeout); for (;;) { fd = -1; while(fd < 0) { //得到串列埠裝置的檔案描述符 ....... } ...... //開啟AT裝置,傳入回撥函式 ret = at_open(fd, onUnsolicited); ....... //向Rild提交一個超時任務,該任務的處理函式為initializeCallback RIL_requestTimedCallback(initializeCallback, NULL, &;TIMEVAL_0); ....... //如果AT模組被關閉,則waitForClose返回,但是該執行緒不會退出,而是從for迴圈那開始重新執行一次 //因此AT模組一旦被關閉,將會重新被開啟 waitForClose(); ....... }}

從上面的程式碼可以看出,mainLoop的工作其實就是初始化並監控AT模組,一但AT模組被關閉,那麼mainLoop就要重新開啟並初始化它。

2.2.1 at_open

int at_open(int fd, ATUnsolHandler h) { ........ pthread_attr_init (&;attr); pthread_attr_setdetachstate(&;attr, PTHREAD_CREATE_DETACHED); ret = pthread_create(&;s_tid_reader, &;attr, readerLoop, &;attr); .........}

在at_open中建立了一個工作執行緒,執行函式為readLoop:

static void *readerLoop(void *arg){ for (;;) { const char * line; //從串列埠裝置讀取資料 line = readline(); ...... if(isSMSUnsolicited(line)) { ....... //呼叫回撥函式 if (s_unsolHandler != NULL) { s_unsolHandler (line1, line2); } ....... } else { //根據line中的資料,呼叫不同的回撥函式 processLine(line); } } //如果從for迴圈退出,則通知mainLoop AT裝置關閉 onReaderClosed(); ...........}

從上面的程式碼,我們知道at_open函式其實就是啟動一個工作執行緒,用於接收AT裝置的資料,然後進行處理。

2.2.1 initializeCallback
呼叫at_open後,main利用RIL_requestTimedCallback向Rild傳送一個超時任務。

#define RIL_requestTimedCallback(a,b,c) s_rilenv->RequestTimedCallback(a,b,c)

可以看到RIL_requestTimedCallback是一個巨集,實際上還是通過RIL_Env中RequestTimedCallback函式傳送超時任務。

我們看看ril.cpp中,該函式的實現:

extern "C" voidRIL_requestTimedCallback (RIL_TimedCallback callback, void *param, const struct timeval *relativeTime) { internalRequestTimedCallback (callback, param, relativeTime);}static UserCallbackInfo *internalRequestTimedCallback (RIL_TimedCallback callback, void *param, const struct timeval *relativeTime){ struct timeval myRelativeTime; UserCallbackInfo *p_info; p_info = (UserCallbackInfo *) calloc(1, sizeof(UserCallbackInfo)); ......... //回撥 p_info->p_callback = callback; //引數 p_info->userParam = param; if (relativeTime == NULL) { /* treat null parameter as a 0 relative time */ memset (&;myRelativeTime, 0, sizeof(myRelativeTime)); } else { /* FIXME I think event_add's tv param is really const anyway */ //時間 memcpy (&;myRelativeTime, relativeTime, sizeof(myRelativeTime)); } //構造ril_event ril_event_set(&;(p_info->event), -1, false, userTimerCallback, p_info); //加入到timer_list中 ril_timer_add(&;(p_info->event), &;myRelativeTime); //觸發EventLoop處理 triggerEvLoop(); return p_info;}

當Rild中的eventLoop處理該超時任務時,就會回撥Reference-ril庫中的initializeCallback:

static void initializeCallback(void *param __unused){ ........ //同步radio狀態 setRadioState (RADIO_STATE_OFF); //不斷地嘗試傳送AT指令給BP並等待回覆,以確定AT channel正常 at_handshake(); //下發一系列的AT指令,完成modem初始化 ..........}

Android7.0 Rild工作流程

至此,我們以Reference-ril庫為例,分析了RIL_Init函式的基本功能。
如上圖所示,RIL_Init的主要工作包括:
1、建立一個mainLoop工作執行緒,該執行緒用於完成實際的工作。
2、在mainLoop執行緒中,通過at_open開啟AT模組,同時啟動readLoop工作執行緒。readLoop工作執行緒負責從AT裝置中讀取資訊,並執行對應的函式呼叫。
3、呼叫at_open後,mainLoop執行緒向Rild程序傳送一個超時事件,該事件被Rild處理後,將呼叫initializeCallback函式。initializeCallback函式,將完成Modem的初始化工作。
其實上,mainLoop可以直接進行Modem初始化的工作;這裡傳送超時事件給Rild,通過回撥進行初始化,可能是為了確保RIL_startEventLoop已經執行成功。
4、mainLoop最後通過waitForClose監控AT模組,一旦AT模組被關閉,mainLoop將重新進行初始化AT模組的工作。

3、RIL_register
現在我們分析一下Rild的main函式中,最後一個關鍵函式RIL_register:

extern "C" voidRIL_register (const RIL_RadioFunctions *callbacks) { ......... //判斷之前是否初始化過 if (s_registerCalled > 0) { RLOGE("RIL_register has been called more than once. " "Subsequent call ignored"); return; } ......... /* 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 */ }; ............ s_registerCalled = 1; ............ // start listen socket1 startListen(RIL_SOCKET_1, &;s_ril_param_socket); //程式碼中的SIM_COUNT巨集並未定義,略去下文 ...........}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;

從上面的程式碼可以看出,RIL_register實際就是初始化監聽socket所需的引數,然後開始監聽socket是否有資料到來。

在前面已經提到過,Android中會創建出兩個Rild程序,每個Rild程序均會呼叫RIL_register函式。
雖然s_registerCalled為一個靜態變數,但在程序的維度上,它是私有的。因此,每個Rild程序均會成功呼叫一次RIL_register。

接下來,我們看看startListen函式:

static void startListen(RIL_SOCKET_ID socket_id, SocketListenParam* socket_listen_p) { ......... switch(socket_id) { case RIL_SOCKET_1: //注意此處的RIL_getRilSocketName strncpy(socket_name, RIL_getRilSocketName(), 9); break; .......... } //根據socket_name獲取對應的檔案描述符 fdListen = android_get_control_socket(socket_name); .............. //使Rild程序變成被動服務程序 ret = listen(fdListen, 4); .............. socket_listen_p->fdListen = fdListen; /* note: non-persistent so we can accept only one connection at a time */ //構造一個非超時任務加,注意persist為false,處理函式為listenCallback ril_event_set (socket_listen_p->listen_event, fdListen, false, listenCallback, socket_listen_p); //加入佇列,並trigger rilEventAddWakeup (socket_listen_p->listen_event);}

3.1 RIL_getRilSocketName
startListen函式中,通過呼叫RIL_getRilSocketName得到需監聽的socket的名稱。

static char * RIL_getRilSocketName() { return rild;}

RIL_getRilSocketName的內容很簡單,就是返回變數rild。
那麼rild變數又是何時設定的呢?

對於第一個Rild程序,在ril.cpp中,定義了rild的內容為“rild”。

.........extern "C"char rild[MAX_SOCKET_NAME_LENGTH] = SOCKET_NAME_RIL;........#define SOCKET_NAME_RIL "rild"

對於第二個Rild程序,在Rild啟動後,對應的main函式中,利用RIL_setRilSocketName修改rild的內容:

int main(int argc, char **argv) { ........ //第二個Rild程序,clientId不為0 if (strncmp(clientId, "0", MAX_CLIENT_ID_LENGTH)) { strlcat(rild, clientId, MAX_SOCKET_NAME_LENGTH); RIL_setRilSocketName(rild); } ........} extern "C"void RIL_setRilSocketName(const char * s) { strncpy(rild, s, MAX_SOCKET_NAME_LENGTH);}

從上面的程式碼,我們可以看出兩個Rild程序確實監聽的是不同的socket。

3.2 listenCallback
利用listen函式將Rild變成監聽程序後,start_listen通過ril_event_set構造了一個非超時的任務,並利用rilEventAddWakeup將該任務加入到watch_table中。
注意到ril_event的fd為待監聽的socket,因此當ril_event被加入到watch_table後,該socket對應的fd將被加入到readFds中。

一旦該socket可讀(即客戶端connect成功),那麼eventLoop中的select函式將會返回,執行listenCallback函式。
實際上,由於該任務的persist屬性為false,因此執行完畢後,ril_event將從watch_table中移除,socket對應的fd也將被從readFds中移除。
這表明,Rild程序不會再監聽socket對應的connect請求,只支援一個客戶端的連線,僅會呼叫一次listenCallback函式。

static void listenCallback (int fd, short flags, void *param) { ........ //儲存配置的socket監聽引數 SocketListenParam *p_info = (SocketListenParam *)param; ........ //接收客戶端連線,並將返回的socket儲存起來 fdCommand = accept(fd, (sockaddr *) &;peeraddr, &;socklen); //進行許可權控制 ........ //設定socket為非阻塞的 ret = fcntl(fdCommand, F_SETFL, O_NONBLOCK); ........ if(NULL == sapSocket) { ........ p_info->fdCommand = fdCommand; //為socket分配一個接收快取 p_rs = record_stream_new(p_info->fdCommand, MAX_COMMAND_BYTES); p_info->p_rs = p_rs; //構造一個新的非超時任務,此時persist屬性為true(1),於是eventLoop將一致select監聽socket是否有資料到來 //當有資料到來時,將呼叫processCommandsCallback進行處理 ril_event_set (p_info->commands_event, p_info->fdCommand, 1, p_info->processCommandsCallback, p_info); rilEventAddWakeup (p_info->commands_event); //向客戶端傳送主動上報資訊,即向RIL.java上報資訊 onNewCommandConnect(p_info->socket_id); } else { .......... }}

至此,RIL_register的主要工作介紹完畢。從上述分析,我們可以看出RIL_register其實就是創建出與RIL.java通訊的服務端socket,然後監聽客戶端請求。一旦監聽到客戶端請求後,利用accept分配出對應的通訊用socket。然後,再監聽該分配出的socket,以處理客戶端發來的資料。

4、 Rild main函式總結
現在我們已經分析完Rild main函式的主要流程了,回過頭來看看Rild整體的設計思路:
Android7.0 Rild工作流程

1、利用RIL_startEventLoop,初始化通訊框架。不論是初始化AT裝置,還是接收來自RIL.java的請求,都依賴於Rild程序的通訊架構,因此在main函式的最開始,對通訊框架進行了初始化。
2、利用RIL_Init開啟AT裝置,並完成modem的初始化。AP側利用RIL.java下發指令,最終還是需要利用AT傳給modem來執行。因此,在通訊框架初始化完畢後,首先就要完成AT和modem的配置。
3、利用RIL_register將Rild程序變成服務程序,等待RIL.java中socket的連線;連線成功後,開始處理來自RIL.java的資料。

三、例項分析
我們已經分析了Rild程序的工作,現在來結合資料業務撥號,看看實際過程中,Rild的工作情況。

1、RIL.java
首先,在之前的部落格中,介紹資料業務基礎類的建立時,我們提到過RIL.java在PhoneFactory的makeDefaultPhone中建立:

public static void makeDefaultPhone(Context context) { ....... for (int i = 0; i < numPhones; i++) { ........ sCommandsInterfaces[i] = new RIL(context, networkModes[i], cdmaSubscription, i); } ...... }

我們看看RIL的建構函式:

public RIL(Context context, int preferredNetworkType, int cdmaSubscription) { this(context, preferredNetworkType, cdmaSubscription, null);}public RIL(Context context, int preferredNetworkType, int cdmaSubscription, Integer instanceId) { ........ mSenderThread = new HandlerThread("RILSender" + mInstanceId); mSenderThread.start(); Looper looper = mSenderThread.getLooper(); //負責向Rild傳送訊息 mSender = new RILSender(looper); ConnectivityManager cm = (ConnectivityManager)context.getSystemService( Context.CONNECTIVITY_SERVICE); if (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false) { riljLog("Not starting RILReceiver: wifi-only"); } else { ........ //負責接收Rild傳送的訊息 mReceiver = new RILReceiver(); mReceiverThread = new Thread(mReceiver, "RILReceiver" + mInstanceId); mReceiverThread.start(); ........ } ..........}

2、 RILReceiver
我們看看RILReceiver相關的函式:

class RILReceiver implements Runnable { byte[] buffer; RILReceiver() { buffer = new byte[RIL_MAX_COMMAND_BYTES]; } @Override public void run() { int retryCount = 0; String rilSocket = "rild"; try { for(;;) { LocalSocket s = null; LocalSocketAddress l; //根據InstanceId決定連線哪個Rild程序的socket if (mInstanceId == null || mInstanceId == 0 ) { rilSocket = SOCKET_NAME_RIL[0]; } else { rilSocket = SOCKET_NAME_RIL[mInstanceId]; } try { s = new LocalSocket(); l = new LocalSocketAddress(rilSocket, LocalSocketAddress.Namespace.RESERVED); //連線rild程序 s.connect(l); } catch(IOException ex) { ......... } retryCount = 0; //連線Rild程序socket後,保留創建出的socket mSocket = s; try { InputStream is = mSocket.getInputStream(); for (;;) { Parcel p; //不斷讀取到來的資料 length = readRilMessage(is, buffer); //解析位元組流 ...... //進行處理 processResponse(p); ...... } catch (java.io.IOException ex) { ....... } catch (Throwable tr) { ....... } //異常斷開,執行關閉socket的操作 ....... } } catch (Throwable tr) { .......... } } .........}

從RILReceiver的程式碼可以看出,其主要功能就是完成與Rild程序中server socket的連線,然後接收並處理Rild程序發來的資料。

3、 setupDataCall
之前的部落格介紹資料業務撥號流程時,我們知道在DataConnection中,最終將通過呼叫RIL的setupDataCall函式,將訊息發往modem:

@Overridepublic void setupDataCall(....) { //構造一個request,有唯一的serialNumber,RIL_REQUEST_SETUP_DATA_CALL表明該Request的目的 RILRequest rr = RILRequest.obtain(RIL_REQUEST_SETUP_DATA_CALL, result); //將引數寫入到RILRequest的Parcel物件中 ........ send(rr);}private void send(RILRequest rr) { Message msg; //RILReceiver中已經創建出mSocket,同時連線了Rild程序 if (mSocket == null) { rr.onError(RADIO_NOT_AVAILABLE, null); rr.release(); return; } //構造訊息傳送給RILSender msg = mSender.obtainMessage(EVENT_SEND, rr); acquireWakeLock(rr, FOR_WAKELOCK); msg.sendToTarget();}

我們看看RILSender:

class RILSender extends Handler implements Runnable { ....... @Override public void handleMessage(Message msg) { RILRequest rr = (RILRequest)(msg.obj); RILRequest req = null; switch (msg.what) { case EVENT_SEND: case EVENT_SEND_ACK: try { LocalSocket s; s = mSocket; //將資料打包到data,發往Rild程序 ......... s.getOutputStream().write(dataLength); s.getOutputStream().write(data); ..... catch(IOException ex) { ....... } catch (RuntimeException exc) { ....... } break; ........ } }}

4、processCommandsCallback
根據前面對Rild程序的分析,我們知道當Rild程序收到RIL.java中傳送來的資料後,將利用processCommandsCallback進行處理:

static void processCommandsCallback(int fd, short flags, void *param) { ............ for (;;) { /* loop until EAGAIN/EINTR, end of stream, or other error */ //record_stream_get_next將socket中接收的資料全部讀取到緩衝區 //當緩衝區還有資料時,優先解析緩衝區資料;緩衝區無資料時,才從socket再次read ret = record_stream_get_next(p_rs, &;p_record, &;recordlen); if (ret == 0 &;&; p_record == NULL) { /* end-of-stream */ break; } else if (ret < 0) { break; } else if (ret == 0) { /* &;&; p_record != NULL */ //解析出的命令利用processCommandBuffer處理 processCommandBuffer(p_record, recordlen, p_info->socket_id); } } //錯誤處理 ............}static intprocessCommandBuffer(void *buffer, size_t buflen, RIL_SOCKET_ID socket_id) { //判斷引數有效性,解析資料等操作 .......... pRI = (RequestInfo *)calloc(1, sizeof(RequestInfo)); ......... pRI->token = token; //決定了對應的執行函式 pRI->pCI = &;(s_commands[request]); pRI->socket_id = socket_id; ........... pRI->pCI->dispatchFunction(p, pRI); return 0;}

上面的程式碼中,出現了一個s_commands陣列,它儲存了一些CommandInfo結構,這個結構封裝了Rild對AT指令的處理函式。另外,Rild還定義了一個s_unsolResponses陣列,它封裝了unsolicited Response對應的一些處理函式。

static CommandInfo s_commands[] = {#include "ril_commands.h"};static UnsolResponseInfo s_unsolResponses[] = {#include "ril_unsol_commands.h"};typedef struct { //請求號 int requestNumber; //請求處理函式 void (*dispatchFunction) (Parcel &;p, struct RequestInfo *pRI); //結果處理函式 int(*responseFunction) (Parcel &;p, void *response, size_t responselen);} CommandInfo;typedef struct { int requestNumber; int (*responseFunction) (Parcel &;p, void *response, size_t responselen); WakeType wakeType;} UnsolResponseInfo;

這裡我們重點看一下s_commands陣列。
CommandInfo按照requestNumber的先後順序加入到s_commands中,因此requestNumber就是對應CommandInfo的索引。
我們看看ril_commands.h:

{0, NULL, NULL},......{RIL_REQUEST_SETUP_DATA_CALL, dispatchDataCall, responseSetupDataCall},......

在RIL.java中,指定了撥號對應的請求號為RIL_REQUEST_SETUP_DATA_CALL,因此Rild中對應的處理函式為dispatchDataCall。

5、dispatchDataCall

static void dispatchDataCall(Parcel&; p, RequestInfo *pRI) { ......... //轉換輸入引數後處理 if (s_callbacks.version < 4 &;&; numParams > numParamsRilV3) { .......... dispatchStrings(p2, pRI); } else { ......... dispatchStrings(p, pRI); }}static voiddispatchStrings (Parcel &;p, RequestInfo *pRI) { ........ startRequest; //處理輸入引數 ......... removeLastChar; closeRequest; //CALL_ONREQUEST是個巨集,實際上呼叫s_callbacks的onRequest函式 //s_callbacks的型別為RIL_RadioFunctions,在rild.c的main函式中,利用動態庫的RIL_Init函式得到;利用RIL_register函式儲存 CALL_ONREQUEST(pRI->pCI->requestNumber, pStrings, datalen, pRI, pRI->socket_id); .............}

6、onRequest
此處,我們以Reference-ril中的onRequest為例,進行分析:

static voidonRequest (int request, void *data, size_t datalen, RIL_Token t) { //判斷當前radio狀態,是否能夠下發AT指令 ....... switch (request) { ....... case RIL_REQUEST_SETUP_DATA_CALL: //下發AT指令,完成撥號 requestSetupDataCall(data, datalen, t); break; ....... }}

7、RIL_onRequestComplete
當指令處理完畢後,Reference-ril將呼叫RIL_onRequestComplete通知RIL.java處理結果。

#define RIL_onRequestComplete(t, e, response, responselen) s_rilenv->OnRequestComplete(t,e, response, responselen)

可以看到RIL_onRequestComplete是一個巨集,實際上呼叫的是s_rilenv的OnRequestComplete函式。
在Rild程序的main函式中,呼叫RIL_Init時傳入了s_rilEnv,我們看看ril.cpp中的RIL_onRequestComplete:

extern "C" voidRIL_onRequestComplete(RIL_Token t, RIL_Errno e, void *response, size_t responselen) { ........ //從引數中,知道請求是從哪個socket發過來的 socket_id = pRI->socket_id; fd = findFd(socket_id); ........ if (pRI->cancelled == 0) { ........ if (response != NULL) { // there is a response payload, no matter success or not. //呼叫responseDataCall,在返回結果中加入ip地址等資訊 ret = pRI->pCI->responseFunction(p, response, responselen); ........ } ........ sendResponse(p, socket_id); } ........}static intsendResponse (Parcel &;p, RIL_SOCKET_ID socket_id) { printResponse; //利用write函式,將資料發往RIL.java return sendResponseRaw(p.data(), p.dataSize(), socket_id);}

8、processResponse
在前面介紹RILReceiver時,我們知道RILReceiver與Rild連線成功後,將會一直監聽發來的資料,並呼叫processResponse進行處理。

private voidprocessResponse (Parcel p) { int type; type = p.readInt(); if (type == RESPONSE_UNSOLICITED || type == RESPONSE_UNSOLICITED_ACK_EXP) { //處理主動上報 processUnsolicited (p, type); } else if (type == RESPONSE_SOLICITED || type == RESPONSE_SOLICITED_ACK_EXP) { RILRequest rr = processSolicited (p, type); ....... } else if (type == RESPONSE_SOLICITED_ACK) { ....... }}private RILRequestprocessSolicited (Parcel p, int type) { ....... //根據serialNumber,將佇列中的記錄移除 rr = findAndRemoveRequestFromList(serial); ....... if (error == 0 || p.dataAvail() > 0) { try {switch (rr.mRequest) { ........ case RIL_REQUEST_SETUP_DATA_CALL: ret = responseSetupDataCall(p); break; ........ } .... } ....... if (error == 0) { ....... if (rr.mResult != null) { AsyncResult.forMessage(rr.mResult, ret, null); //將結果返回給DataConnection rr.mResult.sendToTarget(); } } .......}

在理解了Rild搭建的通訊架構後,分析底層撥號的流程還是比較簡單的。