写在前面
1、本文从宏观的角度去分析问题,因此忽略了一些非主线的函数。
2、同理,对于函数内部非主要的逻辑部分,也采取了省略。
3、受限于知识的积累和理解能力,文中描述如有分析不妥之处,希望能够得到大家更正。
从Main函数开始的故事
Android的智能机架构是应用处理器+基带芯片,也就是AP+Modem的模式,AP部分相当于CPU,Modem相当于网卡,而且每个厂商使用的Modem都有可能不一样。每种通讯协议如GSM/CDMA又有很大的不同,为了将所有的Modem抽象为统一的模式,因此Android搭建了RIL(Radio Interface Layer)层。在这个层的下面,每个厂商可以有自己不同的实现方式,但是经过RIL层协议的转换,就将所有的Modem抽象为统一的对象向上层负责。
RILC与上层的RILJ沟通方式是通过Socket传输数据与命令,而与底层Modem的信号传输是通过串口用AT命令来实现。
我们从RIL的入口开始分析。
@rild.c
int main(int argc, char **argv){
//连接库地址:/system/lib/libreference-ril.so
#define REFERENCE_RIL_PATH "/system/lib/libreference-ril.so"
rilLibPath = REFERENCE_RIL_PATH;
//切换UID为AID_RADIO
switchUser();
//打开链接库
dlHandle = dlopen(rilLibPath, RTLD_NOW);
//开启EventLoop循环
RIL_startEventLoop();
//从链接库中(也就是reference-ril.c)寻找RIL_Init函数地址
rilInit = (const RIL_RadioFunctions *(*)(const struct RIL_Env *, int, char **))dlsym(dlHandle, "RIL_Init");
//调用reference-ril.c中的RIL_Init函数进行初始化INIT,同时得到reference-ril的回调函数
funcs = rilInit(&s_rilEnv, argc, rilArgv);
//注册得到的reference的回调函数
RIL_register(funcs);
}
从上面可以看出,入口函数主要完成了3个作用:
1、开启EventLoop循环,完成RIL与RILJ层数据交互(通过Socket)
2、打开动态库reference并构建ReaderLoop循环,完成RIL与Modem层数据交互(通过AT)
3、注册reference的回调函数
下面我们详细介绍具体流程。而在介绍之前,先把整个RIL层的数据流向用一张图片展示:
一、Event机制
1.1、Event框架
Event要做的就是循环检测EventLoop中添加的句柄池,如果在当前的句柄池中有任意一个句柄所代表的通道中有新的数据进来,就去处理当前的新数据。而在句柄池中最重要的句柄就是RILJ与RILC之间的Socket通道。
Event的实现主要在ril_event.cpp文件中。我们先来看一下一个标准的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;
};
从上面的结构体可以看出,Event的管理是通过链表实现的,一些重要的成员变量的意义如下:
fd:事件相关设备句柄。最重要的就是RILJ与RILC之间的Socket文件句柄
persist:说明当前的Event需要保持,不能从watch_table中删除
func:当前Event的处理函数
param:调用当前Event处理函数时的参数
接下来,我们在看具体的处理流程之前,先来看一下处理Event需要哪些函数:
//Event的初始化,其实就是对3个主要链表的初始化
static void init_list(struct ril_event * list)
//添加Event到链表
static void addToList(struct ril_event * ev, struct ril_event * list)
//从链表删除Event
static void removeFromList(struct ril_event * ev)
//从watch连表中删除某个Event
static void removeWatch(struct ril_event * ev, int index)
//处理超时的Event
static void processTimeouts()
//初始化Event链表
void ril_event_init()
//初始化一个Event
void ril_event_set(struct ril_event * ev, int fd, bool persist, ril_event_cb func, void * param)
//把一个Event添加到watch_table中
void ril_event_add(struct ril_event * ev)
//把一个Event添加到timer_list中
void ril_timer_add(struct ril_event * ev, struct timeval * tv)
//把一个Event从watch_table中删除
void ril_event_del(struct ril_event * ev)
//主循环
void ril_event_loop()
通过上面的主要函数我们可以大致推测出,管理Event的过程应该是生成相应的Event节点,然后将Event添加到链表,处理Event之后就需要把当前的Event从链表中删除。而且我们看出,Event管理中应该存在多个链表,那么究竟有哪些链表在运行呢?
RIL的Event管理体系中存在3个链表结构:watch_table,timer_list,pending_list,并使用了一个设备句柄池readFDS,把所有的Socket管道的文件句柄保存起来。而管理的过程可以归纳为以下6点:
1、可以将一个Event添加到watch_table或者timer_list中;
2、如果Event是添加到watch_table中,需要把当前Event的fd(事件设备句柄)添加到readFDS中;
3、如果Event是添加到timer_list中,不需要把当前Event的fd(事件设备句柄)添加到readFDS中,而且当前Event的fd值是无效的;
4、在循环检测过程中,如果发现watch_table中有Event就会把当前Event添加到pending_list中,如果当前Event的persist属性为false,说明不需要保留当前节点,就把当前的Event从watch_table中删除;如果persist为true,说明需要保留,就不需要从watch_table中删除当前节点。
5、在循环检测过程中,如果发现timer_list中的Event超时时,把当前Event移动到pending_list中,同时删除timer_list中的节点。
6、在循环检测的过程中,等watch_table和timer_list处理完毕后,就去pending_list中执行里面的Event所指向的func。
有了以上的认识,我们来看一下具体的流程。从Event的创建入口开始:
@ril.cpp
RIL_startEventLoop(void) {
//打开Event线程,并调用eventLoop进入循环
ret = pthread_create(&s_tid_dispatch, &attr, eventLoop, NULL);
}
然后我们进入到线程的入口函数中查看:
eventLoop(void *param) {
//Event的初始化
ril_event_init();
//创建一个Event
ril_event_set (&s_wakeupfd_event, s_fdWakeupRead, true,processWakeupCallback, NULL);
//将上面创建的Event加入到watch_table中,这里起到唤醒LoopEvent的作用,后面详细说明
rilEventAddWakeup (&s_wakeupfd_event);
//进入loop循环
ril_event_loop();
}
可以看出,Event的搭建由两个步骤:1、初始化链表;2、进入loop循环。我们分别看一下两个过程:
1.2、EventLoop的搭建过程
1.2.1、Event初始化过程。其实就是ril_event_init过程。
@ril_event.cpp
void ril_event_init()
{
FD_ZERO(&readFds);
init_list(&timer_list);
init_list(&pending_list);
memset(watch_table, 0, sizeof(watch_table));
}
初始化的过程很简单,就是对loop框架中涉及到的文件句柄集和3个重要链表的初始化。
1.2.2、Event循环的过程
@ril_event.cpp
void ril_event_loop()
{
//死循环检测Event消息
for (;;) {
//计算下一次超时时间
if (-1 == calcNextTimeout(&tv)) {
//NULL说明select是阻塞模式
ptv = NULL;
} else {
//非空说明在超时时间内是阻塞模式
ptv = &tv;
}
//用select去扫描readFds中的所有管道集合,检测RILJ是否有新数据出现。
n = select(nfds, &rfds, NULL, NULL, ptv);
//检查超时事件,如果超时,将Event放入pending_list中
processTimeouts();
//检查watch_table,将Event加入pending_list中
processReadReadies(&rfds, n);
//执行pending_list中的Event
firePending();
}
}
上面的for循环可以清晰的看到,当RILJ发送数据后,在EventLoop中会依次被timer_list、watch_table、pending_list处理;接着来分别看一下3个表的处理流程:
a、timer_list表
static void processTimeouts()
{
//如果timer_list中某个事件已经超时
while ((tev != &timer_list) && (timercmp(&now, &tev->timeout, >))) {
//从timer_list中删除
removeFromList(tev);
//添加到pending_list中
addToList(tev, &pending_list);
}
}
b、watch_table表
static void processReadReadies(fd_set * rfds, int n)
{
for (int i = 0; (i < MAX_FD_EVENTS) && (n > 0); i++) {
//添加到pending_list
addToList(rev, &pending_list);
if (rev->persist == false) {
//如果persist为false才去删除当前Event
removeWatch(rev, i);
}
}
}
c、pending_list表
static void firePending()
{
while (ev != &pending_list) {
//删除当前节点
removeFromList(ev);
//执行其func并把参数传递进去
ev->func(ev->fd, 0, ev->param);
}
}
上面的循环过程说明,eventLoop的是通过在内部循环中用Linux中的select方法检测readFds中所有的文件句柄(或者说管道),如果发现有新的数据进来,就去遍历watch_table和timer_list表,把需要处理的eventLoop加入到pending_list中,然后进入pending_list中去执行每个Event的func。
上面提到了循环接收数据的过程,那么具体的处理这些命令的过程是怎样的呢?这个过程等我们了解了reference的过程后再去讲解。
再次提醒一下,这里侦测到的数据主要是RILJ发送下来的命令(而不是Modem侧上来的AT命令)。
二、reference库的加载
在这一步中,RIL需要加载一个AT相关的***ril.so的动态链接库。之所以使用库的形式,就是考虑到每个厂商使用的Modem不同,我们没法用统一的接口去向底层负责,因此使用库的形式。这样一来,不同的Modem厂商提供不同的链接库,只要符合RIL层的框架即可。而当前的链接库中最主要的就是就是reference-ril.c和atchannel.c文件。
而reference库需要完成两个任务:
1、将eventLoop中的命令通过AT发送给Modem;
2、构建一个readerLoop循环,接受Modem消息,并根据消息的不同(URC和非URC)将消息返回给eventLoop(非URC消息)或者直接发送给RILJ(URC消息)。
我们先看readerLoop构建过程(发送AT的过程在文档的最后一章介绍):
2.1、reference中readerLoop建立和循环过程
在这一步中,需要完成reference的初始化,并且打开的ReaderLoop循环。 @reference-ril.c
const RIL_RadioFunctions *RIL_Init(const struct RIL_Env *env, int argc, char **argv)
{
//开启ril的线程,入口函数是mainLoop
ret = pthread_create(&s_tid_mainloop, &attr, mainLoop, NULL);
//把ril的回调函数返回出来
return &s_callbacks;
}
我们来看入口函数:
static void * mainLoop(void *param)
{
//初始化AT通道的关闭方法和超时方法
at_set_on_reader_closed(onATReaderClosed);
at_set_on_timeout(onATTimeout);
for (;;) {
//打开AT并把处理URC消息的方法onUnsolicited传进去
ret = at_open(fd, onUnsolicited);
RIL_requestTimedCallback(initializeCallback, NULL, &TIMEVAL_0);
waitForClose();
}
}
上面可以看到,不仅打开了AT通道,而且还设置了超时方法。当前线程在打开AT通道后,在waitForClose中阻塞等待,如果AT通道在检测超时后,将会主动的关闭当前的AT通道,此时将会激活waitForClose中的阻塞线程,然后waitForClose将会返回。而一旦waitForClose函数返回,将会再次进入for循环,重新打开AT通道。
我们主要跟踪AT通道打开的过程,以及事件的处理流程:
@atchannel.c
int at_open(int fd, ATUnsolHandler h)
{
//URC消息的处理方式:onUnsolicited() @reference-ril.c
s_fd = fd;
//创建线程读取AT命令并处理Modem发过来的信息
ret = pthread_create(&s_tid_reader, &attr, readerLoop, &attr);
}
看一下read线程的入口函数readerLoop:
static void *readerLoop(void *arg)
{
for (;;) {
//读取命令
line = readline();
if(isSMSUnsolicited(line)) {
if (s_unsolHandler != NULL) {
//URC消息可以直接发送给RILJ
s_unsolHandler (line1, line2);
}
} else {
processLine(line);
}
}
//关闭read
onReaderClosed();
return NULL;
}
上面的readerLoop就是在不断侦测Modem上报的消息,然后根据是否是URC消息来采用不同的处理方式。至于具体的判断依据,在两个地方可以体现:1、通过isSMSUnsolicited判断(如果以CMT/CDS/CBM开头则判断成立);2、也可以在processLine中判断(这是主要的判断依据)。
我们简要说一下processLine判断URC消息的依据。我们知道,如果不是URC消息,那么就是我们主动发送的请求,Modem是作为回应给我们发的消息,而在我们给Modem发送消息时,会注册各种的回调函数和用于放置Modem返回值的指针sp_response。而如果是URC消息,那么就没有回调函数,而且sp_response是为空,reference正是通过判断sp_response的内容来达到区分URC消息的目的。
在processLine中对于不同的消息有不同的处理流程:
static void processLine(const char *line)
{
if (sp_response == NULL) {
//URC消息处理
handleUnsolicited(line);
} else if (isFinalResponseSuccess(line)) {
//非URC消息处理
sp_response->success = 1;
//发送回应消息给EventLoop
handleFinalResponse(line);
} else switch (s_type) {
case NO_RESULT:
case NUMERIC:
//非URC消息处理
if (sp_response->p_intermediates == NULL
&& isdigit(line[0])
) {
addIntermediate(line);
} else {
/* either we already have an intermediate response or
the line doesn't begin with a digit */
handleUnsolicited(line);
}
break;
}
}
可以看到,URC消息是通过handleUnsolicited处理的,而非URC消息有两个地方处理。下面分别介绍两种处理方式:
2.2、URC消息处理流程
我们看URC消息的处理函数:
@atchannel.c
static void handleUnsolicited(const char *line)
{
s_unsolHandler(line, NULL);
}
这里的s_unsolHandler来自于at_open时的参数,也就是reference-ril.c中的onUnsolicited:
@reference-ril.c
static void onUnsolicited (const char *s, const char *sms_pdu)
{
if (strStartsWith(s, "%CTZV:")) {
//时区改变
RIL_onUnsolicitedResponse (RIL_UNSOL_NITZ_TIME_RECEIVED,response, strlen(response));
} else if (strStartsWith(s,"+CRING:")
|| strStartsWith(s,"RING")
|| strStartsWith(s,"NO CARRIER")
|| strStartsWith(s,"+CCWA")
) {
//通话状态改变
RIL_onUnsolicitedResponse (RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED, NULL, 0);
} else if (strStartsWith(s,"+CREG:")|| strStartsWith(s,"+CGREG:")) {
//网络注册状态改变
RIL_onUnsolicitedResponse (RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED,NULL, 0);
} else if (strStartsWith(s, "+CMT:")) {
//新短信通知
RIL_onUnsolicitedResponse (RIL_UNSOL_RESPONSE_NEW_SMS,sms_pdu, strlen(sms_pdu));
} else if (strStartsWith(s, "+CDS:")) {
//短信报告
RIL_onUnsolicitedResponse (RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT,sms_pdu, strlen(sms_pdu));
} else if (strStartsWith(s, "+CGEV:")) {
RIL_requestTimedCallback (onDataCallListChanged, NULL, NULL);
}
}
可以看出,URC消息的处理流程基本上就是根据命令头的不同将其转化为不同的命令索引,然后调用RIL_onUnsolicitedResponse函数,而RIL_onUnsolicitedResponse的实现:
#define RIL_onUnsolicitedResponse(a,b,c) s_rilenv->OnUnsolicitedResponse(a,b,c)
说明这个函数调用的是s_rilenv变量的OnUnsolicitedResponse方法。那么s_rilenv是哪里初始化的呢?
我们在rild.c中的main函数中对reference库初始化时是这样的形式:
funcs = rilInit(&s_rilEnv, argc, rilArgv);
上面的初始化过程将s_rilEnv全局变量传递给了reference,然后在reference-ril.c内部将这个值传给了s_rilenv,而s_rilEnv的各个处理函数是在ril.cpp中实现的。
上面绕了一圈,还是把对消息的处理从动态库(也就是reference-ril.c文件)饶回到了ril.c文件中。这也符合整个RIL架构的设计理念:框架和处理方式由ril.c管理,差异化的AT命令由reference实现。
@ril.cpp
void RIL_onUnsolicitedResponse(int unsolResponse, void *data,
size_t datalen, RILId id)
{
//得到当前命令的请求码
unsolResponseIndex = unsolResponse - RIL_UNSOL_RESPONSE_BASE;
//从ril_unsol_commands.h文件中得到命令的类型
wakeType = s_unsolResponses[unsolResponseIndex].wakeType;
//根据不同命令的不同类型进行解析
switch (wakeType) {
case WAKE_PARTIAL:
case DONT_WAKE:
}
appendPrintBuf("[UNSL]< %s", requestToString(unsolResponse));
Parcel p;
p.writeInt32 (RESPONSE_UNSOLICITED);
p.writeInt32 (unsolResponse);
//调用当前命令的打包函数进行数据打包
ret = s_unsolResponses[unsolResponseIndex].responseFunction(p, data, datalen);
//把数据发送到RILJ中
ret = sendResponse(p,id);
if (shouldScheduleTimeout) {
}
return;
}
上面的处理过程分为2步:1、调用当前命令对应的打包函数进行数据打包;2、将数据发送给RILJ。
数据打包的过程涉及到一个数组s_unsolResponses,他的详细作用在本文的最后一张有说明,这里简要介绍一下。s_unsolResponses是一个文件,文件中对所有的URC消息都有一个对应的数组相对应,每个数组分3部分:1、命令的请求码;2、命令的打包函数;3、命令的类型;我们要做的就是用当前Modem给出的命令,找到对应的请求码,然后得到相应的打包函数进行数据打包。
而发送的过程就是调用sendResponse把Parcel数据发送给RILJ。
在上面的过程中,用reference中通过AT头转换的命令值与RIL_UNSOL_RESPONSE_BASE相减得到在s_unsolResponses表中对应的命令索引。查找相应的wakeType类型去决定是否计算超时(shouldScheduleTimeout),之后就用s_unsolResponses中的responseFunction去解析命令,最后通过sendResponse将数据发送给RILJ:
static int sendResponse (Parcel &p) {
return sendResponseRaw(p.data(), p.dataSize());
}
发送数据:
static int sendResponseRaw (const void *data, size_t dataSize) {
//发送通道的Socket ID
int fd = s_fdCommand;
int ret;
uint32_t header;
//将数据长度这个整数转换为适合Socket 传输的字节顺序
header = htonl(dataSize);
//先发送一个关于数据大小的字节
ret = blockingWrite(fd, (void *)&header, sizeof(header));
//再发送数据本身
ret = blockingWrite(fd, data, dataSize);
return 0;
}
继续看发送过程:
static int blockingWrite(int fd, const void *buffer, size_t len) {
size_t writeOffset = 0;
const uint8_t *toWrite;
toWrite = (const uint8_t *)buffer;
while (writeOffset < len) {
ssize_t written;
do {
//发送数据到fd指定的句柄中,也就是RILJ对应的Socket通道
written = write (fd, toWrite + writeOffset,len - writeOffset);
} while (written < 0 && ((errno == EINTR) || (errno == EAGAIN)));
}
return 0;
}
这里注意到,发送的最终操作,就是把数据放到一个fd指向的句柄中,那么这个句柄从哪里来的呢?
#define SOCKET_NAME_RIL "rild"
//得到"rild"的Socket连接
s_fdListen = android_get_control_socket(SOCKET_NAME_RIL);
//accept的函数默认会阻塞进程,直到有一个客户连接建立后把可用的连接套接字返回
s_fdCommand = accept(s_fdListen, (sockaddr *) &peeraddr, &socklen);
上面的递归关系我们可以看出,s_fdCommand就是RILJ与RILC之间建立的Socket通道的套接字!因此我们可以通过这个通道发送数据给RILJ。
2.3、非URC消息处理流程
上面介绍了URC消息的流程,下面分析一下更普遍的非URC消息的处理流程。
前面说道,非URC消息就是一种回应。当上层通过AT向Modem发送请求后,会一直处于阻塞状态等待回应,一旦readerLoop得到了非URC的消息,就会去唤醒Event端等待的进程。
我们在此主要介绍reference如何唤醒eventLoop端的线程。
非URC消息的上报流程也是从processLine开始的:
@atchannel.c
static void processLine(const char *line)
{
if (sp_response == NULL) {
} else if (isFinalResponseSuccess(line)) {
sp_response->success = 1;
//发送回应消息给eventLoop
handleFinalResponse(line);
} else if (isFinalResponseError(line)) {
} else if (s_smsPDU != NULL && 0 == strcmp(line, "> ")) {
} else switch (s_type) {
case NO_RESULT:
case NUMERIC:
if (sp_response->p_intermediates == NULL && isdigit(line[0])
) {
addIntermediate(line);
} else {
handleUnsolicited(line);
}
break;
case SINGLELINE:
if (sp_response->p_intermediates == NULL
&& strStartsWith (line, s_responsePrefix)
) {
addIntermediate(line);
} else {
handleUnsolicited(line);
}
break;
case MULTILINE:
break;
}
}
这里简要介绍以下Modem对于非URC消息的回复格式。消息一般大于2行,前几行是返回值的有效数据,最后一行是作为当前命令结束的标志位。如果是有效数据,那么当前数据就有一个s_type的类型与之相关联(从EventLoop发送给reference时决定)。因此就会在processLine中的switch中进入addIntermediate函数,而这个函数的作用就是把当前的数据放入到反馈数据的sp_response->p_intermediates里面。等到命令的最后,因为是标志位,就会走到processLine的handleFinalResponse中,将数据发送给Event侧。
下面贴出涉及到的重要函数:
a、将数据放到反馈数据中:addIntermediate
static void addIntermediate(const char *line)
{
ATLine *p_new;
p_new = (ATLine *) malloc(sizeof(ATLine));
p_new->line = strdup(line);
p_new->p_next = sp_response->p_intermediates;
//把有效的返回值放到sp_response->p_intermediates中
sp_response->p_intermediates = p_new;
}
b、判断是否已经把所有有效数据传输完毕:isFinalResponseSuccess
//结束符
static const char * s_finalResponsesSuccess[] = {
"OK",
"CONNECT"
};
//判断
static int isFinalResponseSuccess(const char *line)
{
size_t i;
for (i = 0 ; i < NUM_ELEMS(s_finalResponsesSuccess) ; i++) {
if (strStartsWith(line, s_finalResponsesSuccess[i])) {
return 1;
}
}
return 0;
}
c、发送数据给Event侧,取消Event侧阻塞的线程:handleFinalResponse
static void handleFinalResponse(const char *line)
{
//把回应消息返回给eventLoop
sp_response->finalResponse = strdup(line);
//发信号给s_commandcond线程,使其脱离阻塞状态
pthread_cond_signal(&s_commandcond);
}
三、一个完整的过程
经过上面的eventLoop和readerLoop过程分析,我们分别对eventLoop的机制和readerLoop中两种消息的处理有个大致的了解,但是还有一些问题我们没有解决,比如:
1、eventLoop所构建的循环如何接收RILJ的消息?又如何通过reference将消息发送到Modem?
2、上面说道reference接收到非URC消息后需要通知eventLoop读取消息,具体怎么通知eventLoop的?
这些问题将在这一节中详细说明。我们用一个完整的数据流来把两个loop串起来。而一个完整的数据流应该包括以下四个步骤:
1、Eventloop接收RILJ的请求,并负责把请求发送给reference库:Eventloop--->reference
2、reference负责把命令转化为AT命令,然后发送给Modem:reference--->Modem
3、reference通过readerLoop得到Modem回应后把数据返回给Eventloop: Modem--->ReaderLoop
4、Eventloop再把数据返回给RILJ:ReaderLoop--->Eventloop
下面我们就分别介绍这4个步骤的详细流程。
3.1、Eventloop把RILJ命令发送给reference库。
我们再次回到RILC层的入口处,前面两节介绍了Eventloop和reference,接下来就是RIL_register的入口: @rild.c
int main(int argc, char **argv)
{
//搭建EventLoop循环
RIL_startEventLoop();
//对reference动态库进行初始化
funcs = rilInit(&s_rilEnv, argc, rilArgv);
//注册reference 的回调函数
RIL_register(funcs);
}
上面看到,当我们调用reference的初始化函数(也就是RIL_Init)后,将会得到一个RIL_RadioFunctions类型的返回值:
@reference-ril.c
static const RIL_RadioFunctions s_callbacks = {
RIL_VERSION,
onRequest,
currentState,
onSupports,
onCancel,
getVersion
};
这个变量的类型为:
typedef struct {
int version; //当前链接库的版本信息
RIL_RequestFunc onRequest; //用于Event侧向动态库发起请求
RIL_RadioStateRequest onStateRequest; //得到当前库的状态
RIL_Supports supports; //查询是否支持某个命令
RIL_Cancel onCancel; //取消一个Event的处理
RIL_GetVersion getVersion; //得到版本号
} RIL_RadioFunctions;
这些成员函数中最重要的是onRequest,当我们在Event侧向reference库发起请求时,就是用的这个入口函数。而我们将用这个对象去完成注册的过程。
@ril.cpp
//将reference中的回调函数注册给RIL的框架
void RIL_register (const RIL_RadioFunctions *callbacks) {
//把返回值传给s_callbacks(全局变量)
memcpy(&s_callbacks, callbacks, sizeof (RIL_RadioFunctions));
//#define SOCKET_NAME_RIL "rild" 打开RILC与RILJ之间的Socket通道
s_fdListen = android_get_control_socket(SOCKET_NAME_RIL);
ret = listen(s_fdListen, 4);
//用这个Socket通道句柄创建一个Event
ril_event_set (&s_listen_event, s_fdListen, false, listenCallback, NULL);
//添加到Eventloop中
rilEventAddWakeup (&s_listen_event);
}
在上面的注册函数中主要完成了两个任务:1、将我们从reference得到的回调函数callbacks传递给一个全局变量s_callbacks;2、打开Socket通道并添加句柄到Eventloop中。对于第二个任务,我们看到在注册的过程中,通过android_get_control_socket的方法打开了RILJ与RILC之间的Socket通道,而且这个通道的文件句柄为s_fdListen(全局变量),并用这个通道的句柄构建一个Event,然后添加到Eventloop中。并且为这个Event注册了的回调函数listenCallback。
经过上面的过程,就在RILJ与EventLoop之间建立了沟通的渠道。
还记得我们在Eventloop中提到如果检测到句柄池中的某个句柄有新数据的话,就会调用将timer_list和watch_table中的Event放入pending_list中,然后调用当前Event的回调函数:
@ril_event.cpp
static void firePending()
{
struct ril_event * ev = pending_list.next;
while (ev != &pending_list) {
struct ril_event * next = ev->next;
removeFromList(ev);
//这里的func就是listenCallback
ev->func(ev->fd, 0, ev->param);
ev = next;
}
}
对于RILJ中过来的Event,我们注册的回调函数是listenCallback:
@ril.cpp
static void listenCallback (int fd, short flags, void *param) {
//从s_fdListen侦听套接字得到s_fdCommand(RILJ与RILC之间的流套接字)
s_fdCommand = accept(s_fdListen, (sockaddr *) &peeraddr, &socklen);
//得到当前命令的选项值
err = getsockopt(s_fdCommand, SOL_SOCKET, SO_PEERCRED, &creds, &szCreds);
//对文件描述符s_fdCommand进行控制,当前是设置其非阻塞模式
ret = fcntl(s_fdCommand, F_SETFL, O_NONBLOCK);
//再次把当前命令放入Eventloop
ril_event_set (&s_commands_event, s_fdCommand, 1, processCommandsCallback, p_rs);
rilEventAddWakeup (&s_commands_event);
//发送URC消息,通知RIL状态发生改变
onNewCommandConnect();
}
上面的过程分为2步:1、把当前消息重新发送到Eventloop;2、发送URC消息,通知RILJ(RIL状态改变了)。我们先来看一下第二个过程:
static void onNewCommandConnect() {
//给RILJ发送URC消息,RIL连接成功
RIL_onUnsolicitedResponse(RIL_UNSOL_RIL_CONNECTED, &rilVer, sizeof(rilVer));
//给RILJ发送URC消息,告诉RILJ,Radio状态改变了
RIL_onUnsolicitedResponse(RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED, NULL, 0);
}
我们再分析上面的第一个步骤,也就是把消息发送到Eventloop中的过程。当发送到Eventloop后,Eventloop就会调用当前Event的回调函数,现在的回调函数是processCommandsCallback:
static void processCommandsCallback(int fd, short flags, void *param) {
for (;;) {
ret = record_stream_get_next(p_rs, &p_record, &recordlen);
if (ret == 0 && p_record == NULL) {
break;
} else if (ret < 0) {
break;
} else if (ret == 0) { /* && p_record != NULL */
//把RILJ层数据通过AT发送到Modem
processCommandBuffer(p_record, recordlen);
}
}
if (ret == 0 || !(errno == EAGAIN || errno == EINTR)) {
//命令已经发送完成,关闭当前命令的流套接字
close(s_fdCommand);
s_fdCommand = -1;
//删掉当前Event
ril_event_del(&s_commands_event);
record_stream_free(p_rs);
//重新添加RILJ与RILC之间的Socket Event
rilEventAddWakeup(&s_listen_event);
onCommandsSocketClosed();
}
}
上面看到,发送命令给Modem是通过processCommandBuffer实现的:
static int processCommandBuffer(void *buffer, size_t buflen) {
Parcel p;
int32_t request;
int32_t token;
RequestInfo *pRI;
p.setData((uint8_t *) buffer, buflen);
//得到请求码和令牌
status = p.readInt32(&request);
status = p.readInt32 (&token);
pRI = (RequestInfo *)calloc(1, sizeof(RequestInfo));
//设置当前请求的令牌
pRI->token = token;
//s_commands中针对不同的命令对应不同的处理函数
pRI->pCI = &(s_commands[request]);
//链表结构
pRI->p_next = s_pendingRequests;
s_pendingRequests = pRI;
//调用reference 中的
pRI->pCI->dispatchFunction(p, pRI);
return 0;
}
首先说明一个很重要的标志位:token令牌;这个令牌可以看作当前请求的ID,当我们从Modem得到数据后需要根据不同的令牌找到当初的请求命令,然后做出相应的答复。
这里还涉及到一个特殊的数组s_commands。他的作用和s_unsolResponses类似,详细说明在本文的最后一章,这里还是简单介绍一下他的作用:
s_commands是一个数组,每个RILJ发送过来的命令在s_commands中都对应一个元素,而每个元素包含3个数据:
typedef struct {
int requestNumber;
void (*dispatchFunction) (Parcel &p, struct RequestInfo *pRI);
int(*responseFunction) (Parcel &p, void *response, size_t responselen);
} CommandInfo;
其中requestNumber表示当前命令的编号;dispatchFunction的作用是,当前命令可以通过这个接口把数据发送到reference库;responseFunction的作用是:当Modem返回数据后reference侧可以用这个函数把数据进行打包,然后传递给Event侧。
而在processCommandBuffer中要做的就是通过当前的命令号,找到对应的发送函数(dispatchFunction)和打包函数(responseFunction)。然后把这三个数据连同当前命令的令牌(当前命令的ID)构建一个在Event和reference侧通用的数据类型(RequestInfo)并把它发送给reference侧。
假如我们从RILJ得到的命令号为RIL_REQUEST_GET_SIM_STATUS(得到当前SIM卡的状态),那么对应的,就需要调用当前命令的发送函数,而在s_commands中对于这条命令的描述为:
{RIL_REQUEST_GET_SIM_STATUS, dispatchVoid, responseSimStatus},
因此,它对应的发送函数就是dispatchVoid:
@ril.cpp
static void dispatchVoid (Parcel& p, RequestInfo *pRI) {
//发送数据到Modem
clearPrintBuf;
//打印Log信息RLOGD("[%04d]> %s %s", token, requestToString(req), printBuf)
printRequest(pRI->token, pRI->pCI->requestNumber);
//s_callbacks是从reference注册过来的
s_callbacks.onRequest(pRI->pCI->requestNumber, NULL, 0, pRI);
}
这里看到,将命令发送到reference库是通过s_callbacks的onRequest方法实现的,而且发送时会将命令号和ril_commands.h中对应的当前命令的信息一同发送。
而关于s_callbacks这个全局变量,我们在这一节的最初就讲到,当我们调用reference中的RIL_Init完成初始化时,就会得到reference返回当前链接库提供的接口函数,而s_callbacks正是来自于这些接口:
@reference-ril.c
static const RIL_RadioFunctions s_callbacks = {
RIL_VERSION,
onRequest,
currentState,
onSupports,
onCancel,
getVersion
};
因此,当上面的Eventloop将数据通过s_callbacks.onRequest发送给reference的过程就是调用reference-ril.c中的onRequest的过程。
之后,Eventloop就完成了下发命令的任务,接下来需要reference完成把命令发送给Modem的任务。
3.2、reference将Eventloop的数据发送到Modem
上面说道,s_callbacks.onRequest其实就是reference-ril.c中的onRequest,经过这次调用,就将数据由Eventloop侧传到了reference侧。
@reference-ril.c
static void onRequest (int request, void *data, size_t datalen, RIL_Token t)
{
ATResponse *p_response;
RLOGD("onRequest: %s", requestToString(request));
//我们重点看RIL_REQUEST_GET_SIM_STATUS
switch (request) {
case RIL_REQUEST_GET_SIM_STATUS: {
RIL_CardStatus_v6 *p_card_status;
char *p_buffer;
int buffer_size;
//与Modem交互,发送命令并得到回应
int result = getCardStatus(&p_card_status);
//把回应传回给Eventloop
RIL_onRequestComplete(t, result, p_buffer, buffer_size);
freeCardStatus(p_card_status);
break;
}
case RIL_REQUEST_GET_CURRENT_CALLS:
//得到当前通话
requestGetCurrentCalls(data, datalen, t);
break;
case RIL_REQUEST_DIAL:
//拨号
requestDial(data, datalen, t);
break;
case RIL_REQUEST_HANGUP:
//挂起
requestHangup(data, datalen, t);
}
}
从onRequest可以看出,reference中对所有的命令请求进行判别,然后选择不同的处理方式。对于尝试得到SIM状态这个请求来说,不仅需要通过getCardStatus得到SIM卡状态,而且还需要调用RIL_onRequestComplete将得到的状态通过Eventloop返回给RILJ。我们先看给Modem发送数据的过程
static int getCardStatus(RIL_CardStatus_v6 **pp_card_status) {
//SIM卡所有可能的状态
static RIL_AppStatus app_status_array[] = {
//SIM_ABSENT = 0 SIM不存在
{ RIL_APPTYPE_UNKNOWN, RIL_APPSTATE_UNKNOWN, RIL_PERSOSUBSTATE_UNKNOWN,
NULL, NULL, 0, RIL_PINSTATE_UNKNOWN, RIL_PINSTATE_UNKNOWN },
// SIM_NOT_READY = 1 SIM未就绪
{ RIL_APPTYPE_SIM, RIL_APPSTATE_DETECTED, RIL_PERSOSUBSTATE_UNKNOWN,
NULL, NULL, 0, RIL_PINSTATE_UNKNOWN, RIL_PINSTATE_UNKNOWN },
// SIM_READY = 2 SIM就绪
{ RIL_APPTYPE_SIM, RIL_APPSTATE_READY, RIL_PERSOSUBSTATE_READY,
NULL, NULL, 0, RIL_PINSTATE_UNKNOWN, RIL_PINSTATE_UNKNOWN },
....
};
//查询SIM状态,将AT命令发送到Modem,然后得到Modem的数据
int sim_status = getSIMStatus();
RIL_CardStatus_v6 *p_card_status = malloc(sizeof(RIL_CardStatus_v6));
if (num_apps != 0) {
// Only support one app, gsm
p_card_status->num_applications = 2;
p_card_status->gsm_umts_subscription_app_index = 0;
p_card_status->cdma_subscription_app_index = 1;
// Get the correct app status
p_card_status->applications[0] = app_status_array[sim_status];
p_card_status->applications[1] = app_status_array[sim_status + RUIM_ABSENT];
}
*pp_card_status = p_card_status;
//将状态返回给reference
return RIL_E_SUCCESS;
}
继续看发送过程
static SIM_Status getSIMStatus()
{
//将命令转换为AT命令发送到Modem,而modem的回应放到p_response中
err = at_send_command_singleline("AT+CPIN?", "+CPIN:", &p_response);
//取得返回值
cpinLine = p_response->p_intermediates->line;
err = at_tok_start (&cpinLine);
err = at_tok_nextstr(&cpinLine, &cpinResult);
//根据返回值得到当前SIM卡状态
if (0 == strcmp (cpinResult, "SIM PIN")) {
//PIN锁
ret = SIM_PIN;
goto done;
} else if (0 == strcmp (cpinResult, "SIM PUK")) {
//PUK锁
ret = SIM_PUK;
goto done;
} else if (0 == strcmp (cpinResult, "PH-NET PIN")) {
return SIM_NETWORK_PERSONALIZATION;
} else if (0 != strcmp (cpinResult, "READY")) {
//SIM卡不存在
ret = SIM_ABSENT;
goto done;
}
//返回结果
done:
at_response_free(p_response);
return ret;
}
继续看at_send_command_singleline的发送过程:
@atchannel.c
int at_send_command_singleline (const char *command,
const char *responsePrefix,
ATResponse **pp_outResponse)
{
err = at_send_command_full (command, SINGLELINE, responsePrefix, NULL, 0, pp_outResponse);
return err;
}
static int at_send_command_full (const char *command, ATCommandType type,
const char *responsePrefix, const char *smspdu,
long long timeoutMsec, ATResponse **pp_outResponse)
{
//发送
err = at_send_command_full_nolock(command, type,
responsePrefix, smspdu,
timeoutMsec, pp_outResponse);
return err;
}
继续看
static int at_send_command_full_nolock (const char *command, ATCommandType type,
const char *responsePrefix, const char *smspdu,
long long timeoutMsec, ATResponse **pp_outResponse)
{
//给Modem发消息AT
err = writeline (command);
//创建新的sp_response作为回应
sp_response = at_response_new();
//上面发送完消息后需要阻塞等待Modem回应
while (sp_response->finalResponse == NULL && s_readerClosed == 0) {
if (timeoutMsec != 0) {
//线程进入阻塞状态,等待另一线程满足s_commandcond 后解除
err = pthread_cond_timeout_np(&s_commandcond, &s_commandmutex, timeoutMsec);
}
}
if (pp_outResponse == NULL) {
//释放sp_response
at_response_free(sp_response);
} else {
reverseIntermediates(sp_response);
//将回应发送给当初请求AT的线程
*pp_outResponse = sp_response;
}
return err;
}
在上面的函数中,完成了两个重要动作:1、通过writeline发送数据到Modem;2、阻塞当前线程,等待Modem回应。我们先来看writeline的过程:
static int writeline (const char *s)
{
size_t len = strlen(s);
ssize_t written;
//Log信息
RLOGD("AT> %s\n", s);
while (cur < len) {
do {
//s_fd就是Modem与RILC之间的串口
written = write (s_fd, s + cur, len - cur);
} while (written < 0 && errno == EINTR);
cur += written;
}
do {
//以r结尾
written = write (s_fd, "\r" , 1);
} while ((written < 0 && errno == EINTR) || (written == 0));
return 0;
}
经过上面的操作,就将一条命令传输到了Modem侧。
上面说道,at_send_command_full_nolock函数的另一个作用是阻塞当前线程,等待Modem回应,那么,如何实现阻塞的过程?又需要在什么样的情况下解除阻塞状态?又是如何在解除阻塞时把Modem的数据读取出来呢?这一串的疑问我们在接下来的小节中讲解。
3.3、reference通过readerLoop得到Modem回应后把数据返回给Eventloop
我们在上面的at_send_command_full_nolock函数中,调用writeline将命令写入Modem后,还做了一个很重要的动作,就是阻塞当前线程,等待Modem回应。我们再次回到at_send_command_full_nolock: static int at_send_command_full_nolock (const char *command, ATCommandType type,
const char *responsePrefix, const char *smspdu,
long long timeoutMsec, ATResponse **pp_outResponse)
{
//给Modem发消息AT
err = writeline (command);
//创建新的sp_response作为回应
sp_response = at_response_new();
//上面发送完消息后需要阻塞等待Modem回应
while (sp_response->finalResponse == NULL && s_readerClosed == 0) {
if (timeoutMsec != 0) {
//线程进入阻塞状态,等待另一线程满足s_commandcond 后解除
err = pthread_cond_timeout_np(&s_commandcond, &s_commandmutex, timeoutMsec);
}
}
if (pp_outResponse == NULL) {
//释放sp_response
at_response_free(sp_response);
} else {
reverseIntermediates(sp_response);
//将回应发送给当初请求AT的线程
*pp_outResponse = sp_response;
}
return err;
}
上面的sp_response是返回给RILC的数据。而while循环中就是在阻塞状态下等待Modem的回应。这里需要简要说明一下pthread_cond_timeout_np的作用,pthread_cond_timeout_np的第一个参数s_commandcond是一种条件变量,而条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。
在这里,为了等待s_commandcond条件变量而自动阻塞了当前线程。那么,只有当另一个线程通过pthread_cond_signal接口是s_commandcond条件满足时,当前阻塞的线程才会被唤醒。
那么是在哪里去唤醒当前线程的呢?
我们分析一下,当我们发送数据或命令给Modem的时候,阻塞了当前的线程,阻塞的目的就是等待Modem的回应,而如果Modem有数据上来,那么肯定是先被reference的ReaderLoop检测到并处理,因此,也应该是在ReaderLoop的消息处理中去唤醒当前阻塞的线程,而且应该把Modem的反馈传输给阻塞线程。
我们直接来看ReaderLoop的处理函数,因为作为Modem的回应,当前消息一定不是URC消息,因此处理函数就是processLine:
@atchannel.c
static void processLine(const char *line)
{
if (sp_response == NULL) {
} else if (isFinalResponseSuccess(line)) {
sp_response->success = 1;
//发送回应消息给EventLoop
handleFinalResponse(line);
} else if (isFinalResponseError(line)) {
} else if (s_smsPDU != NULL && 0 == strcmp(line, "> ")) {
} else switch (s_type) {
case NO_RESULT:
case NUMERIC:
case SINGLELINE:
if (sp_response->p_intermediates == NULL
&& strStartsWith (line, s_responsePrefix)
) {
addIntermediate(line);
} else {
/* we already have an intermediate response */
handleUnsolicited(line);
}
break;
case MULTILINE:
break;
}
}
上面在分析processLine时就说过,Modem给的数据可能有很多行,组后一行是OK的标志位,而之前的行都是有效数据。我们还知道,对于得到SIM卡状态这个请求,他的s_type是SINGLELINE(传递给reference时决定的)。
因此,我们接受的Modem数据应该先被switch中的SINGLELINE处理,而这一步的处理就是把Modem的有效数据放到sp_response->p_intermediates中:
static void addIntermediate(const char *line)
{
ATLine *p_new;
p_new = (ATLine *) malloc(sizeof(ATLine));
p_new->line = strdup(line);
p_new->p_next = sp_response->p_intermediates;
sp_response->p_intermediates = p_new;
}
等有效数据处理完后,再次进入processLine时,遇到了标志位行,因此isFinalResponseSuccess判断成立,去唤醒另一端阻塞的线程:
static void handleFinalResponse(const char *line)
{
//把回应消息返回给RIL
sp_response->finalResponse = strdup(line);
//s_commandcond条件变量的条件得到满足,将会唤醒相应的阻塞线程
pthread_cond_signal(&s_commandcond);
}
到这里,完全符合我们的预期,也就是在ReaderLoop的处理过程中,满足条件变量,从而使等待在pthread_cond_timeout_np的线程脱离阻塞状态,同时,把Modem的回应(line)传递给sp_response->p_intermediates。
此时,我们可以再次回到被阻塞的线程,看看接下来的数据传输过程:
@atchannel.c
static int at_send_command_full_nolock (const char *command, ATCommandType type,
const char *responsePrefix, const char *smspdu,
long long timeoutMsec, ATResponse **pp_outResponse)
{
//给Modem发消息AT
err = writeline (command);
//创建新的sp_response作为回应
sp_response = at_response_new();
//上面发送完消息后需要阻塞等待Modem回应
while (sp_response->finalResponse == NULL && s_readerClosed == 0) {
if (timeoutMsec != 0) {
//线程进入阻塞状态,等待另一线程满足s_commandcond后解除
err = pthread_cond_timeout_np(&s_commandcond, &s_commandmutex, timeoutMsec);
}
}
if (pp_outResponse == NULL) {
//释放sp_response
at_response_free(sp_response);
} else {
reverseIntermediates(sp_response);
//将回应发送给当初请求AT的线程
*pp_outResponse = sp_response;
}
return err;
}
当这个函数从pthread_cond_timeout_np中唤醒后,如果当前的命令确实要求Modem给出相应的回应,就会把回应的数据放到pp_outResponse中,返回给上层使用。我们知道,当前的sp_response->finalResponse就是Modem给出的回应。接着,at_send_command_full_nolock就返回了。
我们再次回顾一下当初调用到pthread_cond_timeout_np的路径:
getSIMStatus()-->at_send_command_singleline()->at_send_command_full()->at_send_command_full_nolock();
因此,我们再次回到getSIMStatus中查看:
static SIM_Status getSIMStatus()
{
//将命令转换为AT命令发送到Modem,而modem的回应放到p_response中
err = at_send_command_singleline("AT+CPIN?", "+CPIN:", &p_response);
//取得返回值
cpinLine = p_response->p_intermediates->line;
err = at_tok_start (&cpinLine);
err = at_tok_nextstr(&cpinLine, &cpinResult);
//根据返回值得到当前SIM卡状态
if (0 == strcmp (cpinResult, "SIM PIN")) {
//PIN锁
ret = SIM_PIN;
goto done;
} else if (0 == strcmp (cpinResult, "SIM PUK")) {
//PUK锁
ret = SIM_PUK;
goto done;
} else if (0 == strcmp (cpinResult, "PH-NET PIN")) {
return SIM_NETWORK_PERSONALIZATION;
} else if (0 != strcmp (cpinResult, "READY")) {
//SIM卡不存在
ret = SIM_ABSENT;
goto done;
}
}
在getSIMStatus中,当我们从at_send_command_singleline中返回后,用Modem给的数据p_response->p_intermediates得到了当前SIM的状态。
我们在往上层看,当初是在getCardStatus中调用getSIMStatus的:
static int getCardStatus(RIL_CardStatus_v6 **pp_card_status) {
//SIM卡所有可能的状态
static RIL_AppStatus app_status_array[] = {
{ RIL_APPTYPE_UNKNOWN, RIL_APPSTATE_UNKNOWN, RIL_PERSOSUBSTATE_UNKNOWN,
NULL, NULL, 0, RIL_PINSTATE_UNKNOWN, RIL_PINSTATE_UNKNOWN },
{ RIL_APPTYPE_SIM, RIL_APPSTATE_DETECTED, RIL_PERSOSUBSTATE_UNKNOWN,
NULL, NULL, 0, RIL_PINSTATE_UNKNOWN, RIL_PINSTATE_UNKNOWN },
};
//查询SIM状态,将AT命令发送到Modem,然后得到Modem的数据
int sim_status = getSIMStatus();
//得到返回值
p_card_status->applications[0] = app_status_array[sim_status];
p_card_status->applications[1] = app_status_array[sim_status + RUIM_ABSENT];
//把返回值带回到上一级中
*pp_card_status = p_card_status;
return RIL_E_SUCCESS;
}
在这个函数中,我们通过getSIMStatus得到的int型数据,由app_status_array数组转换为各个更容易理解的状态,然后放入pp_card_status返回值中,返回给父一级。
@reference-ril.c
static void onRequest (int request, void *data, size_t datalen, RIL_Token t)
{
switch (request) {
case RIL_REQUEST_GET_SIM_STATUS: {
RIL_CardStatus_v6 *p_card_status;
char *p_buffer;
int buffer_size;
//与Modem交互,发送命令并得到回应
int result = getCardStatus(&p_card_status);
p_buffer = (char *)p_card_status;
buffer_size = sizeof(*p_card_status);
//把回应传回给Eventloop
RIL_onRequestComplete(t, result, p_buffer, buffer_size);
freeCardStatus(p_card_status);
break;
}
}
}
这个函数就是当Eventloop中有请求时,我们把请求发送到注册的reference库的回调函数中,而reference的回调函数就是onRequest。在这里,当我们从getCardStatus返回后,就把数据转换为buffer,然后通过RIL_onRequestComplete重新返回给Eventloop,当我们去观察这个函数参数时,除了表示成功或失败的result和表示数据的buffer外,还有一个t,而这个t就是我们前面一再提到的令牌。当我们从RILJ发送数据时,就会把一个表示当前这个请求的的令牌传递下去,而当Modem返回数据后,我们再通过这个令牌找到当初发送的请求,然后把数据传递给他。接下来我们看这个函数的具体实现:
#define RIL_onRequestComplete(t, e, response, responselen) s_rilenv->OnRequestComplete(t,e, response, responselen)
我们看到,RIL_onRequestComplete就是s_rilenv的OnRequestComplete函数,而s_rilenv的来历我们在前面已经介绍过,他的各个处理函数都在ril.cpp中。
因此,到这里,我们的流程就又走到了ril.cpp中的RIL_onRequestComplete里面。而从流程上来讲,我们就将从Modem得到的回应,又由reference库交接给了Event端。
接下来要做的就是在Event侧把数据重新返回给RILJ。
3.4、Eventloop把数据返回给RILJ
我们直接来看此时的接收函数: @ril.cpp
void RIL_onRequestComplete(RIL_Token t, RIL_Errno e, void *response, size_t responselen) {
RequestInfo *pRI;
pRI = (RequestInfo *)t;
Parcel p;
p.writeInt32 (RESPONSE_SOLICITED);
p.writeInt32 (pRI->token);
errorOffset = p.dataPosition();
p.writeInt32 (e);
if (response != NULL) {
//先调用当前请求的回调函数
ret = pRI->pCI->responseFunction(p, response, responselen);
if (ret != 0) {
p.setDataPosition(errorOffset);
p.writeInt32 (ret);
}
}
//发送数据给RILJ
sendResponse(p);
}
我们看到,在RILC发送数据给RILJ之前,会先调用当前命令的回应处理函数,也就是responseFunction,对将要传输的数据进行差异化处理,然后再通过sendResponse发送给RILJ。
那么responseFunction函数的具体形式是什么呢?
我们回顾一下,我们当初在接到RILJ的命令后,由Eventloop把命令交给reference之前,在processCommandBuffer中构建了用于在RILC层传输的RequestInfo数据类型,而构建这个数据的原理就是在ril_commands.h中查找当前命令所对应的两个回调函数,一个用于向reference传输时使用,另一个用于reference给Eventloop回应时使用。responseFunction指的就是第二个回调函数。
{RIL_REQUEST_GET_SIM_STATUS, dispatchVoid, responseSimStatus},
也就是说,发送到reference时我们需要调用dispatchVoid,而从reference接受Modem的回应时,我们就要从responseSimStatus进入了:
@ril.cpp
static int responseSimStatus(Parcel &p, void *response, size_t responselen) {
RIL_CardStatus_v6 *p_cur = ((RIL_CardStatus_v6 *) response);
p.writeInt32(p_cur->card_state);
p.writeInt32(p_cur->universal_pin_state);
p.writeInt32(p_cur->gsm_umts_subscription_app_index);
p.writeInt32(p_cur->cdma_subscription_app_index);
p.writeInt32(p_cur->ims_subscription_app_index);
sendSimStatusAppInfo(p, p_cur->num_applications, p_cur->applications);
}
接下来:
static void sendSimStatusAppInfo(Parcel &p, int num_apps, RIL_AppStatus appStatus[]) {
p.writeInt32(num_apps);
startResponse;
for (int i = 0; i < num_apps; i++) {
p.writeInt32(appStatus[i].app_type);
p.writeInt32(appStatus[i].app_state);
p.writeInt32(appStatus[i].perso_substate);
writeStringToParcel(p, (const char*)(appStatus[i].aid_ptr));
writeStringToParcel(p, (const char*)(appStatus[i].app_label_ptr));
p.writeInt32(appStatus[i].pin1_replaced);
p.writeInt32(appStatus[i].pin1);
p.writeInt32(appStatus[i].pin2);
}
closeResponse;
}
其实这里所谓的打包处理,不过是根据不同的命令,把相应的回应写入到给RILJ的返回数据包中。
然后在RIL_onRequestComplete中调用sendResponse把数据发送到RILJ层。
以上就是RIL数据传输的整个过程。
现在,我们再来回顾一下在本文开始处展示的RIL层数据流向图,希望这次看到他时不会那么的陌生:
四、一些重要概念
4.1、s_commandcond
我们知道,RIL是一个框架,并不会在意每个命令之间的差异,但是每个命令所发送的数据不同,要求的回应更是不同。而这个数组就是完成了从标准化到差异化的转换过程。先来看一下定义:
static CommandInfo s_commands[] = {
#include "ril_commands.h"
};
以上这个变量定义在是一个数组,类型如下:
typedef struct {
int requestNumber;
void (*dispatchFunction) (Parcel &p, struct RequestInfo *pRI);
int(*responseFunction) (Parcel &p, void *response, size_t responselen);
} CommandInfo;
这个结构体中有3个数据:requestNumber表示一个命令的请求码;dispatchFunction表示当前命令发送给reference库时的入口函数;而responseFunction表示reference给出应答后需要用这个函数对应答数据进行封装,然后传递给RILJ;
主要内容如下:
{RIL_REQUEST_GET_SIM_STATUS, dispatchVoid, responseSimStatus},
{RIL_REQUEST_ENTER_SIM_PIN, dispatchStrings, responseInts},
{RIL_REQUEST_ENTER_SIM_PUK, dispatchStrings, responseInts},
结合到流程来说,当我们从Eventloop中读取一条RILJ发送的请求后,在Eventloop的发送最后(processCommandBuffer函数)根据当前命令去s_commands中查找相应的dispatchFunction和responseFunction,组成一个RequestInfo的数据。然后调用当前命令的dispatchFunction函数将RequestInfo数据发送给reference库。
而当reference接收到Modem数据后,根据当前命令的RequestInfo信息,查找当前命令的应答回调(responseFunction)对回应数据进行封装,封装后调用统一的reference到Event侧的接口函数(RIL_onRequestComplete)把数据发送给Event侧,再由Event侧发送到RILJ。
4.2、s_unsolResponses
对比上面的s_commands来看,这个变量就显得比较简单了。我们先来看一下他的定义: static UnsolResponseInfo s_unsolResponses[] = {
#include "ril_unsol_commands.h"
};
然后列出几个重要的数组内容
{RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED, responseVoid, WAKE_PARTIAL},
{RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED, responseVoid, WAKE_PARTIAL},
{RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED, responseVoid, WAKE_PARTIAL},
再来看数据类型:
typedef struct {
int requestNumber;
int (*responseFunction) (Parcel &p, void *response, size_t responselen);
WakeType wakeType;
} UnsolResponseInfo;
这里我们看到,他和s_commands的区别就在于他的里面只有responseFunction而没有dispatchFunction。为什么会这样呢?原因就在于URC消息与非URC消息的区别上。
上面我们讲到,非URC消息是由RILJ主动发起的并且需要接受Modem的数据;而URC消息是Modem主动上报的(比如网络状态改变的消息),因此没有发送的过程,只有接受消息的过程。因此不需要dispatchFunction。
再次结合流程来看一下,当我们在reference的ReaderLoop中检测到Modem消息时,如果是URC消息,就在reference中根据AT命令头的不同转换为相应的命令号(onUnsolicited中完成),然后把数据发送给Event侧(RIL_onUnsolicitedResponse接收),在Event侧根据命令号的不同找到s_unsolResponses中对应的responseFunction,对数据进行不同的封装,然后发送给RILJ(仍在RIL_onUnsolicitedResponse中处理)。
4.3、RequestInfo
这个数据类型是Event侧与reference侧协定的统一命令格式。所有的命令,当从Event侧发送到reference侧时,都要把他标准化为RequestInfo结构。数据结构:
typedef struct RequestInfo {
int32_t token;
CommandInfo *pCI;
struct RequestInfo *p_next;
char cancelled;
char local;
} RequestInfo;
其中token是当前命令的令牌,相当于命令的ID。pCI的数据结构:
typedef struct {
int requestNumber;
void (*dispatchFunction) (Parcel &p, struct RequestInfo *pRI);
int(*responseFunction) (Parcel &p, void *response, size_t responselen);
} CommandInfo;
这个数据表明了当前命令的3个很重要属性:1、当前命令的请求码;2、当前命令发送到reference侧的入口;3、当前命令从reference返回给Event侧时要调用responseFunction进行数据封装。