AndroidRIL结构分析与移植

介绍

本文档对AndroidRIL部分的内容进行了介绍,其重点放在了AndroidRIL的原生代码部分。

包括四个主题:

1.AndroidRIL框架介绍

2.AndroidRIL与WindowsMobileRIL

3.AndroidRILporting

4.AndroidRIL的java框架

在本文档中将Android代码中的重要模块列出进行分析,并给出了相关的程序执行流程介绍,以加深对模块间交互方式的理解。

对于java代码部分,这里仅进行简单的介绍。如果需要深入了解,可以查看相关参考资料。

本文档中还对AndroidRIL的Porting部分内容进行了描述和分析。

针对对unix操作系统环境并不熟悉的读者,本文档中所涉及到的相关知识包括:


Unixfilesystem


Unixsocket


Unixthread


Unix下I/O多路转接

以上信息可以在任意一份描述Unix系统调用的文档中找到。

1.AndroidRIL框架介绍

术语:

fd
unix文件描述符

pipe
unix管道

cond
一般是conditionvariable的缩写

tty
通常使用tty来简称各种类型的终端设备

unsolicitedresponse
被动请求命令来自baseband

eventloop
android的消息队列机制,由unix的系统调用select()实现

init.rc
init守护进程启动后被执行的启动脚本。

HAL
硬件抽象层(HardwareAbstractionLayer,HAL)

1.1.AndroidRIL概况:

AndroidRIL提供了无线硬件设备与电话服务之间的抽象层。

下图展示了RIL在Android体系中的位置。


android的ril位于应用程序框架与内核之间,分成了两个部分,一个部分是rild,它负责socket与应用程序框架进行通信。另外一个部分是VendorRIL,这个部分负责向下是通过两种方式与radio进行通信,它们是直接与radio通信的AT指令通道和用于传输包数据的通道,数据通道用于手机的上网功能。

对于RIL的java框架部分,也被分成了两个部分,一个是RIL模块,这个模块主要用于与下层的rild进行通信,另外一个是Phone模块,这个模块直接暴露电话功能接口给应用开发用户,供他们调用以进行电话功能的实现。

1.2.AndroidRIL目录结构:

Android的RIL模块位于Android/hardware/ril文件夹,有三个子模块:rild,libril,reference-ril

●include文件夹:

包含RIL头文件,最主要的是ril.h

●rild文件夹:

RIL守护进程,开机时被init守护进程调用启动,里面仅有main函数作为入口点,负责完成RIL初始化工作。

在rild.c文件中,将完成ril的加载过程,它会执行如下操作:


动态加载VendorRIL的.so文件


执行RIL_startEventLoop()开启消息队列以进行事件监听


通过执行VendorRIL的rilInit()方法来进行VendorRIL与libril的关系建立。

在rild文件夹中还包括一个radiooptions.c文件,它的作用是通过串口将一些radio相关的参数直接传给rild来对radio进行配置。

●libril文件夹:

在编译时libril被链入rild,它为rild提供了event处理功能,还提供了在rild与VendorRIL之间传递请求和响应消息的能力。

Libril提供的主要功能分布在两个主要方法内,一个是RIL_startEventLoop()方法,另一个是RIL_register()方法

RIL_startEventLoop()方法所提供的功能就是启用eventLoop线程,开始执行RIL消息队列。

RIL_register()方法的主要功能是启动名为rild的监听端口,等待java端通过socket进行连接。

●reference-ril文件夹:

Android自带的VendorRIL的参考实现。被编译成.so文件,由于本部分是厂商定制的重点所在。所以被设计为松散耦合,且可灵活配置的。在rild中通过opendl()的方式加载。

librefrence.so负责直接与radio通信,这包括将来自libril的指令转换为AT指令,并且将AT指令写入radio中。

reference-ril会接收调用者传来的参数,参数内容为与radio的通信方式。如通过串口连接radio,那么参数为这种形式:-d/dev/ttySx

1.3.AndroidRIL中的消息(event)队列机制:

在AndroidRIL中,为了达到等待多路输入并且不出现阻塞的目的,使用了IO多路复用机制。

如果使用阻塞I/O进行网络的读取写入,这意味着假如需要同时从两个网络文件描述符中读内容,那么如果读取操作在等待网络数据到来,这将可能很长时间阻塞在一个描述符上,另一个网络文件描述符不管有没有数据到来都无法被读取。

一种解决方案是:

