Android RIL开发介绍
------------------海信移动技术公司第三研究所陈泽元20130314
篇前语
RIL(Radio Interface Layer)的设计目的,就是为了将modem功能抽象出来,使其不会因不同的modem而做重大的颠覆性修改和调试。它提供了AP与modem 之间通讯的桥梁,在保证AP与modem正常有效通讯的同时,尽可能的简化AP 与modem之间的交互。
RIL将用户所需要用到的各种功能抽象成对应的RIL request和unsolicited command,最终转换成3GPP标准的AT命令,交由modem处理,并将处理结果返回给UI。
AT命令通常被用作与第三方modem通讯的一个非常有效的方式,当第三方模块无法轻易的移植到某个平台上或仅提供库文件、二进制文件使用时,通常会使用AT命令来与模块进行交互,以实现相应的功能。AT命令在3GPP中都有相
关规定,能够支持modem的各种功能,个人理解,实际它上就是modem各种
功能的抽象,提供与外界通讯的接口。(实际上,我们也可以根据AT命令的格式,自己进行扩展,应用
于非无线通讯的第三方交互中,比如PC客户端与某模块之间的通讯,或某两个PC应用程序之间的通讯,都是可以的,关键就看你如何理解,如何定义,如何抽象出你的AT命令了)。
AT命令按照请求方式分为两种:一种是主动请求命令,称作request AT command;另外一种是非请求时的modem上报命令,称作unsolicited AT command。
在Android平台中,RIL已被模块化,包含三部分:RILD、libril和Vendor RIL。
本篇的目的就是讲解Android平台这三个模块相关的重点概念和关键点,同时对扩展新的一个RIL request的处理和新的unsolicited AT command的处理做个
介绍,适合于刚接手RIL开发的工程师以尽快的熟悉自己的开发工作。
一、Android RILD介绍
RILD是一个开机启动的后台服务进程,在中定义。当系统起来后,init 进程在解析中的service时,就会启动并加载RILD进程,同时创建两个名为rild和rild-debug的socket。RILD作为该socket的服务器端运行,等待客户端RILJ的连接。Android的socket通讯是基于linux的socket,在linux socket基础上做了一层封装,供Java层调用。
RILD在中的启动:
service ril-daemon /system/bin/rild
class late_start
socket rild stream 660 root radio
socket rild-debug stream 660 radio system
user root
group radio cache inet misc audio sdcard_rw
RILD进程启动后,主要做以下几件事情:
(1) 依次通过dlopen()和dlsym()来到Vendor RIL链接库,并调用RIL_Init()来初始化RIL和modem (将在后面Vendor RIL中讲解)
(2) 调用RIL_startEventLoop()创建一个独立线程,在该线程中建立一个基于pipe的多I/O复用的RIL消息队列处理机制,并进入ril_event_loop()循环,select pipe中的fd来处理各个RIL消息(将在libril中讲解)
(3) 调用RIL_register()监听客户端的socket连接,一旦发现有客户端连接成功,将进入processCommandsCallback()循环,不断读取来自客户端的request数据并分发处理。
其中,(1)将进入Vendor RIL;(2)和(3)将进入libril,而RILD在处理完这三件事情后,就没有任何工作了,将在代码
while(1) {
sleep(0x00ffffff);
}
中一直sleep()下去。
下面对RILD所做的三件事情做个较详细的介绍:
(1) 加载Vendor RIL链接库
Vendor RIL库有两种方式可以指定:
①在中通过-l参数指定,如:
service ril-daemon /system/bin/rild -l /system/lib/libreference-ril-gsm.so
②在system.prop中通过rild.libpath来指定,如:
rild.libpath=/system/lib/libreference-ril-gsm.so
另外,在modem通讯时,必须指定通讯的端口,用于AT命令通讯,也有两种方式可以指定通讯端口:
①在中通过-d参数指定,如:
service ril-daemon /system/bin/rild -l /system/lib/libreference-ril-gsm.so -- -d
/dev/ttyS0
②在system.prop中通过rild.libargs来指定,如:
rild.libargs=-d /dev/ttyS0
Vendor RIL库是通过接口dlopen()来加载的,dlHandle = dlopen(rilLibPath, RTLD_NOW);加载后,将通过代码rilInit = (const RIL_RadioFunctions *(*)(const struct RIL_Env *, int, char **))dlsym(dlHandle, "RIL_Init");来到接口RIL_Init(),并在接下来调用该接口(funcs = rilInit(&s_rilE
nv, argc, rilArgv);),从而让Vendor RIL进行RIL及modem相关的一系列初始化动作,为接下来的AT通讯做好准备,这部分内容将在Vendor RIL部分重点讲解。
值得重点提到的一点是,在RIL_init()调用处理结束后,会返回
RIL_RadioFunctions的一组函数指针,其中最为核心接口是onRequest(),用于AT request的处理。该组函数指针将由RIL_register()传递到libril中,在处理AT request 时,及时调用onRequest()将来自RILJ的request转换成AT命令,并发送给modem 进行处理。
(2) 启动RIL消息队列处理机制
RIL消息队列机制的启动位于RIL_startEventLoop(),它内部将启动一个独立
线程并进入eventLoop(),开始消息的循环处理。
而在eventLoop()内部,创建了一个pipe,这是I/O多路复用处理RIL消息队列的核心。每种RIL_event,都将与一个fd一一对应,每个fd都有一个回调函数与其绑定,即每一种RIL_event都有一个handler与其对应,该handler就是在添加RIL_event时,调用ril_event_set()所绑定的回调函数。当有新的一种RIL_event 需要处理时,会首先调用ril_event_set()将event与回调函数绑定,然后调用rilEventAddWakeup()将其加入watch_table待处理事件链表中,并将其fd放入readfs集中,等待pipe的
select调度,一旦得到调度,就会依次调用processReadReadies(&rfds, n)和firePending()对该event进行处理。
实现RIL消息队列处理最核心的接口有:(I/O多路复用机制)
RIL_startEventLoop():启动RIL消息队列循环机制的总入口。
eventLoop():启动一个独立线程,用来处理RIL消息队列中的各个消息,并在其中创建一个pipe,这是设计I/O多路复用机制处理RIL_event的核心。
ril_event_loop():实现RIL_event的处理,当有fd准备好时,select将得到
返回。(后续流程是:到一个准备好的fd,将其对应的event取出并通过该event 早先绑定的回调函数进行处理,处理完毕,该event将移除出pending_list队列。)rilEventAddWakeup():将调用ril_event_add()向watch_table中加入等待处理的RIL_event,同时还会调用triggerEvLoop()向pipe中写入一个空格字符,目的是让ril_event_loop()中的select能够得到一次执行,这样新加入的RIL_event
就可以得以被pipe跟踪。
processTimeouts():处理timer_list中做过延迟的RIL_event,将其转移到pending_list中,等待处理。
像有些internal事件在调用RIL_requestTimedCallback()后,就会被放入该队列中。典型的,比如:当处理来自RILJ的某个request时,当前RIL不满足处理条件,则会调用该接口。开机SIM卡未初始化好时,RIL就会调用该接口,过一段时间来检查一次SIM是否ready,从而processReadReadies():将watch_table中待处理的RIL_event转移到pending_list中,表示已经准备好了,可以处理了。
firePending():处理pending_list队列中的已经准备好的RIL_event,通过回
调函数来处理该event,处理完毕,就清除出队列。
(3) 监听来自客户端的socket连接
监听客户端的socket连接,是RIL_event的一种,即s_listen_event,绑定的回调函数是listenCallback()。当发现有客户端连接时,事件s_commands_event
就会被加入RIL消息队列,该事件就是不停的从socket读取来自客户端的数据(即RILJ的request请求)。在rild socket中交互的数据,不管是客户端发给服务器端的request,还是服务器端应答的response,或者服务器端主动上报的unsolicited 数据,都是序列化后的字节流数据(通过Parcel进行数据的字节化处理)。
RILD在做完这三件事情后,任务就完成了,剩下的工作交由libril和Vendor RIL 负责。
libril负责socket数据的通讯,将RILJ的request请求通过dispatchFunction()
分配给Vendor RIL的onRequest()处理,流程由此转入VendorRIL;Vendor RIL在处理完后,将调用RIL_onRequestComplete(),将response数据交给libril,流程又由此转入libril。它是整个RIL处理流程的核心部分。libril部分与modem无关,Android平台已经做得很好了,更换不同厂家、不同制式的modem,该部分也无需修改。
Vendor RIL负责具体处理RILJ的request,以及主动上报的AT命令。它是用来辅助libril的,处理AT命令相关以及与modem进行通讯。根据不同的modem,来对该模块进行定制。由于modem的不同,这部分内容是需要我们独立维护,并且改动最大的。
二、Android libril介绍
Android libri的代码实现位于/hardware/ril/libril文件夹下的ril.cpp和
ril_event.cpp文件。
libril是Android RIL的核心,从RILD的main()接口,可以看出,libril提供的主要功能分布在两个接口内:
一个是RIL_startEventLoop()接口,另一个是RIL_register()接口。
RIL_startEventLoop()方法所提供的功能就是启用eventLoop线程,开始执行RIL消息队列。
RIL_register()方法的主要功能是启动名为rild 的监听端口,等待java客户端通过socket进行连接,一旦连接上,RIL将通过接口processCommandsCallback(),不断的从socket端口,以非阻塞的方式读取来自RILJ的request请求。
由于这部分基本上是以回调函数的流程来处理的,所以此处不讲解代码的处理流程,而是着重梳理流程处理中的关键变量和环节。
1、关于libril中RIL消息队列所涉及的几个RIL_event说明(这是RIL的真正核心,看看Android是如何来设计这个框架的:多路I/O复用机制. 每种RIL_event 都与一个fd对应,因此pipe在select到准备好的fd时,意味这该RIL_event可以进行处理了):
(1) s_commands_event: (与s_fdCommand对应)
该RIL_event在RILJ和RILD之间的socket建立连接后,将初始化并放入RIL 消息队列,同时绑定回调接口processCommandsCallback(),该接口是一个死循环操作,不断的从socket读取来自RILJ的request请求,并依次调用processCommandBuffer()--->dispatchFunction()将request分发到vendor RIL,交由onRequest()来统一处理。正常情况下,processCommandsCallback()的死循环是不会退出的,一旦退出,则预示着socket连接出现问题了,RILJ与RILD之间将无法继续通讯了。
s_commands_event的初始化和回调接口的绑定是在s_listen_event的回调函数中进行的,当s_listen_event监听到来自RILJ的socket连接请求后,
pendings_commands_event将会被立即加入RIL消息队列;当socket连接出现异常后,ril_event_del(&s_commands_event)将被调用,从而将s_commands_event清除除RIL消息队列,此时,RILD也就不再接收来自RILJ的request请求了。
dispatchFunction()是控制流程由RILD转入Vendor RIL的转折点,从该接口调
用开始,request流程将交由Vendor RIL来处理,并在处理完毕,modem返回结果后,Vendor RIL将调用RIL_onRequestComplete(),流程又由Vendor RIL回到libril,将结果通过socket回传给RILJ,以依次上报给Android Framework及UI。
(2) s_wakeupfd_event: (与s_fdWakeupRead对应)
RILD启动后,最先加入RIL消息队列的一个消息。其初始化流程为:
RIL_register()--> RIL_startEventLoop()--> pthread_create(&s_tid_dispatch, &attr, eventLoop, NULL) --> eventLoop() --> ril_event_loop()--> for(;;){n = select(nfds, &rfds, NULL, NULL, ptv); firePending()}
RIL消息的循环处理就是在ril_event_loop()中实现的。它所对应的回调函数是processWakeupCallback()。但processWakeupCallback()内部并没有太多的处理,目的仅仅是让pipe的select()能够得到一次返回,这样select()就能够跟踪新的消息队列了。
从上面流程可知,s_wakeupfd_event实际上是处理整个RIL消息的核心部分,单独启动了一个thread,并在for循环中循环处理各个消息,当遇到有消息到达后,将通过firePending()接口中的语句ev->func(ev->fd, 0, ev->param),回调
ril_event_set()中该event所绑定的回调函数来处理相应的消息。(ril_event_set()将各个消息与相应的回调函数做了绑定,保证ev->func(ev->fd, 0, ev->param)能够准确的调用event所对应的回调函数以处理各个消息)
从libril代码看,RIL消息一共定义了5个,那么它们又是如何保证互不干扰并及时得到处理的呢?
这是由于在eventLoop()中实现了一个pipe,(它实现了I/O多路复用,以避免如果只有一个I/O时,不管其它I/O是否有数据,都会被阻塞,即使采用I/O 轮询的方式,也会浪费很多的cpu时间;该I/O多路复用机制带来的好处是:它会先构造一张有关I/O描述符的列表,然后调用select函数,当IO描述符列表中的一个描述符准备好进行I/O时,该函数返回,并告知可以读或写哪个描述符,而具体的处理来自各I/O描述符的数据,则交由各自的回调函数),各消息通过它来统一调度维护,它包含一个s_fdWak
eupRead和一个s_fdWakeupWrite,分别对应pipe的读操作和写操作,并在ril_event_loop()中通过select()来调度到来的各个event,得到调度的event都交由firePending()及时处理。
在各个event加入pipe统一管理时,google的牛人也做了一个很巧妙的设计,即他们并不是直接通过调用ril_event_add()将event加入RIL消息队列,而是调用了rilEventAddWakeup()来将event加入RIL消息队列,rilEventAddWakeup()不仅仅执行了ril_event_add()的动作,而且调用了triggerEvLoop()接口。我们看接口triggerEvLoop()的实现,该接口代码很简单,就一个核心语句ret = write
(s_fdWakeupWrite, " ", 1),其作用是向pipe写一个空格字符,当向pipe写入内容后,ril_event_loop()中的select()操作将得到执行,从而将新添加的event加入了RIL消息队列并得以跟踪,否则如果只是简单的调用ril_event_add(),将只能添加事件到队列中,但达不到及时跟踪该event的目的,这就是向RIL消息队列添加新的event时,为何先调用rilEventAddWakeup(),而不是直接调用ril_event_add()。
添加一个新RIL_event的操作是通过ril_event_set()来完成的。在调用接口
ril_event_add()将这个新的event加入到消息队列的过程中,add会把队列里所有ril_event的fd,放入一个fd集合readFds中。这样ril_event_loop能通过一个多
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论