之前看了许多其他同行写的这方面的好文章了,最近重温这一块,觉得应该自己写一篇,以后再看也好上手。
Rild是Android提供的框架,位置处于AP侧,对接Phone进程和BP侧Modem。
双卡手机在phone进程会有2个Phone的实例对应各个卡槽,相应的Rild进程也会有2个,Phone和Rild之间通过Socket保持连接,各卡槽之间的操作互相独立。
但是由于基带需要由第三方的芯片厂商来提供,因此Google只提供了Rild的框架和接口,剩下的事情需要由芯片供应商自行来实现,这里说的就是vendor RIL。
关于Vendor RIL的实现方法各有不同,如高通用qcril + qmi机制实现,而MTK则直接使用AT指令与Modem通讯,在RIL层不做过多的处理。
具体的实现以后有时间再来整理。接下来的内容以一个高通的工程为范本来分析。
1. RILD进程的启动
用手上的一台双卡手机验证了rild进程确实存在2个:
root@s2:/ # ps |grep rild
radio 464 1 107284 14088 hrtimer_na 7fa7f60604 S /system/bin/rild
radio 759 1 106252 13920 hrtimer_na 7f95db4604 S /system/bin/rild
Rild为系统服务,很明显会在init.rc中能找到启动的描述(Android7.0被定义在hardware\ril\rild\Rild.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
这里启动了一个rild,那么第二个rild在哪里呢?通过OpenGrok的帮忙,在工程中搜索到device/qcom/common/rootdir/etc/init.qcom.rc有相关定义:
service ril-daemon2 /system/bin/rild -c 2
class main
socket rild2 stream 660 root radio
socket rild-debug2 stream 660 radio system
user root
disabled
group radio cache inet misc audio sdcard_r sdcard_rw qcom_diag diag log
service ril-daemon3 /system/bin/rild -c 3
class main
socket rild3 stream 660 root radio
socket rild-debug3 stream 660 radio system
user root
disabled
group radio cache inet misc audio sdcard_r sdcard_rw qcom_diag diag log
这里有rild2和rild3的描述,猜想在工程编译期间脚本会通过编译参数判断工程支持的SIM卡数,然后对应地将这些启动描述整合到最终的init.rc中。(以后再来验证。)
2. RILD main函数
根据init.rc中描述可知,rild的入口函数名字为main。
下面过滤出main函数主要的处理,先大致看一下:
int main(int argc, char **argv) {
const char * rilLibPath = NULL;
char **rilArgv;
void *dlHandle;
const RIL_RadioFunctions *(*rilInit)(const struct RIL_Env *, int, char **);
const RIL_RadioFunctions *funcs;
unsigned char hasLibArgs = 0;
int i;
const char *clientId = NULL;
...
//1.从启动参数中获取clientId,代表是第几个RILD
for (i = 1; i < argc ;) {
...
if (0 == strcmp(argv[i], "-c") && (argc - i > 1)) {
clientId = argv[i+1];
i += 2;
} ...
}
//2.使用clientId设置RILD的socket名字
if (strncmp(clientId, "0", MAX_CLIENT_ID_LENGTH)) {
strlcat(rild, clientId, MAX_SOCKET_NAME_LENGTH);
RIL_setRilSocketName(rild);
}
//3.获取vendor ril库的路径
//#define LIB_PATH_PROPERTY "rild.libpath"
if (rilLibPath == NULL) {
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;
}
}
...
//4.打开vendor ril库
dlHandle = dlopen(rilLibPath, RTLD_NOW);
//5.启动消息循环
RIL_startEventLoop();
...
//6.获取vendor RIL的初始化入口方法
rilInit =
(const RIL_RadioFunctions *(*)(const struct RIL_Env *, int, char **))
dlsym(dlHandle, "RIL_Init");
...
//7.调用vendor RIL的RIL_Init方法,此方法会返回RIL_RadioFunctions
rilArgv[argc++] = "-c";
rilArgv[argc++] = clientId;
rilArgv[0] = argv[0];
funcs = rilInit(&s_rilEnv, argc, rilArgv);
//8.注册vendor RIL的处理方法
RIL_register(funcs);
...
}
我们看到,main函数主要的流程如下:
一)获取vendor ril并打开。(3和4步)
二)启动消息循环。(第5步)
三)获得vendor ril入口方法,并取得vendor ril的消息处理函数列表。(6和7)
四)注册vendor ril回调方法。(第8步)
用本文接下来的篇幅来分析这上面列出的4个步骤。
3. 深入分析main函数
3.1 获取Vendor RIL库文件路径
main函数中通过getprop获取了vendor ril文件的路径,property名为:”rild.libpath”
#define LIB_PATH_PROPERTY "rild.libpath"
property_get(LIB_PATH_PROPERTY, libPath, NULL)
在adb shell中输入getprop命令,找到库路径:/vendor/lib64/libril-qc-qmi-1.so
root@s2:/ # getprop rild.libpath
/vendor/lib64/libril-qc-qmi-1.so
这个so文件由qcril源码生成,mk文件中有描述:
/vendor/qcom/proprietary/qcril/qcril_qmi/qcril_qmi.mk
ifndef QCRIL_DSDA_INSTANCE
LOCAL_MODULE:= libril-qc-qmi-1
在获得了vendor ril库文件路径之后,打开库并取得文件句柄,流程似乎即将进入vendor RIL。但在此之前,Rild会先启动一个子线程消息循环,然后再开始qcril的流程。
3.2 RIL_startEventLoop 启动监听消息循环
RIL_startEventLoop创建了一个线程,用于执行eventLoop这个函数。
Ril.cpp
extern "C" void
RIL_startEventLoop(void) {
/* spin up eventLoop thread and wait for it to get started */
s_started = 0;
...
//启动线程,执行eventLoop函数
int result = pthread_create(&s_tid_dispatch, &attr, eventLoop, NULL);
...
//s_started会在eventLoop被执行后置成1,保证了线程启动后才离开这个函数。
while (s_started == 0) {
pthread_cond_wait(&s_startupCond, &s_startupMutex);
}
...
}
在RIL_startEventLoop退出之前,还进入了一个while循环去,这里意图是通过判断s_started 的值,保证线程已经启动且eventLoop已经开始被执行,因为s_started在eventLoop开始时会被赋值成1。
eventLoop可以说是Rild中消息获取和分发的中心,其实现的关键字如下:
无限循环、select 、fd_set、ril_event、callback。