Android Radio Interface Layer

Android Radio Interface Layer

(2013-3-7 23:08)
 

1.简述

Radio Interface Layer,简称RIL,在手机上是Modem与AP通讯的桥梁,RIL扮演的角色非常重要,RIL被设计成能够可靠的高效的传输数据一个模块。以下是RIL在Modem与AP中的位置:

   

    Android RIL可以分成2个模块,一个部分RIL Demon(RILD),用于通过socketframework通讯;另一部分是第三方自己客制化的部分,在这里暂时称之为vendor RIL。之所以这样设计是因为不同的厂商使用的Modem不一样,而RIL又和Modem紧密联系,所以Android有把和Modem联系紧密的部分和公共部分剥离开,让不同的厂商可以客制化vendor RIL以适应厂商自己的ModemVendor RIL专门负责通过ATModem进行通讯。所以又可以细化称为:

 

了解RIL的用途之后,接下来看看RIL的文件结构。RIL有三个重要的目录: rild, librilreference-ril


2. 多路复用介绍

Androidril有开多个socket用于通讯,由于socket的部分函数为阻塞式的,如果某一个socket连接上发生阻塞,会影响到其他的socket连接的处理数据,所以androidril使用了select来实现同时接收socket的连接(多路复用模型)Selectlinux系统中实现多路复用的函数之一,它可以同时监听多个文件描述符(file descriptor, fd),有一个或多个fd上有数据可读、可写或者出现异常,该函数则返回并带回需要处理的socketfd以及个数。

A. 使用普通的无限循环轮询方式,一次接收与处理一个客户端:

  


B. 使用select实现多路复用,通过监听客户端的socket,同时服务。


 
3. Android Ril的事件及其处理方法


3.1 ril_event --- ril_event.cpp

过目一下事件的结构

 


int fd; // 文件描述符 pipe/socket,这就是要加到fdset集合中的玩意,select时会用到。

int index;//watch_table数组的索引,用于清空该数字对应的元素

Bool persist;//指示该ev在触发之后是否需要移出watch_table

Timeval timeout;//计时器触发的时间,微秒级

Ril_event_cb func;//事件触发时的回调函数

Void *param;//以上回调函数的参数

事件有两种:

(1)监听socketpipe的事件,若socketpipe可读、可写或者异常时会触发select返回。

(2)计时器事件,该类事件没有文件描述符,指定一个未来的时间,当时间到时select返回。

3.2 selectI/O多路复用

Android RIL使用Select来实现多路复用,一下是函数列表 


Select有五个参数

参数1int, 为所监听的fd数值的最大值+1

参数2fd_set类型的fd集合,此处参数为监听读的fd,当fd有数据需要读取时触发。

参数3fd_set类型的fd集合,此处为监听写的fd集合。

参数4fd_set类型的fd集合,此处为发生异常的fd的集合。

参数5:为timeval的时间,用于设定在规定时间到时select返回。当该值为NULL时,表示函数在有fd发生变化时才返回;为0,则立即返回。

返回值为发生可读、可写或者异常的fd的个数。

默认情况下,select可以同时监听1024fd,参见宏定义

#define FD_SETSIZE __FD_SETSIZE  //1024 --- time.h

fd的操作时通过以下系列函数:

#define FD_SET(fd,fdsetp) __FD_SET(fd,fdsetp)  //fd设定到fdset集合中

#define FD_CLR(fd,fdsetp) __FD_CLR(fd,fdsetp)  //fdset集合中清除fd

#define FD_ISSET(fd,fdsetp) __FD_ISSET(fd,fdsetp)  //判断fd是否在fdset集合中

#define FD_ZERO(fdsetp) __FD_ZERO(fdsetp)  //清空fdset集合

3.3 RIL事件处理解析

在有了上面的基础之后,接下来去挖一挖RIL是如何控制事件的。RIL事件相关的操作在文件ril_event.cpp中。关于RIL事件处理的话,涉及到参数:

static struct ril_event * watch_table[MAX_FD_EVENTS];//所需要监听的事件,都放到这个数组中,长度为8

static struct ril_event timer_list; //如果监听的是计时器,这加到这个链表中。上面有讲到这是ril_event个具有双向链表性质的结构体。提问为什么这里使用到链表,而watch_table却是用数组呢???

static struct ril_event pending_list; //这玩意也是一个链表,装载的是已经触发了的事件或计时器。

3.3.1让我们从rild.c开始寻找,

Rild.c编译成rild的模块,在init.rc中以一个service的形式被创建。


在该文件的mian函数中RIL开始初始化,做了三件事:

