全部博文(2005)
分类: 嵌入式
2011-02-25 23:28:19
request流程
1. 多路复用
I/O
机制的运转
上文说到request
是接收
,
是通过
ril_event_loop
中的多路复用
I/O,
也对初始化做了分析
.
现在我们来仔细看看这个机制如何运转
.
ril_event_set负责配置一个
event,
主要有两种
event
:
ril_event_add添加使用多路
I/O
的
event,
它负责将其挂到队列
,
同时将
event
的通道句柄
fd
加入到
watch_table,
然后通过
select
等待
.
ril_timer_add添加
timer event,
它将其挂在队列
,
同时重新计算最短超时时间
.
无论哪种add,
最后都会调用
triggerEvLoop
来刷新队列
,
更新超时值或等待对象
.
刷新之后, ril_event_loop
从阻塞的位置
,select
返回
,
只有两种可能
,
一是超时
,
二是等待到了某
I/O
操作
.
超时的处理在processTimeouts
中
,
摘下超时的
event,
加入
pending_list.
检查有I/O
操作的通道的处理在
processReadReadies
中
,
将超时的
event
加入
pending_list.
最后在firePending
中
,
检索
pending_list
的
event
并依次执行
event->func.
这些操作完之后,
计算新超时时间
,
并重新
select
阻塞于多路
I/O.
前面的初始化流程已分析得知,
初始化完成以后
,
队列上挂了
3
个
event
对象
,
分别是:
s_listen_event: 名为
rild
的
socket,
主要
requeset & response
通道
s_debug_event: 名为
rild-debug
的
socket,
调试用
requeset & response
通道(流程与
s_listen_event
基本相同
,
后面仅分析
s_listen_event
)
s_wakeupfd_event: 无名管道
,
用于队列主动唤醒(前面提到的队列刷新
,
就用它来实现
,
请参考使用它的相关地方)
2. request的传入和
dispatch
明白了event
队列的基本运行流程
,
我们可以来看看
request
是怎么传入和
dispatch
的了
.
上 层的部分,
核心代码在
frameworks/base/telephony/java/com/android/internal/telephony /gsm/RIL.java,
这是
android java
框架处理
radio(gsm)
的核心组件
.
本文因为主要关注
rild,
也就是驱动部分
,
所以这里只作简单介绍
.
我们看一个具体的例子,RIL.java
中的
dial
函数:
public void
dial (String address, int clirMode, Message result)
{
RILRequest rr = RILRequest.obtain(RIL_REQUEST_DIAL, result);
rr.mp.writeString(address);
rr.mp.writeInt(clirMode);
if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
send(rr);
}
rr是以
RIL_REQUEST_DIAL
为
request
号而申请的一个
RILRequest
对象
.
这个
request
号在
java
框架和
rild
库中共享(参考
RILConstants.java
中这些值的由来
:)
)
RILRequest初始化的时候
,
会连接名为
rild
的
socket
(也就是
rild
中
s_listen_event
绑定的
socket
)
,
初始化数据传输的通道
.
rr.mp 是
Parcel
对象
,Parcel
是一套简单的序列化协议
,
用于将对象(或对象的成员)序列化成字节流
,
以供传递参数之用
.
这里可以看到
String address
和
int clirMode
都是将依次序列化的成员
.
在这之前
,rr
初始化的时候
,request
号跟
request
的序列号(自动生成的递增数)
,
已经成为头两个 将被序列化的成员
.
这为后面的
request
解析打下了基础
.
接下来是send
到
handleMessage
的流程
,send
将
rr
直接传递给另 一个线程的
handleMessage,handleMessage
执行
data = rr.mp.marshall()
执行序列化操作
,
并将
data
字节流写入到
rild socket.
接下来回到我们的rild,select
发现
rild socket
有了请求链接的信号
,
导致
s_listen_event
被挂入
pending_list,
执行
event->func,
即
static void listenCallback (int fd, short flags, void *param);
接下来,s_fdCommand = accept(s_fdListen, (sockaddr *) &peeraddr, &socklen),
获取传入的
socket
描述符
,
也就是上层的
java RIL
传入的连接
.
然 后,
通过
record_stream_new
建立起一个
record_stream,
将其与
s_fdCommand
绑定
,
这里我们不关注
record_stream
的具体流程
,
我们来关注
command event
的回调
, processCommandsCallback
函数
,
从前面的
event
机制分析
,
一旦
s_fdCommand
上有数据
,
此回调函数就会被调用
.
(略过
onNewCommandConnect
的分析)
processCommandsCallback通过
record_stream_get_next
阻塞读取
s_fdCommand
上发来的 数据
,
直到收到一完整的
request(request
包的完整性由
record_stream
的机制保证
),
然后将其送达
processCommandBuffer.
进入processCommandBuffer
以后
,
我们就正式进入了命令的解析部分
.
每个命令将以
RequestInfo
的形式存在
.
typedef struct RequestInfo {
int32_t token; //this is not RIL_Token
CommandInfo *pCI;
struct RequestInfo *p_next;
char cancelled;
char local; // responses to local commands do not go back to command process
} RequestInfo;
这 里的pRI
就是一个
RequestInfo
结构指针
,
从
socket
过来的数据流
,
前面提到是
Parcel
处理过的序列化字节流
,
这里会通过反序列化的方法提取出来
.
最前面的是
request
号
,
以及
token
域
(request
的递增序列号
).
我们更关注这个
request
号
,
前面提到
,
上层和
rild
之间
,
这个号是统一的
.
它的定义是一个包含
ril_commands.h
的枚举
,
在
ril.cpp
中
static CommandInfo s_commands[] = {
#include "ril_commands.h"
};
pRI直接访问这个数组
,
来获取自己的
pCI.
这是一个CommandInfo
结构
:
typedef struct {
int requestNumber;
void (*dispatchFunction) (Parcel &p, struct RequestInfo *pRI);
int(*responseFunction) (Parcel &p, void *response, size_t responselen);
} CommandInfo;
基本解析到这里就完成了,
接下来
, pRI
被挂入
pending
的
request
队列
,
执行具体的
pCI->dispatchFunction,
进行详细解析
.
3. request的详细解析
对dial
而言
, CommandInfo
结构是这样初始化的
:
{RIL_REQUEST_DIAL, dispatchDial, responseVoid},
这 里执行dispatchFunction,
也就是
dispatchDial
这一函数
.
我们可以看到其实有很多种类的
dispatch function,
比如
dispatchVoid, dispatchStrings, dispatchSIM_IO
等等
,
这些函数的区别
,
在于
Parcel
传入的参数形式
,Void
就是不带参数的
,Strings
是以
string[]
做参数
,
又如
Dial
等
,
有自己的参数解析方式
,
以此类 推
.
request号和参数现在都有了
,
那么可以进行具体的
request
函数调用了
.
s_callbacks.onRequest(pRI->pCI->requestNumber, xxx, len, pRI)完成这一操作
.
s_callbacks 是上篇文章中提到的获取自
libreference-ril
的
RIL_RadioFunctions
结构指针
,request
请求在这里转入底层的
libreference-ril
处理
,handler
是
reference-ril.c
中的
onRequest.
onRequest进行一个简单的
switch
分发
,
我们依然来看
RIL_REQUEST_DIAL
流程是 onRequest-->requestDial-->at_send_command-->at_send_command_full-->at_send_command_full_nolock-->writeline
requestDial中将命令和参数转换成对应的
AT
命令
,
调用公共
send command
接口
at_send_command.
除 了这个接口之外,
还有
at_send_command_singleline,at_send_command_sms,at_send_command_multiline
等
,
这是根据
at
返回值
,
以及发命令流程的类型来区别的
.
比如
at+csq
这类
,
需要
at_send_command_singleline,
而发送短 信
,
因为有
prompt
提示符
">",
传裸数据
,
结束符等一系列操作
,
需要专门用
at_send_command_sms
来实现
.
然后执行at_send_command_full,
前面几个接口都会最终到这里
,
再通过一个互斥的
at_send_command_full_nolock
调用
,
然后完成最终的写出操作
,
在
writeline
中
,
写出到初始化时打开的设备中
.
writeline返回之后
,
还有一些操作
,
如保存
type
等信息
,
供
response
回来时候使用
,
以及一些超时处理
.
不再详述
.
到这里,request
的详细流程
,
就分析完毕了
.
response流程
前文对request
的分析, 终止在了
at_send_command_full_nolock
里的
writeline
操作,因为这里完成命令写出到硬件设备的操作,接下来就是等待硬件响应,也就是
response
的过程了。我们的分析也是从这里开始。
response信息的获取,是在第一篇初始化分析中,提到的
readerLoop
中。由
readline
函数以
‘
行
’
为单位接收上来。
AT的
response
有两种,一是主动上报的,比如网络状态,短信,来电等都不需要经过请求,有一
unsolicited
词语专门描述。另一种才是真正意义上的
response
,也就是命令的响应。
这 里我们可以看到,所有的行,首先经过sms
的自动上报筛选,因为短信的
AT
处理通常比较麻烦,无论收发都单独列出。这里是因为要即时处理这条短信消息(两 行,标志+
pdu
),而不能拆开处理。处理函数为
onUnsolicited
(由
s_unsolHandler
指向),我们等下介绍。
除开sms
的特例,所有的
line
都要经过
processLine
,我们来看看这个流程:
processLine
|----no cmd--->handleUnsolicited //主动上报
|----isFinalResponseSuccess--->handleFinalResponse //成功
,
标准响应
|----isFinalResponseError--->handleFinalResponse //失败,标准响应
|----get '>'--->send sms pdu //收到
>
符号,发送
sms
数据再继续等待响应
|----switch s_type--->具体响应
//
命令有具体的响应信息需要对应分析
我 们这里主要关注handleUnsolicited
自动上报(会调用到前面
smsUnsolicite
也调用的
onUnsolicite
),以及
switch s_type
具体响应信息,另外具体响应需要
handleFinalResponse
这样的标准响应来最终完成。
1. onUnsolicite(主动上报响应)
static void onUnsolicited (const char *s, const char *sms_pdu);
短信的AT
设计真是麻烦的主,以致这个函数的第二个参数完全就是为它准备的。
response 的主要的解析过程,由
at_tok.c
中的函数完成,其实就是字符串按块解析,具体的解析方式由每条命令或上报信息自行决定。这里不再详 述,
onUnsolicited
只解析出头部
(
一般是
+XXXX
的形式
)
,然后按类型决定下一步操作,操作为
RIL_onUnsolicitedResponse
和
RIL_requestTimedCallback
两种。
a)RIL_onUnsolicitedResponse:
将 unsolicited
的信息直接返回给上层。通过
Parcel
传递,将
RESPONSE_UNSOLICITED
,
unsolResponse
(
request
号)写入
Parcel
先,然后通过
s_unsolResponses
数组,查找到对应的
responseFunction
完成进一步的的解析,存入
Parcel
中。最终通过
sendResponse
将其传递回原进程。流程:
sendResponse-->sendResponseRaw-->blockingWrite-->write to s_fdCommand(前面建立起来的和上层框架的
socket
连接
)
这些步骤之后有一些唤醒系统等其他操作。不再详述。
b)RIL_requestTimedCallback:
通 过event
机制(参考文章二)实现的
timer
机制,回调对应的内部处理函数。通过
internalRequestTimedCallback
将回调添 加到
event
循环,最终完成
callback
上挂的函数的回调。比如
pollSIMState
,
onPDPContextListChanged
等回 调, 不用返回上层, 内部处理就可以。
2. switch s_type(命令的具体响应)及
handleFinalResponse
(标准响应)
命 令的类型(s_type
)在
send command
的时候设置(参考文章二),有
NO_RESULT
,
NUMERIC
,
SINGLELINE
,
MULTILINE
几种,供不同的
AT
使用。比 如
AT+CSQ
是
singleline,
返回
at+csq=xx,xx
,再加一行
OK
,比如一些设置命令,就是
no_result,
只有一行
OK
或
ERROR
。
这几个类型的解析都很相仿,通过一定的判断(比较AT
头标记等),如果是对应的响应,就通过
addIntermediate
挂到一个临时结果
sp_response->p_intermediates
队列里。如果不是对应响应,那它其实应 该是穿插其中的自动上报,用
onUnsolicite
来处理。
具体响应,只起一个获取响应信息到临时结果,等待具体分析的作用。无论有无具体响应,最终都得以标准响应handleFinalResponse
来完成,也就是接受到
OK,ERROR
等标准
response
来结束,这是大多数
AT
命令的规范。
handleFinalResponse 会设置
s_commandcond
这一
object
,也就是
at_send_command_full_nolock
等待的对象。到这里,响应的完整信息 已经完全获得,
send command
可以进一步处理返回的信息了(临时结果,以及标准返回的成功或失败,都在
sp_response
中)。
pp_outResponse参数将
sp_response
返回给调用
at_send_command_full_nolock
的函数。
继续我们在文章二的分析的话,这个函数其实是requestDial
,不过
requestDial
忽略了响应,所以我们另外看个例子,如
requestSignalStrength
,命令其实就是前面提到的
at+csq
:
可以看到确实是通过at_send_command_singleline
来进行的操作,
response
在
p_response
中。
p_response如果返回失败(也就是标准响应的
ERROR
等造成),则通过
RIL_onRequestComplete
发送返回数据给上层,结束命令。
如果成功,则进一步分析p_response->p_intermediates
, 同样是通过
at_tok.c
里的函数进行分析。并同样将结果通过
RIL_onRequestComplete
返回。
RIL_onRequestComplete:
RIL_onRequestComplete和
RIL_onUnsolicitedResponse
很相仿,功能也一致。
通 过Parcel
来传递回上层,同样是先写入
RESPONSE_SOLICITED
(区别于
RESPONSE_UNSOLICITED
),
pRI->token(
上层传下的
request
号),错误码(
send command
的错误,不是
AT
响应)。如果有
AT
响应,通过访问
pRI->pCI->responseFunction
来完成具体
response
的解析,并写入
Parcel
。
然后通过同样的途径:
sendResponse-->sendResponseRaw-->blockingWrite-->write to s_fdCommand
完成最终的响应传递。