Android 平台通讯架构研究

3)几个重要的Event 
ril.cpp中定义的ril_event有5个,分别为:s_commands_event、s_wakeupfd_event、s_listen_event、s_wake_timeout_event、s_debug_event。其中最为重要的是s_listen_event和s_commands_event。 
① s_listen_event (s_fdListen, listenCallback) 
listenCallback处理函数用于接收客户端连接,其实现如下:s_fdCommand = accept(s_fdListen, (sockaddr *) &peeraddr, &socklen)。其次用于添加s_commands_event,重新建立s_listen_event,等待下一次连接。 
②s_commands_event(s_fdCommand,processCommandsCallback) 
从s_fdCommand Socket连接中读取record_stream,使用processCommandBuffer()处理数据。s_listen_event在大的功能上处理客户端连接(RIL-JAVA层发起的connect),并建立s_commands_event去处理Socket连接发来的RIL命令。ProcessCommandBuffer()包含了RIL指令的下行过程。  
(4)下行命令的翻译及其组织—processCommandBuffer 
Ril-JAVA传递的命令格式—Parcel,由命令号、令牌、内容组成。RIL-JAVA到达RIL-C时转换为构建本地RequestInfo,并被翻译成具体的AT指令。由于每条AT命令的参数是不同的,所以对不同的AT指令,有不同的转换函数。在此Android做了抽象,做了个分发框架,通过命令号,利用sCommand数组,获得该命令的处理函数。sCommand存在于ril_commands.h中。&sCommand[ ]= 

       {RIL_REQUEST_DIAL, dispatchDial, responseVoid}, 
       {RIL_REQUEST_HANGUP,dispatchInts,responseVoid},       
{……} > 
ril.cpp中dispatchxxx函数的具体实现放在reference-ril.c中,reference-ril.c就是我们需要根据不同的Modem来修改的文件。  
(5)at_send_command 

at_send_command是同步的,其函数功能具体实现在atchannel.c中。 命令发送后,at_send_command将等待在s_commandcond,直到有p_response->finalResponse。



2.2 Read Loop 
Read Loop解决的问题是:解析从Modem发过来的回应。具体实现依赖于atchannel.c文件,其工作机制如下图所示。如果遇到URC则通过handleUnsolicited函数上报给RIL-JAVA;如果是命令的应答,则通过handFinalResponse函数通知at_send_command有应答结果。handleUnsolicited函数原型和handFinalResponse函数原型在atchannel.c中的定义如下:     static void handleUnsolicited(const char *line) 

        if (s_unsolHandler != NULL) {             s_unsolHandler(line, NULL);         }     

static void handleFinalResponse(const char *line) { 
    sp_response->finalResponse = strdup(line);     pthread_cond_signal(&s_commandcond); } 
 
Serial
Read Loop
readline
ProcessLine
at_send_command
等待应答
Event Loop
Socket
URC
通知应答
 
 
3  Android RIL 研究之RIL-Java 
RIL-Java质上就是一个RIL代理,起到一个转发的作用,在RILD的分析中,我们知道RILD建立了一个监听套接字,等待RIL-Java的连接。一旦连接成功,RIL-Java就可以发起一个请求,并等待应答,并将结构发送到目标处理对象。在


RIL-Java中,这个请求称为RILRequest。RIL-Java的框架图如下: 
RIL-Java
ReceiverSender
Command Interface
 
RIL-Java框架包含了四个方面:Receiver,Sender,Command Interface和异步通知机制。 3.1 Command Interface  
包括诸如getCurrentCalls、getIccCardStatus、dial、 acceptCall、rejectCall、sendDtmf、sendSMS、setupDataCall、setRadioPower等命令接口函数,这些函数的实现在frameworks/base/telephony/java/com/android/internal/telephony /RIL.java中。这些命令接口函数都是对电话基本功能的描述,是对Modem AT指令的提炼与抽象。 3.2 Receiver 
Receiver对Response Parcel的处理机制如下图所示。 
XXX.notifyRegistran通知注册表中的Handler
processUnsolicitedprocessSolicited
组合结果
Result.sendToTarget()将结果发送到
目标Handler
processResponse
URC
 
