基于Android6.0的RIL底层模块分析

来源:互联网 发布:日程提醒软件 编辑:程序博客网 时间:2024/05/17 11:07

看代码的时候不要看到细节里面,先构建模块的运行框架,后续有需要再深入细节。必要的时候需要拿一个本子将主要流程画出来或者写出来。

我们先看看,从系统刚开机是如何启动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函数可以在这个源码文件里面去找。后续再详细分析这个初始化函数。

  1. 列表内容

RIL_startEventLoop-启动一个线程,并将事件监听机制建立并启动

我们先看看RIL_startEventLoop的源码:

//Ril.cppextern "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.cppvoid 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里面的内容是通过二人组进行添加的。你看这样分析之后就很简单了吧。

  1. rilInit(&s_rilEnv, argc, rilArgv)
    这个函数实际调用的是libreference-ril.so库文件里面的RIL_Init函数:
//Reference-ril.cconst 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};
  1. 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分析后续有时间再奉上。如本文有错漏之处,请各位朋友指正。

0 0
原创粉丝点击