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 charRIL_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提交一个超时任务。

voidRequestTimedCallback)(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没有分析,下面来看看它。