### 文本阅读理解摘要 本文详细介绍了Android系统中的RIL(Radio Interface Layer)子系统,它是Android软件框架中负责与BaseBand芯片通信并提供电话相关服务(如Call、SMS等)的关键部分。RIL通过Linux虚拟串口与基带芯片通信,并通过套接字与Android Framework交互。Google将RIL分为RILD、libril.so和reference-ril.so三部分以保护OEM厂商的IP。RILD和libril.so静态链接,而vendor-ril.so通过动态加载与RILD通信,以降低私有模块和开源模块间的耦合度。RIL_startEventLoop启动事件循环线程,处理I/O操作和硬件通信。RIL架构通过函数指针和事件机制实现模块间的调用和通信,确保系统高效运行。
(本文独家发布在金蝶云社区上)
RIL 概览
RIL是“Radio Interface Layer”的缩写。在Android软件框架中,RIL子系统向下与BaseBand芯片通信,向上给Android框架提供电话相关服务,如Call,SMS等。它在Android软件栈中的位置如下图所示:
在更底层的软件子系统中,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例程的摘要,请参见下图:
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,触发所有这些事件,调用它们自己的回调函数。