💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
下面看第二个关键函数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没有分析了,下面来看看它。