如果使用非阻塞I/O进行网络的读取写入,在读取其中一个网络文件描述符如果阻塞将直接返回,再读取另外一个,这种方式的循环被称之为轮询。轮询方式确实能解决进行多路io操作时的阻塞问题,但是这种方法的不足之处是反复的执行读写调用将浪费cpu时钟。

I/O多路转接技术在这里提供了另一种比较好的解决方案:

它会先构造一张有关I/O描述符的列表,然后调用select函数,当IO描述符列表中的一个描述符准备好进行I/O时,该函数返回,并告知可以读或写哪个描述符。

AndroidRIL中消息队列的核心实现思想就是这种I/O多路转接技术。

消息队列机制的实现在ril_event.cpp中,其中被定义的ril_event结构是消息的主体。

每个ril_event结构,与一个fd句柄绑定(可以是文件,socket,管道等),并且带一个func指针,这个func指针所指的函数是个回调函数,它指定了当所绑定的fd准备好进行读取时所要进行的操作。

消息队列的开始点为RIL_startEventLoop函数。RIL_startEventLoop在ril.cpp中实现,它的主要目的是通过pthread_create(&s_tid_dispatch,&attr,eventLoop,NULL)建立一个dispatch线程,线程入口点在eventLoop.而在eventLoop中,会调ril_event.cpp中的ril_event_loop()函数,建立起消息队列机制。

ril_event是一个带有链表行为的struct,它最主要的成员一个是fd,一个是func:

[c-sharp] view plain copy
  1. structril_event{
  2. structril_event*next;
  3. structril_event*prev;
  4. intfd;
  5. intindex;
  6. boolpersist;
  7. structtimevaltimeout;
  8. ril_event_cbfunc;
  9. void*param;
  10. };

初始化一个新ril_event的操作是通过ril_event_set()来完成的,并通过ril_event_add()加入到消息队列之中,add会把队列里所有ril_event的fd,放入一个fd集合readFds中。这样ril_event_loop能通过一个多路复用I/O的机制(select)来等待这些fd。

在进入ril_event_loop()之前,在eventLoop中已经创建和挂入了s_wakeupfd_event,它是通过pipe的机制实现的,这个管道fd的回调函数并没有实现什么功能,它的目的只是为了让select方法能返回一次,这样select()方法就能重新跟踪新加入事件队列的fd和timeout设置。

所以在添加新fd到eventLoop时,往往不是直接调用ril_event_add,实际通常用rilEventAddWakeup来添加,这个方法除了会间接调用ril_event_add外,还会调用triggerEvLoop()函数来向s_fdWakeupWrite中写入一个空字符,这样select()函数会返回并重新执行,新加入的文件描述符便得以被select()加载并跟踪。

如果在ril_event队列中任何一个fd已经准备好,则进入分析流程:

processTimeouts(),processReadReadies(&rfds,n),firePending().其中firePending()方法执行这个event的func,也就是回调函数。

在AndroidRIL初始化完成后,将有几个event被挂入到eventLoop中:

1.s_listen_event:名为rild的socket,主要requeset&response通道

2.s_debug_event:名为rild-debug的socket,调试用requeset&response通道

3.s_wakeupfd_event:无名管道,用于队列主动唤醒

这其中最为重要的event就是s_listen_event,它作为request与response的通道实现。

在ril_event.cpp中还持有一个watch_table数组,一个timer_list链表和一个pending_list链表。

watch_table数组的目的很单纯,存放当前被eventLoop等待的ril_event(非timerevent),供eventLoop唤醒时使用。

timer_list是存放timerevent的链表,在eventLoop唤醒时要对这些timerevent单独进行处理

pending_list:待处理(对其回调函数进行调用)的所有ril_event的链表。


1.4.AndroidRIL中初始化流程分析:

●Rild的初始化流程

初始化流程从rild.c中的main函数开始,它被init守护进行调用执行:

首先在main()函数内会首先通过dlopen()函数加载VendorRIL(在自带的参考实现中为librefrence_ril.so)。接着调用RIL_startEventLoop()函数来启动消息队列机制。

调用librefrence_ril.so的RIL_Init()函数来进行VendorRIL的初始化。RIL_Init()函数执行后会返回一个RIL_RadioFunction结构体,这个结构体内最重要的成员就是onRequest()方法。onRequest()方法会被dispatchFunction调用,也就是说dispatchFunction调用是程序流从rild转入VendorRIL的分界点。

