RILD及qcril初始化流程
之前看了許多其他同行寫的這方面的好文章了,最近重溫這一塊,覺得應該自己寫一篇,以後再看也好上手。
Rild是Android提供的框架,位置處於AP側,對接Phone程式和BP側Modem。
雙卡手機在phone程式會有2個Phone的例項對應各個卡槽,相應的Rild程式也會有2個,Phone和Rild之間通過Socket保持連線,各卡槽之間的操作互相獨立。
但是由於基帶需要由第三方的晶片廠商來提供,因此Google只提供了Rild的框架和介面,剩下的事情需要由晶片供應商自行來實現,這裡說的就是vendor RIL。
關於Vendor RIL的實現方法各有不同,如高通用qcril + qmi機制實現,而MTK則直接使用AT指令與Modem通訊,在RIL層不做過多的處理。
具體的實現以後有時間再來整理。接下來的內容以一個高通的工程為範本來分析。
1. RILD程式的啟動
用手上的一臺雙卡手機驗證了rild程式確實存在2個:
root@s2:/ # ps |grep rild
radio 464 1 107284 14088 hrtimer_na 7fa7f60604 S /system/bin/rild
radio 759 1 106252 13920 hrtimer_na 7f95db4604 S /system/bin/rild
Rild為系統服務,很明顯會在init.rc中能找到啟動的描述(Android7.0被定義在hardware\ril\rild\Rild.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
這裡啟動了一個rild,那麼第二個rild在哪裡呢?通過OpenGrok的幫忙,在工程中搜尋到device/qcom/common/rootdir/etc/init.qcom.rc有相關定義:
service ril-daemon2 /system/bin/rild -c 2
class main
socket rild2 stream 660 root radio
socket rild-debug2 stream 660 radio system
user root
disabled
group radio cache inet misc audio sdcard_r sdcard_rw qcom_diag diag log
service ril-daemon3 /system/bin/rild -c 3
class main
socket rild3 stream 660 root radio
socket rild-debug3 stream 660 radio system
user root
disabled
group radio cache inet misc audio sdcard_r sdcard_rw qcom_diag diag log
這裡有rild2和rild3的描述,猜想在工程編譯期間指令碼會通過編譯引數判斷工程支援的SIM卡數,然後對應地將這些啟動描述整合到最終的init.rc中。(以後再來驗證。)
2. RILD main函式
根據init.rc中描述可知,rild的入口函式名字為main。
下面過濾出main函式主要的處理,先大致看一下:
int main(int argc, char **argv) {
const char * rilLibPath = NULL;
char **rilArgv;
void *dlHandle;
const RIL_RadioFunctions *(*rilInit)(const struct RIL_Env *, int, char **);
const RIL_RadioFunctions *funcs;
unsigned char hasLibArgs = 0;
int i;
const char *clientId = NULL;
...
//1.從啟動引數中獲取clientId,代表是第幾個RILD
for (i = 1; i < argc ;) {
...
if (0 == strcmp(argv[i], "-c") && (argc - i > 1)) {
clientId = argv[i+1];
i += 2;
} ...
}
//2.使用clientId設定RILD的socket名字
if (strncmp(clientId, "0", MAX_CLIENT_ID_LENGTH)) {
strlcat(rild, clientId, MAX_SOCKET_NAME_LENGTH);
RIL_setRilSocketName(rild);
}
//3.獲取vendor ril庫的路徑
//#define LIB_PATH_PROPERTY "rild.libpath"
if (rilLibPath == NULL) {
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;
}
}
...
//4.開啟vendor ril庫
dlHandle = dlopen(rilLibPath, RTLD_NOW);
//5.啟動訊息迴圈
RIL_startEventLoop();
...
//6.獲取vendor RIL的初始化入口方法
rilInit =
(const RIL_RadioFunctions *(*)(const struct RIL_Env *, int, char **))
dlsym(dlHandle, "RIL_Init");
...
//7.呼叫vendor RIL的RIL_Init方法,此方法會返回RIL_RadioFunctions
rilArgv[argc++] = "-c";
rilArgv[argc++] = clientId;
rilArgv[0] = argv[0];
funcs = rilInit(&s_rilEnv, argc, rilArgv);
//8.註冊vendor RIL的處理方法
RIL_register(funcs);
...
}
我們看到,main函式主要的流程如下:
一)獲取vendor ril並開啟。(3和4步)
二)啟動訊息迴圈。(第5步)
三)獲得vendor ril入口方法,並取得vendor ril的訊息處理函式列表。(6和7)
四)註冊vendor ril回撥方法。(第8步)
用本文接下來的篇幅來分析這上面列出的4個步驟。
3. 深入分析main函式
3.1 獲取Vendor RIL庫檔案路徑
main函式中通過getprop獲取了vendor ril檔案的路徑,property名為:”rild.libpath”
#define LIB_PATH_PROPERTY "rild.libpath"
property_get(LIB_PATH_PROPERTY, libPath, NULL)
在adb shell中輸入getprop命令,找到庫路徑:/vendor/lib64/libril-qc-qmi-1.so
root@s2:/ # getprop rild.libpath
/vendor/lib64/libril-qc-qmi-1.so
這個so檔案由qcril原始碼生成,mk檔案中有描述:
/vendor/qcom/proprietary/qcril/qcril_qmi/qcril_qmi.mk
ifndef QCRIL_DSDA_INSTANCE
LOCAL_MODULE:= libril-qc-qmi-1
在獲得了vendor ril庫檔案路徑之後,開啟庫並取得檔案控制程式碼,流程似乎即將進入vendor RIL。但在此之前,Rild會先啟動一個子執行緒訊息迴圈,然後再開始qcril的流程。
3.2 RIL_startEventLoop 啟動監聽訊息迴圈
RIL_startEventLoop建立了一個執行緒,用於執行eventLoop這個函式。
Ril.cpp
extern "C" void
RIL_startEventLoop(void) {
/* spin up eventLoop thread and wait for it to get started */
s_started = 0;
...
//啟動執行緒,執行eventLoop函式
int result = pthread_create(&s_tid_dispatch, &attr, eventLoop, NULL);
...
//s_started會在eventLoop被執行後置成1,保證了執行緒啟動後才離開這個函式。
while (s_started == 0) {
pthread_cond_wait(&s_startupCond, &s_startupMutex);
}
...
}
在RIL_startEventLoop退出之前,還進入了一個while迴圈去,這裡意圖是通過判斷s_started 的值,保證執行緒已經啟動且eventLoop已經開始被執行,因為s_started在eventLoop開始時會被賦值成1。
eventLoop可以說是Rild中訊息獲取和分發的中心,其實現的關鍵字如下:
無限迴圈、select 、fd_set、ril_event、callback。
深入到eventLoop函式,具體主要做了3件事情:
a. 初始化(fd_set和3個ril_event結構體)
b. 向fd_set中新增了一個fd_read,並向寫管道fd_write寫入字元(將會喚醒select)
c. 啟動一個無限迴圈,等待select(fd_set)返回,隨後呼叫ril_event回撥函式。
Ril.cpp
static void *
eventLoop(void *param) {
int ret;
int filedes[2];
//1.初始化fd_set(readFds)
//2.初始化幾個訊息連結串列(watch_table/pending_list/timer_list)
ril_event_init();
...
//使RIL_startEventLoop退出
s_started = 1;
pthread_cond_broadcast(&s_startupCond);
...
//獲得讀&寫管道
ret = pipe(filedes);
...
s_fdWakeupRead = filedes[0];
s_fdWakeupWrite = filedes[1];
fcntl(s_fdWakeupRead, F_SETFL, O_NONBLOCK);
//初始化一個event,回撥函式為processWakeupCallback
ril_event_set (&s_wakeupfd_event, s_fdWakeupRead, true, processWakeupCallback, NULL);
//1.向watch_list加入這個event
//2.向fd_set加入s_fdWakeupRead,由於persist為true,此fd和event會一直保留
//3.呼叫triggerEvLoop,向s_fdWakeupWrite寫入字元,(將會喚醒select)
rilEventAddWakeup (&s_wakeupfd_event);
//無限迴圈的event loop,select fd_set(readFds)
ril_event_loop();
RLOGE ("error in event_loop_base errno:%d", errno);
// kill self to restart on error
kill(0, SIGKILL);
return NULL;
}
接下來分3節解析上面提到的abc3個處理。
3.2.1. ril_event_init 為eventLoop初始化資料
此函式作用是初始化資料,具體來看程式碼:
void ril_event_init()
{
FD_ZERO(&readFds); //重置fd_set
//清空3個ril_event連結串列結構體
init_list(&timer_list);
init_list(&pending_list);
memset(watch_table, 0, sizeof(watch_table));
}
//watch_table陣列設定了上限大小為8
#define MAX_FD_EVENTS 8
static struct ril_event * watch_table[MAX_FD_EVENTS];
static struct ril_event timer_list;
static struct ril_event pending_list;
//ril_event是連結串列結構,
struct ril_event {
struct ril_event *next;
struct ril_event *prev;
int fd;//會加入到readFds
int index;
bool persist;//決定ril_event及fd是否一直保留在watch_table和readFds中
struct timeval timeout; //超時
ril_event_cb func;//回撥函式
void *param;
};
readFds是一個fd_set,後續連線客戶端後,socket fd會加入到這個set去,監聽輸入。
ril_event是連結串列結構的結構體,儲存了前後2個ril_event的指標,元素中還有fd、persist值、回撥函式,和空指標型別的param。
而看func在被具體呼叫時,引數就是ril_event自身的fd和param:
ev->func(ev->fd, 0, ev->param);
watch_table是ril_event陣列指標,顧名思義,就是儲存這需要觀察的ril_event。
使用ril_event_add(struct ril_event)函式,將一個ril_event加入到watch_table,event->fd也會加入到readFds中。
timer_list 是ril_event結構體,帶有超時屬性的event,使用到ev->timeout值。通過ril_timer_add函式加入,在eventLoop中判斷超時後處理。
pending_list 同樣是ril_event結構體,存放需要馬上被回撥的event。eventLoop中select返回後,watch_table和timer_list中需要進行回撥的event都會加入到這裡,在回撥完成後,便會移除。
看完後面的流程再返回這裡看這幾個struct會更清晰。
3.2.2 建立pipe
上一步中準備好基礎資料後,接下來向fd_set加入讀管道,同時加入一個ril_event到watch_table。具體作用還不明確,先看流程。
執行pipe函式獲得讀寫fd:s_fdWakeupRead 及 s_fdWakeupWrite
ret = pipe(filedes);
...
s_fdWakeupRead = filedes[0];
s_fdWakeupWrite = filedes[1];
執行ril_event_set函式:初始化一個ril_event,fd為s_fdWakeupRead ,回撥函式為processWakeupCallback,persist值為true(決定了event的fd不會從fd_set移除,獲得持續監聽和觸發回撥的能力)
//初始化一個event,引數傳入fd、persist及回撥函式
void ril_event_set(struct ril_event * ev, int fd, bool persist, ril_event_cb func, void * param)
{
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);
}
原來processWakeupCallback做的操作是從s_fdWakeupRead中讀出資料,相當於清空管道。這樣來看這個ril_event存在的作用就是清道夫了。
static void processWakeupCallback(int fd, short flags, void *param) {
char buff[16];
int ret;
/* empty our wakeup socket out */
do {
ret = read(s_fdWakeupRead, &buff, sizeof(buff));
} while (ret > 0 || (ret < 0 && errno == EINTR));
}
event建立好後,接下來執行rilEventAddWakeup:將s_fdWakeupRead加入readFds集合,event加入到watch_table,然後向s_fdWakeupWrite 寫入資料。
//顧名思義rilEventAddWakeup做了add和wakeup兩件事情
static void rilEventAddWakeup(struct ril_event *ev) {
ril_event_add(ev);
triggerEvLoop();
}
//將event新增如watch_table,並將event->fd加入readFds集合
void ril_event_add(struct ril_event * ev)
{
...
for (int i = 0; i < MAX_FD_EVENTS; i++) {
if (watch_table[i] == NULL) {
watch_table[i] = ev;
ev->index = i;
dump_event(ev);
FD_SET(ev->fd, &readFds);
if (ev->fd >= nfds) nfds = ev->fd+1;
break;
}
}
...
}
//向s_fdWakeupWrite寫入空字元喚醒
static void triggerEvLoop() {
int ret;
if (!pthread_equal(pthread_self(), s_tid_dispatch)) {
do {
ret = write (s_fdWakeupWrite, " ", 1);
} while (ret < 0 && errno == EINTR);
}
}
現在只等待有人來select和處理這個event了。
3.2.3 ril_event_loop 監聽socket訊息的迴圈
ril_event_loop函式進入for迴圈後,將等待select函式返回。(因為在3.2.2中已經加入了一個event,這裡會馬上返回。)
待select返回後,將timer_list中超時的event和watch_table中event加入到pending_list中待處理。最後遍歷pending list,執行所有ril event的回撥函式。
然後,迴圈會進入下一輪。
這裡將會具體看到event中fd、persist、func等成員如何被使用。
void ril_event_loop()
{
int n;
fd_set rfds;
struct timeval tv;
struct timeval * ptv;
for (;;) {
memcpy(&rfds, &readFds, sizeof(fd_set));
if (-1 == calcNextTimeout(&tv)) {
ptv = NULL;
} else {
ptv = &tv;
}
//select等待返回
n = select(nfds, &rfds, NULL, NULL, ptv);
if (n < 0) {
if (errno == EINTR) continue;
return;
}
// Check for timeouts
processTimeouts();
// Check for read-ready
processReadReadies(&rfds, n);
// Fire away
firePending();
}
}
接下來看後面3個函式如何處理timer_list, watch_table和pending_list中的event。
static void processTimeouts()
{
MUTEX_ACQUIRE();
struct timeval now;
struct ril_event * tev = timer_list.next;
struct ril_event * next;
getNow(&now);
// walk list, see if now >= ev->timeout for any events
while ((tev != &timer_list) && (timercmp(&now, &tev->timeout, >))) {//判斷是否已經timeout
// Timer expired
next = tev->next;
//從timer_list移除該event
removeFromList(tev);
//將event加到pending_list待處理
addToList(tev, &pending_list);
tev = next;
}
MUTEX_RELEASE();
}
static void processReadReadies(fd_set * rfds, int n)
{
MUTEX_ACQUIRE();
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)) {
//event加入pending_list
addToList(rev, &pending_list);
if (rev->persist == false) {//判斷persist
//從fd_set移除fd
//並從watch_table移除該event
removeWatch(rev, i);
}
n--;
}
}
MUTEX_RELEASE();
}
static void firePending()
{
struct ril_event * ev = pending_list.next;
while (ev != &pending_list) {
struct ril_event * next = ev->next;
//從pending_list中移除該event
removeFromList(ev);
//觸發event的回撥方法
ev->func(ev->fd, 0, ev->param);
//繼續處理下一個
ev = next;
}
}
此圖大致描繪了這個loop的工作流程。
eventloop已經準備妥當,回到rild mian函式中,接下來就是關聯vendor ril的處理了。
3.3 初始化Qcril並獲取回撥函式
rild和qcril之間通過確定的幾個介面函式來互相交流,現在rild需要和qcril互相交換各自的介面列表。
rild.c呼叫qcril RIL_Init完成了這個工作。
rilInit =
(const RIL_RadioFunctions *(*)(const struct RIL_Env *, int, char **))
dlsym(dlHandle, "RIL_Init");
funcs = rilInit(&s_rilEnv, argc, rilArgv);
Ril_Init的第一個引數為s_rilEnv,就是作為rild執行response處理的函式表,qcril需要上報訊息給rild時,就可以通過這些函式實現:
static struct RIL_Env s_rilEnv = {
RIL_onRequestComplete,
RIL_onUnsolicitedResponse,
RIL_requestTimedCallback,
RIL_onRequestAck
};
RIL_Env 定義如下:
ril.h
struct RIL_Env {
void (*OnRequestComplete)(RIL_Token t, RIL_Errno e,void *response, size_t responselen);
void (*OnUnsolicitedResponse)(int unsolResponse, const void *data, size_t datalen, RIL_SOCKET_ID socket_id);
void (*RequestTimedCallback) (RIL_TimedCallback callback,void *param, const struct timeval *relativeTime);
void (*OnRequestAck) (RIL_Token t);
};
接著進去qcril,來看RIL_Init的流程:
1. 儲存rild傳入的回撥函式。
2. 初始化qcril環境。
3. 啟動子執行緒執行迴圈接收從Modem上報的訊息,並處理(類似rild的eventloop)。
4. 返回qcril的回撥函式給rild,作為rild下發request的入口。
qcril.c
const RIL_RadioFunctions *RIL_Init
(
const struct RIL_Env *env,
int argc,
char **argv
)
{
...
//RIL_Env是rild.c傳入的引數,是rild側的回撥函式表
//這裡qcril_response_api陣列下標可以理解成對應著卡槽
qcril_response_api[ QCRIL_DEFAULT_INSTANCE_ID ] = (struct RIL_Env *) env;
qcril_response_api[ QCRIL_SECOND_INSTANCE_ID ] = (struct RIL_Env *) env;
// flow control
//內部是一些設定及其取值範圍,此處具體作用還不太明確
qmi_ril_fw_android_request_flow_control_init();
//初始化pending_unsol_resp_list並分配空間
qmi_ril_init_android_unsol_resp_pending_list();
//建立子執行緒並啟動訊息迴圈處理來自Modem的訊息,實現類似於Rild的eventloop
//訊息迴圈還沒有實際開始工作。
qcril_event_init();
//初始化qcril中所有模組及引數,包括資料庫、ims、voice等模組的引數狀態等
qcril_init(c_argc, c_argv);
//使event迴圈開始工作
qcril_event_start();
...
//返回qcril的回撥函式處理入口
return &qcril_request_api[ QCRIL_DEFAULT_INSTANCE_ID ];
} /* RIL_Init() */
先看下返回的qcril_request_api,這是是一個RIL_RadioFunctions 結構體:
ril.h
typedef struct {
int version; /* set to RIL_VERSION */
RIL_RequestFunc onRequest;
RIL_RadioStateRequest onStateRequest;
RIL_Supports supports;
RIL_Cancel onCancel;
RIL_GetVersion getVersion;
} RIL_RadioFunctions;
qcril_request_api就是提供給rild下發request的入口:
static const RIL_RadioFunctions qcril_request_api[] = {
{ QCRIL_RIL_VERSION, onRequest_rid, currentState_rid, onSupports_rid, onCancel_rid, getVersion_rid }
};
回頭看訊息迴圈的定義和啟動。
qcril_event_init中起了子執行緒,執行qcril_event_main函式進入迴圈。
void qcril_event_init( void )
{
pthread_attr_t attr;
int ret;
...
pthread_attr_init (&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
//啟動子執行緒執行qcril_event_main
ret = pthread_create(&qcril_event.tid, &attr, qcril_event_main, NULL);
pthread_attr_destroy( &attr );
...
//等待qcril_event_main被執行
while (qcril_event.started == 0)
{
pthread_cond_wait(&qcril_event_startupCond, &qcril_event.startup_mutex);
}
} /* qcril_event_init() */
qcril_event_main所執行的操作:
1. 初始化qcril_event.list,這是名為qcril_event_buf的結構體,以鏈的方式關聯訊息。
2. 初始化fd_set,建立讀寫管道。
3. 啟動訊息迴圈,使用select fd_set方式監聽訊息。
4. select返回後,清空讀管道,並迴圈處理所有訊息。
與rild的 eventLoop比較類似。
static void *qcril_event_main
(
QCRIL_UNUSED(void *param)
)
{
int ret;
int filedes[2];
int n;
fd_set rfds;
qcril_event_type *ev;
char buff[16];
...
//初始化qcril_event_type,這是個訊息結構體,同樣以連結串列方式關聯其他訊息
qcril_event_init_list(&qcril_event.list);
//重置fd_set
FD_ZERO(&qcril_event.readFds); /* Needed to use select() system call */
...
qcril_event.started = 1;
//建立讀寫管道
ret = pipe(filedes);
...
qcril_event.fdWakeupRead = filedes[0];
qcril_event.fdWakeupWrite = filedes[1];
fcntl(qcril_event.fdWakeupRead, F_SETFL, O_NONBLOCK);
//fdWakeupRead加入到readFds
FD_SET(qcril_event.fdWakeupRead, &qcril_event.readFds);
pthread_cond_broadcast(&qcril_event_startupCond);
//此處block,等待qcril_event_start()呼叫後才能繼續執行
while (qcril_event.started < 2)
{
pthread_cond_wait(&qcril_event_startupCond, &qcril_event.startup_mutex);
}
...
//訊息迴圈
for (;;)
{
memcpy(&rfds, &qcril_event.readFds, sizeof(fd_set));
/* Block waiting for a event to be put on the queue */
n = select(qcril_event.fdWakeupRead + 1, &rfds, NULL, NULL, NULL);
...
/* Empty the socket */
do
{
//清空管道
ret = read(qcril_event.fdWakeupRead, &buff, sizeof(buff));
} while (ret > 0 || (ret < 0 && errno == EINTR));
...
//處理訊息
do
{
if ( ( NULL != ( ev = qcril_event.list.next ) && ( ev != &qcril_event.list ) ) )
{
//從qcril_event.list中去掉本次的event
qcril_event_remove_from_list( ev );
...
//處理event
err_no = qcril_process_event( ev->instance_id, ev->modem_id, ev->event_id, ev->data, ev->datalen, ev->t );
...
boolean to_check_ev_data_free = TRUE;
...
//釋放資源
if ( to_check_ev_data_free &&
ev->data_must_be_freed && ev->data )
{
qcril_free( ev->data );
}
qcril_free( ev );
}
go_on = ( (
NULL != ( ev =qcril_event.list.next ) &&
( ev != &qcril_event.list ) ) );
//迴圈處理完qcril_event.list中所有訊息
} while ( go_on );
...
}
...
return NULL;
} /* qcril_event_main() */
看過rild eventLoop之後,其實qcril_event_main的流程比較明顯,這裡就不再畫圖了。
這裡的幾個細節需要記錄下:
1. 這裡用到的訊息列表qcril_event.list,是名為qcril_event_buf的結構體,以鏈的方式關聯訊息。
//訊息結構體同樣是以連結串列形式儲存訊息
typedef struct qcril_event_buf
{
struct qcril_event_buf *next;
struct qcril_event_buf *prev;
qcril_instance_id_e_type instance_id;//訊息id
qcril_modem_id_e_type modem_id;
qcril_evt_e_type event_id;
void *data;//訊息附帶的內容
size_t datalen;
RIL_Token t;
boolean data_must_be_freed;
} qcril_event_type;
2.select返回後,先清空read管道,然後遍歷qcril_event.list,並分發給qcril_process_event函式處理。執行結束後進行下一輪迴圈,等待select返回。
現在qcril完成了初始化,訊息處理迴圈也已經啟動,RIL_Init函式最終返回了qcril_request_api。
接下來看rild.c最後一步如何處理。
3.4 RIL_register-註冊qcril回撥並與phone程式連線
RIL_register從命名可知,肯定是儲存了qcril RIL_Init中返回的回撥函式,但實際上還進行了建立socket和連線Phone程式的處理。
Ril.cpp
extern "C" void
RIL_register (const RIL_RadioFunctions *callbacks) {
int ret;
int flags;
...
//儲存callbacks函式結構體到本地
memcpy(&s_callbacks, callbacks, sizeof (RIL_RadioFunctions));
//初始化socket的引數
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,
NULL, /* p_rs */
RIL_TELEPHONY_SOCKET /* type */
};
#if (SIM_COUNT >= 2)
s_ril_param_socket2 = {
RIL_SOCKET_2, /* socket_id */
-1, /* fdListen */
-1, /* fdCommand */
PHONE_PROCESS, /* processName */
&s_commands_event_socket2, /* commands_event */
&s_listen_event_socket2, /* listen_event */
processCommandsCallback,
NULL, /* p_rs */
RIL_TELEPHONY_SOCKET /* type */
};
#endif
...
//開始監聽socket
startListen(RIL_SOCKET_1, &s_ril_param_socket);
#if (SIM_COUNT >= 2)
// start listen socket2
startListen(RIL_SOCKET_2, &s_ril_param_socket2);
#endif /* (SIM_COUNT == 2) */
//啟動一個用於debug的socket,猜測是高通工具會使用到這個介面
#if 1
char *inst = NULL;
if (strlen(RIL_getRilSocketName()) >= strlen(SOCKET_NAME_RIL)) {
inst = RIL_getRilSocketName() + strlen(SOCKET_NAME_RIL);
}
char rildebug[MAX_DEBUG_SOCKET_NAME_LENGTH] = SOCKET_NAME_RIL_DEBUG;
if (inst != NULL) {
strlcat(rildebug, inst, MAX_DEBUG_SOCKET_NAME_LENGTH);
}
s_fdDebug = android_get_control_socket(rildebug);
...
ret = listen(s_fdDebug, 4);
...
ril_event_set (&s_debug_event, s_fdDebug, true,
debugCallback, NULL);
rilEventAddWakeup (&s_debug_event);
#endif
}
開始Listen之前,初始化的s_ril_param_socket結構體是SocketListenParam類,具體結構及元素作用如下:
typedef struct SocketListenParam {
RIL_SOCKET_ID socket_id;
int fdListen; //用於監聽客戶端連線請求的fd
int fdCommand; //客戶端連線後,接收訊息的fd
const char* processName; //指定這個socket接受哪個程式的連線
struct ril_event* commands_event; //fdCommand對應的event
struct ril_event* listen_event; //fdListen對應的event
void (*processCommandsCallback)(int fd, short flags, void *param); //處理客戶端請求的回撥函式
RecordStream *p_rs; //客戶端請求的具體資料
RIL_SOCKET_TYPE type;//指定socket是Telephony還是SAP(與藍芽相關)型別
} SocketListenParam;
接下來深入看startListen。這裡啟動了socket服務端,等待client端接入。但並沒有看到accept()函式,留意最後2句程式碼處設定了一個ril_event並喚醒了eventLoop,回撥函式是listenCallback,persist值是false,這個event只會執行一次,應該跟連線客戶端有關。
繼續看listenCallback。
static void startListen(RIL_SOCKET_ID socket_id, SocketListenParam* socket_listen_p) {
int fdListen = -1;
int ret;
char socket_name[10];
memset(socket_name, 0, sizeof(char)*10);
switch(socket_id) {
case RIL_SOCKET_1:
strncpy(socket_name, RIL_getRilSocketName(), 9);
break;
#if (SIM_COUNT >= 2)
case RIL_SOCKET_2:
strncpy(socket_name, SOCKET2_NAME_RIL, 9);
break;
...
}
//建立一個socket
fdListen = android_get_control_socket(socket_name);
...
//開始接受客戶端連線
ret = listen(fdListen, 4);
..
//監聽fd儲存到SocketListenParam
socket_listen_p->fdListen = fdListen;
//將fd和listen event加入watch table,並喚醒eventLoop
ril_event_set (socket_listen_p->listen_event, fdListen, false,
listenCallback, socket_listen_p);
rilEventAddWakeup (socket_listen_p->listen_event);
}
在listenCallback中成功accept()函式,流程進入最後階段:
1. 獲取到client端的fd(如果返回錯誤則重新執行一次listenCallback)
2. 初始化用於監聽和執行上層下發命令的ril_event,persist為true,回撥方法為processCommandsCallback,持續監聽來自客戶端的訊息。
3. 上報Radio初始狀態到客戶端
static void listenCallback (int fd, short flags, void *param) {
int ret;
int err;
int is_phone_socket;
int fdCommand = -1;
const char* processName;
RecordStream *p_rs;
MySocketListenParam* listenParam;
RilSocket *sapSocket = NULL;
socketClient *sClient = NULL;
SocketListenParam *p_info = (SocketListenParam *)param;
if(RIL_SAP_SOCKET == p_info->type) {
listenParam = (MySocketListenParam *)param;
sapSocket = listenParam->socket;
}
struct sockaddr_un peeraddr;
socklen_t socklen = sizeof (peeraddr);
struct ucred creds;
socklen_t szCreds = sizeof(creds);
struct passwd *pwd = NULL;
...
//獲取等待中的客戶端fd
fdCommand = accept(fd, (sockaddr *) &peeraddr, &socklen);
//返回錯誤,重新執行一次
if (fdCommand < 0 ) {
RLOGE("Error on accept() errno:%d", errno);
/* start listening for new connections again */
if(NULL == sapSocket) {
rilEventAddWakeup(p_info->listen_event);
} else {
rilEventAddWakeup(sapSocket->getListenEvent());
}
return;
}
...
err = getsockopt(fdCommand, SOL_SOCKET, SO_PEERCRED, &creds, &szCreds);
...
ret = fcntl(fdCommand, F_SETFL, O_NONBLOCK);
...
//已經獲得客戶端連線,listen_event的使命暫時結束
//接下來開始從fdCommand中獲取客戶端下發的命令,並執行處理
if(NULL == sapSocket) {
p_info->fdCommand = fdCommand;
p_rs = record_stream_new(p_info->fdCommand, MAX_COMMAND_BYTES);
p_info->p_rs = p_rs;
//persist為1,持續監聽來自fdCommand的資料
//回撥函式為processCommandsCallback
ril_event_set (p_info->commands_event, p_info->fdCommand, 1,
p_info->processCommandsCallback, p_info);
rilEventAddWakeup (p_info->commands_event);
//連線客戶端成功,上報Radio狀態給上層
onNewCommandConnect(p_info->socket_id);
}
...
}
最後的最後,看看成功連線Phone程式後,RIL向上層上報了什麼訊息:
//包括RIL_CONNECTED、RADIO_STATE_CHANGED、NITZ資料
//設定PROPERTY_RIL_IMPL
static void onNewCommandConnect(RIL_SOCKET_ID socket_id) {
// Inform we are connected and the ril version
int rilVer = s_callbacks.version;
RIL_UNSOL_RESPONSE(RIL_UNSOL_RIL_CONNECTED,
&rilVer, sizeof(rilVer), socket_id);
// implicit radio state changed
RIL_UNSOL_RESPONSE(RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED,
NULL, 0, socket_id);
// Send last NITZ time data, in case it was missed
if (s_lastNITZTimeData != NULL) {
sendResponseRaw(s_lastNITZTimeData, s_lastNITZTimeDataSize, socket_id);
free(s_lastNITZTimeData);
s_lastNITZTimeData = NULL;
}
// Get version string
if (s_callbacks.getVersion != NULL) {
const char *version;
version = s_callbacks.getVersion();
property_set(PROPERTY_RIL_IMPL, version);
} else {
property_set(PROPERTY_RIL_IMPL, "unavailable");
}
}
現在Phone程式已經可以通過RILJ來與RILD通訊了,RILJ與qcril也建立了連線,至於qcril的event迴圈如何獲得modem的訊息,這裡順便再引申分析一下。
3.5 qcril event迴圈從何訊息輸入?
從3.4的分析發現qcril_event_main函式中有讀寫管道,而fd read也被設定到了Fd_set中:
qcril_event.fdWakeupRead = filedes[0];
qcril_event.fdWakeupWrite = filedes[1];
fcntl(qcril_event.fdWakeupRead, F_SETFL, O_NONBLOCK);
FD_SET(qcril_event.fdWakeupRead, &qcril_event.readFds);
要知道訊息在哪裡輸入,這裡先找到哪裡喚醒了select,於是搜尋fdWakeupWrite尋找往管道寫入資料的地方, 定位到qcril_event.c檔案的qcril_event_queue函式:
qcril_event.c
IxErrnoType qcril_event_queue
(
qcril_instance_id_e_type instance_id,
qcril_modem_id_e_type modem_id,
qcril_data_src_e_type data_src,
qcril_evt_e_type event_id,
void *data,
size_t datalen,
RIL_Token t
)
{
int ret;
qcril_event_type *ev;
IxErrnoType result;
do
{
result = E_SUCCESS;
//初始化一個qcril_event並填充資料
ev = (qcril_event_type *) qcril_malloc( sizeof(qcril_event_type) );
...
ev->instance_id = instance_id;
ev->modem_id = modem_id;
ev->event_id = event_id;
ev->t = t;
ev->data_must_be_freed = data_src;
if ( data == NULL )
{
ev->data = NULL;
ev->datalen = 0;
}
...
else
{
ev->data = data;
ev->datalen = datalen;
}
...
//向list加入event
qcril_event_add_to_list( ev, &qcril_event.list );
...
if (!pthread_equal(pthread_self(), qcril_event.tid))
{
do
{
//喚醒event迴圈
ret = write (qcril_event.fdWakeupWrite, " ", 1);
} while (ret < 0 && errno == EINTR);
}
} while(0);
...
return result;
} /* qcril_event_queue */
尋根問底,繼續搜尋qcril_event_queue的呼叫,但返回的結果實在太多。
事已至此總不能半途而棄,抱著不放棄的精神,再檢視了其中一些結果,並找到了一處比較典型的呼叫作為例子。
從呼叫者函式的命名來看應該是跟qmi有關聯的,深入調查後也證實了這個猜測,但超出這篇文章的範圍,更具體的分析留待後面來說。
Qcril_data_netctrl.c
void qcril_data_post_qmi_events(
qcril_data_call_info_tbl_type * info_tbl_ptr,
dsi_net_evt_t net_evt
)
{
...
if(E_SUCCESS != qcril_event_queue( instance_id,
modem_id,
QCRIL_DATA_NOT_ON_STACK,
QCRIL_EVT_DATA_EVENT_CALLBACK,
( void * )evt,
sizeof( qcril_data_event_data_t ),
tok ))
{
/* free allocated memory for immediate failure from qcril_event_queue */
free(evt);
break;
}
..
}
qcril的event main迴圈輸入及訊息處理過程:
4.小結
rild main函式已經執行完畢,rild與phone程式通過socket連線,並建立了一個訊息迴圈來監聽處理phone下發的指令;而qcril也啟動了一個訊息迴圈,監聽處理modem上報的訊息。
至於目前想到的一些問題,有時間會再探究:
MTK的rild是怎樣工作的?
訊息具體怎麼在rild和qcril中傳遞和處理?
qcril如何與modem通訊(qmi)?
如何新增一個新的訊息?
相關文章
- 類載入器及類的初始化流程
- main之前初始化流程AI
- Linux的初始化流程Linux
- 著色器初始化流程圖流程圖
- Java類初始化執行流程Java
- 深入分析 Flutter 初始化流程Flutter
- webrtc PeerConnectionFactory 的初始化流程分析Web
- hive初始化、處理流程詳解Hive
- webpack 流程解析(2):引數初始化完成Web
- Vue.js原始碼解析-Vue初始化流程Vue.js原始碼
- spring ioc原理-容器初始化的大致流程Spring
- Spring啟動流程(九)——初始化主題Spring
- buffalo 安裝及框架初始化框架
- SpringMVC原始碼剖析(三)- DispatcherServlet的初始化流程SpringMVC原始碼Servlet
- 交房手續及流程
- 深度學習 Caffe 初始化流程理解(資料流建立)深度學習
- 深度學習 Caffe 初始化流程理解(資料流建立)深度學習
- 開發日記(一)JAVA中變數初始化流程Java變數
- Kafka原始碼篇 --- 你一定能get到的Producer的初始化及後設資料獲取流程Kafka原始碼
- webpack構建流程及梳理Web
- Flask——安裝、建立目錄及初始化Flask
- spring-IOC容器原始碼分析(一)bean初始化流程Spring原始碼Bean
- ogg不停業務重新初始化目標資料庫流程資料庫
- ogg停止業務重新初始化目標資料庫流程資料庫
- 淺談GPU 及 “App渲染流程”GPUAPP
- MapReduce入門及核心流程案例
- 瀏覽器渲染原理及流程瀏覽器
- C++中常資料的使用及初始化C++
- MySQL資料庫部署及初始化相關MySql資料庫
- codis服務部署前的操作及初始化
- mysql初始化後修改密碼及賦權MySql密碼
- RequestMappingHandlerMapping請求地址對映的初始化流程!APP
- iOS 淺談GPU及“App渲染流程”iOSGPUAPP
- 瀑布流程式碼實現及思路
- weblogic考證及報名流程Web
- Android UI繪製流程及原理AndroidUI
- java中Stream的使用流程及注意Java
- 資料開發流程及規範