Receiver 连接到RILD的服务套接口,接收并读取RILD传递过来的Response Parcel。Response分为两种类型,一种是URC,一种是命令应答。对于URC将会直接分发到通知注册表中的Handler。而命令应答则通过Receiver的异步通知机制传递到命令的发送者进行相应处理。


3.3 Sender 
Sender应该分为两部分框架:上层函数调用Command Interface将请求消息发送到Sender的框架;Sender接收到EVENT_SEND消息后,将请求发送到RILD的架构。具体实现流程如下图所示。 
Command Interface 
具体实现
doCommandXXX(data,resultxxx)RILRequest RRRR.mSerial=XXXRR.mResult=xxx
RR.mRequest=RIL_REQUEST_xxxdata->RRsend(RR)
handleMessage@RILSender 
EVENT_SEND
 mRequestList.add(RR)
RR->Parcel
writeToRILD(parcel)
Sender
 
 
3.4  异步通知 
对于异步应答来讲,命令的发起者发送命令请求后,并不等待应答就返回,应答的回应是异步的,处理结果通过消息的方式返回。对于异步系统,首先应该考虑的是如何标识命令和结果,让命令和结果有个对应的关系,其次对于命令没有响应了,应该如何管理命令超时? 
Android系统设计者利用了Result Message 和RILRequest对象来完成Request和Result的对应关系。在上层做调用时生成Result Message对象传递到RIL-Java,并在Modem有应答后,通过Result Message对象带回结果。为了保证该应答是该RILRequest的,系统设计者还提供了一个Token(令牌)的概念。在源代码中RILRequest的mSerial就用作了Token。Token用来唯一标识每次发送的请求,并且Token将被传送到RILD,RILD在组装应答时将Token写入,并传回到RIL-Java,RIL-Java根据该Token找到相应的Request对象。


(1) RIL命令的发送模式 
协议的真正实现是在RILD中,RIL-Java更多的是一个抽象和代理,本文在研究源代码的过程中发现RIL-Java中的命令函数都有一个共同的框架: 
SendxxxCmd(传入参数data,传出参数result){ 
组合RILRequest(请求号,result,mSerial) data->RR 
send(RILRequest)  } 
 
请求号将被传递到RILD用以标识命令,请求号代表某个电话功能。例如拨号的request号为:RIL_REQUEST_DIAL。在hardware/ril/libril/ 
ril_commands.h中有定义。RILRequest obtain(int request, Message result)函数根据命令请求号,传入参数Message result。Message result将带回应答信息回到命令的发起者。mSerial则构造了一个RILRequest。 
Android使用了一个RILRequest对象池来管理RILRequest。mSerial是一个递增的变量,用来唯一标识一个RILRequest。在发送时正是用了该变量为Token,在RILD层看到的token就是该mSerial。 
RIL命令的具体发送步骤如下:第一步:生成RILRequest,此时将生成mSerial(请求的Token),并将请求号及其Message result对象填入到RILRequest中;第二步:使用send将RILRequest打包到EVENT_SEND消息中发送到RIL Sender Handler;第三步:RILSender接收到EVENT_SEND消息后,将RILRequest通过套接口发送到RILD,同时将RILRequest保存在mRequest中以便应答消息的返回。 
 
(2)RIL命令的接收模式 
     RIL命令的具体接收步骤如下:第一步:分析接收到得Pacel(type|token|error|response|data),根据类型不同进行处理;第二步:根据数据中的Token(mSerial),反查mRequest,找到对应的请求信息;第三步:将数据转换成结果数据;第四步:将结果数据放在RequestMessage中发回到请求的发起者。 
 
4  Android RIL研究之GsmCallTracker 
GsmCallTracker在本质上是一个Handler。GsmCallTracker是Android的通话管理层。GsmCallTracker建立了ConnectionList来管理现行的通话连接,并向上层提供电话调用接口。 
在GsmCallTracker中维护着通话列表:connections。顺序记录了正连接上的通话。这些通话包括:ACTIVE 、IDLE、INCOMING、WAITING、 HOLDING   、ALERTING 、DIALING等连接状态。GsmCallTracke将这些连接分为了三类进行管理: 
Ringing call: INCOMING,WAITING 
Foreground call:ACTIVE、DIALING、ALERTING