(1)创建事件循环,其实就是呼叫RIL_startEventLoop()

(2)打开vendor RIL的链接库,并呼叫其中RIL_Init函数

(3)RIL注册(暂且这么说吧,其实也没注册什么东西),呼叫RIL_register()

A. 先看第一件事:呼叫RIL_startEventLoop(ril.cpp)的函数:

这个函数很简单,就是单单创建一个线程(线程id=s_tid_dispatch),等到线程创建并运行了再返回。

extern "C" void

RIL_startEventLoop(void) {

    ...

    ret = pthread_create(&s_tid_dispatch, &attr, eventLoop, NULL); //创建eventLoop线程

    while (s_started == 0) {<==等待eventLoop创建并运行

        pthread_cond_wait(&s_startupCond, &s_startupMutex);

    }

    ...

}

再接着看eventLoop的代码,创建一个匿名管道,并把管道的read端加到事件列表里面(这里在添加事件是有一个callback,在事件触发是执行该callbackprocessWakeupCallback里面很简单,就是读取管道上的数据),然后就呼叫ril_event_loop()进入循环。

static void *

eventLoop(void *param) {

    int ret;

    int filedes[2];

    ril_event_init();

    pthread_mutex_lock(&s_startupMutex);

    s_started = 1;

    pthread_cond_broadcast(&s_startupCond);

    pthread_mutex_unlock(&s_startupMutex);

    ret = pipe(filedes); //创建一个无名管道

    if (ret < 0) {

        ALOGE("Error in pipe() errno:%d", errno);

        return NULL;

    }

    s_fdWakeupRead = filedes[0];  //管道读端

    s_fdWakeupWrite = filedes[1];  //管道写段

    fcntl(s_fdWakeupRead, F_SETFL, O_NONBLOCK);

    ril_event_set (&s_wakeupfd_event, s_fdWakeupRead, true,

                processWakeupCallback, NULL); //使用管道的读端生成一个事件,这个事件的用途下面会介绍到。

    rilEventAddWakeup (&s_wakeupfd_event);

    // Only returns on error

    ril_event_loop();

    ALOGE ("error in event_loop_base errno:%d", errno);

    // kill self to restart on error

    kill(0, SIGKILL);

    return NULL;

}

Ril_event_loop就是一个for的无限循环,在这个for内部看到了前面讲到的select函数了,其实select值监测readfd_set,所要监听的fd都存放在全局变量readFds中,ptv决定select block的形态,要么设定时间block直到到期,要么无限block直到有监听fd上数据可读,当select返回后就会查找是哪个事件的fd的触发的,然后通过firePending()呼叫该事件的callback。注意这是循环的内部,也就是说每当select返回并执行其他动作之后,又会重新把readFds加到select中。

void ril_event_loop()

{

    int n;

    fd_set rfds;

    struct timeval tv;

    struct timeval * ptv;

    for (;;) {

        // make local copy of read fd_set

        memcpy(&rfds, &readFds, sizeof(fd_set)); //因为在select返回时,rfds中仅仅是触发的fd,没有触发的不在里面。也就是说rfds是会被修改的。

        if (-1 == calcNextTimeout(&tv)) {

            // no pending timers; block indefinitely

            dlog("~~~~ no timers; blocking indefinitely ~~~~");

            ptv = NULL;//无限时的block

        } else {

            dlog("~~~~ blocking for %ds + %dus ~~~~", (int)tv.tv_sec, (int)tv.tv_usec);

            ptv = &tv;//设定block

        }

        printReadies(&rfds);

        n = select(nfds, &rfds, NULL, NULL, ptv); //监听可读的fd

        printReadies(&rfds);

        dlog("~~~~ %d events fired ~~~~", n);

        if (n < 0) {

            if (errno == EINTR) continue;

            ALOGE("ril_event: select error (%d)", errno);

            // bail?

            return;

        }

        // Check for timeouts

        processTimeouts(); //捞到期的事件

        // Check for read-ready

        processReadReadies(&rfds, n); //捞触发可读的事件

        // Fire away

        firePending(); //发射~ orz

    }

}

整个RIL_startEventLoop的时间序列式这个样子的(不好意思,画的糟糕了)


 


所以可以看到,要添加一个事件很简单,呼叫以下两个函数就可以了:

ril_event_set()

rilEventAddWakeup()

