RIL研究报告

一.整体架构综述
ril用于实现打电话,发短信等通信业务功能,它主要通过AT命令与硬件模块交互实现功能。  

这里一共涉及两个进程,一个是Phone process,一个是ril进程,Phone显然是上层进程,ril是负责与硬件打交道的进程。在ril中有四个线程,rild启动了一个主线程和一个eventLoop线程,主线程一直处于休眠状态,eventLoop线程在接收到Phone进程的命令之后,会调用ril.so的api函数发送request,这个request实际上就是AT command,也就是说eventLoop线程是发送AT command的线程。ril.so也启动了两个线程,一个是mainLoop线程,一个是readerLoop,mainLoop线程主要用于启动和恢复readerLoop线程,readerLoop线程则用于读取AT command的响应。下面将详细分析ril的实现。

二.rild
hardware/ril/rild/rild.c
main函数
----->    dlHandle = dlopen(rilLibPath, RTLD_NOW);
----->    RIL_startEventLoop();
----->    rilInit = (const RIL_RadioFunctions *(*)(const struct RIL_Env *, int, char **))dlsym(dlHandle, "RIL_Init");
----->    funcs = rilInit(&s_rilEnv, argc, rilArgv);
----->    RIL_register(funcs);
该函数主要是载入so文件,然后调用so文件的RIL_Init函数,该函数的作用是使得rild和so文件互相注册回调函数,它把RIL_Env注册给so,同时返回RIL_RadioFunctions,使得rild得到一组函数指针,RIL_RadioFunctions和RIL_Env的结构如下:
//so文件交给ril调用的api
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;

//ril交给so文件调用的callback函数,用于通知某个事件
struct RIL_Env {
    void (*OnRequestComplete)(RIL_Token t, RIL_Errno e,
                           void *response, size_t responselen);
    void (*OnUnsolicitedResponse)(int unsolResponse, const void *data,
                                    size_t datalen);
    void (*RequestTimedCallback) (RIL_TimedCallback callback,
                                   void *param, const struct timeval *relativeTime);
};

static struct RIL_Env s_rilEnv = {
    RIL_onRequestComplete,
    RIL_onUnsolicitedResponse,
    RIL_requestTimedCallback
};

RIL_startEventLoop函数
hardware/ril/libril/ril.cpp
RIL_startEventLoop(void)该函数启动eventLoop进程
--->eventLoop(void *param)
              ----->    ril_event_init();
              ----->    ret = pipe(filedes);
              ----->    s_fdWakeupRead = filedes[0];
                          s_fdWakeupWrite = filedes[1];
              ----->ril_event_set (&s_wakeupfd_event, s_fdWakeupRead, true,processWakeupCallback, NULL);
                       rilEventAddWakeup (&s_wakeupfd_event);
                                   -------->void ril_event_add(struct ril_event * ev)  //add event to watch_table and rfds read set
                                   -------->static void triggerEvLoop()
              ----------->void ril_event_loop()
                                  for (;;) {
                                     ----->calcNextTimeout//获取timer_list第一个节点的timeout减去当前时间的差值作为select的timeout时间
                                     ----->        n = select(nfds, &rfds, NULL, NULL, ptv);//等待读事件或者超时
                                     ----->processTimeouts//把timer_list中的超时节点取出来放到pending_list中
                                     ----->processReadReadies//把watch_table中所有读事件置位的节点取出来放到pending_list中
                                     ----->firePending//触发pending_list中的事件
                                 }
首先要说明的是ril_event结构,它的定义如下,该结构定义了rild的基本事件,其中fd表示一个读事件,timeout表示一个超时时间,在触发读事件或者超时到期后,将会调用func回调函数,这就是ril_event_loop循环所做的事情。
struct ril_event {
    struct ril_event *next;
    struct ril_event *prev;

    int fd;
    int index;
    bool persist;
    struct timeval timeout;
    ril_event_cb func;
    void *param;
};
ril_event_init
对几个重要的结构进行了初始化,readFds读事件FD_SET,timer_list超时列表,pending_list是一个临时列表,watch_table是所有ril_event的列表
void ril_event_init()
{
    MUTEX_INIT();

    FD_ZERO(&readFds);
    init_list(&timer_list);
    init_list(&pending_list);
    memset(watch_table, 0, sizeof(watch_table));
}

