看代码的时候不要看到细节里面,先构建模块的运行框架,后续有需要再深入细节。必要的时候需要拿一个本子将主要流程画出来或者写出来。
我们先看看,从系统刚开机是如何启动RIL功能的。首先先查看一下init.rc(这个文件包含一些初始化的服务或者功能,在开机阶段占有很重要的地位)。
service ril-daemon /system/bin/rild
class main
socket rild stream 660 root radio
socket sap_uim_socket1 stream 660 bluetooth bluetooth
socket rild-debug stream 660 radio system
user root
group radio cache inet misc audio log qcom_diag
可以看到启动了三个socket,其中sap_uim_socket1和rild-debug这两个我们不需要关注,sap是跟蓝牙接听有关的,大家如果有兴趣可以另外在了解。debug从命名上看应该是跟调试功能有关的,我们现在也不关注,所以我们只看:
socket rild stream 660 root radio
Android系统解析这句的时候,会创建一个名为rild的socket,这个socket就是上层跟底层进行通信的socket了。可以看看RIL.java里面的RILReceiver和RILSender类,里面都是针对这个socket进行的读写操作,可见该socket确实就是上下层进行通信的途径了。
而这个service的对应的可执行文件地址为:
/system/bin/rild 对应的逻辑代码为:hardware\ril\rild\Rild.c
去掉不相干的代码,只研究最核心的代码如下:
int main(int argc, char **argv) {
const RIL_RadioFunctions *(*rilInit)(const struct RIL_Env *, int, char **);
const RIL_RadioFunctions *funcs;
dlHandle = dlopen(rilLibPath, RTLD_NOW);
RIL_startEventLoop();// 1
rilInit = (const RIL_RadioFunctions *(*)(const struct RIL_Env *, int, char **))dlsym(dlHandle, "RIL_Init");
funcs = rilInit(&s_rilEnv, argc, rilArgv);// 2
RIL_register(funcs);// 3
}
这个main函数里面主要就是我上面标明的1/2/3三个函数调用,而这三个函数调用就建立了rild进程的整个事件循环机制,与底层modem通信机制,与上层java层的framework里面的RIL.java的通信机制。其实上层的拨打电话,发短信什么的都是通过rild来实现。rild类似一个中间层,将java层的请求(打电话,发短信等)转发到modem端,将底层modem的来电或者来短信通知到上层(就是程序中经常看到的Unsolicited,意为不请自来。也就是来电,来短信之类的事件)。
在程序里面先是载入了libreference-ril.so库文件,这个库的实现是在“hardware\ril\reference-ril\Reference-ril.c”里面,所以第二步里面执行的rilInit函数可以在这个源码文件里面去找。后续再详细分析这个初始化函数。
- 列表内容
RIL_startEventLoop-启动一个线程,并将事件监听机制建立并启动
我们先看看RIL_startEventLoop的源码:
//Ril.cpp
extern "C" void RIL_startEventLoop(void) {
int result = pthread_create(&s_tid_dispatch, &attr, eventLoop, NULL);
}
static void *eventLoop(void *param) {
int filedes[2];
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);
// Only returns on error
ril_event_loop();
kill(0, SIGKILL);
return NULL;
}
总结一下,实际上另外启动了一个线程s_tid_dispatch(从名字dispatch上看,应该是一个分发事件的线程),这个线程的函数主体是eventLoop。在eventLoop里面首先调用的ril_event_init函数,这个函数里面对fd_set readFds,timer_list,pending_list,watch_table进行了初始化(或者说清零)。从这个重要的初始化函数,大家应该可以大概猜到,这个线程主要是操作这几个数据结构的,至于后续如何操作可以再深入了解,但是第一遍我们只需要知道大概流程。里面用到了fd_set,大家应该去搜索一下Linux的select机制和FD_SET这两个关键字。这里简单描述一下,Linux会对fd_set集合里面的一组socket或者文件句柄进行监听,如果有部分socket有数据变化,那么就会返回这部分socket的数组。这样就可以不用一直轮询所有的socket或者文件句柄,节省了大量的cpu资源。
除此之外还建立了一个管道,并且将这个管道的读端绑定到了s_wakedupfd_event上面。注意看这个event设置二人组,在程序里面很多地方都出现过:
ril_event_set (&s_wakeupfd_event, s_fdWakeupRead, true,
processWakeupCallback, NULL);
rilEventAddWakeup (&s_wakeupfd_event);
先用一些参数设置一个event,然后将这个event添加到watch_table和readFds里面,我们上面在ril_event_init函数里面也提到过过这两个变量。其实翻译成人类理解的语言就是,如果在s_fdWakeupRead这个fd上有数据变化,那么就发送给processWakeupCallback进行处理。将这两个值包装成一个ril_event,并且另起一个线程建立起eventloop循环来处理ril_event事件。
最后调用ril_event_loop,核心代码如下:
//Ril_event.cpp
void ril_event_loop()
{
for (;;) {
// make local copy of read fd_set
memcpy(&rfds, &readFds, sizeof(fd_set));
n = select(nfds, &rfds, NULL, NULL, ptv);
// Check for timeouts
processTimeouts();
// Check for read-ready
processReadReadies(&rfds, n);
// Fire away
firePending();
}
}
基本上启动了一个无限循环,不停的查看readFds集合里面是否有一些文件句柄fd有数据变化,并将这些有变化的句柄保存到nfds里面。processTimeouts进行超时处理,processReadReadies将nfds里面的ril_event事件取出,进行一定判断,并将其放到pending_list里面(注意这个列表我们在ril_event_init里面也提到过)。firePending,看名字就知道是处理Pending列表了,它的代码里面确实对ril_event事件进行了处理。
static void 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;
}
}
基本上是一个遍历链表,并且处理每个节点的过程。注意,ril_event结构里面本身就包含了一个func函数指针,这个函数的作用就是在这个时候处理自己的。所以其实这个事件本身就包含了如何处理自身数据的功能,这样也算是一种程度上的解耦吧。类似一种自治的结构,不需要外部模块提供处理函数,同时提高了封装性。
再从前面的代码:
ril_event_set (&s_wakeupfd_event, s_fdWakeupRead, true,
processWakeupCallback, NULL);
通体再读一遍,会发现其实这个事件的处理函数processWakeupCallback,其实只是起到清空wakeup socket的作用。
阶段总结
RIL_startEventLoop会另起一个线程,监听readFds这个集合里面的socket(或者文件或者管道)句柄,并且对监听到的变化进行处理。而readFds里面的内容是通过二人组进行添加的。你看这样分析之后就很简单了吧。
- rilInit(&s_rilEnv, argc, rilArgv)
这个函数实际调用的是libreference-ril.so库文件里面的RIL_Init函数:
//Reference-ril.c
const RIL_RadioFunctions *RIL_Init(const struct RIL_Env *env, int argc, char **argv)
{
ret = pthread_create(&s_tid_mainloop, &attr, mainLoop, NULL);
}
static void *mainLoop(void *param __unused)
{
for (;;) {
fd = -1;
while (fd < 0) {
if (s_port > 0) {
fd = socket_loopback_client(s_port, SOCK_STREAM);
} else if (s_device_path != NULL) {
fd = open (s_device_path, O_RDWR);
if ( fd >= 0 && !memcmp( s_device_path, "/dev/ttyS", 9 ) ) {
}
}
}
ret = at_open(fd, onUnsolicited);
RIL_requestTimedCallback(initializeCallback, NULL, &TIMEVAL_0);
}
}
其实就是启动一个s_tid_mainloop线程,主题函数为mainLoop。在这个函数里面会用at_open打开文件/dev/ttyS。其实这个就是modem在linux系统里面抽象出来的文件。而在at_open里面会另外再起一个线程:
pthread_create(&s_tid_reader, &attr, readerLoop, &attr);
这个readerLoop函数需要自己去看了,我只简单说一下(因为其实我也没有深入去看啊,不过只是了解流程的话并无必要,除非碰到实际问题需要解决)。这个readerLoop从AT channel读取内容,并且解析该内容,并且根据内容调用不同的函数进行处理。看看下面的处理函数:
static void processLine(const char *line)
{
pthread_mutex_lock(&s_commandmutex);
if (sp_response == NULL) {
/* no command pending */
handleUnsolicited(line);
} else if (isFinalResponseSuccess(line)) {
sp_response->success = 1;
handleFinalResponse(line);
} else if (isFinalResponseError(line)) {
sp_response->success = 0;
handleFinalResponse(line);
}
pthread_mutex_unlock(&s_commandmutex);
}
Unsolicited就是不请自来的来电,来短信等事件了。response就是上层要求的事件比如打电话/发短信等。这些处理函数再深入进去看真的是五花八门,各显神通了。有直接赋值的handle函数,有使用Linux下面的条件互斥,通知另外一个线程进行处理的机制等。
阶段总结
rilInit会启动一个线程mainLoop(其实后面还启动了一个readerLoop),专门用AT命令从modem读取数据,并且进行处理。且最后会返回一个RIL_RadioFunctions到rild.c里面的main函数里面。
static const RIL_RadioFunctions s_callbacks = {
RIL_VERSION,
onRequest,
currentState,
onSupports,
onCancel,
getVersion
};
- RIL_register(funcs);
这是main里面的最后一个函数了。
extern "C" void RIL_register (const RIL_RadioFunctions *callbacks) {
memcpy(&s_callbacks, callbacks, sizeof (RIL_RadioFunctions));
/* Initialize socket1 parameters */
s_ril_param_socket = {
RIL_SOCKET_1, /* socket_id */
-1, /* fdListen */
-1, /* fdCommand */
PHONE_PROCESS, /* processName */
&s_commands_event, /* commands_event */
&s_listen_event, /* listen_event */
processCommandsCallback, /* processCommandsCallback */
NULL /* p_rs */
};
// start listen socket1
startListen(RIL_SOCKET_1, &s_ril_param_socket);
}
干了两件事,一个是初始化了一个s_ril_param_socket,并且将其作为参数传递到了startListen函数。先看看这个结构体SocketListenParam s_ril_param_socket。
typedef struct SocketListenParam {
RIL_SOCKET_ID socket_id;
int fdListen;
int fdCommand;
char* processName;
struct ril_event* commands_event;
struct ril_event* listen_event;
void (*processCommandsCallback)(int fd, short flags, void *param);
RecordStream *p_rs;
RIL_SOCKET_TYPE type;
} SocketListenParam;
用通俗语言解释就是,在某一张卡(针对的是多卡的手机)上,从listen和command的句柄上获取listen到的事件和上层发送过来的command事件。并且还包含有处理完命令之后的回调函数。
下面分析一下startListen函数:
static void startListen(RIL_SOCKET_ID socket_id, SocketListenParam* socket_listen_p) {
fdListen = android_get_control_socket(socket_name);
ret = listen(fdListen, 4);
socket_listen_p->fdListen = fdListen;
/* note: non-persistent so we can accept only one connection at a time */
ril_event_set (socket_listen_p->listen_event, fdListen, false,listenCallback, socket_listen_p);
rilEventAddWakeup (socket_listen_p->listen_event);
}
这个android_get_control_socket通过socket_name(一般是”rild”)获取到socket句柄。用这个句柄名称在init.rc中查找,并且返回指定句柄。见文章开头提到的那个句柄。后续会在这个句柄上监听,该句柄支持最多缓存四个事件,超过的就会被丢弃。并且会把这个句柄用来赋值给socket_listen_p这个变量里面的fdListen值。后面又看到二人组了,ril_event_set和rilEventAddWakeup。将socket_listen_p->listen_event这个ril_event设置一下(包括监听哪个fdListen,使用哪个函数处理自己listenCallback),然后把这个ril_event事件添加到watch_table里面,并且把这个fdListen句柄添加到readFds里面。前面已经讲过,这个readFds上面有个线程在不停监听,并对有数据的fd进行处理。二人组函数ril_event_set和rilEventAddWakeup的组合,其实就是一个机制:将socket添加到多路IO复用机制中,一旦有上层请求,那么就能触发相应的处理函数。
上面就是整个native层的rild模块的主要工作内容了,相信大家已经比较了解了。他对应的Java层的RIL也会对套接字“rild”进行操作,这样就达到了互相通信的目的了。大家可以看看RIL.java这个函数。Java层的RIL分析后续有时间再奉上。如本文有错漏之处,请各位朋友指正。

本文剖析了Android系统中RIL功能的启动过程与通信机制,重点介绍了init.rc配置、rild进程启动、socket通信机制及AT命令交互原理。
2808

被折叠的 条评论
为什么被折叠?