RIL_register()函数将实现两个目地,一个是将RIL_INIT中获得的RIL_RadioFunction进行注册,rild通过此种方式保证自己持有一个RIL_RadioFunction实例,第二个是将s_fdListen加入到消息队列机制中,开启s_fdListen的事件监听。

●VendorRIL的初始化流程:

RIL_Init被调用后首先通过参数获取硬件接口的设备文件或模拟硬件接口的socket。(参见上文中对reference-ril文件夹的介绍)

接下来是创建mainLoop线程,并跳入到线程内执行。mainLoop会建立起与硬件的通信,然后通过read方法阻塞等待硬件的主动上报或响应。mainLoop还会调用initlizeCallBack()函数来向radio发送一系列的AT命令来进行radio的初始化设置工作。

1.5.AndroidRIL中request流程分析:

上层应用开始向rild通过socket传输数据时,通过RIL消息队列机制,s_listen_event的回调函数listenCallBack将会被调用,开始进行数据流的分析与处理。

接下来,s_fdCommand=accept(s_fdListen,(sockaddr*)&peeraddr,&socklen),获取传入的socket描述符,也就是上层的javaRIL传入的连接。

然后,通过record_stream_new()建立起一个RecordStream,将这个record_stream与s_fdCommand绑定,RecordStream实际上是一个用于存放数据的结构体,这个结构体提供了一些操作类来保证这个RecordStream所绑定的文件描述符被读取时里面的数据会被完整读取。

一旦s_fdCommand中有数据,它的回调函数processCommandsCallback()将会被调用,processCommandsCallback()通过record_stream_get_next阻塞读取s_fdCommand上发来的数据,直到收到一完整的request。然后将其传递进processCommandBuffer()函数,processCommandBuffer()正式进入了命令的解析部分。

每个接收到的命令将以RequestInfo的形式存在。从socket过来的数据流,是Parcel处理过的序列化字节流,在这里会通过反序列化的方法提取出来。最前面的是request号,以及token域(request的递增序列号)。request号是一个CommandInfo,它在ril_command.h中定义。

接下来,这个RequestInfo会被挂入pending的request队列,执行具体的dispatchFunction(),进行详细解析。

dispatchFunction方法有着多种实现,如dispatchVoid,dispatchString,它们的调用取决于Parcel的参数传入形式。比如说在dispatchDial方法中,Parcel对象将被解析为RIL_Dial结构。这是disptachFunction的任务之一,它的另一个任务就是调用onRequest()方法,并将解析的内容传入onRequest()方法。

从onRequest方法开始,程序控制流脱离了RILD,进入到了VendorRIL中。

onRequest方法会通过传入的请求类型来调用指定的request×××()方法,request×××()方法则负责组装AT指令并下发给at_send_command()方法集合中的一个,这个方法集合提供了针对不同类型AT指令的实现,如单行AT指令at_send_command_singleline(),短信息指令at_send_command_sms()等。

最后,执行at_send_command_full(),再通过一个互斥的at_send_command_full_nolock()调用,完成最终的写出操作,在writeline()中,写出到初始化时打开的设备中。

需要注意的是:at_send_command_full_nolock()在将指令写入radio后并不会直接返回,而是通过条件变量等待响应信息,得到响应信息后会携带这些信息返回。具体流程可以参考下面的response流程分析。

1.6.AndroidRIL中response流程分析:

AT的response有两种,一种是unsolicited。另一种是普通response,也就是命令的响应。

response信息的获取在readerLoop()中。由readline()函数读取上来。

读取到的line将被传入processLine()函数进行解析,processLine()函数首先会判断当前的响应是主动响应还是普通响应,如果是主动响应,将调用handleUnsolicited()函数,如果为普通响应,那么将调用handleFinalResponse()函数进行处理对响应串的主要的解析过程,由at_tok.c中的各种解析函数完成,提供字符串分析解析功能。

●对主动上报的解析

handleUnsolicited()方法处理主动上报,它会调用onUnsolicited()来进行进一步的解析,这个函数在Vendor-RIL初始化时被传入at_open()函数,onUnsolicited只解析出头部(一般是+XXXX的形式),然后按类型决定下一步操作,操作为RIL_onUnsolicitedResponse和RIL_requestTimedCallback两种。

在RIL_onUnsolicitedResponse()函数中,通过Parcel传递,将RESPONSE_UNSOLICITED,unsolResponse(request号)写入Parcel,然后调用对应的responseFunction完成进一步的的解析,将解析的数据写入Parcel中,最后通过sendResponse()→sendResponseRaw()→blockingWrite()→writeLine()将数据写回给与应用层通信的socket。