那刚才有说,只有select返回,才会重新把事件的fd加到select来监听。所以在rilEventAddWakeup中会有两个动作,一个是添加event(eventfd会写到readFds),然后另外一个是triggerEvLoop,这个triggerEvLoop其实就是往刚才在eventLoop中创建的匿名管道写入一个空格字符而已,目的是让select返回,因为select返回后会再把已经改变的readFds重新加入到select中。那么为什么往匿名管道写入数据,select就会返回呢?是因为在eventLoop中有把管道的read端也加入监听了。到这里估计大家应该明白了RIL event的工作机制了吧~

B. 接下来看vendor RIL中的RIL_Init

Vendor RILlib的形似存在,其中会有名称是RIL_Init的函数,该函数是Vendor RIL的入口,android默认的vendor RILreference-ril.so,里面包含了reference-ril.c这支文件。里面会有函数名为RIL_Init的函数,该函数创建一个tids_tid_mainloop,处理函数为mainLoop的线程。这里还会把RILD丢进来的设备名称保存在全局参数s_device_patch中。(这个函数太长了,只贴片段)


mainLoop的作用比较简单:

(1)打开s_device_patch设备,如果打开失败则会一直尝试直到成功为止。S_device_patch路径指向是串口设备,用于和modem进行数据交换,如果此设备没办法打开,那手机就嗝屁了~mainLoop里主要做了三件事

I. 打开串口设备

II. 注册onUnsolicited,创建线程readLoop读取modem丢过来的unsolicited事件。

III. 添加一个计时器,发送ATModem进行握手通讯。

static void *

mainLoop(void *param)

{

 ...

    for (;;) {

        fd = -1;

        while  (fd < 0) {

            if (s_port > 0) {

                ...

            } else if (s_device_socket) {

                ...

            } else if (s_device_path != NULL) {

                fd = open (s_device_path, O_RDWR); //打开串口

  ...

            }

   ...

        }

        s_closed = 0;

        ret = at_open(fd, onUnsolicited); //创建readLoop线程,用于读取从modme丢过来的unsolicited ev

   ...

        RIL_requestTimedCallback(initializeCallback, NULL, &TIMEVAL_0); //添加一个计时事件,

...

    }

}


 
 


RIL_Init是有返回值的s_callbacks,这个返回值RIL_register会使用到,rildvendor ril的衔接就是依靠这个参数返回值类型是:

typedef struct {

    int version;        /* set to RIL_VERSION */

    RIL_RequestFunc onRequest; //上层下指令的处理函数

    RIL_RadioStateRequest onStateRequest; //查询当前radio状态

    RIL_Supports supports; //查询指定的request是否支持

    RIL_Cancel onCancel; //母鸡干嘛的

    RIL_GetVersion getVersion; //ril的版本

} RIL_RadioFunctions;

RIL_Inits_callbacks是这摸样,感兴趣的同学可以看看函数的实现:


到此RIL_Init就分析道这里。

C. RIL_register()

RIL_Register中的参数就是RIL_Init返回的参数,vendor RILRILD的沟通就是通过这货,直接通过function call方式。

extern "C" void

RIL_register (const RIL_RadioFunctions *callbacks) {//这里的参数就是上面提到的s_callbacks

    ...

    memcpy(&s_callbacks, callbacks, sizeof (RIL_RadioFunctions)); //保存下来,备之后呼叫

   ...

    for (int i = 0; i < (int)NUM_ELEMS(s_commands); i++) { //验证cmd,一个request对应一个整数值,由0往后递增,为什么要验证呢? 因为以后RIL Java发是根据这个request numbercmd的,这个request number就是作为数组s_command的索引,用来呼叫这个cmd的分发和处理本cmd的函数。一会介绍他们之间的关系

        assert(i == s_commands[i].requestNumber);

    }

    for (int i = 0; i < (int)NUM_ELEMS(s_unsolResponses); i++) {//同上

        assert(i + RIL_UNSOL_RESPONSE_BASE

                == s_unsolResponses[i].requestNumber);

    }

    ...

    s_fdListen = android_get_control_socket(SOCKET_NAME_RIL);//获取socketfdsocket貌似在创建ril-demon进程时随之创建的。

    if (s_fdListen < 0) {

        ALOGE("Failed to get socket '" SOCKET_NAME_RIL "'");

        exit(-1);

    }

ret = listen(s_fdListen, 4);

/* note: non-persistent so we can accept only one connection at a time */

//这里的socketRILDRIL Java的连接,RILD作为service端,RIL Java 作为client端。s_fdListen只是servicesocket,目的是监听RIL Javasocket发过来的connect,在listenCallbackAccept返回的socket才是RIL Javasocket,监听这个socket才能能知道RIL Java发过来的请求了。

ril_event_set (&s_listen_event, s_fdListen, false,listenCallback, NULL);

rilEventAddWakeup (&s_listen_event);

#if 1

    // start debug interface socket

//用于android debugsocket,可以通过该socketRILoptions中的socket连接,可以进行开关radio,开启一通通话,挂断一通通话之类的。

    s_fdDebug = android_get_control_socket(SOCKET_NAME_RIL_DEBUG);

    if (s_fdDebug < 0) {

        ALOGE("Failed to get socket '" SOCKET_NAME_RIL_DEBUG "' errno:%d", errno);

        exit(-1);

    }

    ret = listen(s_fdDebug, 4);

    ril_event_set (&s_debug_event, s_fdDebug, true,debugCallback, NULL);

    rilEventAddWakeup (&s_debug_event);

#endif

}

 