Background call:HOLDING 上层函数通过getRingCall(),getForegroundCall()等来获得电话系统中特定通话连接。 
为了管理电话状态,GsmCallTracker在构造时就将自己登记到了电话状态变化通知列表中。RIL-Java一旦收到电话状态变化的通知时,就会使用EVENT_CALL_STATE_CHANGE通知到GsmCallTracker。 
在一般的实现中,通话的Call Table是通过AT+CLCC命令查询得到的,CPI可以通知到电话的改变,但 CPI在各个Modem的实现中差别比较大,所以在RIL实现中都没用到CPI这样的电话连接改变通知,而是使用最为传统的CLCC查询Call Table。在GsmCallTracker中使用connections来管理Android电话系统中的通话连接。每次电话状态发生变化时GsmCallTracker就会使用CLCC查询更新connections内容,如果内容有发生变化时,则向上层发起电话状态改变的通知。  
4.1  RIL-Java中发起的电话连接列表操作  
在RIL-Java中涉及到的CurrentCallList查询的有以下几个操作:hangup 、dial、acceptCall、rejectCall。GsmCallTracker在发起这些调用的时都有一个共同的ResultMessage构造函数—obtainCompleteMessage()。obtainCompleteMessage()实际上是调用:obtainCompleteMessage(EVENT_OPERATION_COMPLETE)。这就意味着在这些电话操作后,GsmCallTracker会收到EVENT_OPERATION_COM PLETE的消息。于是把目标转移到handleMessage()函数的EVENT_OPERATION_ COMPLETE case中。相关核心代码如下: 
public void handleMessage (Message msg) { …… 
switch (msg.what) { …… 
case EVENT_OPERATION_COMPLETE:                 ar = (AsyncResult)msg.obj;                 operationComplete();                 break;         …… 
} …… } 
operationComplete()操作会使用cm.getCurrentCalls(lastRelevantPoll)调用,向RILD发起RIL_REQUEST_GET_CURRENT_CALLS调用,这个最终就是向Modem发起AT+CLCC,获得真正的电话列表。



4.2  在RILD中引起getCurrentCalls调用 
在RILD中,会收到诸如+CRING、RING、NO CARRIER、+CCWA等URC消息 ,此时将会调用RIL_onUnsolicitedResponse(RIL_UNSOL_RESPONSE_CA LL_ STATE_CHANGED)主动向RIL-Java上报RIL_UNSOL_RESPONSE_CALL_ STATE_CHANGED消息。 
在处理requestCurrentCalls时,使用CLCC查询通话连接列表—Call Table后,如发现有Call Table不为空则开启一个定时器,主动上报RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED消息,直到没有电话连接为止。 
在RIL-Java层收到 RIL_UNSOL_RESPONSE_CALL_STATE_CHANGE 这个URC,并利用mCallStateRegistrants. notifyRegistrants()来通知电话状态的变化,此时GsmCallTracker会接收到EVENT_CALL_STATE_CHANGE消息,并使用pollCallsWhenSafe() cm.getCurrentCalls(lastRelevantPoll)来发起查询,并更新Java层的电话列表。  
4.3  handlePollCalls电话列表刷新 
上面分析了Android电话系统中所有引起电话连接列表更新的条件及其处理,它们共同调用了cm.getCurrentCalls(lastRelevantPoll)来完成电话列表的获取。lastRelevantPoll = obtainMessage(EVENT_POLL_CALLS_RESULT),从这里可以看到获取到的电话列表result使用handlePollCalls()进行了处理。result实际上是一个DriverCall列表,handlePollCalls函数的工作就是将当前电话列表与RIL-Java的电话列表对比,使用DriverCall列表更新GsmCallTracker电话列表的connections,并向上传递电话状态改变的通知。 
  
第二章 移植与实现部分 
本文研究基于Android 2.3操作系统,开发宿主平台为Ubuntu 9.04,目标平台为freescale I.MX51 EVK for Android。I.MX51是基于ARM Cortex-A8应用处理器。I.MX51 EVK有Mini-PCI接口。无线通讯模块采用的是基于CDMA 2000 1x/EvDo Rev.A技术的MI800模块,即通常所说的Modem。无线通讯模块通过Mini-PCI接口与目标平台连接。




