9.3.3 RIL_Init分析
最后更新于:2022-04-02 05:53:35
下面看第二个关键函数RIL_Init。这个函数必须由动态库实现,对于下面这个例子来说,它将由RefRil库实现,这个函数定义在Reference_ril.c中:
**Reference_ril.c**
~~~
pthread_t s_tid_mainloop;//看来又会创建一个线程
//动态库必须实现的RIL_Init函数。
const RIL_RadioFunctions *RIL_Init(const structRIL_Env *env,
int argc, char **argv)
{
intret;
int fd= -1;
intopt;
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也为一个结构体
staticconst 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,void *data,
size_tdatalen, 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) (void *param);
typedef const char * (*RIL_GetVersion) (void);
typedef struct {
intversion; //RIL的版本
//通过这个接口可向BP提交一个请求,注意这个函数的返回值为空,这是为什么?
RIL_RequestFunc onRequest;
RIL_RadioStateRequest onStateRequest;//查询BP的状态
RIL_Supports supports;
RIL_CancelonCancel;
//查询动态库的版本,RefRil库中该函数的实现将返回字符串”android reference-ril 1.0”
RIL_GetVersion getVersion;
} RIL_RadioFunctions;
~~~
对于上面的结构体,应重点关注函数onRequest,它被Rild用来向动态库提交一个请求,也就是说,AP向BP发送请求的接口就是它,但是这个函数却没有返回值,那么该请求的执行结果是怎么得到的呢?
这里不卖关子,直接告诉大家。Rild架构中最大的特点就是采用了异步请求/处理的方式。这种方式和异步I/O有异曲同工之妙。那么什么是异步请求/处理呢?它的执行流程如下:
- Rild通过onRequest向动态库提交一个请求,然后返回去做自己的事情。
- 动态库处理这个请求,请求的处理结果通过回调接口通知。
这种异步请求/处理的流程和酒店的MorningCall服务很类似,具体相似之处如下所示:
- 在前台预约了一个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 void *data,
size_t datalen);
//给Rild提交一个超时任务
void*(*RequestTimedCallback) (RIL_TimedCallback callback,
void *param,const struct timeval *relativeTime);
//从Rild的超时任务队列中移除一个任务
void(*RemoveTimedCallback) (void *callbackInfo);
};
~~~
结合图9-7和上面的分析可以发现,Rild在设计时将请求的应答接口和动态库的通知接口都放在了RIL_Env结构体中。
关于Rild和动态库的交互接口就分析到这里。相信读者已经明白其中的原理了。下面来看RefRil库创建的工作线程mainLoop。
1. 工作线程mainLoop的分析
RefRil库的RIL_Init函数会创建一个工作线程mainLoop,其代码如下所示:
**Reference_Ril.c**
~~~
static void *
mainLoop(void *param)
{
intfd;
intret;
......
/*
为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的第一个参数是一个代表串口设备的文件描述符。
intret;
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);
......
return0;
}
~~~
at_open函数会另外创建一个工作线程readerLoop,从名字上看,它会读取串口设备。下面来看它的工作,代码如下所示:
**Atchannle.c**
~~~
static void *readerLoop(void *arg)
{
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();
returnNULL;
}
~~~
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_TimedCallbackcallback, void *param,
const structtimeval *relativeTime) {
returninternalRequestTimedCallback (callback, param, relativeTime);
}
/*
调用internalRequestTimedCallback,其实就是构造一个Ril_event事件然后加入到
timer_list,并触发event_loop工作线程执行
*/
static UserCallbackInfo * internalRequestTimedCallback(
RIL_TimedCallback callback, void *param,
const structtimeval *relativeTime)
{
structtimeval 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线程
returnp_info;
}
~~~
从上面的代码可知,RIL_requestTimedCallback函数就是向eventLoop提交一个超时任务,这个任务的处理函数则为initialCallback,下面直接来看该函数的内容,如下所示。
**Reference_ril.c**
~~~
static void initializeCallback(void *param)
{
/*
这个函数就是通过发送一些AT指令来初始化BP中的无线通信Modem,不同的modem可能有
不同的AT指令。这里仅列出部分代码。
*/
ATResponse *p_response = NULL;
interr;
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没有分析了,下面来看看它。
';