RIL 异步处理模型(RIL Async Model)

4 阅读11分钟

一、系统架构概览

┌─────────────────────────────────────────────────────────────┐
│ Framework Layer (Java)                                       │
│ ┌───────────────────────────────────────────────────────┐   │
│ │ RIL.java                                              │   │
│ │ ├─ RILRequest Pool (可复用的请求对象)                 │   │
│ │ ├─ mRequestList (SparseArray<serial, RILRequest>)    │   │
│ │ ├─ mWakeLock (防止系统睡眠)                           │   │
│ │ └─ RilHandler (消息处理)                              │   │
│ └───────────────────────────────────────────────────────┘   │
└────────────┬────────────────────────────────────────────────┘
             │ AIDL / HIDL Binder
             ↓
┌─────────────────────────────────────────────────────────────┐
│ HAL Service (C++)                                            │
│ ├─ IRadio / IRadioData / IRadioVoice ...                    │
│ └─ ServiceProxy (将 Java 调用转换为 HAL 调用)              │
└────────────┬────────────────────────────────────────────────┘
             │
             ↓
┌─────────────────────────────────────────────────────────────┐
│ RIL Daemon (C++)                                             │
│ ├─ RIL Library (libril.so)                                   │
│ │  ├─ RequestInfo 链表 (s_pendingRequests)                 │
│ │  ├─ Token 匹配机制                                        │
│ │  ├─ RIL_onRequestComplete() 回调                         │
│ │  └─ Wake Lock 管理                                        │
│ │                                                            │
│ └─ Vendor RIL (libril-vendor.so)                           │
│    ├─ onRequest() 处理请求                                 │
│    ├─ AT Command 发送                                       │
│    └─ RIL_onRequestComplete() 返回响应                     │
└────────────┬────────────────────────────────────────────────┘
             │
             ↓
┌─────────────────────────────────────────────────────────────┐
│ Modem (Baseband)                                             │
│ └─ AT Interface / Protocol Stack                           │
└─────────────────────────────────────────────────────────────┘

二、RequestInfo 链表管理 (C++ RIL)

2.1 RequestInfo 数据结构

typedef struct RequestInfo {
    int32_t token;           // 唯一标识符(由 serial 转换)
    CommandInfo *pCI;        // 指向命令信息(包含对应的 requestNumber)
    struct RequestInfo *p_next;  // 链表指针(头插法)
    char cancelled;          // 是否已取消
    char local;              // 是否为本地命令(响应不上报)
    RIL_SOCKET_ID socket_id; // 所属的 SIM 卡槽(多卡时区分)
    int wasAckSent;         // Ack 是否已发送(用于异步长耗时请求)
} RequestInfo;

2.2 请求入队过程

RequestInfo *
addRequestToList(int serial, int slotId, int request) {
    RequestInfo *pRI;
    int ret;
    RIL_SOCKET_ID socket_id = (RIL_SOCKET_ID) slotId;
    
    // 1. 根据卡槽选择正确的互斥锁和链表头
    pthread_mutex_t* pendingRequestsMutexHook = &s_pendingRequestsMutex;
    RequestInfo** pendingRequestsHook = &s_pendingRequests;
    
#if (SIM_COUNT >= 2)
    if (socket_id == RIL_SOCKET_2) {
        pendingRequestsMutexHook = &s_pendingRequestsMutex_socket2;
        pendingRequestsHook = &s_pendingRequests_socket2;
    }
    // ... 处理其他卡槽 ...
#endif
    
    // 2. 分配 RequestInfo 结构
    pRI = (RequestInfo *)calloc(1, sizeof(RequestInfo));
    if (pRI == NULL) {
        RLOGE("Memory allocation failed for request %s", requestToString(request));
        return NULL;
    }
    
    // 3. 初始化字段
    pRI->token = serial;                    // token 直接使用 serial
    pRI->pCI = &(s_commands[request]);      // 关联命令信息数组
    pRI->socket_id = socket_id;
    
    // 4. 原子地添加到链表头(LIFO)
    ret = pthread_mutex_lock(pendingRequestsMutexHook);
    assert (ret == 0);
    
    pRI->p_next = *pendingRequestsHook;     // 指向原来的头
    *pendingRequestsHook = pRI;             // 新插入的成为头
    
    ret = pthread_mutex_unlock(pendingRequestsMutexHook);
    assert (ret == 0);
    
    return pRI;
}