上面有提到,RILD和上层RIL Java的沟通是通过socket进行通讯,因为RIL Java属于Java层次,之间的数据传递使用Parcel。当RIL Java要让RILD帮忙做事时,协定好它们之间之间的沟通方式,RIL Java把一个cmd号码发给RILD,其需要发送的参数写到Parcel,在通过socket发送到RILD,然后RILD根据cmd码,呼叫就其绑定不同的发送与接收函数。

RIL有两种cmd

(1)一种是一来一回的requestRIL JavarequestRILD处理后回给RIL Java结果。例如获取手机IMEI号,RIL JavarequestRILD,再到modemmodem处理完后依次返回到RIL Java

(2)另一种只回的unsolicitedRILDmodem主动给过来的事件上报给RIL Java。例如,手机信号强度的改变,网络时间到达之类的。这些cmd不需要AP做下任何请求,当modem发现其需要报告状态给AP时,就会主动的发出。

这两种cmd对应的cmd格式

typedef struct {

    int requestNumber; //request number

    void (*dispatchFunction) (Parcel &p, struct RequestInfo *pRI); //request发送的函数

    int(*responseFunction) (Parcel &p, void *response, size_t responselen); //request处理完成之后回应

} CommandInfo;


每一个cmd id(也就上request number)对应一个发送和接收函数,如果cmd有带Sting参数的话,那就会选用dispatchString, 如果RILD有返回值Int数组需要回传给RILJ的话,就选用responseInts。当然如果想传自己定义类型参数的话,可以自己写一个dispatchMyData,其实就是把Parcel里面的值,按照存放的顺序读出来发送出去。

typedef struct {

    int requestNumber; //同上

    int (*responseFunction) (Parcel &p, void *response, size_t responselen); //unsolicited 的处理函数

    WakeType wakeType; //??? 母鸡这个的意思

} UnsolResponseInfo;


4. RIL Java解析

RIJ Java比较简单,就是负责RILD和上层Telephony的衔接。在PhoneFactory.java中根据不同的PhoneType创建不同的Phone,例如GSMPhone, CDMAPhone或者CDMALTEPhone。不管是哪一种Phone,都需要一个CommandsInterface作为参数,这个就是RILJ,每种Phone都需要RILJ帮忙和RILD进行通讯。RILJ就是在创建某一种Phone之前创建的,在Phone进程中运行。RILJ的源文件就是RIL.java,这个文件中有两个类RILRILRequestRIL class继承与BaseCommandsCommandsInterface


RILRequest主要功能就是把要发送的request打包,Parcel存放需要送到RILD的数据。成员变数mSerial (Token)为一个整型,每生产一个RILRequest其值递增1,用于记录一个Request的编号。因为request不是这个一来一回后再发下一个,允许本request没有返回之前就可以发送发一个,这就需要一个编号记录每个request,用于辨别回来的结果对应到确定的request

RILSender发送requestRILDRIL的内部类。

RILReceiver接送从rild丢过来的数据,RIL的内部类。

RIL重要的成员mSender/mReceiver,就是以上两内部类的实力。另外一个是mRequestList,是一个ArrayList,记录发送到RILDrequest,在RILD处理完成之后回传数据时根据Token,取出对应的Request,来把结果分发到Framework的呼叫者。mSocket就是和RILD连接的socket

RIL在初始化初期,会创建两个线程mSendmReceiver,在mReceiver中会创建一个socket并和RILdsocket连接(这个时候eventLoop中的s_listen_event就会触发,透过accept就可以获取到这个RILJsocket)。当RILJ要往RILD发请求时,会透过RILRequest创建一个request,并把需要携带的参数写到Request中,然后透过mSend去发送。

下面以一副数据流程图来解说一下RILJ的内部运作:

 

5. RIL Request过程分析

这小节将通过一个具体的Request介绍RILJRILDflow。以 public void

