4、RLC/PDCP层开发:分段与重组、ARQ自动重传请求、头压缩与加密、PDCP序列号管理

好,咱们今天聊聊RLC和PDCP这两层。说实话,很多做通信芯片的朋友,对物理层和MAC层研究得特别深,但一到了RLC和PDCP,就容易犯迷糊。我当年刚入行时也是这样,觉得这两层不就是“打包拆包”嘛,有啥难的?直到第一次调试空口吞吐量,发现死活上不去,才意识到这里面的门道有多深。

4.1 RLC层的分段与重组

RLC层,全称是Radio Link Control。它的核心任务之一,就是分段与重组。你想想看,MAC层从物理层拿到的传输块(TB)大小是动态变化的,而上层PDCP下来的IP包大小却是固定的。这中间怎么匹配?靠的就是RLC的分段。

分段的基本逻辑

  • 当PDCP SDU太大,塞不进一个MAC TB时,RLC就把它切成若干段。
  • 每个段加上RLC头,组成RLC PDU。
  • 接收端根据RLC头里的序列号(SN)和分段偏移(SO),把这些段重新拼回去。

我个人习惯:在设计RLC分段逻辑时,一定要关注“分段粒度”。有些芯片为了省事,把分段粒度设得很大,结果MAC层调度时发现剩余空间不够,又得重新分段,白白浪费了处理时间。我建议分段粒度至少做到1字节,或者跟MAC层的资源块粒度对齐。

代码示例:RLC分段处理伪代码

// RLC分段处理
function rlc_segment(pdcp_sdu, tb_size) {
    if (pdcp_sdu.size <= tb_size) {
        // 不分段,直接发送
        return [pdcp_sdu];
    }
    
    segments = [];
    offset = 0;
    while (offset < pdcp_sdu.size) {
        seg_size = min(tb_size, pdcp_sdu.size - offset);
        segment = {
            data: pdcp_sdu.data[offset : offset + seg_size],
            sn: get_next_sn(),
            so: offset,
            is_last: (offset + seg_size >= pdcp_sdu.size)
        };
        segments.push(segment);
        offset += seg_size;
    }
    return segments;
}

避坑指南:我曾经在项目中遇到过一个问题——RLC重分段。当MAC层第一次调度给了100字节,第二次调度只给了50字节,那之前分好的段就得重新切。这里最容易出的bug是:重分段后,SO字段没更新,导致接收端重组时数据错位。嗯,这个坑我踩过,后来加了个单元测试专门测重分段场景。

4.2 ARQ自动重传请求

ARQ,说白了就是“没收到就重发”。RLC层的ARQ和MAC层的HARQ不一样。HARQ是快速重传,在MAC层就搞定了;ARQ是慢速但可靠的重传,由RLC层负责。

ARQ的工作模式

  • AM模式(确认模式):发送端发完数据后,等着接收端回ACK。如果超时没收到,或者收到了NACK,就重传。
  • UM模式(非确认模式):发了就不管了,适合实时性要求高的业务,比如VoIP。
  • TM模式(透明模式):啥也不加,直接透传。

ARQ状态变量管理

变量 含义 我踩过的坑
VT(A) 发送端确认收到的最大SN 更新时机不对,导致重复重传
VT(S) 发送端下一个待发送的SN 溢出后没处理wrap-around
VR(R) 接收端期望收到的下一个SN 乱序到达时处理复杂
VR(X) 接收端触发重传的SN阈值 阈值设太小,频繁触发重传

注意:ARQ的重传定时器(t-Reordering)设置很关键。设太短,网络稍微抖动一下就开始疯狂重传,浪费带宽;设太长,业务延迟受不了。我一般建议根据实际空口时延的3-5倍来设置,然后通过实际测试微调。

4.3 PDCP层的头压缩与加密

PDCP层,Packet Data Convergence Protocol。它主要干三件事:头压缩、加密、完整性保护。

头压缩(ROHC)