关键特点

  • LIFO 队列:新请求插入链表头,最后进来的最先处理
  • 多卡支持:每个 SIM 卡有独立的链表和互斥锁(避免锁竞争)
  • Token 映射token == serial,便于响应时匹配

三、Token 机制与异步响应

3.1 Java 层的 RILRequest

private static RILRequest obtain(int request, Message result) {
    RILRequest rr = null;

    synchronized (sPoolSync) {
        // 对象复用池(避免频繁分配和垃圾回收)
        if (sPool != null) {
            rr = sPool;
            sPool = rr.mNext;
            rr.mNext = null;
            sPoolSize--;
        }
    }

    if (rr == null) {
        rr = new RILRequest();
    }

    // 原子性递增 serial(每个请求的唯一标识)
    // 当达到 Integer.MAX_VALUE 时,循环回 0
    rr.mSerial = sNextSerial.getAndUpdate(n -> ((n + 1) % Integer.MAX_VALUE));

    rr.mRequest = request;
    rr.mResult = result;  // 响应时将通过 Message 回调

    rr.mWakeLockType = RIL.INVALID_WAKELOCK;
    rr.mWorkSource = null;
    rr.mStartTimeMs = SystemClock.elapsedRealtime();
    if (result != null && result.getTarget() == null) {
        throw new NullPointerException("Message target must not be null");
    }

    return rr;
}

3.2 响应匹配流程

┌─ 1. Framework 发送请求 (serial=0x0042)
│      ├─ RILRequest rr = RILRequest.obtain(RIL_REQUEST_DIAL, result)
│      └─ mRequestList.append(0x0042, rr)  // 保存映射关系
│
├─ 2. RIL Daemon 接收请求
│      ├─ addRequestToList(0x0042, slotId, RIL_REQUEST_DIAL)
│      │  └─ 创建 RequestInfo,token = 0x0042
│      │  └─ pRI->token 被强制转换为 RIL_Token
│      └─ 调用 vendor onRequest(RIL_REQUEST_DIAL, data, datalen, (RIL_Token)pRI)
│
├─ 3. Vendor RIL 处理请求
│      ├─ 发送 AT Command 到 Modem
│      └─ 等待 Modem 响应(可能是阻塞的)
│
├─ 4. Modem 返回响应
│      └─ AT 响应从 Modem 回来
│
└─ 5. Vendor RIL 调用回调
       ├─ RIL_onRequestComplete(token, RIL_E_SUCCESS, response, responselen)
       │  (token = 原来的 RequestInfo* 指针)
       └─ RIL Library 通过 token 找回 RequestInfo
          ├─ pRI = (RequestInfo *)t
          ├─ 验证 pRI 是否还在待决链表中
          ├─ 调用 responseFunction() 编码响应
          └─ 通过 Binder 回调到 Framework 的 RadioResponse

3.3 Token 的妙用

extern "C" void
RIL_onRequestComplete(RIL_Token t, RIL_Errno e, void *response, size_t responselen) {
    RequestInfo *pRI;
    int ret;
    RIL_SOCKET_ID socket_id = RIL_SOCKET_1;

    // 关键:Token 就是 RequestInfo* 指针
    pRI = (RequestInfo *)t;

    // 验证 token 有效性并从链表中删除
    if (!checkAndDequeueRequestInfoIfAck(pRI, false)) {
        RLOGE ("RIL_onRequestComplete: invalid RIL_Token");
        return;  // Token 已失效(可能是请求被取消或超时)
    }

    socket_id = pRI->socket_id;  // 获取原始卡槽 ID

    if (pRI->local > 0) {
        // 本地命令(响应不回送到上层)
        RLOGD("C[locl]< %s", requestToString(pRI->pCI->requestNumber));
        free(pRI);
        return;
    }

    appendPrintBuf("[%04d]< %s",
        pRI->token, requestToString(pRI->pCI->requestNumber));

    if (pRI->cancelled == 0) {
        // 请求未被取消,准备响应
        int responseType;
        if (s_callbacks.version >= 13 && pRI->wasAckSent == 1) {
            // 已发送过 Ack 的异步请求,需要获取 Wakelock
            responseType = RESPONSE_SOLICITED_ACK_EXP;
            grabPartialWakeLock();
        } else {
            responseType = RESPONSE_SOLICITED;
        }

        // 调用 responseFunction 编码响应(由命令信息指定)
        ret = pRI->pCI->responseFunction((int) socket_id,
                responseType, pRI->token, e, response, responselen);
    }
    free(pRI);  // 清理 RequestInfo
}

