1.简述
Radio Interface Layer,简称RIL,在手机上是Modem与AP通讯的桥梁,RIL扮演的角色非常重要,RIL被设计成能够可靠的高效的传输数据一个模块。以下是RIL在Modem与AP中的位置:
Android RIL可以分成2个模块,一个部分RIL Demon(RILD),用于通过socket和framework通讯;另一部分是第三方自己客制化的部分,在这里暂时称之为vendor RIL。之所以这样设计是因为不同的厂商使用的Modem不一样,而RIL又和Modem紧密联系,所以Android有把和Modem联系紧密的部分和公共部分剥离开,让不同的厂商可以客制化vendor RIL以适应厂商自己的Modem。Vendor RIL专门负责通过AT和Modem进行通讯。所以又可以细化称为:
了解RIL的用途之后,接下来看看RIL的文件结构。RIL有三个重要的目录: rild, libril和reference-ril。
2. 多路复用介绍
Android的ril有开多个socket用于通讯,由于socket的部分函数为阻塞式的,如果某一个socket连接上发生阻塞,会影响到其他的socket连接的处理数据,所以android的ril使用了select来实现同时接收socket的连接(多路复用模型)。Select是linux系统中实现多路复用的函数之一,它可以同时监听多个文件描述符(file descriptor, fd),有一个或多个fd上有数据可读、可写或者出现异常,该函数则返回并带回需要处理的socket的fd以及个数。
A. 使用普通的无限循环轮询方式,一次接收与处理一个客户端:
B. 使用select实现多路复用,通过监听客户端的socket,同时服务。
3. Android Ril的事件及其处理方法
3.1 ril_event --- ril_event.cpp
过目一下事件的结构
int fd; // 文件描述符 pipe/socket,这就是要加到fdset集合中的玩意,select时会用到。
int index;//watch_table数组的索引,用于清空该数字对应的元素
Bool persist;//指示该ev在触发之后是否需要移出watch_table
Timeval timeout;//计时器触发的时间,微秒级
Ril_event_cb func;//事件触发时的回调函数
Void *param;//以上回调函数的参数
事件有两种:
(1)监听socket或pipe的事件,若socket或pipe可读、可写或者异常时会触发select返回。
(2)计时器事件,该类事件没有文件描述符,指定一个未来的时间,当时间到时select返回。
3.2 select的I/O多路复用
Android RIL使用Select来实现多路复用,一下是函数列表
Select有五个参数
参数1:int, 为所监听的fd数值的最大值+1。
参数2:fd_set类型的fd集合,此处参数为监听读的fd,当fd有数据需要读取时触发。
参数3:fd_set类型的fd集合,此处为监听写的fd集合。
参数4:fd_set类型的fd集合,此处为发生异常的fd的集合。
参数5:为timeval的时间,用于设定在规定时间到时select返回。当该值为NULL时,表示函数在有fd发生变化时才返回;为0,则立即返回。
返回值为发生可读、可写或者异常的fd的个数。
默认情况下,select可以同时监听1024个fd,参见宏定义
#define FD_SETSIZE __FD_SETSIZE //1024 --- time.h
fd的操作时通过以下系列函数:
#define FD_SET(fd,fdsetp) __FD_SET(fd,fdsetp) //把fd设定到fdset集合中
#define FD_CLR(fd,fdsetp) __FD_CLR(fd,fdsetp) //从fdset集合中清除fd
#define FD_ISSET(fd,fdsetp) __FD_ISSET(fd,fdsetp) //判断fd是否在fdset集合中
#define FD_ZERO(fdsetp) __FD_ZERO(fdsetp) //清空fdset集合
3.3 RIL事件处理解析
在有了上面的基础之后,接下来去挖一挖RIL是如何控制事件的。RIL事件相关的操作在文件ril_event.cpp中。关于RIL事件处理的话,涉及到参数:
static struct ril_event * watch_table[MAX_FD_EVENTS];//所需要监听的事件,都放到这个数组中,长度为8
static struct ril_event timer_list; //如果监听的是计时器,这加到这个链表中。上面有讲到这是ril_event个具有双向链表性质的结构体。提问: 为什么这里使用到链表,而watch_table却是用数组呢???
static struct ril_event pending_list; //这玩意也是一个链表,装载的是已经触发了的事件或计时器。
3.3.1让我们从rild.c开始寻找,
Rild.c编译成rild的模块,在init.rc中以一个service的形式被创建。
在该文件的mian函数中RIL开始初始化,做了三件事:
(1)创建事件循环,其实就是呼叫RIL_startEventLoop()
(2)打开vendor RIL的链接库,并呼叫其中RIL_Init函数
(3)RIL注册(暂且这么说吧,其实也没注册什么东西),呼叫RIL_register()
A. 先看第一件事:呼叫RIL_startEventLoop(ril.cpp)的函数:
这个函数很简单,就是单单创建一个线程(线程id=s_tid_dispatch),等到线程创建并运行了再返回。
extern "C" void
RIL_startEventLoop(void) {
...
ret = pthread_create(&s_tid_dispatch, &attr, eventLoop, NULL); //创建eventLoop线程
while (s_started == 0) {<==等待eventLoop创建并运行
pthread_cond_wait(&s_startupCond, &s_startupMutex);
}
...
}
再接着看eventLoop的代码,创建一个匿名管道,并把管道的read端加到事件列表里面(这里在添加事件是有一个callback,在事件触发是执行该callback。processWakeupCallback里面很简单,就是读取管道上的数据),然后就呼叫ril_event_loop()进入循环。
static void *
eventLoop(void *param) {
int ret;
int filedes[2];
ril_event_init();
pthread_mutex_lock(&s_startupMutex);
s_started = 1;
pthread_cond_broadcast(&s_startupCond);
pthread_mutex_unlock(&s_startupMutex);
ret = pipe(filedes); //创建一个无名管道
if (ret < 0) {
ALOGE("Error in pipe() errno:%d", errno);
return NULL;
}
s_fdWakeupRead = filedes[0]; //管道读端
s_fdWakeupWrite = filedes[1]; //管道写段
fcntl(s_fdWakeupRead, F_SETFL, O_NONBLOCK);
ril_event_set (&s_wakeupfd_event, s_fdWakeupRead, true,
processWakeupCallback, NULL); //使用管道的读端生成一个事件,这个事件的用途下面会介绍到。
rilEventAddWakeup (&s_wakeupfd_event);
// Only returns on error
ril_event_loop();
ALOGE ("error in event_loop_base errno:%d", errno);
// kill self to restart on error
kill(0, SIGKILL);
return NULL;
}
Ril_event_loop就是一个for的无限循环,在这个for内部看到了前面讲到的select函数了,其实select值监测read的fd_set,所要监听的fd都存放在全局变量readFds中,ptv决定select block的形态,要么设定时间block直到到期,要么无限block直到有监听fd上数据可读,当select返回后就会查找是哪个事件的fd的触发的,然后通过firePending()呼叫该事件的callback。注意这是循环的内部,也就是说每当select返回并执行其他动作之后,又会重新把readFds加到select中。
void ril_event_loop()
{
int n;
fd_set rfds;
struct timeval tv;
struct timeval * ptv;
for (;;) {
// make local copy of read fd_set
memcpy(&rfds, &readFds, sizeof(fd_set)); //因为在select返回时,rfds中仅仅是触发的fd,没有触发的不在里面。也就是说rfds是会被修改的。
if (-1 == calcNextTimeout(&tv)) {
// no pending timers; block indefinitely
dlog("~~~~ no timers; blocking indefinitely ~~~~");
ptv = NULL;//无限时的block
} else {
dlog("~~~~ blocking for %ds + %dus ~~~~", (int)tv.tv_sec, (int)tv.tv_usec);
ptv = &tv;//设定block
}
printReadies(&rfds);
n = select(nfds, &rfds, NULL, NULL, ptv); //监听可读的fd
printReadies(&rfds);
dlog("~~~~ %d events fired ~~~~", n);
if (n < 0) {
if (errno == EINTR) continue;
ALOGE("ril_event: select error (%d)", errno);
// bail?
return;
}
// Check for timeouts
processTimeouts(); //捞到期的事件
// Check for read-ready
processReadReadies(&rfds, n); //捞触发可读的事件
// Fire away
firePending(); //发射~ orz
}
}
整个RIL_startEventLoop的时间序列式这个样子的(不好意思,画的糟糕了)
所以可以看到,要添加一个事件很简单,呼叫以下两个函数就可以了:
ril_event_set()
rilEventAddWakeup()
那刚才有说,只有select返回,才会重新把事件的fd加到select来监听。所以在rilEventAddWakeup中会有两个动作,一个是添加event(event的fd会写到readFds中),然后另外一个是triggerEvLoop,这个triggerEvLoop其实就是往刚才在eventLoop中创建的匿名管道写入一个空格字符而已,目的是让select返回,因为select返回后会再把已经改变的readFds重新加入到select中。那么为什么往匿名管道写入数据,select就会返回呢?是因为在eventLoop中有把管道的read端也加入监听了。到这里估计大家应该明白了RIL event的工作机制了吧~
B. 接下来看vendor RIL中的RIL_Init
Vendor RIL以lib的形似存在,其中会有名称是”RIL_Init”的函数,该函数是Vendor RIL的入口,android默认的vendor RIL是reference-ril.so,里面包含了reference-ril.c这支文件。里面会有函数名为RIL_Init的函数,该函数创建一个tid为s_tid_mainloop,处理函数为mainLoop的线程。这里还会把RILD丢进来的设备名称保存在全局参数s_device_patch中。(这个函数太长了,只贴片段)
mainLoop的作用比较简单:
(1)打开s_device_patch设备,如果打开失败则会一直尝试直到成功为止。S_device_patch路径指向是串口设备,用于和modem进行数据交换,如果此设备没办法打开,那手机就嗝屁了~,mainLoop里主要做了三件事
I. 打开串口设备
II. 注册onUnsolicited,创建线程readLoop读取modem丢过来的unsolicited事件。
III. 添加一个计时器,发送AT与Modem进行握手通讯。
static void *
mainLoop(void *param)
{
...
for (;;) {
fd = -1;
while (fd < 0) {
if (s_port > 0) {
...
} else if (s_device_socket) {
...
} else if (s_device_path != NULL) {
fd = open (s_device_path, O_RDWR); //打开串口
...
}
...
}
s_closed = 0;
ret = at_open(fd, onUnsolicited); //创建readLoop线程,用于读取从modme丢过来的unsolicited ev
...
RIL_requestTimedCallback(initializeCallback, NULL, &TIMEVAL_0); //添加一个计时事件,
...
}
}
RIL_Init是有返回值的s_callbacks,这个返回值RIL_register会使用到,rild与vendor ril的衔接就是依靠这个参数。返回值类型是:
typedef struct {
int version; /* set to RIL_VERSION */
RIL_RequestFunc onRequest; //上层下指令的处理函数
RIL_RadioStateRequest onStateRequest; //查询当前radio状态
RIL_Supports supports; //查询指定的request是否支持
RIL_Cancel onCancel; //母鸡干嘛的
RIL_GetVersion getVersion; //ril的版本
} RIL_RadioFunctions;
RIL_Init中s_callbacks是这摸样,感兴趣的同学可以看看函数的实现:
到此RIL_Init就分析道这里。
C. RIL_register()
RIL_Register中的参数就是RIL_Init返回的参数,vendor RIL和RILD的沟通就是通过这货,直接通过function call方式。
extern "C" void
RIL_register (const RIL_RadioFunctions *callbacks) {//这里的参数就是上面提到的s_callbacks
...
memcpy(&s_callbacks, callbacks, sizeof (RIL_RadioFunctions)); //保存下来,备之后呼叫
...
for (int i = 0; i < (int)NUM_ELEMS(s_commands); i++) { //验证cmd,一个request对应一个整数值,由0往后递增,为什么要验证呢? 因为以后RIL Java发是根据这个request number发cmd的,这个request number就是作为数组s_command的索引,用来呼叫这个cmd的分发和处理本cmd的函数。一会介绍他们之间的关系
assert(i == s_commands[i].requestNumber);
}
for (int i = 0; i < (int)NUM_ELEMS(s_unsolResponses); i++) {//同上
assert(i + RIL_UNSOL_RESPONSE_BASE
== s_unsolResponses[i].requestNumber);
}
...
s_fdListen = android_get_control_socket(SOCKET_NAME_RIL);//获取socket的fd,socket貌似在创建ril-demon进程时随之创建的。
if (s_fdListen < 0) {
ALOGE("Failed to get socket '" SOCKET_NAME_RIL "'");
exit(-1);
}
ret = listen(s_fdListen, 4);
/* note: non-persistent so we can accept only one connection at a time */
//这里的socket是RILD与RIL Java的连接,RILD作为service端,RIL Java 作为client端。s_fdListen只是service的socket,目的是监听RIL Java的socket发过来的connect,在listenCallback中Accept返回的socket才是RIL Java的socket,监听这个socket才能能知道RIL Java发过来的请求了。
ril_event_set (&s_listen_event, s_fdListen, false,listenCallback, NULL);
rilEventAddWakeup (&s_listen_event);
#if 1
// start debug interface socket
//用于android debug的socket,可以通过该socket与RILoptions中的socket连接,可以进行开关radio,开启一通通话,挂断一通通话之类的。
s_fdDebug = android_get_control_socket(SOCKET_NAME_RIL_DEBUG);
if (s_fdDebug < 0) {
ALOGE("Failed to get socket '" SOCKET_NAME_RIL_DEBUG "' errno:%d", errno);
exit(-1);
}
ret = listen(s_fdDebug, 4);
ril_event_set (&s_debug_event, s_fdDebug, true,debugCallback, NULL);
rilEventAddWakeup (&s_debug_event);
#endif
}
上面有提到,RILD和上层RIL Java的沟通是通过socket进行通讯,因为RIL Java属于Java层次,之间的数据传递使用Parcel。当RIL Java要让RILD帮忙做事时,协定好它们之间之间的沟通方式,RIL Java把一个cmd号码发给RILD,其需要发送的参数写到Parcel中,在通过socket发送到RILD,然后RILD根据cmd码,呼叫就其绑定不同的发送与接收函数。
RIL有两种cmd:
(1)一种是一来一回的request。RIL Java下request,RILD处理后回给RIL Java结果。例如获取手机IMEI号,RIL Java下request到RILD,再到modem,modem处理完后依次返回到RIL Java。
(2)另一种只回的unsolicited。RILD把modem主动给过来的事件上报给RIL Java。例如,手机信号强度的改变,网络时间到达之类的。这些cmd不需要AP做下任何请求,当modem发现其需要报告状态给AP时,就会主动的发出。
这两种cmd对应的cmd格式
typedef struct {
int requestNumber; //request number
void (*dispatchFunction) (Parcel &p, struct RequestInfo *pRI); //request发送的函数
int(*responseFunction) (Parcel &p, void *response, size_t responselen); //request处理完成之后回应
} CommandInfo;
每一个cmd id(也就上request number)对应一个发送和接收函数,如果cmd有带Sting参数的话,那就会选用dispatchString, 如果RILD有返回值Int数组需要回传给RILJ的话,就选用responseInts。当然如果想传自己定义类型参数的话,可以自己写一个dispatchMyData,其实就是把Parcel里面的值,按照存放的顺序读出来发送出去。
typedef struct {
int requestNumber; //同上
int (*responseFunction) (Parcel &p, void *response, size_t responselen); //unsolicited 的处理函数
WakeType wakeType; //??? 母鸡这个的意思
} UnsolResponseInfo;
4. RIL Java解析
RIJ Java比较简单,就是负责RILD和上层Telephony的衔接。在PhoneFactory.java中根据不同的PhoneType创建不同的Phone,例如GSMPhone, CDMAPhone或者CDMALTEPhone。不管是哪一种Phone,都需要一个CommandsInterface作为参数,这个就是RILJ,每种Phone都需要RILJ帮忙和RILD进行通讯。RILJ就是在创建某一种Phone之前创建的,在Phone进程中运行。RILJ的源文件就是RIL.java,这个文件中有两个类RIL与RILRequest。RIL class继承与BaseCommands与CommandsInterface。
RILRequest:主要功能就是把要发送的request打包,Parcel存放需要送到RILD的数据。成员变数mSerial (Token)为一个整型,每生产一个RILRequest其值递增1,用于记录一个Request的编号。因为request不是这个一来一回后再发下一个,允许本request没有返回之前就可以发送发一个,这就需要一个编号记录每个request,用于辨别回来的结果对应到确定的request。
RILSender:发送request到RILD,RIL的内部类。
RILReceiver:接送从rild丢过来的数据,RIL的内部类。
RIL:重要的成员mSender/mReceiver,就是以上两内部类的实力。另外一个是mRequestList,是一个ArrayList,记录发送到RILD的request,在RILD处理完成之后回传数据时根据Token,取出对应的Request,来把结果分发到Framework的呼叫者。mSocket就是和RILD连接的socket。
RIL在初始化初期,会创建两个线程mSend和mReceiver,在mReceiver中会创建一个socket并和RILd的socket连接(这个时候eventLoop中的s_listen_event就会触发,透过accept就可以获取到这个RILJ的socket)。当RILJ要往RILD发请求时,会透过RILRequest创建一个request,并把需要携带的参数写到Request中,然后透过mSend去发送。
下面以一副数据流程图来解说一下RILJ的内部运作:
5. RIL Request过程分析
这小节将通过一个具体的Request介绍RILJ到RILD的flow。以 public void
setRadioPower(boolean on, Message result) 为实力来介绍。这个方法是来开关手机的Radio的,参数on需要携带到RILD去,result为 response时通知该方法的呼叫者。首先开一下函数具体实现:
在来看看函数的绑定发送与接收函数:
因为这个Request需要携带一个参数下去,所以发送函数选择dispatchInts(ril.cpp),没有参数需要返回,所以是responseVoid(ril.cpp)。刚好可以接着上小节的request部分,当把数据写到socket的时候,触发s_commands_event,执行processCommandsCallback(ril.cpp)。
Request的数据依次是:
Type:RIL_REQUEST_RADIO_POWER
Token:这个值是运行时的,具体是多少不管他。
RequestData:数组元素个数1,以及具体值。
在这之前需要介绍一下一个结构体:
其中的pCI就是
下面开始分析,因为RILJ 的Request过程上面小节已经介绍,这里就不再累赘,直接从RILD接数据开始:
a. 跑到processCommandBuffer函数,创建一个Parcel装载RILJ数据,读出Type与Token。
b. 在来看dispatchInts里的是怎么处理的,读取出RILJ丢过来的数组长度,本case为1,然后呼叫onRequest。
c. Referencr-ril中的onRequest(),针对request type下不同的指令。
本case的request是RIL_REQUEST_RADIO_POWER,看看这个case是怎么处理的
呼叫到了requestRadioPower函数,在该函数中依照RILJ传过来的参数想modem发送AT cmd。在modem处理完成之后返回,然后呼叫RIL_onRequestCompleate结束本次request。
d. RIL_onRequestCompleate,完成数据装载,把结果回传给RILJ,完成本次onRequest。
e. sendReponse, 把数据通过socket回传给RILJ,这个时候RILJ的mReceiver就读取这些数据了。之后的RILJ如何处理的情参考第四节的讲述。
6. RIL Unsolicited过程分析
当RILD或者modem察觉有数据或状态变化时,需要把这些信息告诉framework,所以会丢出一个事件,这中就是Unsolicited event。Framework可以选择自己感兴趣的事件来监听,当被监听的事件触发时,就会被通知到。这里有一个疑问,因为modem只是打回一个AT名称给RILD,怎么知道这个AT是该让谁来处理呢?怎么知道哪些AT是Request下下去的AT的回应,哪些是AT是modem主动上报的呢?带着这个问题来分析一下unsolicited的处理过程。正如上面分析RIL_Init是所讲到的,mainLoop(reference-ril.c)中透过at_open来创建一个线程readLoop(atchannel.c)来读取modem丢回来的AT。At_open(atchannel.c)这个函数有两个参数,一个是串口设备的fd,另一个是一个函数指针,当modem有丢回来数据时,就是透过这个回调函数来处理滴。onUnsolicited是实现在reference-ril.c.
在进入正题之前先看一个结构体ATResponse:
当透过request下的AT,AT返回的结果就是存放在这个结构体里面。
其中p_intermediates存放的就是AT的字符串。
a. 在readLoop线程中无限循环的读取modem丢过来的AT,读到数据之后会然后呼叫processLine(atchannel.c)来处理AT。
b. processLine中有个参数sp_response,当request下AT到modem的时候,都会创建ATResponse这个结构,用于存放AT返回的结果;如果是modem主动丢回来的AT,那sp_response就是空的。就是透过这个来区分哪些AT是modem主动丢回来的,哪些是request回来的。
c. handleUnsolicited函数呼叫的s_unsolHandler是reference-ril.c中的onUnsolicited()
d. onUnsolicited(),根据AT返回的数值确定该谁来处理,比如遇到”%CTZV”那么其对应的unsolicited就是RIL_UNSOL_NITZ_TIME_RECEIVED。
e. RIL_onUnsolicitedResponse中有些地方还不是很明白:( ~
这个函数做的事情(函数篇幅太大,就不全贴了):
确定这个unsolicited在数值在s_unsolResponses中的索引
先把Type和unsolicited写到Parcel中,然后很据索引取出这个unsolicited对应的response函数,其实就是把这个unsolicited带回的数据写到Parcel。
最后就是呼叫sendResponse,把Parcel里的数据送给RILJ。
余下的部分可以参考第四节。
PS: 本文提到的RILD == RIL Daemon, RILJ = RIL Java,在介绍RILJ是提到的RIL指得就是RILJ(因为RILJ的类名称为RIL)。估计会造成困扰,实在抱歉! 52RD的文字框编辑功能太烂了,没办法调间距,visio画的图上传上来全都是黑的