在RIL_requestTimedCallback()函数中。通过event机制实现的timer机制,回调对应的内部处理函数。通过internalRequestTimedCallback将回调添加到event循环,最终完成callback上挂的函数的回调。比如pollSIMState,onPDPContextListChanged等回调,不用返回上层,内部处理就可以。

●对普通上报的解析

IsFinalResponse()和isFinalResponseError()所处理的是一条AT指令的响应上报,它们将转入handleFinalResponse方法。

handleFinalResponse()函数会将所有响应信息装入sp_response,这是一个ATResponse结构,它的成员包括成功与否(success)以及一个中间结果(p_intermediates)。

handleFinalResponse()在将响应结果保存至sp_response后,设置s_commandcond这一条件变量,此条件变量由at_send_command_full_nolock等待。

at_send_command_full_nolock获得到了完整的响应信息(在sp_response中),便开始进行响应信息的处理,最后由RIL_onRequestComplete将响应数据序列化并通过sendResponse传递至与应用层通信的socket,这一部分与RIL_onUnsolicitedResponse()函数的功能非常相似,请参考对主动上报的解析部分。

2.AndroidRIL与WindowsMobileRIL

AndroidRIL与WindowsMobileRIL在设计思路上都是作为一个radio的抽象,为上层提供电话服务,但在实现方式上两者有着一定的差异,这种差异的产生主要是源自操作系统机制的不同。

AndroidRIL被实现为HAL,相对于windowsmobile中被实现为驱动的方式,AndroidRIL模块的内聚性更为理想,可维护性也将更强,你也可以把AndroidRil看做一个中间件。AndroidRIL部分的开发工作,只需要拿到相应的radio文件描述符,就可以进行操作,无需关注radio的I/O驱动实现。

2.1两者在与应用通信上的实现对比

WindowsMobileRIL在实现与应用的通信时提供了RILProxy,在这个层面中它定义了大量的RIL_***()函数来作为电话服务请求。这一点与AndroidRIL的实现比较相似,AndroidRIL中在ril.h内提供了一系列的宏来定义电话服务请求。

在Android中的rild功能类似于windowsmobileRIL的RILproxy。它同样也是起到一个中介的作用,为上层接口向下传递请求,并上传回响应。在windowsmobileRIL中要为每一个应用程序客户提供一份RilProxy实例。

对于这两种操作系统平台,RIL所定义的所有请求是不可更改的。


2.2两者在线程结构与回调机制上的对比

在windowsmobile的设计中,request与response被设计为异步执行的,他们分别使用两个队列来对它们的异步行为进行管理,执行命令下发和上报命令处理的过程也互不影响,下发命令与命令的相应响应之间的依赖关系由应用程序来捏合。

在androidril中的request与response设计与windowsmobile不同,它的命令与响应之间是同步的过程。也就是说一条命令被下发后,将等待执行结果,并进行处理,再上向上层发。而不是直接异步的进行处理和向上发送。

3.AndroidRILporting

3.1.命名

要实现某个无线模块的RIL,需要创建一个实现了所有请求方法的共享库,保证Android能够响应无线通信请求。所有的请求被定义ril.h中。

不同的Modem使用不同的端口,这个在init.rc中设置。

  Android提供了一个参考VendorRIL,RIL参考源码在reference-ril。

  将你自己的VendorRIL实现编译为共享库形式:libril-<companyname>-<RILversion>.so

  比如:

  libril-techfaith-124.so

  其中:

  libril:所有vendorRIL的开头

  <companyname>:公司缩写

  <RILversion>:RIL版本number

  so:文件扩展

3.2.AndroidRIL的配置与加载

在init.rc文件中,将通过这种方式来进行AndroidRIL的加载。

serviceril-daemon/system/bin/rild-l/system/lib/libreference-ril.so---d/dev/ttyS0

也可以手动加载:

/system/bin/rild-l/system/lib/libreference-ril.so---d/dev/ttyS0

这两种方式,都将启动rild守护进程,然后通过-l参数将libreference-ril.so共享库链入,libreference-ril.so的参数-d是指加载一个串口设备,/dev/ttyS0则是这个串口设备的具体设备文件,除了参数-d外,还有-s代表加载类型为socket的设备,-p代表回环接口。

3.3.AndroidRIL的编译结构

rild:

被编译成可执行文件,rild以守进程的形式执行。

libril:

将被编译为共享库,并被链入rild。