RIL_register (const RIL_RadioFunctions *callbacks)
----->    s_fdListen = android_get_control_socket(SOCKET_NAME_RIL);设备路径是//dev/socket/rild,属性名是ANDROID_SOCKET_rild
                ret = listen(s_fdListen, 4);
            ril_event_set (&s_listen_event, s_fdListen, false,//这个事件在触发回调函数后,会被删除,如果需要再次触发,需要重新加入事件
                listenCallback, NULL);
            rilEventAddWakeup (&s_listen_event);
    //把rild-debug加入到读事件列表中
    s_fdDebug = android_get_control_socket(SOCKET_NAME_RIL_DEBUG);
    ret = listen(s_fdDebug, 4);
    ril_event_set (&s_debug_event, s_fdDebug, true,
                debugCallback, NULL);
    rilEventAddWakeup (&s_debug_event);
该函数主要就是加入监听事件,用于监听Phone Process的连接

listenCallback
在发生listen的callback的时候,连接会被加入到读事件列表中,同时监听事件会从读事件列表中删除,这样就不会有新的连接进来,在连接退出的时候,监听事件会被重新加入进来
----->    s_fdCommand = accept(s_fdListen, (sockaddr *) &peeraddr, &socklen);
------>     err = getsockopt(s_fdCommand, SOL_SOCKET, SO_PEERCRED, &creds, &szCreds);//这里所做的检查是只接受来自PHONE_PROCESS("radio")进程的连接
------> p_rs = record_stream_new(s_fdCommand, MAX_COMMAND_BYTES);
------>    ril_event_set (&s_commands_event, s_fdCommand, 1,//这个事件会一直存在于读事件列表中
        processCommandsCallback, p_rs);//
    rilEventAddWakeup (&s_commands_event);//把得到的连接加入到read列表中

processCommandsCallback
void processCommandsCallback(int fd, short flags, void *param)
这个fd即是连接对应的fd,s_fdCommand,param即为listenCallback中生成的p_rs,p_rs的类型是struct RecordStream,它有一段8k的内存用于从fd中读取数据,数据的格式是长度加内容
    for (;;) {
        ret = record_stream_get_next(p_rs, &p_record, &recordlen);每次从RecordStream中读取到的内容称为一个record,其格式为request+参数
            processCommandBuffer(p_record, recordlen);
            ----->   读取record中的request和token
                        p.setData((uint8_t *) buffer, buflen);
                        status = p.readInt32(&request);
                        status = p.readInt32 (&token);
                        //建立RequestInfo,并把它加入到s_pendingRequests链表中
                        pRI = (RequestInfo *)calloc(1, sizeof(RequestInfo));
                        pRI->token = token;
                        pRI->pCI = &(s_commands[request]);//s_commands是一个列表,定义在ril_commands.h中
                        pRI->p_next = s_pendingRequests;
                        s_pendingRequests = pRI;
                        //dispatch函数会根据不同的request类型,在调用s_callbacks.onRequest函数时传入不同的参数,该函数就是向so文件发送请求.
                        pRI->pCI->dispatchFunction(p, pRI);
}
我们返回去再去看main函数,在调用RIL_startEventLoop函数启动了eventLoop线程后,该线程由于没有事件发生,由select函数导致处于休眠状态。在调用RIL_register函数之后,加入了监听事件,在Phone进程连接进来后,eventLoop将处于等待Phone进程的命令状态,在接收到命令之后,会调用dispatchFunction,向so文件发送请求,在后面的分析中,我们知道,实际上就是发送AT命令,也就是说发送AT命令是运行在eventLoop线程上的。

综上所述,Phone进程和Rild进程通过socket进行通信,它们之间的数据被称为RecordStream,rild和so互相注册回调函 数,rild调用so的RIL_RadioFunctions函数来发送请求,so通过RIL_Env中的函数指针通知rild,请求的响应 (solicit),或者异步事件的发生(unsolicit)。
rild在收到Phone process的command后,使用RIL_RadioFunctions的onRequest发送请求,so通过env的 RIL_onRequestComplete发送响应,该函数会继续通过socket把响应发送给Phone process。
在异步事件发生的时候,so通过env的RIL_onUnsolicitedResponse把事件发送给Phone process
so也会通过env的RIL_requestTimedCallback,启动一个定时器,超时后回调它指定的函数。
在rild进程中,有两个线程,一个是主线程,一个是eventLoop线程,所有的命令都是运行在eventLoop线程

