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管理的几个关键点:
- SN分配:每个新来的PDCP SDU,分配下一个可用的SN。
- SN去重:接收端收到重复的SN,直接丢弃。
- SN排序:接收端按SN顺序递交给上层,乱序的包先缓存。
- SN溢出处理:当SN达到最大值时,HFN加1,SN归零。
我个人习惯:在设计PDCP SN管理模块时,我会把SN分配和HFN管理放在同一个状态机里。这样能保证SN和HFN的原子性更新,避免出现SN已经归零但HFN还没加1的中间状态。另外,我会加一个看门狗定时器,如果长时间没有收到任何PDCP PDU,就主动触发HFN同步流程。
好了,RLC和PDCP层的内容就聊到这儿。这两层虽然不像物理层那样“硬核”,但它们是保证数据可靠传输的关键。下次咱们聊聊调度器和资源分配,那又是另一番天地了。