Android7.0 Rild工作流程

本文详细解析了Android 7.0中Rild的工作流程,包括Rild作为AP与BP通信中枢的角色,Rild的启动方式,以及Rild如何通过事件循环处理通信。Rild启动涉及rild.rc配置,启动过程包括初始化事件循环、调用RIL_Init及注册库函数。RIL_startEventLoop创建工作线程,构建事件处理框架。RIL_Init在动态库中启动mainLoop线程,初始化AT模块。RIL_register创建监听socket并处理客户端连接。整个Rild进程设计巧妙,利用命令模式处理任务,确保了AP与BP间高效通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、基于Rild的通信架构

一般智能手机的硬件架构都是两个处理器:
一个处理器用来运行操作系统,上面运行应用程序,这个处理器称作Application Processor,简称AP;另一个处理负责和射频无线通信相关的工作,叫Baseband Processor,简称BP。

在Android系统中,Rild运行在AP上,它是AP和BP在软件层上通信的中枢。

目前通过Rild,AP和BP的通信方式可以分为两种:
第一种是AP发送请求给BP,BP响应并回复AP。此时,BP通过Rild回复的请求被称为solicited Response。
第二种是BP主动发送信息给AP。在这种情况下,BP通过Rild发送的请求被称为unsolicited Response。

基于Rild进程的整个通信架构,基本上如上图所示。
从图中我们可以看出:
1、Android框架部分,是通过Phone进程与Rild进程通信的。它们之间的通信方式采用的是socket。
在前面介绍PhoneApp启动时,我们知道Phone进程中有两个Phone对象。每个Phone对象持有一个socket,与对应的Rild进程通信。因此,我们知道手机中实际上启动了两个Rild进程(双卡手机)。

shell:/ $ ps | grep rild
radio     572   1     113732 14792 hrtimer_na 0000000000 S /system/bin/rild
radio     869   1     109604 13944 hrtimer_na 0000000000 S /system/bin/rild

shell:/ $ ps | grep phone
radio     2621  605   2019984 74424 SyS_epoll_ 0000000000 S com.android.phone

shell:/ $ ps | grep init
root      1     0     9648   1712  SyS_epoll_ 0000000000 S /init

shell:/ $ ps | grep zygote
root      605   1     2195280 70956 poll_sched 0000000000 S zygote64
root      606   1     1610708 59144 poll_sched 0000000000 S zygote

我们通过usb连接手机后,通过adb shell进入终端,通过ps和grep命令,可以得到上述结果。
明显可以看到一个Phone进程对应者两个Rild进程;同时Rild进程由init进程加载,Phone进程由zygote进程加载。

2、Rild与BP之间并没有直接通信,而是引入了厂商的动态库。
这种设计应该是为了保证灵活性吧。
用面向对象的思想来看,我们可以认为Rild是一个接口,定义了AP、BP双向通信时需要使用的最基本的函数。不同的厂商都需要满足这个接口,以提供手机最基本的通信功能。
至于具体如何实现,是完全独立和自由的。

二、Rild的启动
在hardware/ril/rild/rild.rc中定义了Rild启动时对应的选项:

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 readproc wakelock

在Android 7.0之前的版本中,该文件的内容是被定义在init.rc中的。
到了Android7.0 之后,init.rc文件中的许多内容均被移出,添加到各个进程中。如前面分析Vold进程时,对应的启动文件定义于vold.rc中。
个人猜测这些文件应该会在编译时,重新集成起来,毕竟在在rild对应的Android.mk中增加了下述字段:

.......
LOCAL_MODULE:= rild
LOCAL_MODULE_TAGS := optional
//新增字段
LOCAL_INIT_RC := rild.rc
.......

目前手边没有Android7.0的机器,还不好验证,以后有机会再做尝试。

init进程根据rild.rc文件启动一个Rild进程,还需要根据厂商定义的rc文件启动另一个Rild进程。
厂商定义的rc文件中,与Rild进程相关的主要内容与rild.rc相似,就是socket名称不同。对于第二个Rild进程,其socket名应该为rild2。

现在我们看看Rild进程的main函数,定义于rild.c中:

int main(int argc, char **argv) {
    //rilLibPath用于指定动态库的位置
    const char * rilLibPath = NULL;
    ........
    //Rild规定动态库必须实现一个叫做Ril_init的函数,这个函数的第一个参数指向结构体RIL_Env
    //而它的返回值指向结构体RIL_RadioFunctions
    const RIL_RadioFunctions *(*rilInit)(const struct RIL_Env *, int, char **);
    ........
    const RIL_RadioFunctions *funcs;
    char libPath[PROPERTY_VALUE_MAX];
    //解析参数
    ........

    if (strncmp(clientId, "0", MAX_CLIENT_ID_LENGTH)) {
        strlcat(rild, clientId, MAX_SOCKET_NAME_LENGTH);
        //注意此处调用了ril.cpp中的函数,保存了Rild进程对应socket的名字,后文还会提到
        RIL_setRilSocketName(rild);
    }

    if (rilLibPath == NULL) {
        //读取系统属性,LIB_PATH_PROPERTY的值为rild.libpath
        //原生的属性值定义于build/target/board/generic/system.prop文件中
        //实际的手机中将会使用厂商指定的system.prop文件
        if ( 0 == property_get(LIB_PATH_PROPERTY, libPath, NULL)) {
            // No lib sepcified on the command line, and nothing set in props.
            // Assume "no-ril" case.
            goto done;
        } else {
            rilLibPath = libPath;
        }
    }
    ..........
    //根据动态库位置,利用dlopen打开动态库
    dlHandle = dlopen(rilLibPath, RTLD_NOW);
    ..........
    //1、启动EventLoop,事件处理
    RIL_startEventLoop()

    //从动态库中的到RIL_Init函数的地址
    rilInit =
        (const RIL_RadioFunctions *(*)(const struct RIL_Env *, int, char **))
        dlsym(dlHandle, "RIL_Init");
    ......
    //2、调用RIL_init函数
    funcs = rilInit(&s_rilEnv, argc, rilArgv);
    RLOGD("RIL_Init rilInit completed");

    //3、注册funcs到Rild中
    RIL_register(funcs);
    ........
done:

    RLOGD("RIL_Init starting sleep loop");
    while (true) {
        sleep(UINT32_MAX);
    }
}

