9.3.3 RIL_Init分析
下面看第二个关键函数RIL_Init。这个函数必须由动态库实现,对于下面这个例子来说,它将由RefRil库实现,这个函数定义在Reference_ril.c中:
[—>Reference_ril.c]
pthread_t s_tid_mainloop;//看来又会创建一个线程。
//动态库必须实现的RIL_Init函数。
const RIL_RadioFunctionsRIL_Init(const struct RIL_Envenv,
int argc,char**argv)
{
int ret;
int fd=-1;
int opt;
pthread_attr_t attr;
s_rilenv=env;//将外部传入的env保存为s_rilenv。
……//一些参数处理,不必管它。
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
//创建一个工作线程mainLoop。
ret=pthread_create(&s_tid_mainloop,&attr,mainLoop,NULL);
/*
s_callbacks也为一个结构体。
static const RIL_RadioFunctions s_callbacks={
RIL_VERSION,//RIL的版本。
onRequest,//下面是一些函数指针。
currentState,
onSupports,
onCancel,
getVersion
};
*/
return&s_callbacks;
}
RefRil的RIL_Init函数比较简单,主要有三项工作要做:
保存Rild传入的RIL_Env结构体。
创建一个叫mainLoop的工作线程。
返回一个RIL_RadioFunctions的结构体。
上面的RIL_Env和RIL_RadioFunctions结构体,就是Rild架构中用来隔离通用代码和厂商相关代码的接口。先来看RIL_RadioFunctions,这个结构体由厂商的动态库实现,它的代码如下:
//函数指针定义。
typedef void(RIL_RequestFunc)(int request,voiddata,
size_t datalen,RIL_Token t);
typedef RIL_RadioState(*RIL_RadioStateRequest)();
typedef int(*RIL_Supports)(int requestCode);
typedef void(*RIL_Cancel)(RIL_Token t);
typedef void(RIL_TimedCallback)(voidparam);
typedef const char(RIL_GetVersion)(void);
typedef struct{
int version;//RIL的版本
//通过这个接口可向BP提交一个请求,注意这个函数的返回值为空,这是为什么?
RIL_RequestFunc onRequest;
RIL_RadioStateRequest onStateRequest;//查询BP的状态。
RIL_Supports supports;
RIL_Cancel onCancel;
//查询动态库的版本,RefRil库中该函数的实现将返回字符串"android reference-ril 1.0"
RIL_GetVersion getVersion;
}RIL_RadioFunctions;
对于上面的结构体,应重点关注函数onRequest,它被Rild用来向动态库提交一个请求,也就是说,AP向BP发送请求的接口就是它,但是这个函数却没有返回值,那么该请求的执行结果是怎么得到的呢?
这里不卖关子,直接告诉大家。Rild架构中最大的特点就是采用了异步请求/处理的方式。这种方式和异步I/O有异曲同工之妙。那么什么是异步请求/处理呢?它的执行流程如下:
Rild通过onRequest向动态库提交一个请求,然后返回去做自己的事情。
动态库处理这个请求,请求的处理结果通过回调接口通知。
这种异步请求/处理的流程和酒店的Morning Call服务很类似,具体相似之处如下所示:
在前台预约了一个Morning Call,这好比向酒店提交了一个请求。预约完后,就可以放心地做自己的事情了。
酒店登记了这个请求,记录是哪个房间申请的服务,然后由酒店安排工作人员值班,这些都是酒店对这个请求的处理,作为房客则无须知道处理细节。
第二天早上,约好的时间一到,酒店给房客打电话,房客就知道这个请求被处理了。为了检查一下宾馆服务的效果,最好是拿表看看接到电话的时间是不是之前预约的时间。
有了上面的介绍,读者对异步请求/处理机制或许有了一些直观的感受。那么,动态库是如何通知请求的处理结果的呢?这里用到了另外一个接口RIL_Env结构,它的定义如下所示:
[—>Ril.h]
struct RIL_Env{
//动态库完成一个请求后,通过下面这个函数通知处理结果,其中第一个参数标明是哪个请求
//的处理结果。
void(*OnRequestComplete)(RIL_Token t,RIL_Errno e,
void*response,size_t responselen);
//动态库用于进行unsolicited Response通知的函数。
void(OnUnsolicitedResponse)(int unsolResponse,const voiddata,
size_t datalen);
//给Rild提交一个超时任务。
void(RequestTimedCallback)(RIL_TimedCallback callback,
voidparam,const struct timevalrelativeTime);
//从Rild的超时任务队列中移除一个任务。
void(RemoveTimedCallback)(voidcallbackInfo);
};
结合图9-7和上面的分析可以发现,Rild在设计时将请求的应答接口和动态库的通知接口都放在了RIL_Env结构体中。
关于Rild和动态库的交互接口就分析到这里。相信读者已经明白其中的原理了。下面来看RefRil库创建的工作线程mainLoop。
1.工作线程mainLoop分析
RefRil库的RIL_Init函数会创建一个工作线程mainLoop,其代码如下所示:
[—>Reference_Ril.c]
static void*
mainLoop(void*param)
{
int fd;
int ret;
……
/*
为AT模块设置一些回调函数,AT模块用来和BP交互,对于RefRil库来说,AT模块就是对串口设备通信的封装,这里统称为AT模块。
*/
at_set_on_reader_closed(onATReaderClosed);
at_set_on_timeout(onATTimeout);
for(;){
fd=-1;
//下面这个while循环的目的是为了得到串口设备的文件描述符,我们省略其中的一些内容while(fd<0){
if(s_port>0){
fd=socket_loopback_client(s_port,SOCK_STREAM);
}else if(s_device_socket){
if(!strcmp(s_device_path,"/dev/socket/qemud")){
……
}else if(s_device_path!=NULL){
fd=open(s_device_path,O_RDWR);
if(fd>=0&&!memcmp(s_device_path,"/dev/ttyS",9)){
struct termios ios;
tcgetattr(fd,&ios);
ios.c_lflag=0;
tcsetattr(fd,TCSANOW,&ios);
}
}
……
}
s_closed=0;
//①打开AT模块,传入一个回调函数onUnsolicited。
ret=at_open(fd,onUnsolicited);
……
//②下面这个函数向Rild提交一个超时任务,该任务的处理函数是initializeCallback。
RIL_requestTimedCallback(initializeCallback,NULL,&TIMEVAL_0);
sleep(1);
/*
如果AT模块被关闭,则waitForClose返回,但是该线程并不会退出,而是从for循环那开始重新执行一次。所以这个mainLoop工作线程是用来监控AT模块的,一旦它被关闭,就需要重新打开,也就是说不允许AT模块被关闭。
*/
waitForClose();
……
}
}
可以看到,mainLoop的工作其实就是初始化AT模块,并监控AT模块,一旦AT模块被关闭,那么mainLoop就要重新打开并初始化它。这几项工作主要由at_open和超时任务的处理函数initializeCallback完成。
(1)at_open分析
来看at_open这个函数,其代码如下所示:
[—>Atchannle.c]
int at_open(int fd,ATUnsolHandler h)
{
//at_open的第一个参数是一个代表串口设备的文件描述符。
int ret;
pthread_t tid;
pthread_attr_t attr;
s_fd=fd;
s_unsolHandler=h;
s_readerClosed=0;
s_responsePrefix=NULL;
s_smsPDU=NULL;
sp_response=NULL;
……//和电源管理相关的操作。
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
//创建一个工作线程readerLoop,这个线程的目的就是从串口设备读取数据。
ret=pthread_create(&s_tid_reader,&attr,readerLoop,&attr);
……
return 0;
}
at_open函数会另外创建一个工作线程readerLoop,从名字上看,它会读取串口设备。下面来看它的工作,代码如下所示:
[—>Atchannle.c]
static voidreaderLoop(voidarg)
{
for(;){
const char*line;
line=readline();//从串口设备读取数据。
……
if(isSMSUnsolicited(line)){
char*line1;
const char*line2;
line1=strdup(line);
line2=readline();
if(line2==NULL){
break;
}
if(s_unsolHandler!=NULL){
s_unsolHandler(line1,line2);//调用回调,处理SMS的通知。
}
free(line1);
}else{
//处理接收到的数据,也就是根据line中的AT指令调用不同的回调。
processLine(line);
}
……//电源管理相关。
//这个线程退出前会调用通过at_set_on_reader_closed设置的回调函数,以通知
//AT模块关闭。
onReaderClosed();
return NULL;
}
readerLoop工作线程比较简单,就是从串口设备中读取数据,然后进行处理。这些数据有可能是solicited response,也有可能是unsolicited response,具体的处理函数我们在后续的实例分析中再来介绍,下面我们看第二个函数RIL_requestTimedCallback。
(2)initializeCallback分析
在分析initializeCallback函数前,我们先看看RefRil向Rild提交超时任务的RIL_requestTimedCallback函数,它其实是一个宏,不过这个宏比较简单,就是封装了RIL_Env结构体中对RequestTimedCallback函数的调用,代码如下所示:
define RIL_requestTimedCallback(a,b,c)\
s_rilenv->RequestTimedCallback(a,b,c)
//向Rild提交一个超时处理函数。
下面我们看看Rild实现的这个RequestTimedCallback函数,代码如下所示。
[—>Ril.cpp]
extern"C"void*
RIL_requestTimedCallback(RIL_TimedCallback callback,void*param,
const struct timeval*relativeTime){
return internalRequestTimedCallback(callback,param,relativeTime);
}
/*
调用internalRequestTimedCallback,其实就是构造一个Ril_event事件然后加入到timer_list中,并触发event_loop工作线程执行。
*/
static UserCallbackInfo*internalRequestTimedCallback(
RIL_TimedCallback callback,void*param,
const struct timeval*relativeTime)
{
struct timeval myRelativeTime;
UserCallbackInfo*p_info;
p_info=(UserCallbackInfo*)malloc(sizeof(UserCallbackInfo));
p_info->p_callback=callback;
p_info->userParam=param;
if(relativeTime==NULL){
memset(&myRelativeTime,0,sizeof(myRelativeTime));
}else{
memcpy(&myRelativeTime,relativeTime,sizeof(myRelativeTime));
}
ril_event_set(&(p_info->event),-1,false,userTimerCallback,p_info);
//将该任务添加到timer_list中去。
ril_timer_add(&(p_info->event),&myRelativeTime);
triggerEvLoop();//触发eventLoop线程。
return p_info;
}
从上面的代码可知,RIL_requestTimedCallback函数就是向eventLoop提交一个超时任务,这个任务的处理函数则为initialCallback,下面直接来看该函数的内容,如下所示:
[—>Reference_ril.c]
static void initializeCallback(void*param)
{
/*
这个函数就是通过发送一些AT指令来初始化BP中的无线通信Modem,不同的modem可能有不同的AT指令。这里仅列出部分代码。
*/
ATResponse*p_response=NULL;
int err;
setRadioState(RADIO_STATE_OFF);
at_handshake();
……
err=at_send_command("AT+CREG=2",&p_response);
……
at_response_free(p_response);
at_send_command("AT+CGREG=1",NULL);
at_send_command("AT+CCWA=1",NULL);
……
if(isRadioOn()>0){
setRadioState(RADIO_STATE_SIM_NOT_READY);
}
……
}
2.关于RIL_Init的总结
RIL_Init函数由动态库提供,以上面RefRil库的代码为参考,这个函数执行完后,将完成RefRil库的几项重要工作,它们是:
创建一个mainLoop工作线程,mainLoop线程的任务是初始化AT模块,并监控AT模块,一旦AT模块被关闭,则会重新初始化AT模块。
AT模块内部会创建一个工作线程readerLoop,该线程的作用是从串口设备中读取信息,也就是直接和BP打交道。
mainLoop通过向Rild提交超时任务,完成了对Modem的初始化工作。
在Rild的main函数中还剩下最后一个关键函数RIL_register没有分析,下面来看看它。