三.libreference-ril.so
reference-ril.c
RIL_Init
const RIL_RadioFunctions *RIL_Init(const struct RIL_Env *env, int argc, char **argv)
----->    s_rilenv = env;//保存rild的env
-----> s_dev_path = optarg;//在模拟器上so使用ttyS0,即串口与模块通信
----->    ret = pthread_create(&s_tid_mainloop, &attr, mainLoop, NULL);//启动一个线程

mainLoop
    for (;;) {
                fd = open (s_device_path, O_RDWR);//打开s_device_path设备,即ttyS0设备
                s_closed = 0
        ret = at_open(fd, onUnsolicited);//onUnsolicited会在readerLoop线程中调用
                    ----->    s_fd = fd;
                   ----->    s_unsolHandler = h;
                    ----->    ret = pthread_create(&s_tid_reader, &attr, readerLoop, &attr);//启动一个新的线程
        RIL_requestTimedCallback(initializeCallback, NULL, &TIMEVAL_0);//在rild的eventLoop线程中,执行initializeCallback
        sleep(1);
        waitForClose();//等待s_state_cond条件,该函数一旦退出,那将导致重新初始化
              ----->    while (s_closed == 0) {
                                  pthread_cond_wait(&s_state_cond, &s_state_mutex);
                          }
}
由此可见,mainLoop线程主要是为了启动和恢复readerLoop的。在so中有三个宏需要特别说明一下:
#define RIL_onRequestComplete(t, e, response, responselen) s_rilenv->OnRequestComplete(t,e, response, responselen)
#define RIL_onUnsolicitedResponse(a,b,c) s_rilenv->OnUnsolicitedResponse(a,b,c)
#define RIL_requestTimedCallback(a,b,c) s_rilenv->RequestTimedCallback(a,b,c)
RIL_onRequestComplete
用于在rild发送的请求有响应的时候,回调该函数通知rild
RIL_onUnsolicitedResponse
用于底层有异步事件发生的时候,回调该函数通知rild
RIL_requestTimedCallback
用于请求eventLoop执行一个超时回调
这三个函数指针都是rild注册给so的,他们分别对应ril.cpp中的
RIL_onRequestComplete(RIL_Token t, RIL_Errno e, void *response, size_t responselen)
该函数会调用sendResponse函数,这个函数会把response通过socket,以Record的格式发送给Phone Process,该函数运行在eventLoop线程上,特别主要,该函数调用了checkAndDequeueRequestInfo函数,该函数将会检查请求是否在pending队列中,如果在的话,则将其删除,因为已经收到该请求的响应了。
void RIL_onUnsolicitedResponse(int unsolResponse, void *data,
                                size_t datalen)
该函数会调用sendResponse函数,这个函数会把response通过socket,以Record的格式发送给Phone Process,该函数运行在eventLoop线程上
RIL_requestTimedCallback (RIL_TimedCallback callback, void *param,
                                const struct timeval *relativeTime)
该函数会在eventLoop中加入一个超时事件

在mainLoop中就调用了RIL_requestTimedCallback函数,它将会导致initializeCallback函数被调用

initializeCallback
static void initializeCallback(void *param)
    at_handshake();
         -----> err = at_send_command_full_nolock ("ATE0Q0V1", NO_RESULT,NULL, NULL, HANDSHAKE_TIMEOUT_MSEC, NULL);
                                      ----->    err = writeline (command);
                                      ----->    err = pthread_cond_wait(&s_commandcond, &s_commandmutex);//等待s_commandcond条件,在readerLoop线程中读到OK响应的时候,该条件会置位
    at_send_command
        ----->    err = at_send_command_full_nolock(command, type,
                    responsePrefix, smspdu,
                    timeoutMsec, pp_outResponse);
    。。。
该函数会调用一系列的at_send_command,发送一系列的AT command