关键点

  • Token 本质上是 RequestInfo 的内存地址
  • 这样做的好处:
    • 零开销查找:O(1) 时间直接定位 RequestInfo
    • 内存安全:包含所有上下文信息
    • 防重复:通过 checkAndDequeueRequestInfoIfAck 验证有效性

四、Java 层的异步处理

private void addRequest(RILRequest rr) {
    // 1. 获取 Wake Lock 防止系统睡眠
    acquireWakeLock(rr, FOR_WAKELOCK);
    
    // 2. 追踪请求(用于性能分析)
    Trace.asyncTraceForTrackBegin(
            Trace.TRACE_TAG_NETWORK, "RIL", rr.mSerial + "> "
            + RILUtils.requestToString(rr.mRequest), rr.mSerial);
    
    // 3. 将请求存储在 SparseArray(按 serial 索引)
    synchronized (mRequestList) {
        rr.mStartTimeMs = SystemClock.elapsedRealtime();
        mRequestList.append(rr.mSerial, rr);  // Key = serial, Value = RILRequest
    }
}

private RILRequest obtainRequest(int request, Message result, WorkSource workSource) {
    // 创建并初始化 RILRequest(自动分配 serial)
    RILRequest rr = RILRequest.obtain(request, result, workSource);
    addRequest(rr);
    return rr;
}

Java 层特点

  • SparseArray:按 serial 快速查找
  • Message 回调:响应通过异步 Message 发送到调用者
  • WorkSource 跟踪:记录谁发起了这个请求(用于电量消耗分析)

五、超时处理

5.1 Wake Lock 超时

private void acquireWakeLock(RILRequest rr, int wakeLockType) {
    synchronized (rr) {
        if (rr.mWakeLockType != INVALID_WAKELOCK) {
            riljLog("Failed to acquire wakelock for " + rr.serialString());
            return;
        }

        switch (wakeLockType) {
            case FOR_WAKELOCK:
                synchronized (mWakeLock) {
                    mWakeLock.acquire();  // 获取 Wake Lock
                    mWakeLockCount++;
                    mWlSequenceNum++;

                    // 设置 Wake Lock 超时(默认 60 秒)
                    Message msg = mRilHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT);
                    msg.arg1 = mWlSequenceNum;
                    mRilHandler.sendMessageDelayed(msg, mWakeLockTimeout);  // 60000ms
                }
                break;
            ...
        }
        rr.mWakeLockType = wakeLockType;
    }
}

5.2 超时事件处理

case EVENT_WAKE_LOCK_TIMEOUT:
    if (msg.arg1 == mWlSequenceNum && clearWakeLock(FOR_WAKELOCK)) {
        if (mRadioBugDetector != null) {
            mRadioBugDetector.processWakelockTimeout();  // 记录异常
        }
        if (RILJ_LOGD) {
            // 打印待决请求列表,用于诊断
            int count = mRequestList.size();
            riljLog("WAKE_LOCK_TIMEOUT mRequestList=" + count);
            for (int i = 0; i < count; i++) {
                rr = mRequestList.valueAt(i);
                riljLog(i + ": [" + rr.mSerial + "] "
                        + RILUtils.requestToString(rr.mRequest));
            }
        }
    }
    break;

case EVENT_BLOCKING_RESPONSE_TIMEOUT:
    int serial = (int) msg.obj;
    rr = findAndRemoveRequestFromList(serial);
    // 如果请求已处理,则不做任何操作
    if (rr == null) {
        break;
    }

    // 为调用者生成响应(解除阻塞)
    if (rr.mResult != null) {
        Object timeoutResponse = getResponseForTimedOutRILRequest(rr);
        AsyncResult.forMessage(rr.mResult, timeoutResponse, null);
        rr.mResult.sendToTarget();
        mMetrics.writeOnRilTimeoutResponse(mPhoneId, rr.mSerial, rr.mRequest);
    }

    decrementWakeLock(rr);
    rr.release();  // 回收 RILRequest 对象
    break;