你想想看,一个VoIP包,IP头+UDP头+RTP头,加起来可能有40-60字节,而语音载荷可能只有20-30字节。这效率太低了。ROHC(Robust Header Compression)就是干这个的——把几十字节的头压缩到1-3个字节。

ROHC的工作模式

  • 单向模式(U模式):压缩端只管压缩,解压端自己猜。猜错了就丢包。
  • 双向乐观模式(O模式):压缩端先乐观地压缩,如果解压端反馈错了,再发完整头。
  • 双向可靠模式(R模式):压缩端必须等到解压端确认,才切换到压缩模式。

我建议:在芯片实现中,ROHC的上下文管理是最大的难点。每个流都要维护一个上下文,里面存着上一次的包头信息。如果芯片支持的并发用户数很多,上下文的内存开销会很大。我见过一个方案,用哈希表来管理上下文,查找效率高,但哈希冲突处理不好会导致压缩失败。嗯,这里要权衡。

加密与完整性保护

PDCP层的加密算法,常用的有AES、SNOW 3G、ZUC(祖冲之算法)。加密的输入参数包括:密钥、COUNT(由HFN和PDCP SN组成)、Bearer ID、Direction。

加密处理流程

// PDCP加密伪代码
function pdcp_encrypt(pdcp_pdu, key, count, bearer_id, direction) {
    // 1. 生成密钥流
    keystream = cipher_algorithm(key, count, bearer_id, direction, pdcp_pdu.length);
    
    // 2. 异或加密
    encrypted_data = pdcp_pdu.data XOR keystream;
    
    // 3. 替换PDCP PDU的数据部分
    pdcp_pdu.data = encrypted_data;
    
    return pdcp_pdu;
}

避坑指南:我曾经在项目中遇到过加密性能瓶颈。当时用的是软件加密,每个包都要调用加密算法,结果CPU占用率飙到80%以上。后来改成硬件加速器,把加密卸载到专用硬件模块,CPU占用率直接降到5%。所以,如果你的芯片有加密硬件加速器,一定要用起来。

4.4 PDCP序列号管理

PDCP序列号(SN)是PDCP层的核心。每个PDCP SDU都被分配一个SN,接收端根据SN来排序、去重、递交给上层。

SN的长度

  • 对于AM DRB(确认模式数据无线承载),SN长度可以是12位或18位。
  • 对于UM DRB,SN长度可以是7位或12位。
  • 对于SRB(信令无线承载),SN长度固定为5位。

HFN(超帧号)管理

SN是会循环的。比如12位SN,范围是0-4095。当SN从4095回到0时,HFN就加1。COUNT = HFN * 2^SN_length + SN。

注意:HFN的管理是PDCP层最容易出bug的地方。如果发送端和接收端的HFN不同步,那加密和解密用的COUNT就不一样,解密出来的数据全是乱码。我见过一个案例,因为HFN没处理好,导致VoIP通话全是噪音,查了三天才找到原因。

SN的窗口机制

接收端维护一个重排序窗口。窗口大小通常是SN空间的一半。比如12位SN,窗口大小是2048。只有落在窗口内的SN才会被处理,窗口外的直接丢弃。

SN管理的几个关键点

  1. SN分配:每个新来的PDCP SDU,分配下一个可用的SN。
  2. SN去重:接收端收到重复的SN,直接丢弃。
  3. SN排序:接收端按SN顺序递交给上层,乱序的包先缓存。
  4. SN溢出处理:当SN达到最大值时,HFN加1,SN归零。

我个人习惯:在设计PDCP SN管理模块时,我会把SN分配和HFN管理放在同一个状态机里。这样能保证SN和HFN的原子性更新,避免出现SN已经归零但HFN还没加1的中间状态。另外,我会加一个看门狗定时器,如果长时间没有收到任何PDCP PDU,就主动触发HFN同步流程。

好了,RLC和PDCP层的内容就聊到这儿。这两层虽然不像物理层那样“硬核”,但它们是保证数据可靠传输的关键。下次咱们聊聊调度器和资源分配,那又是另一番天地了。