TE
MT
AT 命令
Information text
Result codes 
2.1  AT命令简介[ 
AT即Attention,AT命令集是从TE(Terminal Equipment)或DTE(Data Terminal Equipment )向TA(Terminal Adapter)或DCE(Data Circuit Terminal Equipment)发送的。其对所传输的数据包大小有定义:即对于AT指令的发送,除AT两个字符外,最多可以接收1056个字符的长度(包括最后的空字符)。每个AT命令行中只能包含一条AT指令,对于由终端设备主动向PC端报告的URC指示或者response响应,也要求一行最多有一个,不允许上报的一行中有多条指示或者响应。AT指令以回车作为结尾,响应或上报以回车换行为结尾。 2.2 通话相关AT命令介绍 
(一)语音呼叫接口 
(1)、呼叫发起指示^ORIG 
    ^ORIG表示MT正在发起呼叫,其语法结构如下表: Command Possible responses  <CR><LF>^ORIG:<call_x>,<call_type><CR><LF> 
<call_x>:呼叫ID,范围1~7。如果连续拨打多次,ID由1~7循环往复。 
<call_type>:呼叫类型。为0时代表语音呼叫,为9时代表紧急呼叫。  
(2)、呼叫接通指示^CONN 
当呼叫接通后,MT向TE上报此指示,表明当前状态已经变为通话状态。其语法结构和取值说明同上。 
  (3)、通话结束指示^CEND 
    当呼叫结束后,MT(Mobile Terminal)向TE(Terminal Equipment)上报此指示,告知TE通话结束原因和通话时长。其语法结构如下表: 
Command Possible responses 
 
<CR><LF>^CEND:<call_x>,<duration>,<end_status>[,<cc_cause>] <CR><LF> 
 
<call_x>:呼叫ID,范围1~7。


<duration>:通话时长,以s为单位。 
<end_status>:呼叫结束原因。原因有21种,如当该字段取值为28表示来电时收到了振铃停止的信令,为105时表明话费用完,为106时表示不在服务区。由于篇幅有限,在这里不再赘述。 
<cc_cause>:呼叫控制信息,该字段为可选字段。如果为网侧引发的呼叫结束才会有此字段;如果本地发出的呼叫,还没得到网侧的响应呼叫就结束,此时不会有<cc_cause>上报。该字段取值情况有68个。如当取值为17时表示USER_BUSY,当取值为28时表示错误的号码格式。  
(二)呼叫控制命令与方法 
(1)、来电指示RING 
当移动终端有被叫来电时,MT会周期性(T=5s)地上报此指示通知TE。 
(2)、接听命令A 
当移动终端有来电时,TE用此命令告知MT接听电话。  
(3)、呼叫发起命令+CDV 
本接口用于TE通过MT向网络侧发起语音呼叫。其语法结构如下: Command Possible responses +CDV[digits] <CR><LF>OK<CR><LF>; 与MT相关错误时:<CR> 
<LF>+CME ERROR:<err><CR><LF> 
<digits>:被叫的电话号码,ASCII字符,合法的字符包括:‘0‘-‗9‘、‘*‘、 ‗#‘、‘+‘。‘+‘只能出现在号码的最前面,号码的最大长度不能超过24(不包括―+‖)。  
(4)、呼叫挂断命令+CHV 
本接口用于在CDMA系统中挂断语音呼叫。  
(5)、呼叫状态查询命令+CLCC 
该命令用于查询当前存在几个呼叫及各个呼叫的状态,指令只有在呼叫过程中和通话过程中查询才有效。其语法结构如下: Command Possible responses +CLCC <CR><LF>+CLCC:<idx>,<dir>,<state>,<mode>,<mpty>,<nu
mber>,<type><CR><LF> 
<idx>:呼叫ID。 
<dir>:呼叫方向,0表示MO,1表示MT。 
<state>:呼叫状态,取值如下:0表示激活状态(active);1表示呼叫保持状态(held);2表示发起呼叫,拨号状态(dialing);3表示发起呼叫,振铃状态(alerting);4表示来电振铃状态(incoming);5表示等待状态(waiting)。 
<mode>:呼叫类型,取值如下:0表示语音呼叫(voice);1表示数据呼叫(data);2表示传真(fax)。 
<mpty>:多方通话。取值如下:0表示非多方通话,1表多方通话。



<number>:呼叫号码,ASCII字符,合法的字符包括:‘0‘-‗9‘、‘*‘、 ‗#‘、‘+‘。‘+‘只能出现在号码的最前面。 
<type>:呼叫号码类型,一般为129。  
2.3 通话功能在RIL中的具体实现 
Android的RIL驱动模块加载后,只需在系统调用的对应接口函数中,接收上层传递过来的参数并实现相应的AT命令的发送即可。下图[16]表明了Android系统一个主动请求的电话过程。 
 
lemote-RIL
Phone
rildRIL implbaseband
4  Start Dial
Dial complete   Dial()Unix scokets
1
  Dial()
func call2
 onRequest()
AT+CDV
3
Listen thread
6
onRequestComplete()
7
Async return
8
Immediate return
5  Dial FinishAT+CHV
 
Android
 
当MT主动发起呼叫时(此时Android 系统界面表现行为为用户选中了拨打电话键),RIL-Java通过Scoket传递请求给RILD守护进程,RILD加载Vendor-RIL,调用Vendor-RIL中的请求函数—requestDial(),从而实现电话的呼叫。此时MT终端通过网络侧会上报呼叫发起指示^ORIG,以表示MT正在发起呼叫。当被呼叫方接通电话时,MT向TE上报呼叫接通指示^CONN,表明当前状态已经变为通话状态。当通话结束时,MT向TE上报通话结束指示^CEND。具体上报内容可以在MT终端输入―adb logcat –b radio‖命令查看。requestDial()函数具体实现代码如下所示: 
static void requestDial(void *data, size_t datalen, RIL_Token t) { 
      RIL_Dial *p_dial; 
      char *cmd;  //用于保存要发送AT命令的字符串       const char *clir;       int ret;



p_dial = (RIL_Dial *)data;  //接收data字段,并强制转换成RIL_Dial类型         switch (p_dial->clir) { 
                case 1: clir = "I"; break;  /*invocation*/                 case 2: clir = "i"; break;  /*suppression*/                 default: 
                case 0: clir = ""; break;   /*subscription default*/         } 
        asprintf(&cmd, "AT+CDV%s%s;", p_dial->address, clir);         // p_dial->address为被呼叫方电话号码 
        ret = at_send_command(cmd, NULL);  //发送呼叫命令         free(cmd); 
        RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0); 
        //调用onRequestComplete接口,通知RIL-Java层电话状态变化 