超时机制

  • Wake Lock Timeout (60s):检测 RIL Daemon 是否卡死

    • 如果 Modem 响应但 RIL Daemon 未调用 RIL_onRequestComplete() 就会超时
    • 触发 RadioBugDetector 记录异常
  • Blocking Response Timeout (2s):同步调用的超时

    • 某些 API 需要同步等待响应
    • 超时后返回默认响应,解除调用者阻塞

六、Modem Crash 检测与恢复

6.1 服务死亡检测

final class RadioProxyDeathRecipient implements HwBinder.DeathRecipient {
    @Override
    public void serviceDied(long cookie) {
        // 服务突然崩溃或断连
        riljLog("serviceDied");
        if (mFeatureFlags.combineRilDeathHandle()) {
            // 优先级最高:发到消息队列前面
            mRilHandler.sendMessageAtFrontOfQueue(mRilHandler.obtainMessage(
                    EVENT_RADIO_PROXY_DEAD,
                    HAL_SERVICE_RADIO, 0 /* ignored arg2 */, cookie));
        } else {
            mRilHandler.sendMessage(mRilHandler.obtainMessage(EVENT_RADIO_PROXY_DEAD,
                    HAL_SERVICE_RADIO, 0 /* ignored arg2 */, cookie));
        }
    }
}

6.2 恢复流程

case EVENT_RADIO_PROXY_DEAD:
    int service = msg.arg1;
    riljLog("handleMessage: EVENT_RADIO_PROXY_DEAD cookie = " + msg.obj
            + ", service = " + serviceToString(service));
    if ((long) msg.obj == mServiceCookies.get(service).get()) {
        mIsRadioProxyInitialized = false;
        resetProxyAndRequestList(service);  // ← 关键恢复步骤
    }
    break;

resetProxyAndRequestList() 的作用

  1. ✅ 断开与 HAL Service 的连接
  2. 清空待决请求列表(返回错误给所有待决请求)
  3. 重置 Wake Lock(防止卡住)
  4. 重新连接 HAL Service(自动重试)

七、队列执行优先级

7.1 队列配置

┌─────────────────────────────────────────────────┐
│ RIL Daemon Event Loop                           │
├─────────────────────────────────────────────────┤
│                                                  │
│  1. Pending List (最高优先级)                    │
│     └─ 立即执行的事件(如定时器触发)            │
│                                                  │
│  2. Command Socket (正常优先级)                  │
│     └─ Framework 发来的新请求                   │
│     └─ 处理顺序:LIFO(链表头优先)            │
│                                                  │
│  3. Timer Queue (定时优先级)                    │
│     └─ 已到期的定时器                          │
│                                                  │
│  4. Reader Thread                               │
│     └─ 从 Modem 读取响应                       │
│                                                  │
└─────────────────────────────────────────────────┘

7.2 请求处理顺序

新请求 → 链表头插入 → LIFO 顺序处理

Example:
  Request 1 (serial=0x0001) 发送 → 加入链表
  Request 2 (serial=0x0002) 发送 → 加入链表头
  Request 3 (serial=0x0003) 发送 → 加入链表头
  
  待决链表:[Request3][Request2][Request1]
  
  Modem 响应顺序(可能不同):
  - Response2 回来 → 立即匹配 Request2
  - Response1 回来 → 立即匹配 Request1
  - Response3 回来 → 立即匹配 Request3

重点

  • LIFO 是 Modem 响应处理顺序,不是请求发送顺序
  • Modem 可能乱序响应,Token 确保正确匹配
  • Blocking 请求有单独的超时机制

八、深度问题解答

Q1:如何处理 Modem 响应超时?

┌─ Wake Lock Timeout (60s) ← 第一层防御
│  └─ Modem 可能卡死或网络中断
│     └─ RIL_onRequestComplete() 永不回调
│
├─ 触发条件:
│  └─ 60s 内 Modem 未响应且无新响应
│
└─ 处理:
   ├─ 清除 Wake Lock(唤醒系统)
   ├─ 记录 RadioBug(AnomalyReport)
   ├─ 待决请求仍在列表中(等待 Crash 检测)
   └─ [可选] Modem Crash 检测触发恢复

