編輯:關於Android編程
一、基於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。
基於Rild進程的整個通信架構,基本上如上圖所示。
從圖中我們可以看出:
1、Android框架部分,是通過Phone進程與Rild進程通信的。它們之間的通信方式采用的是socket。
在前面介紹PhoneApp啟動時,我們知道Phone進程中有兩個Phone對象。每個Phone對象持有一個socket,與對應的Rild進程通信。因此,我們知道手機中實際上啟動了兩個Rild進程(雙卡手機)。
shell:/ $ ps | grep rild radio 572 1 113732 14792 hrtimer_na 0000000000 S /system/bin/rild radio 869 1 109604 13944 hrtimer_na 0000000000 S /system/bin/rild shell:/ $ ps | grep phone radio 2621 605 2019984 74424 SyS_epoll_ 0000000000 S com.android.phone shell:/ $ ps | grep init root 1 0 9648 1712 SyS_epoll_ 0000000000 S /init shell:/ $ ps | grep zygote root 605 1 2195280 70956 poll_sched 0000000000 S zygote64 root 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:= rild LOCAL_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" void RIL_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_event static 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 event void 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 list void 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的工作介紹完畢,雖然還沒有實際開始工作,但它搭建出了整個事件的處理框架。這裡涉及的代碼比較繁雜,我們還是借助於圖形總結一下整個過程:
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" void RIL_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初始化 .......... }
至此,我們以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" void RIL_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整體的設計思路:
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:
@Override public 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 int processCommandBuffer(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 void dispatchStrings (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 void onRequest (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" void RIL_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 int sendResponse (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 void processResponse (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 RILRequest processSolicited (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搭建的通信架構後,分析底層撥號的流程還是比較簡單的。
概述把圖片切分很多份,點擊交換拼成一張完整的;這樣關卡也很容易設計, 3*3 ; 4*4 ; 5*5 ; 6*6 ;一直下去效果加了個切換動畫,效果還是不錯的,其實游戲就
微信如何轉發文章,使用微信,有時候會讀到一些覺得很好的文章,我們就想轉發分享給好友,那麼微信如何轉發文章呢,其實很簡單的,下面我就來一步一步的講解給大家。1
在Android上開發一些小應用既可以積累知識又可以增加樂趣,與任務式開發不同,所以想到在And
微信運動是微信開發的基於第三方個人運動數據實現微信好友運動數據記錄和PK的一項服務,微信運動通過讀取第三方運動數據實現與微信好友運動數據PK,目前微信運動支