个人学习过程总结
相关参考资料:
深入剖析 Android 系统_杨长刚/第 9 章 RIL
安卓 4.1 MTK 源码
RIL(Radio Interface Layer) 是上层程序使用地射频功能的接口层,它更像一个支持数据格式转换的
通道。
上层程序的 API 调用最终转换为底层射频能识别出的命令字符串,底层上报的字符串信息被翻译解释
后,又能被上层的程序识别,这就是 RIL 层的功能。
Phone 进程 【Java】
RILJ
----- socket -------
Rild 【C/C++】
----- serial ------- AT 命令
Modem 【硬件】
【rild 进程】
1. 创建 eventLoop 线程
【eventLoop 线程】: 套接字监听执行回调,执行提交来的定时任务函数
2. 创建 mainLoop 的工作线程
/////////////////////////////////////////////////////////////////////////
// 以下两个线程都是 reference-ril.c 硬件动态库中创建的
【mainLoop 线程】:打开 AT 串口,创建一个定时任务给 eventLoop 初始化 Modem
创建工作线程 readerLoop:
【readerLoop 线程】:主要任务就是从 AT 的串口设备读取数据,并解析处理
RILConstants.java // 保存了 Java 层的请求号等信息,要求与【硬件库的 ril.h 文件使用的请求号保持一致】
ril_commands.h // 定义了请求号与对应分发命令与回复命令的对应关系,举例:
// {RIL_REQUEST_DIAL, dispatchDial, responseVoid},
// 请求号 10 对应的分发函数为 dispatchDial, 回复函数为 responseVoid
// dispatchDial()/responseVoid() 实现在 ril.cpp 中
//
// 【RIL_REQUEST_DIAL】: 定义在 Ril.h 中,被 Java 层与 Reference-ril.c 共用,以便请求保存一致
// 【dispatchDial()】: 调用 reference-ril.c 导出的 onRequest() 函数请求 Modem 服务
// 【responseVoid()】: 用于在命令成功返回时,将 AT 回复的数据写入 Parcel
ril_unsol_commands.h // 定义了 unsolicited Response 的请求号与命令对应关系,举例:
// {RIL_UNSOL_CALL_RING, responseCallRing, WAKE_PARTIAL},
// 【RIL_UNSOL_CALL_RING】:请求号,同样定义在 ril.h 中
// 【responseCallRing】:定义在 ril.cpp 中,将 AT 回复的数据写入 Parcel
// 【WAKE_PARTIAL】:回复时获得一个 wake lokc 休眠锁
/////////////////////////////////////////////////////////////////////////
// 上面两个头文件都定义在 ril.cpp 中引用
// 【Index == requestNumber】
// static CommandInfo s_commands[] = {
// #include "ril_commands.h"
// };
//
// static UnsolResponseInfo s_unsolResponses[] = {
// #include "ril_unsol_commands.h"
// };
/////////////////////////////////////////////////////////////////////////
ril.cpp // rild 中调用相关函数实现,如 RIL_startEventLoop(), RIL_register 等等函数,是 rild 核心实现
// 这里定义了会被 reference-ril 厂家库调用的回调函数
// static struct RIL_Env s_rilEnv =
// {
// RIL_onRequestComplete, // 动态库完成一个请求后,通过这个函数通知处理结构,其中第一个参数标明是哪个请求的处理结果
// RIL_onUnsolicitedResponse, // 动态库用于进行 unsolicited Response 通知的函数: 即 BP 主动上报的事件的处理
// RIL_requestTimedCallback // 向 Rild 提交一个超时任务, 即在 eventloop 指定时间后执行的函数
// };
reference-ril.c // modem 相关硬件操作的库函数,用于与 modem 通信
// 动态库开放出来的外部操作函数接口,供外部函数请求调用
// static const RIL_RadioFunctions s_callbacks = {
// RIL_VERSION,
// onRequest,
// currentState,
// onSupports,
// onCancel,
// getVersion
// };
rild.c // 是 rild 模块的相关逻辑程序,引用上面两个库函数,控制先做什么,后做什么
rild 监听套接字 /dev/socket/rild 等待连接
-> 上层发起连接
-> eventloop 进程
-> listenCallback()
-> 检查连接权限
1. 正确
新套接字 = accept()
创建一个 processWakeupCallback() 函数处理新套接字请求
2. 不正确
重新提交 listenCallback() 函数监听 /dev/socket/rild
上层通过套接字发命令
----------- eventloop 线程 ----------------------------------------
-> eventloop 线程
-> processCommandsCallback()
-> 调用 ril_commands.h 定义的分发函数发送命令给 AT
-> 等待 AT 设备响应
------------ reader 线程 ----------------------------------
reader 线程
-> AT 串口有数据发送上来
-> processLine
唤醒 eventloop 线程
----------- eventloop 线程 -------------------------------------
-> eventloop 继续执行
-> sendResponse
-> 调用 ril_commands.h 定义的回复函数封装一些消息到 Parcel 对象中
-> 通过套接字将执行结果返回给上层
////////////////////////////////////////////////////////////////
// 【上层发来的请求的格式】
// typedef struct RequestInfo {
// int32_t token; //this is not RIL_Token
// CommandInfo *pCI;
// typedef struct {
// int requestNumber; // 请求号
// void (*dispatchFunction) (Parcel &p, struct RequestInfo *pRI);
// int(*responseFunction) (Parcel &p, void *response, size_t responselen);
// } CommandInfo;
// struct RequestInfo *p_next;
// char cancelled;
// char local; // responses to local commands do not go back to command process
// } RequestInfo;
Rild.c (hardware\ril\rild)
// 1. 读取 system.prop 中的系统属性:
// rild.libpath=/system/lib/libreference-ril.so // 与 modem 通信的具体厂家实现的动态库
// rild.libargs=-d /dev/ttyS0 // 与 modem 通信使用的串口
// 通过 dlopen 系统加载厂家实现的动态库
//
// 2. 启动 EventLoop 线程, 在这里进行事件处理,监听管道套接字,收到数据后调用回调处理
// 同时也可以注册定时函数让他到期时调用回调处理
// -------------------
// eventLoop 线程
// 初始化 readFds, 看来 Ril 会使用 select 来做多路 IO 复用
// 创建匿名管道 【用于被唤醒使用】
// 进入事件等待循环中,等待外界触发事件并做出对应的处理
// 1. 定时任务,由 ril_timer_add() 函数添加,到期执行
// 2. 非定时任务,这些任务的 FD 加入到 select 中监控,有数据可读时唤醒执行
// 3. 遍历 pending_list,执行任务函数
//
// 3. 得到 RefRil 库中的 RIL_Init 函数的地址
// 【注意】此函数是在硬件动态库中实现的,里面所有函数都是动态库内部的,除了传入的接口
// 调用 RefRil 库输出的 RIL_Init 函数,注意此函数传输的第一个参数和它的返回值
// 参考动态库的源码位于: Reference-ril.c
// 此函数主要完成工作为:
// 1. 创建一个 mainLoop 工作线程,它的任务是初始化 AT 模块,并监控 AT 模块,一旦
// AT 模块被关闭,则会重新初始化 AT 模块
// 2. AT 模块内部会创建一个工作线程 readerLoop,该线程的作用是从串口设备中读取信息
// ,也就是直接和 BP 打交道
// 3. mainLoop 通过向 Rild 提交超时任务,完成了对 Modem 的初始化工作
//
//
// 4. 注册上面 rilInit 函数的返回值(一个 RIL_RadioFunctions 类型的结构体)到 Rild 中,用于 AP 发送命令给 BP
// // RIL_register() 将创建两个监听端 socket:
// // 1. rild : 用于与 Java 层的应用通信
// // 2. rild-debug: 用来接收测试程序的命令
// RIL_register(funcs);
//
// 5. 主线程 sleep, 具体工作交给工作线程完成
// while(1) {
// // sleep(UINT32_MAX) seems to return immediately on bionic
// sleep(0x00ffffff);
// }
与 rild 守护进程交互的 Java 部分是 com.android.internal.telephony.RIL 类(简称为 RILJ),
它是通过 UNIX 套接字(/dev/socket/rild)进行通信。进程 com.android.phone 通过使用 Teleohony
框架来使用系统的各种 Teleohony 功能。
------ phone 进程 -------- 【Java】
android Telephony
framework
-------------
RILJ
--------socket ----------
/\
||
\/
------- rild 模块 ----- 【C/C++】
--------- AT 串口 -------
硬件设备
// RILJ 负责与 rild 守护进程交互,提供 API 用于执行 RIL 请求,供 Telephony Framework 调用
// 它还提供底层对 RIL 请求的响应回复的处理,它将以消息的形式发送给调用者。
RILJ {
RILSender: 是 Handler 的子类,将在发送线程中处理发送消息等事件
RILReceiver: 实现了 Runnable 接口类的 Run 接口函数,作为线程的执行体运行在 RILJ 创建
接收线程(见 RIL 类的构造函数)中,负责消息的接收
}
RILConstants:定义了 RIL 的各种请求号和 unsolicited 号,它们必须与 ril.h 中定义保持一致,
用于标识请求号和 unsolicited 消息号,这些标志用在硬件动态库与 rild 中使用。
CommandsInterface: 接口类,定义了 RILJ 和 rild 交互的接口
BaseCommands implements CommandsInterface: 实现了 CommandsInterface 的部分接口,用于通知手机各种内部
状态的变化,它里面包含了很多注册者 Registrant 和注册者列表 RegistrantList, 它们
代表着对某些事件感兴趣希望接收事件变化通知的接收者。
当对某种状态变化感兴趣时,就可以调用 registerXXX 函数将自己注册为一个对某种
状态感 兴趣的通知接收者。
注册时,在 BaseCommands 内部创建一个对应的 registrant 将 registrant 添加到
列表 registrantList 中。
调用 registerXXX() 时,调用者将 message 和 handler 传递给 Registrant 对象,
当有状态变化(processUnsolicited 处理主动上报的信息则往往意味着状态的改变)时,则
通过 Registrant.NotifyXXX() 调用 hander 将消息发到消息队列上,Handler 所在线程将
处理这些消息,这也保证了尽可能的实时通知调用者的目的。
RILRequest: 代表着一个即将发送出去的 RIL 请求,它里面包含了 Request 请求号、序列号(自 0 开始累加)和保存
请求结果的 Message,Java 部分的 Request 请求号就是上述的 RILConstants 中的常量(与 C/C++
部分的请求号保持一致)。当需要执行某种 RIL 请求时,则需创建一个新的 RILRequest 使用
RILRequest 的 obtain 函数
///////////////////////////////////////////////////////////////////////////////////////////
// 1. 外部创建一个对象,使用时应该是直接将此类当作一个处理线程运行,用来与 rild 通信发命令
// 是一个内部类
RIL::RILSender extends Handler implements Runnable // 继承 Runnable 多线程类,
// Handler: 给消息队列发消息并调用回调处理
{
// 构造函数传入的参数是一个消息队列
// 表示与一个消息队列绑定,给此消息队列发消息,
// 当目标进程处理消息队列中此条消息时,会调用发送时传入的回调处理,即本类的 handleMessage() 处理
RILSender(Looper looper)
{
super(looper);
}
// 外部调用 start 时将此函数当线程启动
run(){}
// 当在构造函数中绑定的消息队列有消息时,调用此函数进行解析处理
handleMessage(Message msg)
{
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// 2. 创建一个接收,接收 rild 回复的消息
// 内部类
RIL::RILReceiver implements Runnable // 继承 Runnable ,多线程类
{
// 外部 start 时将此函数当线程启动
run() {
for (;;) {
// 连接上 rild 内部的套接字 /dev/socket/rild
s = new LocalSocket();
l = new LocalSocketAddress(socketRil, LocalSocketAddress.Namespace.RESERVED);
s.connect(l);
InputStream is = mSocket.getInputStream();
for (;;) {
// 从套接字读数据
length = readRilMessage(is, buffer);
// 没数据退出循环
if (length < 0) {
// End-of-stream reached
break;
}
// 获得套接字传入的数据
p = Parcel.obtain();
p.unmarshall(buffer, 0, length);
p.setDataPosition(0);
///////////////////////////////////////
// 进行数据处理
processResponse(p);
}
// 从套接读数据返回却没读到数据?复位 AT 串口
setRadioState (RadioState.RADIO_UNAVAILABLE);
mSocket.close();
RILRequest.resetSerial(mySimId);
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// 调用相应的 RIL 请求对应的成员函数发给命令,比如: 向 SIM/USIM 卡提供 PIN 码对应的函数
// 所有的接口位于 CommandsInterface 接口类中定义中:
RIL::supplyIccPinForApp(String pin, String aid, Message result)
{
//Note: This RIL request has not been renamed to ICC,
// but this request is also valid for SIM and RUIM
///////////////////////////////////////////////////////////////////////////////////
// 1. 获得一个请求对象,他保存在一个缓冲池中,当命令回复时需要释放
RILRequest rr = RILRequest.obtain(RIL_REQUEST_ENTER_SIM_PIN, result, mySimId);
// 获得一个 RILRequest 对象
RILRequest::obtain()
// 从内部维护的 RIL_request 池 sPool中取下一个 request, 得到一个 RILRequest 实例
// 它里面的请求号和请求结构消息来自传递的实参, 传递的参数:
// 1. RIL 请求号
// 2. 请求结果保存位置
// 3. 返回结果的处理者(Message 中的 Handler),【当有结果时通过此给目标消息队列发消息】
if (sPool != null) {
rr = sPool;
sPool = rr.mNext;
rr.mNext = null;
sPoolSize--;
}
if (rr == null) {
rr = new RILRequest();
}
//////////////////////////////////////////////////////////////////////
// 待写入 socket 中的字符串个数
rr.mParcel.writeInt(2);
// 写入 pin 码字符串
rr.mParcel.writeString(pin);
// 写入其他附属信息
rr.mParcel.writeString(aid);
////////////////////////////////////////////////////////////////////
// 发送请求,由 sender 线程完成向 socket 写入数据
send(rr)
send(RILRequest rr) {
Message msg;
if (mSocket == null) {
rr.onError(RADIO_NOT_AVAILABLE, null);
rr.release();
return;
}
// 获得一个消息对象,
// 消息类型为:EVENT_SEND,
// 消息数据为: 要写入套接字发给底层 rild 的数据
msg = mSender.obtainMessage(EVENT_SEND, rr);
// 获得休眠锁?
acquireWakeLock();
//////////////////////////////////////////////////////////////////////////////
// 将消息添加到目标消息队列中,如果有需要,唤醒目标消息队列所在线程
msg.sendToTarget();
Messgae::sendToTarget()
target.sendMessage(this);
//////////////////////////////
// 调用 Handler 类发送消息
Handler::sendMessage()
sendMessageDelayed(msg, 0);
sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
enqueueMessage(queue, msg, uptimeMillis);
queue.enqueueMessage(msg, uptimeMillis);
/////////////////////////////////////
// 调用 MessageQueue 类发送消息
MessageQueue::enqueueMessage()
// 这里仅仅当消息插入目标消息队列,然后判断是否需要唤醒目标线程
// 如果需要,则调用 nativeWake() 唤醒目标线程,【流程见情景分析】
//【注意】目标消息队列是初始化 Hander 对象时指定的。
}
}
//////////////////////////////////////////////////////////////////
// RIL 初始化时构造的 RILSender 线程接收到此上面要发送的数据的消息
// 调用其 handlerMessage() 函数处理消息
RILSender::handleMessage(Message msg)
{
// 获得发来的消息类型
RILRequest rr = (RILRequest)(msg.obj);
// 根据不同的消息类型处理
switch (msg.what) {
///////////////////////////////
// 通过套接字发送命令给 rild
case EVENT_SEND:
// parcel 中是待写入数据的缓冲区,经过 marshall 赋给 data
data = rr.mParcel.marshall();
// 往 socket 写入数据长度
s.getOutputStream().write(dataLength);
////////////////////////////////////////
// 写入数据给 rild 【底层 rild 怎么接收处理数据,见 rild 分析】
s.getOutputStream().write(data);
}
}
以 RILReceiver 初始化已经有介绍怎么收到数据,这里只介绍消息的处理:
// RILReceiver 接收到消息后,最终调用 processResponse() 函数处理
RIL::processResponse(Parcel p)
{
////////////////////////////////////////
// 1. 处理主动上报的消息
if (type == RESPONSE_UNSOLICITED)
processUnsolicited (p);
// 读取回复请求号,位于 ril_unsol_commands.h 中的
// 获得所有回复请求号, 应该与 ril_unsol_commands.h 文件中保持一致?
// cat libs/telephony/ril_unsol_commands.h \
// | egrep "^ *{RIL_" \
// | sed -re 's/\{([^,]+),[^,]+,([^}]+).+/case \1: \2(rr, p); break;/'
//
response = p.readInt();
//////////////////////////////////////////////////////////////
// 1. 根据不同的回复请求,来调用不同处理函数, 以获得上报的数据
switch(response){
case RIL_UNSOL_ON_USSD: ret = responseStrings(p); break;
// 仅仅是从回复的数据中提取出要返回的字符串
responseStrings(Parcel p) {
int num;
String response[];
response = p.readStringArray();
return response;
}
。。。
}
//////////////////////////////////////////////////////////////
// 2. 对相应结果作进一步处理,如通知感兴趣的注册者
switch(response){
case RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED:
// 从回复的消息中获得状态
RadioState newState = getRadioStateFromInt(p.readInt());
// 更新状态
switchToRadioState(newState);
}
/////////////////////////////////////////
// 2. 处理 RIL 请求的执行回复结果
else if (type == RESPONSE_SOLICITED)
processSolicited (p);
// 读取序列号
serial = p.readInt();
// 读取执行成功与否标志
error = p.readInt();
// 在请求列表查找并摘下 RILRequest 对应项,表示命令发送完成
// 【这表明在发送时分配的 RILRequest 对就项应该会添加此链表中管理,but 没找到...】
rr = findAndRemoveRequestFromList(serial);
for (int i = 0, s = mRequestList.size() ; i < s ; i++)
RILRequest rr = mRequestList.get(i);
if (rr.mSerial == serial)
mRequestList.remove(i);
///////////////////////////////////////////////////////////////////////////////////
// 1. 命令发送成功,且有了返回的结果
if (error == 0 || p.dataAvail() > 0)
// 根据回复的请求号,调用不同的函数处理上传的数据,得到要返回的数据
try{
switch (rr.mRequest){
case RIL_REQUEST_GET_SIM_STATUS: ret = responseIccCardStatus(p); break;
// 表示要返回一个 IccCardStatus 的对象,此函数就是根据
// 上传来的数据构造 IccCardStatus 对象
RIL::responseIccCardStatus(Parcel p)
// 创建一个 IccCardStatus 对象
IccCardStatus cardStatus = new IccCardStatus();
// 处理上传数据,填充 IccCardStatus 对应项
cardStatus.setCardState(p.readInt());
cardStatus.setUniversalPinState(p.readInt());
。。。
// 返回 IccCardStatus 对象
return cardStatus;
// 接收到不能识别的命令,抛出异常
default:
throw new RuntimeException("Unrecognized solicited response: " + rr.mRequest);
}
}catch (Throwable tr) {
// 捕捉异常
// AsyncResult.forMessage() 将三个实参封装到一个 AsyncResult 实例中
// 然后将该实例赋值给 Message.obj 成员
AsyncResult.forMessage(rr.mResult, null, tr);
//////////////////////////////////////////////////////////////////////
// 调用 Message.sendToTarget() 将消息发送到初始化时调用的 Handler
// 队列中,由相应的线程去处理,这里是将结果送给调用者处理
// 这个 Message 对象是在调用 RILRequest::obtain() 设置的,见上调用处
rr.mResult.sendToTarget();
// 释放 RILRequest 回池中
rr.release();
return;
}
////////////////////////////////////////////////////////////////////////
// 2. 有执行结果,但出错了,发消息给调用者处理
if (error != 0)
//////////////////////////////////////////////////////////
// 调用 RILRequest 的错误处理函数,发送 msg 消息给调用者
rr.onError(error, ret);
RILRequest::onError(int error, Object ret)
ex = CommandException.fromRilErrno(error);
if (mResult != null)
AsyncResult.forMessage(mResult, ret, ex);
//////////////////////////////////////////////////////////
// mResult 是在 RILRequest::obtain() 时设置的 Message 对应
// 所以是发给调用者处理
mResult.sendToTarget();
// 释放 RILRequest 回池中
rr.release();
return;
///////////////////////////////////////////////////////////////////////////
// 3. 有执行结果,发消息给调用处理
if (rr.mResult != null) {
AsyncResult.forMessage(rr.mResult, ret, null);
rr.mResult.sendToTarget();
}
// 释放 RILRequest 回池中
rr.release();
}