根据Rild的main函数,我们可以看出主要就进行了三件事:启动Event Loop、调用RIL_Init函数和注册库函数。
接下来我们分别分析一下主要事件对应的流程。

1、 RIL_startEventLoop
RIL_startEventLoop定义于hardware/ril/libril/ril.cpp中:

extern "C" void
RIL_startEventLoop(void) {
    /* spin up eventLoop thread and wait for it to get started */
    s_started = 0;
    .........
    //创建工作线程,线程ID存入s_tid_dispatch,对应执行函数为eventLoop
    int result = pthread_create(&s_tid_dispatch, &attr, eventLoop, NULL);
    .........
    //工作线程eventLoop运行后,会设置s_started为1,并触发s_startupCond
    //这里的等待的目的是保证RIL_startEventLoop返回前,工作线程创建并运行成功
    while (s_started == 0) {
        pthread_cond_wait(&s_startupCond, &s_startupMutex);
    }
    .........
}

我们需要跟进eventLoop函数:

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

    //1、初始化内部数据结构
    ril_event_init();

    pthread_mutex_lock(&s_startupMutex);

    //通知RIL_startEventLoop本线程已经创建并成功运行了
    s_started = 1;
    pthread_cond_broadcast(&s_startupCond);

    pthread_mutex_unlock(&s_startupMutex);

    //创建匿名管道
    ret = pipe(filedes);
    ........
    s_fdWakeupRead = filedes[0];
    s_fdWakeupWrite = filedes[1];
    //设置读端口为非阻塞的
    fcntl(s_fdWakeupRead, F_SETFL, O_NONBLOCK);

    //2、创建一个ril_event
    ril_event_set (&s_wakeupfd_event, s_fdWakeupRead, true,
                processWakeupCallback, NULL);

    //3、将创建出的ril_event加入到event队列中
    rilEventAddWakeup (&s_wakeupfd_event);

    //4、进入事件等待循环中
    ril_event_loop();
    .........    
}

1.1 初始化内部数据结构
我们先看看ril_event_init函数:

void ril_event_init()
{
    MUTEX_INIT();

    FD_ZERO(&readFds);
    //初始化timer_list,任务插入时按时间排序
    init_list(&timer_list);
    //初始化pending_list,保存每次需要执行的任务
    init_list(&pending_list);
    //初始化监控表
    memset(watch_table, 0, sizeof(watch_table));
}

static void init_list(struct ril_event * list)
{
    memset(list, 0, sizeof(struct ril_event));
    list->next = list;
    list->prev = list;
    list->fd = -1;
}

//MAX_FD_EVENTS为8
//watchtable将用于保存FD加入到readFDs中的ril_event
static struct ril_event * watch_table[MAX_FD_EVENTS];

可以看出ril_event_init就是初始化readFds、timer_list、pending_list和watch_table,其中后三种数据结构均是用来存放ril_event的。

根据前文的代码,我们知道Rild的main函数中,通过调用RIL_startEventLoop单独启动了一个线程运行eventLoop,这是一个工作线程。
这个工作线程就是靠ril_event结构体来描述自己需要执行的任务,并且它将多个任务按时间顺序组织起来,保存在任务队列中。
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;
};

如果从设计模式的角度来理解Rild的工作线程,易于看出,这其实是比较典型的命令模式。
就如同之前博客分析vold进程一样,CommandListener收到数据后,调用对应Command的runCommand方法进行处理。
此处,工作线程收到ril_event后,加入队列中,当需要处理时,调用ril_event对应的处理函数func。

1.2 创建wakeupfd ril_event
工作线程完成数据结构的初始化后,创建了第一个ril_event:

........
ril_event_set (&s_wakeupfd_event, s_fdWakeupRead, true,
            processWakeupCallback, NULL);
........
// Initialize an 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);
}

从上面的代码可以看出,创建的第一个ril_event的fd为管道的读端、回调函数为processWakeupCallback,同时persist属性为true。

1.3 将创建出的ril_event加入到event队列中

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

// Add event to watch list
void ril_event_add(struct ril_event * ev)
{
    dlog("~~~~ +ril_event_add ~~~~");
    MUTEX_ACQUIRE();
    for (int i
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值