看代碼的時候不要看到細節裏面,先構建模塊的運行框架,後續有需要再深入細節。必要的時候需要拿一個本子將主要流程畫出來或者寫出來。
我們先看看,從系統剛開機是如何啓動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函數可以在這個源碼文件裏面去找。後續再詳細分析這個初始化函數。
- 列表內容
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裏面的內容是通過二人組進行添加的。你看這樣分析之後就很簡單了吧。
- 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
};
- 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分析後續有時間再奉上。如本文有錯漏之處,請各位朋友指正。