setRadioPower(boolean on, Message result) 为实力来介绍。这个方法是来开关手机的Radio的,参数on需要携带到RILD去,result为 response时通知该方法的呼叫者。首先开一下函数具体实现:


在来看看函数的绑定发送与接收函数:


因为这个Request需要携带一个参数下去,所以发送函数选择dispatchInts(ril.cpp),没有参数需要返回,所以是responseVoid(ril.cpp)。刚好可以接着上小节的request部分,当把数据写到socket的时候,触发s_commands_event,执行processCommandsCallback(ril.cpp)

Request的数据依次是

TypeRIL_REQUEST_RADIO_POWER

Token:这个值是运行时的,具体是多少不管他。

RequestData:数组元素个数1,以及具体值。

在这之前需要介绍一下一个结构体:


其中的pCI就是

下面开始分析,因为RILJ Request过程上面小节已经介绍,这里就不再累赘,直接从RILD接数据开始:

a. 跑到processCommandBuffer函数,创建一个Parcel装载RILJ数据,读出TypeToken


b. 在来看dispatchInts里的是怎么处理的,读取出RILJ丢过来的数组长度,本case1,然后呼叫onRequest




c. Referencr-ril中的onRequest(),针对request type下不同的指令。


caserequestRIL_REQUEST_RADIO_POWER,看看这个case是怎么处理的

呼叫到了requestRadioPower函数,在该函数中依照RILJ传过来的参数想modem发送AT cmd。在modem处理完成之后返回,然后呼叫RIL_onRequestCompleate结束本次request
 


d. RIL_onRequestCompleate,完成数据装载,把结果回传给RILJ,完成本次onRequest


e. sendReponse, 把数据通过socket回传给RILJ,这个时候RILJmReceiver就读取这些数据了。之后的RILJ如何处理的情参考第四节的讲述。


6. RIL Unsolicited过程分析

RILD或者modem察觉有数据或状态变化时,需要把这些信息告诉framework,所以会丢出一个事件,这中就是Unsolicited eventFramework可以选择自己感兴趣的事件来监听,当被监听的事件触发时,就会被通知到。这里有一个疑问,因为modem只是打回一个AT名称给RILD,怎么知道这个AT是该让谁来处理呢?怎么知道哪些ATRequest下下去的AT的回应,哪些是ATmodem主动上报的呢?带着这个问题来分析一下unsolicited的处理过程。正如上面分析RIL_Init是所讲到的,mainLoop(reference-ril.c)中透过at_open来创建一个线程readLoop(atchannel.c)来读取modem丢回来的ATAt_open(atchannel.c)这个函数有两个参数,一个是串口设备的fd,另一个是一个函数指针,当modem有丢回来数据时,就是透过这个回调函数来处理滴。onUnsolicited是实现在reference-ril.c.


在进入正题之前先看一个结构体ATResponse


当透过request下的ATAT返回的结果就是存放在这个结构体里面。

其中p_intermediates存放的就是AT的字符串。

a. readLoop线程中无限循环的读取modem丢过来的AT,读到数据之后会然后呼叫processLine(atchannel.c)来处理AT

b. processLine中有个参数sp_response,当requestATmodem的时候,都会创建ATResponse这个结构,用于存放AT返回的结果;如果是modem主动丢回来的AT,那sp_response就是空的。就是透过这个来区分哪些ATmodem主动丢回来的,哪些是request回来的。


c. handleUnsolicited函数呼叫的s_unsolHandlerreference-ril.c中的onUnsolicited()


d. onUnsolicited(),根据AT返回的数值确定该谁来处理,比如遇到%CTZV那么其对应的unsolicited就是RIL_UNSOL_NITZ_TIME_RECEIVED


e. RIL_onUnsolicitedResponse中有些地方还不是很明白:( ~

这个函数做的事情(函数篇幅太大,就不全贴了):

确定这个unsolicited在数值在s_unsolResponses中的索引


先把Typeunsolicited写到Parcel中,然后很据索引取出这个unsolicited对应的response函数,其实就是把这个unsolicited带回的数据写到Parcel


最后就是呼叫sendResponse,把Parcel里的数据送给RILJ


余下的部分可以参考第四节。

PS: 本文提到的RILD == RIL Daemon, RILJ = RIL Java,在介绍RILJ是提到的RIL指得就是RILJ(因为RILJ的类名称为RIL)。估计会造成困扰,实在抱歉! 52RD的文字框编辑功能太烂了,没办法调间距,visio画的图上传上来全都是黑的


文章出自:http://www.52rd.com/Blog/Detail_RD.Blog_Lexucs_66161.html

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值