引言:
这段时间手中的工作,正好好调试一款3g modem,于是乎就分析了一下Android Ril的代码,做了些总结归纳,阅读时可以先看前后两段以及流程图,这样可能更容易把握;
知识在于分享,文档中可能有些地方写的不对或是不完善,希望各位朋友留言指正,大家相互学习;
转载时请说明出处;
欢迎大家留言讨论,大家共同进步。
RIL 架构分析:
上图清楚的标识了ril在整个Android系统各层的表现形式,我们这里主要分析Ril(RIDL、librefrenece_ril.so、libril.so);
…/Hardware/ril/rild RILD的代码实现,有main函数,作为ril层的入口点,常驻系统进程,负责与上下层交互
…/Hardware/ril/libril 负责与守护进程交互???
…/Hardware/ril/reference-ril/ Ril库的实现,主要负责与modem进行交互
实现详细分析:
从init.rc中service ril-daemon /system/bin/rild -l /system/lib/libreference-ril.so -- -d /dev/ttyUSB1 -u /dev/ttyUSB2
可以知道,Android启动时,系统会启动一个与ril相关的service (ril-daemon),其入口命令为/system/bin/rild
(一)那么首先看看rild(/hardware/ril/rild/*);该目录下有两文件radiooptions.c、rild.c
# radiooptions
Usage: radiooptions [option] [extra_socket_args]
0 - RADIO_RESET,
1 - RADIO_OFF,
2 - UNSOL_NETWORK_STATE_CHANGE,
3 - QXDM_ENABLE,
4 - QXDM_DISABLE,
5 - RADIO_ON,
6 apn- SETUP_PDP apn,
7 - DEACTIVE_PDP,
8 number - DIAL_CALL number,
9 - ANSWER_CALL,
10 - END_CALL
1)有main函数入口,带参数输入,其输入参数就是最前面提到的init.rc启动ril-daemon是带的参数;当然如果仔细分析main函数代码,会发现其有多种获取参数的方法。
2)dlHandle = dlopen(rilLibPath, RTLD_NOW),打开动态库(system/lib/libreference-ril.so)。
3)RIL_startEventLoop(),启动监听事件队列(自己的理解),具体作用我们后面分析
4)dlsym(dlHandle, "RIL_Init"),获取动态库的RIL_Init
5)调用RIL_Init方法,并获取到RIL_RadioFunctions类型的返回值,RIL_RadioFunctions结构体包含了ril需要用的的几个重要函数的函数指针
typedef struct {
int version; /* set to RIL_VERSION */
RIL_RequestFunc onRequest;
RIL_RadioStateRequest onStateRequest;
RIL_Supports supports;
RIL_Cancel onCancel;
RIL_GetVersion getVersion;
} RIL_RadioFunctions;
注释:这些函数的具体实现,见/system/ril/reference-ril/*中的具体实现(这部分也是ril中变化最大的一部分,模块或是厂家不一样,其内容也会有较大的不同)
6)RIL_register(funcs),funcs就是5)中提到的RIL_RadioFunctions类型的返回值(具体实现见/system/ril/libril);
1)ril_event_loop是监听事件队列的主循环,当调用RIL_startEventLoop成功以后,其就一直在循环了;
2)现在来看一下事件描述的结构体,理解了这个才能继续后面的;
struct ril_event {
struct ril_event *next;
struct ril_event *prev;
int fd; //事件相关的句柄;比如现在这个事件是正当socket的,那么这个句柄就应该是一个socket句柄;还可以使串口等其他设备
int index;//在list中的index值
bool persist;//如果保持,则其回常驻watch_table,不会被从list中删除
struct timeval timeout;//select等待超时的时间
ril_event_cb func; //回调处理事件函数,通俗说就是当监听的fd有数据来或是select被处罚,则会在将此函数扔到pending_list中待执行
void *param; //回调带的参数
};
3)说明几个重要的队列
watch_table :监听事件队列,土话就是一个ril_event数据类型的链表或者数组;
time_table:超时事件队列,也就是说本来某某事件是在time_table里面的,表示正在被监听,过了些时间,超时了还没被促发,那么这个某某事件就被扔 pending_list里面, 待执行;
pending_list:待执行事件队列;
readFds:所以监听事件相关句柄的集合,土话就是一个数组,里面放着正在被监听的事件的相关的fd;
4)监听事件队列的相关函数(其实就和链表操作差不多,什么插入链表操作,删除链表操作啦)
ril_event_init:初始化个事件队列
ril_event_set:初始化某某事件
ril_event_add:添加事件到watch_table中
ril_timer_add:添加事件到time_table中
ril_event_del:从watch_table中删除某某事件,对了time_table不需要删除函数,其事件到了,事件会自动被清除掉
5)event部分流程图
(二)看看reference-ril(/hardware/ril/reference-ril/*),这里就是ril定制的部分,也是差异化最大的部分。
1)首先我们关注一下结构图RIL_RadioFunctions(也称为回调函数),这里面包含了一系列的ril操作相关的函数指针,最终提供给RILD(通过RIL_register)调用,个函数的字面意思已经比较清楚这里就不多做解释。
static const RIL_RadioFunctions s_callbacks = {
RIL_VERSION,
onRequest,
currentState,
onSupports,
onCancel,
getVersion
};
2)start_uevent_monitor(),现在大部分modem支持串口和usb口通讯,这里我们这要针对USB口,其底层驱动通过USB转串口来实现modem和系统进行交互,而这里是新建一个USB监控线程,监控USB插入、拔出等消息,主要应用与外置modem的热插拔功能。
3)这里还有一些输入参数的处理,这些输入参数主要来自Rild的模块main函数的输入参数
4)新建线程mainLoop,下面我们进入mainLoop分析,这里才是干实事的地方
readerLoop:用来监听接收来自modem AT口发送过来的命令回事数据
readerLoop:用来处理ppp网络拨号上网
另外注意这里onUnsolicited是个函数指针,其主要用来处理当readerLoop接收到数据时,会调用其做一些优先的判断处理,主要是进行一些 事件的主动上报,例如时间更新、短消息、ril状体变化等。
5)RIL_requestTimedCallback(initializeCallback, NULL, &TIMEVAL_DELAYINIT);前开了好些个线程,应该也差不多了,这里应该需要对modem进行一下初始化设置了(通过AT命令),详见initializeCallback,很多模块初始化系列AT 命令都放在这里处理。
这里我把这个家伙拎出来再说说,主要这里是返回response给上层的驱动器;AT命令都是以\r\n或\n\r为分隔符,readloop是line驱动的,当读到一行完整的相应其才会有返回,才会上报。
(三)请求流程(Java层是如何调用到ril里面来的,其实这也是reference-ril的一部分)
1)还记得在(一)中描述的RIL_startEventLoop-------->ril_event_loop------>监听着一系列的nfds,其中这里就监听了socket rild(见(一)中6)的描述),其 向ril_event_loop中就加入了RILD socket的listen event,当java层向此套接字发送连接请求时,其select就会监听到socket的连接请求,则会调用event对应的处理函数listenCallback(),具体如何被调用请参看图4 。
2)接下来看listenCallback()
……
s_fdCommand = accept(s_fdListen, (sockaddr *) &peeraddr, &socklen); //等待接收socket的连接请求,获取socket连接符s_fdCommand
……
p_rs = record_stream_new(s_fdCommand, MAX_COMMAND_BYTES); //建立一个record stream 与s_fdCommand绑定
ril_event_set (&s_commands_event, s_fdCommand, 1,processCommandsCallback, p_rs);//建立一个event,当s_fdCommand有数据,则调用回调 函数processCommandsCallback
rilEventAddWakeup (&s_commands_event); //向watch_table中插入event
3)接着分析processCommandsCallback()
其取到上层发送过来的数据后,会调用processCommandBuffer(p_record, recordlen) ,其中p_record包含上层发送过来的消息和数据
4) 接下来分析processCommandBuffer()
static CommandInfo s_commands[] = {
#include "ril_commands.h"
};
typedef struct {
int requestNumber;
void (*dispatchFunction) (Parcel &p, struct RequestInfo *pRI);
int(*responseFunction) (Parcel &p, void *response, size_t responselen);
} CommandInfo;
5)所以总结下来请求流程最终都会调用reference-ril中的onRequest()函数,所以如果你不想了解那么多,就直接把onRequest函数当成请求流程的处理函数即可,当然如果你要增减一些对应Java层数据处理功能,那么这套流程你还是需要清楚的。
(四)Response流程(Ril接收到的数据是如何返回给上层的,这也是reference-ril的一部分)
1)记得前面提过readerLoop,其实在mainloop中开启的一个线程,主要最用是监听modem AT 通讯口,并且接受其发过来的数据,并进行分类处理,
这里的分类只的是普通AT 命令和短信/自动上报处理;
2)先分析短信处理,在readerloop()中通过readline()函数接收响应,然后经过isSMSUnsolicited检测是否是短信/自动上报,如果是则通过 s_unsolHander(也就是函数onUnsolicited)来处理;如果不是短信/自动上报,则执行processLine来处理;
3)接着我们来看一下非短信/自动上报的处理过程,这里我们之间分析processLine函数,这个东东会把冲modem过来的response信息存储到一个临时变量sp_response中,具体怎么存储的劳烦您还是去看看代码吧,都是泪啊!
总结归纳:
(一)主要是从init.rc、已经main函数这样的程序员思维,先了解ril在Android中时怎么起来的,已经一部分的具体实现。
(二)(三)(四)才是RIl的核心部分,前面只是打基础,他们分别说明了RIL如何初始化、如何接收消息和数据、如何返回消息和数据