readerLoop
mainLoop中启动了新的线程,该线程是读取at command的response
static void *readerLoop(void *arg)
{
    for (;;) {
        line = readline();
        if(isSMSUnsolicited(line)) {
            line1 = strdup(line);
            line2 = readline();
            if (s_unsolHandler != NULL) {
                s_unsolHandler (line1, line2);
            }
        } else {
            processLine(line);
        }
    }
    onReaderClosed();
    return NULL;
}
至此,我们对整个AT command的发送和接收,就比较清楚了,rild的eventLoop调用RIL_RadioFunctions.onRequest发送请求,该函数实际上是so中的onRequest函数,它最终调用了at_send_command_full_nolock函数发送AT command,并进入休眠状态,等待响应。另外一个读线程readerLoop不断读取底层通过ttyS0发来的数据,在确认收到响应后,将唤醒eventLoop线程,继续后面的请求。

at_send_command_full_nolock
但是除此之外,还需要考虑在发送请求后,一直收不到响应,或者读线程在读取数据过程中读取失败的请求(例如硬件错误?),看如下的分析:
static int at_send_command_full_nolock (const char *command, ATCommandType type,
                    const char *responsePrefix, const char *smspdu,
                    long long timeoutMsec, ATResponse **pp_outResponse)
{
    err = writeline (command);

    s_type = type;
    s_responsePrefix = responsePrefix;
    s_smsPDU = smspdu;
    sp_response = at_response_new();

    while (sp_response->finalResponse == NULL && s_readerClosed == 0) {
        if (timeoutMsec != 0) {
#ifdef USE_NP
            err = pthread_cond_timeout_np(&s_commandcond, &s_commandmutex, timeoutMsec);
#else
            err = pthread_cond_timedwait(&s_commandcond, &s_commandmutex, &ts);
#endif /*USE_NP*/
        } else {
            err = pthread_cond_wait(&s_commandcond, &s_commandmutex);
        }

        if (err == ETIMEDOUT) {
            err = AT_ERROR_TIMEOUT;
            goto error;
        }
    }

    if (pp_outResponse == NULL) {
        at_response_free(sp_response);
    } else {
        /* line reader stores intermediate responses in reverse order */
        reverseIntermediates(sp_response);
        *pp_outResponse = sp_response;
    }

    sp_response = NULL;

    if(s_readerClosed > 0) {
        err = AT_ERROR_CHANNEL_CLOSED;
        goto error;
    }

    err = 0;
error:
    clearPendingCommand();

    return err;
}
该函数是一个阻塞函数,它运行在rild的eventLoop线程中,它指定了条件等待的超时时间,在正常情况下,如果在等待时间内,条件满足,也就是读 线程读到了响应,那么at命令发送成功。如果等待超时,则要进行超时处理,即clearPendingCommand(),在mainLoop中调用了 at_set_on_timeout(onATTimeout),clearPendingCommand会调用该函数
static void onATTimeout()
{
    LOGI("AT channel timeout; closing\n");
    at_close();//该函数将导致readerLoop线程退出

    s_closed = 1;

    /* FIXME cause a radio reset here */
    setRadioState (RADIO_STATE_UNAVAILABLE);//s_closed和s_state_cond置位将导致mainLoop中的WaitForClose函数退出,继而导致重新初始化reader线程
}
void at_close()
{
    if (s_fd >= 0) {
        close(s_fd);
    }
    s_fd = -1;

    pthread_mutex_lock(&s_commandmutex);

    s_readerClosed = 1;

    pthread_cond_signal(&s_commandcond);

    pthread_mutex_unlock(&s_commandmutex);

    /* the reader thread should eventually die */
}

在mainLoop中的at_set_on_reader_closed函数,也是为了防止ttyS0读取失败,意外发生,此时也会导致读线程重新初始化
at_set_on_reader_closed(onATReaderClosed)
static void onATReaderClosed()
{
    LOGI("AT channel closed\n");
    at_close();
    s_closed = 1;

    setRadioState (RADIO_STATE_UNAVAILABLE);
}

四.at command
at命令可以看做是硬件和软件的接口,不同的硬件厂家提供不同的AT命令集以提供不同的功能,但是at命令的基本格式是固定,它一般通过串口承载数据,当然也不是一定要使用串口,也可以使用其他的承载方式,例如socket。由于at命令是半双工,即同时只能有一个方向的数据流动,所以一般的情况下,由上层发送AT+CMD,下层发送响应,通常都以\r\n作为结束,如果内容中有\r\n的话,则需要转义。另外一种情况是由底层发送请求,一般的格式是+CMD:,此时上层一般不需要发送响应
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值