Q2:Modem 发生 Crash 时如何恢复?

Crash 检测链路:
1. Modem 崩溃 → HAL Service 死亡
   ├─ Binder Died Callback 触发
   └─ EVENT_RADIO_PROXY_DEAD 发送到 RilHandler
   
2. EVENT_RADIO_PROXY_DEAD 处理:
   ├─ 设置 mIsRadioProxyInitialized = false
   ├─ 调用 resetProxyAndRequestList(service)
   │  ├─ 断开 Service 连接
   │  ├─ 遍历 mRequestList,为所有待决请求返回错误:
   │  │  for (int i = mRequestList.size() - 1; i >= 0; i--) {
   │  │      RILRequest rr = mRequestList.valueAt(i);
   │  │      AsyncResult.forMessage(rr.mResult, null, 
   │  │          CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
   │  │      rr.mResult.sendToTarget();
   │  │  }
   │  └─ 清空 mRequestList
   │
   └─ 自动重新连接到新的 Modem 实例
      ├─ getRadioServiceProxy(service)
      ├─ setResponseFunctions()
      └─ 恢复就绪

Q3:队列中有多个请求时,哪个优先执行?

发送顺序

Request A → Request B → Request C
(按时间顺序发送)

链表结构 (LIFO):

s_pendingRequests → [C][B][A] → NULL
                   (最新)        (最旧)

Modem 响应处理

  • 响应不是按链表顺序处理的
  • 而是 Modem 何时返回响应就何时处理
  • Token 机制确保响应与请求的正确配对

示例

Timeline:

0s:   发送 Request A (RIL_REQUEST_DIAL)
      链表:[A]

1s:   发送 Request B (RIL_REQUEST_SIGNAL_STRENGTH) 
      链表:[B] → [A]

2s:   收到 Response B
      → 通过 token 找到 Request B
      → 立即返回响应给 Framework
      
3s:   收到 Response A
      → 通过 token 找到 Request A
      → 返回响应给 Framework

结论:Response B 先收到并处理,尽管 Request A 先发送

优先级规则

  • ❌ 不是 FIFO(先入先出)
  • ❌ 不是 LIFO(后入先出)
  • 响应驱动(Modem 何时响应就何时处理)
  • Token 保证匹配正确

九、关键概念对比

概念C++ RIL DaemonJava Framework
队列类型RequestInfo* 链表 (LIFO)SparseArray<serial, RILRequest>
Token/KeyRequestInfo* 指针值serial (int)
查找时间O(1) - 直接指针解引用O(log n) - 二分查找
多卡支持每卡独立链表 + 互斥锁单一列表,serial 编码卡槽
取消机制pRI->cancelled 标志Remove from map
内存管理malloc/free对象复用池

十、性能特征与优化建议

10.1 性能特征

指标数值
平均延迟10-100ms (RIL Daemon 处理) + 网络延迟
Wake Lock 超时60s (可配置)
Blocking 超时2s (同步调用)
Request Pool 大小最多 4 个可复用对象
并发请求数无限制(但受 Modem 能力限制)

10.2 优化建议

1. 避免频繁的小请求
   ❌ for (int i = 0; i < 100; i++) {
        ril.requestSignalStrength();  // 100 个单独请求
      }
   ✅ 批量请求或合并操作

2. 合理设置超时
   ❌ mWakeLockTimeout = 1000  // 太短,易误判
   ✅ mWakeLockTimeout = 60000  // 60s,适合大多数请求

3. 避免 Blocking 调用
   ❌ int result = synchronousRilCall();  // 阻塞线程
   ✅ rilCall(message);  // 异步,通过 Message 回调

4. 监控 Wake Lock 超时
   ✅ 启用 RadioBugDetector,及时发现 RIL 卡死

现在让我创建一个完整的流程图总结:

完整的 RIL 异步请求生命周期
═════════════════════════════════════════════════════════════

[Framework - Java 层]
  │
  ├─ 1. 创建请求
  │  ├─ RILRequest rr = RILRequest.obtain(
  │  │      RIL_REQUEST_DIAL, result, workSource)
  │  │  └─ serial = 0x0042 (原子递增)
  │  │
  │  ├─ 2. 添加到待决列表
  │  │  └─ mRequestList.append(0x0042, rr)
  │  │
  │  ├─ 3. 获取 Wake Lock
  │  │  ├─ mWakeLock.acquire()
  │  │  └─ 设置 60s 超时
  │  │
  │  └─ 4. 调用 HAL Service
  │     └─ radioProxy.dial(...) [AIDL/HIDL 调用]
  │
  ↓ (Binder IPC)
  │
[RIL Daemon - C++ 层]
  │
  ├─ 5. 接收请求
  │  ├─ addRequestToList(0x0042, slotId, RIL_REQUEST_DIAL)
  │  │  ├─ 分配 RequestInfo*
  │  │  ├─ token = (RIL_Token)pRI
  │  │  └─ 插入 s_pendingRequests 链表头
  │  │
  │  └─ 6. 调用 Vendor onRequest()
  │     └─ onRequest(RIL_REQUEST_DIAL, data, len, (RIL_Token)pRI)
  │
  ↓ (AT Command / 协议)
  │
[Modem - 基带]
  │
  ├─ 7. 处理请求
  │  └─ 执行拨号逻辑,与网络交互
  │
  ├─ 8. 返回结果
  │  └─ OK / ERROR + response data
  │
  ↓ (AT Response)
  │
[RIL Daemon - C++ 层 (Reader Thread)]
  │
  ├─ 9. 解析响应
  │  └─ 调用 responseFunction()
  │
  └─ 10. 调用回调
     └─ RIL_onRequestComplete(token, e, response, len)
        ├─ pRI = (RequestInfo*)token
        ├─ checkAndDequeueRequestInfoIfAck(pRI)
        │  └─ 从 s_pendingRequests 删除
        └─ responseFunction() 编码响应
  
  ↓ (Binder IPC - 回调)
  │
[Framework - Java 层]
  │
  ├─ 11. 接收响应回调
  │  ├─ RadioResponse.dialResponse(responseInfo, result)
  │  │
  │  └─ 12. 查找原始请求
  │     └─ rr = mRequestList.get(serial)
  │
  ├─ 13. 清理资源
  │  ├─ mRequestList.remove(serial)
  │  ├─ decrementWakeLock(rr)
  │  └─ rr.release() [回收到对象池]
  │
  └─ 14. 回调给上层
     └─ AsyncResult.forMessage(rr.mResult, result, e)
     └─ rr.mResult.sendToTarget()

[上层 (Phone/Voice Services)]
  │
  └─ 15. 处理响应
     └─ onDialComplete(result, error)

好的,现在我已经完成了 RIL 异步处理模型的深度分析。让我总结并提供一个快速参考表:


📋 快速参考

核心数据流

阶段层级关键数据结构时间复杂度
请求创建JavaRILRequest + SerialO(1)
请求入队C++RequestInfo* + LIFOO(1)
响应查找C++Token = RequestInfo*O(1)
响应回调JavaSparseArrayO(log n)

超时和约束

机制超时时间触发条件恢复方式
Wake Lock60sModem 无响应清除锁,记录异常
Blocking2s同步调用未完成返回默认值
Service Death即时Binder 连接断开清空请求,重连

并发性与安全性

  • 线程安全:所有队列操作都在互斥锁保护下
  • 多卡隔离:每个卡槽有独立的链表和锁
  • 响应唯一性:Token 机制保证一对一匹配
  • 内存效率:对象复用池减少 GC 压力

好的,我已经完成了对 RIL 异步处理模型的全面分析。这个文档涵盖了:

  1. 命令队列 - RequestInfo 链表管理
  2. Token 机制 - 请求-响应的唯一配对
  3. 异步响应 - RIL_onRequestComplete() 回调
  4. Wake Lock - 防止系统睡眠
  5. 超时处理 - 三层超时检测
  6. Modem Crash 恢复 - 自动检测与恢复
  7. 队列优先级 - LIFO 处理顺序
  8. 性能优化 - 实践建议

这些内容涵盖了所有你提出的深入问题! 🎉