引言:

这段时间手中的工作,正好好调试一款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.c 看Makefile知道最终会被编译成radiooptions二进制工具,放在/system/bin/下面,具体用法我在这里就不说了,我到终点里面执行一下,把他的help信息打出了,再详细的就自己看吧,源码不长,也不复杂。

# 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

  • Rild.c分析

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);

    • 传递几大重要函数的函数指针
    •  RIL_startEventLoop,注意这里会有判断语句,如果监听事件队列启动,则会监听事件队列机制
    • 获取Socket(rild)和Socket(rild-debug)的listen句柄,并分别将其加入监听事件队列。

 

  • RIL_startEventLoop、ril_event_loop、watch_table、time_table、pending_list、readFds;这些都是监听事件队列很重要的函数,这里分析起来有点拗口

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定制的部分,也是差异化最大的部分。

  • Ril_Init  (注:在Rild main函数中被调用,见上一节rild.c分析)---------在这里我们将其描述为:用户自定义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分析,这里才是干实事的地方

    • open (s_device_path, O_RDWR); 打开AT通讯文件接口,例如/dev/ttyUSB*  
    • at_open(fd, onUnsolicited);这个函数很重要,其建立了两个线程一个是readerLoop,一个是pppcheckLoop

             readerLoop:用来监听接收来自modem AT口发送过来的命令回事数据

             readerLoop:用来处理ppp网络拨号上网

             另外注意这里onUnsolicited是个函数指针,其主要用来处理当readerLoop接收到数据时,会调用其做一些优先的判断处理,主要是进行一些    事件的主动上报,例如时间更新、短消息、ril状体变化等。

5)RIL_requestTimedCallback(initializeCallback, NULL, &TIMEVAL_DELAYINIT);前开了好些个线程,应该也差不多了,这里应该需要对modem进行一下初始化设置了(通过AT命令),详见initializeCallback,很多模块初始化系列AT 命令都放在这里处理。

 

  • readerLoop

      这里我把这个家伙拎出来再说说,主要这里是返回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()

  •  从java层传递下来的命令格式为:parcel+Request号+令牌+内容,这里Request号很重要,它们被定义在RILConstants.java和ril_command.h中,其中它们的定义是一致的;根据Request号我们可以找到对应的dispatchFunction函数和responseFunction函数,详见ril_commands.h以及CommandInfo结构体:

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;

 

  • processCommandBuffer()中我们可以得知,在从java层传递的数据中提取出Request号找到对应的dispatchFunction函数,并调用对应的pRI->pCI->dispatchFunction(p, pRI)函数;
  • 对应的dispatchFunction函数最终会调用s_callbacks.onRequest(pRI->pCI->requestNumber, xxx,sizeof(char *), pRI),这里的s_callbacks就是(二)----1)中所提到的回调函数指针,这些函数的最终实现是在reference-ril.cpp中,这里调用的是reference-ril中的onRequest()函数

5)所以总结下来请求流程最终都会调用reference-ril中的onRequest()函数,所以如果你不想了解那么多,就直接把onRequest函数当成请求流程的处理函数即可,当然如果你要增减一些对应Java层数据处理功能,那么这套流程你还是需要清楚的。 

 

     (四)Response流程(Ril接收到的数据是如何返回给上层的,这也是reference-ril的一部分)

     1)记得前面提过readerLoop,其实在mainloop中开启的一个线程,主要最用是监听modem AT 通讯口,并且接受其发过来的数据,并进行分类处理,

        这里的分类只的是普通AT 命令和短信/自动上报处理;

2)先分析短信处理,在readerloop()中通过readline()函数接收响应,然后经过isSMSUnsolicited检测是否是短信/自动上报,如果是则通过   s_unsolHander(也就是函数onUnsolicited)来处理;如果不是短信/自动上报,则执行processLine来处理;

    • onUnsolicited函数(短信/自动上报处理)最终会命令信息以及上报信息内容,可分成两种操作:RIL_onUnsolicitedResponse和RIL_requestTimedCallback;
    • RIL_onUnsolicitedResponse会将unsolicited信息直接返回给上层,调用流程为 RIL_onUnsolicitedResponse ---->sendResponse---->sendResponseRaw---->blockingWrite;
    • blockingWrite(fd, (void *)&header, sizeof(header))表示想fd写入数据,这里fd就是s_fdCommand即前面提到的和java层建立起来的socket,这里表示通过socke(RILD)将response信息传递给上层框架;
    • RIL_requestTimedCallback通过event机制实现timer机制,回调对应的内部处理函数;

3)接着我们来看一下非短信/自动上报的处理过程,这里我们之间分析processLine函数,这个东东会把冲modem过来的response信息存储到一个临时变量sp_response中,具体怎么存储的劳烦您还是去看看代码吧,都是泪啊!

    • 接下来咋们得分析at_send_command_full_nolock函数,这个函数就充分利用了sp_response这个变量的值,它最终会将sp_response返回给调用at_send_command_full_nolock的函数(时间就是存在于Ril_commands.h中以“request*”开头的函数,其实这些函数最终都会调用到onRequest函数,给详细的理解请回顾(三));
    • 当取得sp_response(即response信息)后,则response信息可以通过RIL_onRequestComplete()函数返回给上层,最后这些信息也是通过soket(RILD)返回给应用的,和前面介绍的基本类似

总结归纳: 

  • 首先是RIL整体架构,从宏观上了解一下下RIL

           (一)主要是从init.rc、已经main函数这样的程序员思维,先了解ril在Android中时怎么起来的,已经一部分的具体实现。

           (二)(三)(四)才是RIl的核心部分,前面只是打基础,他们分别说明了RIL如何初始化、如何接收消息和数据、如何返回消息和数据

  • 接下来我们用图表的形式来归纳一下ril初始化以及接受和返回数据的过程: