Android RIL 代码阅读笔记原创
金蝶云社区-墨家总院
墨家总院
7人赞赏了该文章 1100次浏览 未经作者许可,禁止转载编辑于2019年04月01日 09:49:03
封面

(本文独家发布在金蝶云社区上)

RIL 概览

RIL是“Radio Interface Layer”的缩写。在Android软件框架中,RIL子系统向下与BaseBand芯片通信,向上给Android框架提供电话相关服务,如Call,SMS等。它在Android软件栈中的位置如下图所示:


5c9b1ab15c69d008af000000.png


在更底层的软件子系统中,RIL通过Linux虚拟串口驱动程序与基带芯片通信,这篇文章我们不准备包含UART驱动程序与真实基带芯片之间交互的细节,所以在此文章中,这个串口基本代表了基带芯片。 在上层软件中,RIL通过套接字与Android Framework通信。Android Framework中的Java层也不是我们主要关注的内容,我们将要讨论的是上图中的绿色部分。

RIL的子模块

Google公司将RIL分为三部分,以保护OEM厂商的IP。这些部分是:RIL守护程序应用程序(RILD),libril.so和reference-ril.so。RILD和libril.so在静态链接中相互引用,它们的实现对用户是开放,但reference-ril.so是Google向OEM提供的参考实现。由于各种移动网络的多样性和复杂性,oem供应商在他们的基带芯片中有自己的私有代码来实现无线通信,他们可能不想公开自己的源代码,因此他们实现了自己的vendor-ril.so。为了降低私有模块和开源模块之间的耦合度,RILD使用动态加载方法与vendor-ril.so进行通信。 他们的源代码位于:

RILD--
    hardware/ril/rild

libril--
    hardware/ril/libril

此外,libril.so和reference-ril.so在RIL架构中具有不同的功能,libril.so用于通过socket通过Android Framework处理I / O消息。 但是reference-ril.so负责与串口通信。

从RILD开始

整个RIL系统是从shell调用RILD命令启动的,所以让我们从它开始。

rild.c中有一个静态结构对象:

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

它包括三个函数指针,它们的实现位于libril.so,它们将由供应商ril使用。 相反,在Ril.h中定义了另一个类似的结构:

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里,它们将由libril.so使用。 因此,供应商ril和libril.so调用彼此的功能来实现RIL系统。 顺便说一句,libril.so和rild通过静态链接连接。 但是android希望隐藏OEM供应商的实现,vendor-ril.so通过动态链接与rild连接。 那么让我们看一下reference-ril.so动态加载的代码片段:

dlHandle = dlopen(rilLibPath, RTLD_NOW);
if (dlHandle == NULL) {
    fprintf(stderr, "dlopen failed: %s\n", dlerror());
    exit(-1);
}

RIL_startEventLoop();
rilInit = (const RIL_RadioFunctions *(*)(const struct RIL_Env *, int, char **))dlsym(dlHandle, "RIL_Init");
if (rilInit == NULL) {
    fprintf(stderr, "RIL_Init not defined or exported in %s\n", rilLibPath);
    exit(-1);
}

正如代码片段向我们展示的那样,我们从配置文件中获取供应商ril的路径,然后调用RIL_startEventLoop,其具体实现在libril.so中,代码如下:

extern "C" void
RIL_startEventLoop(void) {
    int ret;
    pthread_attr_t attr;

    /* spin up eventLoop thread and wait for it to get started */
    s_started = 0;
    pthread_mutex_lock(&s_startupMutex);

    pthread_attr_init (&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    ret = pthread_create(&s_tid_dispatch, &attr, eventLoop, NULL);

    while (s_started == 0) {
        pthread_cond_wait(&s_startupCond, &s_startupMutex);
    }

    pthread_mutex_unlock(&s_startupMutex);

    if (ret < 0) {
        LOGE("Failed to create dispatch thread errno:%d", errno);
        return;
    }
}

我们将在下一节讨论这个函数的细节,所以让我们继续阅读RILD的main函数的例程,在调用RIL_startEventLoop之后,我们在vendor ril中得到“RIL_Init”的函数指针,然后在检查函数指针的有效性之后, 我们用main的输入参数调用它:

rilArgv[0] = argv[0];

funcs = rilInit(&s_rilEnv, argc, rilArgv);

LOGD("RIL_register %d", cardNum);
RIL_register(funcs,cardNum);

供应商ril中实现的RIL_Init将执行一些底层操作:

  • 将RIL_RadioFunctions指针设置为vendor ril,因此vendor ril可以调用libril的函数。

  • 启动mainLoop线程来处理硬件,包括初始化UART,从UART读取数据。

  • 返回将在libril.so中使用的五个函数指针到RILD。

我们将在后面的部分中详细讨论它。最后调用的是RIL_register,我们通过这个函数将前面的函数指针传递给libril.so。在RIL_register内部,它连接套接字来和Framework通信,并启动listenCallback事件,就像引擎一样可以随时触发I / O操作。

有关rild例程的摘要,请参见下图:


5c9b1b6f5c69d008af000001.png

RIL事件机制

正如最上一节向我们展示的那样,很明显RIL_startEventLoop创建了一个名为eventloop的线程,其代码如下所示:

    static void * eventLoop(void *param) {
        int ret;
        int filedes[2];

        if (signal(SIGPIPE, rilSignalHandler)== SIG_ERR) {
            LOGE ("register SIGPIPE handler fail!!!");
        }
        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) {
            LOGE("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();
        LOGE ("error in event_loop_base errno:%d", errno);
        // kill self to restart on error
        kill(0, SIGKILL);

        return NULL;
    }

它调用ril_event_init来初始化两个ril_event结构对象:timer_list和pending_list:

    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_event结构指针数组归零,该数组有8个元素:watch_table。 我们稍后会知道这些对象。 那么让我们检查结构ril_event:

    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是一个双向链表。我们使用ril_event_init初始化所有事件表,包括timer_list,pending_list和watch_table,它们的声明如下:

static struct ril_event * watch_table[MAX_FD_EVENTS];
static struct ril_event timer_list;
static struct ril_event pending_list;

我们用ril_event_set当作输入参数来填充ril_event对象。

    void ril_event_set(struct ril_event * ev, int fd, bool persist, ril_event_cb func, void * param)
    {
        dlog("~~~~ ril_event_set %x ~~~~", (unsigned int)ev);
        memset(ev, 0, sizeof(struct ril_event));
        ev->fd = fd;
        ev->index = -1;
        ev->persist = persist;
        ev->func = func;
        ev->param = param;
        fcntl(fd, F_SETFL, O_NONBLOCK);
    }

fd可能是套接字的文件描述符,并通过调用fcntl将其设置为非阻塞状态。

我们还使用ril_event_add将新事件添加到watch_table,并将fd设置到fd_set中去。所以,如果来自这些fd的一些数据可用,可以调用ril_event的回调函数,有一个线程(eventloop)专门做这件事,我们稍后会讨论它。我们还使用了ril_event_del从watch_table中删除特定的ril_event对象。 并且我们还使用函数“ril_timer_add”将事件添加到timer_list,并使用超时时间的升序对其进行排序。

现在,我们将回到函数“eventloop”。在初始化ril_event表之后,定义了一个管道,然后将读取的fd和相关的信息放入一个ril-event,并将其添加到watch_table中,回调函数的实现如下:

static void processWakeupCallback(int fd, short flags, void *param) {
    char buff[16];
    int ret;

    LOGV("processWakeupCallback");

    /* empty our wakeup socket out */
    do {
        ret = read(s_fdWakeupRead, &buff, sizeof(buff));
    } while (ret > 0 || (ret < 0 && errno == EINTR));
}

我们可以写一些数据到管道的fd来唤醒线程,看起来似乎没有理由这样做。但有趣的是,每次添加新的ril_event对象时,都会调用触发器函数“triggerEvLoop”。 rilEventAddWakeup代码如下:

   static void rilEventAddWakeup(struct ril_event *ev) {
       ril_event_add(ev);
       triggerEvLoop();
   }

我猜测如果在另一个线程中调用rilEventAddWakeup,这个调用将激活eventloop线程。 triggerEvLoop函数将触发processWakeupCallback函数:

    static void triggerEvLoop() {
        int ret;
        if (!pthread_equal(pthread_self(), s_tid_dispatch)) {
            /* trigger event loop to wakeup. No reason to do this,
             * if we're in the event loop thread */
             do {
                ret = write (s_fdWakeupWrite, " ", 1);
             } while (ret < 0 && errno == EINTR);
        }
    }

eventloop的核心部分

eventloop最重要的部分是ril_event_loop,它使用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));
            if (-1 == calcNextTimeout(&tv)) {
                // no pending timers; block indefinitely
                dlog("~~~~ no timers; blocking indefinitely ~~~~");
                ptv = NULL;
            } else {
                dlog("~~~~ blocking for %ds + %dus ~~~~", (int)tv.tv_sec, (int)tv.tv_usec);
                ptv = &tv;
            }
            printReadies(&rfds);
            n = select(nfds, &rfds, NULL, NULL, ptv);
            printReadies(&rfds);
            dlog("~~~~ %d events fired ~~~~", n);
            if (n < 0) {
                if (errno == EINTR) continue;

                LOGE("ril_event: select error (%d)", errno);
                // bail?
                return;
            }

            // Check for timeouts
            processTimeouts();
            // Check for read-ready
            processReadReadies(&rfds, n);
            // Fire away
            firePending();
        }
    }

这是一个死循环,但它仍然有一个专门处理错误的case来退出死循环。那么我们先从calcNextTimeout开始吧。

我们使用此函数来检查timer_list中是否有挂起的计时器。如果timer_list中没有元素,则该函数返回-1,并且select系统调用将无限期地阻塞自身。 我们应该进入这个函数看看究竟:

    static int calcNextTimeout(struct timeval * tv)
    {
        struct ril_event * tev = timer_list.next;
        struct timeval now;

        getNow(&now);

        // Sorted list, so calc based on first node
        if (tev == &timer_list) {
            // no pending timers
            return -1;
        }

        dlog("~~~~ now = %ds + %dus ~~~~", (int)now.tv_sec, (int)now.tv_usec);
        dlog("~~~~ next = %ds + %dus ~~~~",
                (int)tev->timeout.tv_sec, (int)tev->timeout.tv_usec);
        if (timercmp(&tev->timeout, &now, >)) {
            timersub(&tev->timeout, &now, tv);
        } else {
            // timer already expired.
            tv->tv_sec = tv->tv_usec = 0;
        }
        return 0;
    }

如果我们有挂起的计时器事件,我们将比较第一个元素的超时值和当前时间(从系统启动开始)。 如果tev->timeout大于当前时间,则事件tev不会超时,因此将tev的超时值与当前时间之间的差值设置为tv。 反之亦然,如果tev->timeout小于当前时间,我们将tv设置为零。回到ril_event_loop,select系统调用可以从calcNextTimeout获取超时值。我想我们应该知道如何获得挂起的计时器,因此让我们来看一下ril_timer_add的细节:

    void ril_timer_add(struct ril_event * ev, struct timeval * tv)
    {
        dlog("~~~~ +ril_timer_add ~~~~");
        MUTEX_ACQUIRE();

        struct ril_event * list;
        if (tv != NULL) {
            // add to timer list
            list = timer_list.next;
            ev->fd = -1; // make sure fd is invalid

            struct timeval now;
            getNow(&now);
            timeradd(&now, tv, &ev->timeout);

            // keep list sorted
            while (timercmp(&list->timeout, &ev->timeout, < )
                && (list != &timer_list)) {
                list = list->next;
            }
            // list now points to the first event older than ev
            addToList(ev, list);
        }

        MUTEX_RELEASE();
        dlog("~~~~ -ril_timer_add ~~~~");
    }

正如之前的代码片段所示,timer_list由ril_event_init中的init_list初始化,因此如果timer_list中没有元素,则timer_list的next指针和prev指针都指向自身。 

我们将相对时间间隔添加到当前时间,然后将此总和设置到新的ril_event指针ev,然后找到链表中能够保持比较大的一个元素位于后面的位置,可以保证第一个事件是最早的事件。

之后,我们可以确认传递给select系统调用的超时值。如果调用select有错误,它将打破死循环,但相应的错误会被忽略。如果文件描述符中的数据可用,则将调用最后的3个函数:

  • processTimeouts

  • processReadReadies

  • firePending

最后三个函数

If the data comes, we left three kicks. So let’s dive into the first kick: 
如果有数据来了,余下的就剩三个函数。我们先看第一个函数:

    static void processTimeouts()
    {
        dlog("~~~~ +processTimeouts ~~~~");
        MUTEX_ACQUIRE();
        struct timeval now;
        struct ril_event * tev = timer_list.next;
        struct ril_event * next;

        getNow(&now);
        // walk list, see if now >= ev->timeout for any events

        dlog("~~~~ Looking for timers <= %ds + %dus ~~~~", (int)now.tv_sec, (int)now.tv_usec);
        while ((tev != &timer_list) && (timercmp(&now, &tev->timeout, >))) {
            // Timer expired
            dlog("~~~~ firing timer ~~~~");
            next = tev->next;
            removeFromList(tev);
            addToList(tev, &pending_list);
            tev = next;
        }
        MUTEX_RELEASE();
        dlog("~~~~ -processTimeouts ~~~~");
    }

解释起来很简单:我们遍历time_list以查找超时小于当前时间的事件,换句话说,我们要查找超时事件,并将其从timer_list中删除,然后添加到pending_list。

第二个函数:

    static void processReadReadies(fd_set * rfds, int n)
    {
        dlog("~~~~ +processReadReadies (%d) ~~~~", n);
        MUTEX_ACQUIRE();

        for (int i = 0; (i < MAX_FD_EVENTS) && (n > 0); i++) {
            struct ril_event * rev = watch_table[i];
            if (rev != NULL && FD_ISSET(rev->fd, rfds)) {
                addToList(rev, &pending_list);
            if (rev->persist == false) {
                removeWatch(rev, i);
                }
                n--;
            }
        }

        MUTEX_RELEASE();
        dlog("~~~~ -processReadReadies (%d) ~~~~", n);
    }

在此函数中,我们扫描watch_table以检查元素是否已准备好接收数据,然后将其添加到pending_list。 我们是否从watch_table中删除ready项取决于此项中的persist值。

最后一个函数:

    static void firePending()
    {
        dlog("~~~~ +firePending ~~~~");
        struct ril_event * ev = pending_list.next;
        while (ev != &pending_list) {
            struct ril_event * next = ev->next;
            removeFromList(ev);
            ev->func(ev->fd, 0, ev->param);
            ev = next;
        }
        dlog("~~~~ -firePending ~~~~");
    }

现在,pending_list包括所有准备读取的事件,因此我们遍历pending_list,触发所有这些事件,调用它们自己的回调函数。


赞 7