VendorRIL:

可以以两种方式来运行,如果定义了RIL_SHLIB宏,那么它将被编译成共享库,如果没定义RIL_SHLIB宏,它将以守护进程程序的方式被调用执行。

4.AndroidRIL的java框架

AndroidRIL的Java部分也被分为了两个模块,RIL模块与Phone模块。其中RIL模块负责进行请求以及相应的处理,它将直接与RIL的原声代码进行通信。而Phone模块则向应用程序开发者提供了一系列的电话功能接口。

4.1.RIL模块结构

在RIL.java中实现了几个类来进行与下层rild的通信。

它实现了如下几个类来完成操作:

RILRequest:代表一个命令请求

RIL.RILSender:负责AT指令的发送

RIL.RILReceiver:用于处理主动和普通上报信息

RIL.RILSender与RIL.RILReceiver是两个线程。

RILRequest提供了obtain()方法,用于得到具体的request操作,这些操作被定义在RILConstants.java中(RILConstants.java中定义的request命令与RIL原生代码中ril.h中定义的request命令是相同的),然后通过send()函数发送EVENT_SEND,在RIL_Sender线程中处理这个EVENT_SEND将命令写入到stream(socket)中去。Socket是来自常量SOCKET_NAME_RIL,它与RIL原生代码部分的s_fdListen所指的socket是同一个。

当有上报信息来到时,系统将通过RILReciver来得到信息,并进行处理。在RILReciver的生命周期里,它一直监视着SOCKET_NAME_RIL这个socket,当有数据到来时,它将通过readRilMessage()方法读取到一个完整的响应,然后通过processResponse来进行处理。

4.2.Phone模块结构

Android通过暴露Phone模块来供上层应用程序用户使用电话功能相关的接口。它为用户提供了诸如电话呼叫,短信息,SIM卡管理之类的接口调用。它的核心部分是类GSMPhone,这个是Gsm的电话实现,需要通过PhoneFactory获取这个GSMPhone。

GSMPhone并不是直接提供接口给上层用户使用,而是通过另外一个管理类TelephonyManager来供应用程序用户使用。

类TelephonyManager实现了android的电话相关操作。它主要使用两个服务来访问telephony功能:

1.ITelephony,提供给上层应用程序用户与telephony进行操作,交互的接口,在packages/apps/Phone中由PhoneInterfaceManager.java实现。

2.ItelephonyRegistry提供了一个通知机制,将底层来的上报通知给框架中需要得到通知的部分,由TelephonyRegistry.java实现。

GSMPhone通过PhoneNotifier的实现者DefaultPhoneNotifier将具体的事件转化为函数调用,通知到TelephonyRegistry。TelephonyRegistry再通过两种方式通知给用户,其一是广播事件,另外一种是通过服务用户在TelephonyRegistry中注册的IphoneStateListener接口,实现回调(回调方式参见android的aidl机制)。

参考资料

相关网站:

http://code.google.com/android/

http://android-dls.com

http://www.ibm.com/developerworks/cn/opensource/theme/android/

http://en.wikipedia.org/wiki/Android_%28operating_system%29

相关书籍:

《UNIX环境高级编程》

《UNIX编程艺术》

《Android系统原理及开发要点详解》


这里有篇文章,介绍有个哥们移植ril经验:针对华为em770 Android 平台

http://blog.csdn.net/embos/archive/2010/11/15/6011122.aspx



更多相关文章

  1. 箭头函数的基础使用
  2. Python技巧匿名函数、回调函数和高阶函数
  3. Android(安卓)动态加载APK--代码安装、获取资源及Intent调用已安
  4. Android:View的getLocalVisibleRect()和getGlobalVisibleRect()的区别
  5. Android(安卓)GPS基础
  6. Android(安卓)Audio代码分析17 - setvolume函数
  7. android视频截图
  8. android调用本地录制程序获取录制文件路径的问题
  9. Android每隔一段时间就重复执行指定代码

随机推荐

  1. Android(安卓)Retrofit 入门教程
  2. Android(安卓)命令行手动编译打包详解
  3. Android(安卓)studio 实现按两次返回退出
  4. Android添加room依赖的正确姿势(附带完整
  5. Drozer 官方使用指南(英文版和中文版)下
  6. android提取字符串中为字母的字符
  7. Android中显示消息通知栏
  8. 关于 android AES 部分机器 javax.crypto
  9. View、Window、WindowManager---Choreogr
  10. android Contentprovider配置记录