MT主动发起呼叫时或通话过程中,通话双方任意一方可以按挂机键实现挂机。根据挂断时机不同,RIL层在具体实现时会调用到不同的接口函数:requestHangup()、requestHangupWaitingOrBackground()、 requestHangupForegroundResumeBackground()、 
requestUDUB()。以requestHangup()为例,当MT主动发起呼叫但通话尚未接通时,呼叫方主动挂机会调用此函数,函数体具体实现如下: 
static void requestHangup(void *data, size_t datalen, RIL_Token t) { 
        at_send_command("AT+CHV", NULL); //直接发送呼叫挂断命令         RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0); 
       调用onRequestComplete接口,通知RIL-Java层电话状态变化 

其他函数实现与此类似,在此不再赘述。 
Android系统一个被动请求的电话过程如下图[16]所示: 
lemote-RIL

当移动终端有被叫来电时,MT会周期性(T=5s)地上报RING 
指示。 此时MT可以选择拒接(挂断)或者接听,当用户选择接听键时, 在RIL层实现时会调用到requestAnswer()函数,该函数体具体实现如下: 
static void requestAnswer(RIL_Token t) { 
        at_send_command("ATA", NULL);   //发送ATA命令用于接听         RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0);         //通知RIL-Java层电话状态变化 

此外在MI800 AT命令手册中,还设定了一条AT命令—AT+CLCC,用来查询当前的通话状态。在RIL具体实现该功能时,会调用到requestGetCurrentCalls()函数用以完成电话列表的刷新操作。



首先第

1

个参数即为我们之前所定义的标识,即

MARK

 

2

个参数是下

层的从数据流中解出数据的函数,

这里要和上层所传下来的类型对应,

例如

上层传下来的是

int

数组,这里也得是

dispathInts

 

否则数据会出错

 

3

个参数是该函数所要返回的值,

 

这里的

<type>

和第

2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值