简介:面向LTE无线接入网研发、协议栈实现与测试岗位工程师的学习资料,聚焦TD-LTE数字蜂窝移动通信网Uu接口中的RLC协议。内容依据中国通信标准化协会技术报告,系统规定E-UTRA RLC子层结构、RLC实体、上下层业务关系、功能划分,以及协议数据单元格式、参数与异常数据处理方式,并覆盖确认模式等关键机制,适合需要理解RLC架构、数据传输与错误检测纠正流程的读者查阅对照。资源包共1个doc文件,约1.74MB,为中文技术报告正文,含目次、前言、范围、规范性引用文件、术语定义与缩略语等章节,便于按条目检索协议要求。目前已有493人学习,可作为协议栈开发、接口联调与测试用例设计时的案头参考,帮助快速定位RLC功能定义与实现测试要点。
1. 一份把 LTE RLC 讲透的中文协议:从状态变量到 ARQ 的落地读法
调 LTE 空口的时候,最让人抓狂的不是物理层解不出星座图,而是 MAC 明明报了传输机会,PDU 也发出去了,可上层业务就是卡着不动。抓 log 一看,RLC 层在重排序窗口边缘反复横跳,t_Reordering 超时、STATUS PDU 迟迟不发、VT(A) 和 VT(S) 之间的窗口卡死——这类问题翻 3GPP 英文原文能看个大概,但状态变量的更新顺序、字节段(byte segment)的重复判定、轮询触发的那几个计数器,英文读三遍还是容易漏。这份《TD-LTE 数字蜂窝移动通信网 Uu 接口技术要求 第 7 部分:RLC 协议》就是冲着这个痛点来的:它是中国通信标准化协会组织制定的通信标准类技术报告,把 E-UTRA RLC 子层的结构、三种传输模式、状态变量、ARQ 与状态报告流程全部落成中文,术语和缩略语对照齐全。适合正在做 LTE 协议栈开发、空口信令分析、或者准备协议一致性测试的从业者,拿它当案头手册比啃英文规范省事得多。
2. RLC 三种模式怎么选:TM/UM/AM 的边界与逻辑信道映射
拿到这份协议,第一章别急着翻过程定义,先把 4.2 节的 RLC 结构吃透。RLC 实体挂在 MAC 之上、PDCP/RRC 之下,一个实体只配一种模式,模式决定了它走哪条逻辑信道、带不带头、有没有重传。选错模式,后面所有状态变量都是白搭。
2.1 TM/UM/AM 的能力对照与适用信道
协议 4.2.1 把三种模式讲得很清楚,我按落地视角整理成一张表,方便你在设计实体时直接对号入座:
| 模式 | 典型逻辑信道 | 分段/级联 | RLC 头 | 重排序 | ARQ 重传 | 重复检测 |
|---|---|---|---|---|---|---|
| TM | BCCH、PCCH、DL/UL CCCH | 不做 | 无 | 无 | 无 | 无 |
| UM | DL/UL DTCH | 做 | 有 | 有 | 无 | 有 |
| AM | DL/UL DCCH、DL/UL DTCH | 做 | 有 | 有 | 有 | 有 |
这张表的读法是:TM 就是一根透明管子,SDU 进来原样出去,连头都不加,所以它只能承载广播、寻呼、公共控制这类不需要可靠性的消息;UM 加了 SN 和分段,能重排序、能丢重复,但不回确认,适合对时延敏感、能容忍少量丢包的业务;AM 才是完整形态,发送部分维护发送窗口做 ARQ,接收部分维护接收窗口发 STATUS,DCCH 上的信令和 DTCH 上的可靠数据都走它。
提示:协议明确写了 TM 实体“RLC SDUs 不进行分段,也不进行级联”,且“TMD PDUs 中不包含任何 RLC 头”。这意味着 TM 的 SDU 大小必须和 MAC 给的传输机会严丝合缝,实际实现里通常靠上层保证,别指望 RLC 帮你切。
2.2 从逻辑信道反推实体配置
实际配置时,我一般是从逻辑信道倒推的。比如你要配一个 SRB(信令无线承载),走 DCCH,那必然是 AM;要配一个语音承载走 DTCH,多数是 UM;系统消息走 BCCH,只能 TM。协议 4.2.1.1.1 到 4.2.1.3.1 分别列了每种实体能挂的信道,这里有个容易翻车的点:AM 实体能挂 DCCH 和 DTCH,但 UM 只能挂 DTCH,你要是把一条 DCCH 配成 UM,协议层面就不成立。
配置实体时还要注意 AM 的特殊结构——它由发送部分和接收部分组成,协议 4.2.1.3.2 和 4.2.1.3.3 是分开描述的。发送部分管分段、级联、重传、轮询;接收部分管重排序、重复检测、STATUS 上报。写代码时这两块的状态变量是独立的,别混在一个结构体里,否则调试时你会分不清是发送窗口卡了还是接收窗口卡了。
2.3 业务接口:SAP 与传输机会
协议 4.3 节定义了 RLC 和上下层的接口。往上,RLC 通过单一 SAP 从 RRC(CCCH 时)或 PDCP(其它情况)收 SDU;往下,通过单一逻辑信道和 MAC 交互。这里最关键的一句是 4.2.1 里的:“仅当下层(即 MAC 层)已通知了一个传输机会时,才形成 RLC PDU”。这句话是整个 RLC 调度的根:RLC 不是想发就发,而是等 MAC 说“这次给你 N 字节”,RLC 才按这个尺寸去分段、级联、组 PDU。
4.3.2 进一步明确,RLC 期望从 MAC 得到两样东西:数据传输能力,以及“传输机会及该机会中将被发送 RLC PDU(s) 尺寸总大小的通知”。落地时这就是一个回调接口,MAC 每次调度把 grant size 传上来,RLC 据此决定这次组多大的 PDU。很多新手在这里踩坑:以为 RLC 自己缓存了数据就能主动推给 MAC,结果实现出来 MAC 没 grant 也在组包,空口上根本发不出去,log 里全是“组了但没发”的假象。
3. 状态变量与窗口:UM/AM 接收侧的重排序怎么落地
RLC 的玄学全在状态变量上。协议第 5 章把 UM 和 AM 的接收操作拆成了“收到 PDU 时”“PDU 入缓存时”“t_Reordering 超时时”三个分支,每个分支里一串 VR 变量的更新顺序不能乱。这一章我按“先建窗口、再走分支”的顺序拆,给你一套能直接对着写代码的读法。
3.1 UM 接收窗口:VR(UH)、VR(UR)、VR(UX) 三件套
UM 接收侧维护一个重排序窗口,协议 5.1.2.2.1 给的判定是:若(VR(UH) – UM_Window_Size) <= SN < VR(UH),则 SN 落在窗口内。三个变量的分工是:
- VR(UH):接收到的最高 SN 加一,窗口的上边界;
- VR(UR):还没向上层递交的第一个 SN,窗口的下边界参考;
- VR(UX):t_Reordering 的触发参考点,记录启动定时器时的 VR(UH)。
收到一个 SN=x 的 UMD PDU 时,先做丢弃判定(5.1.2.2.2):如果VR(UR) < x < VR(UH)且之前已收过,或者(VR(UH) – UM_Window_Size) <= x < VR(UR),直接丢。前者是窗口内的重复包,后者是已经递交过的老包。否则放进接收缓存。
入缓存后的操作(5.1.2.2.3)是重头戏,我把它写成伪代码逻辑,方便对照:
# UM 接收侧:SN=x 的 UMD PDU 放入接收缓存后的状态更新 def on_umd_buffered(x, state): if x >= state.VR_UH: # x 落在重排序窗口之外(上方) state.VR_UH = x + 1 deliver_sdus_below(state.VR_UH) # 重组并升序递交,去 RLC 头 if state.VR_UR < state.VR_UH - UM_WINDOW_SIZE: state.VR_UR = state.VR_UH - UM_WINDOW_SIZE # 下边界滑出窗口则拉回 if state.VR_UR in state.rx_buffer: # 缓存里有下边界那个包 state.VR_UR = first_missing_sn_above(state.VR_UR) deliver_sdus_below(state.VR_UR) if state.t_reordering_running: if state.VR_UX <= state.VR_UR or \ (state.VR_UX < state.VR_UH - UM_WINDOW_SIZE and state.VR_UX != state.VR_UH): stop_and_reset(state.t_reordering) if not state.t_reordering_running and state.VR_UH > state.VR_UR: start(state.t_reordering) state.VR_UX = state.VR_UH这段逻辑的关键在最后几行:t_Reordering 只在“有空洞且定时器没跑”时启动,启动时把 VR(UX) 钉在当前 VR(UH)。定时器超时(5.1.2.2.4)时,把 VR(UR) 推到SN >= VR(UX)的第一个未收包,把之前的都递交,然后如果还有空洞就再启动一次。参数上 UM_Window_Size 由 RRC 配置,常见取值是 512 或 1024,具体看承载类型;t_Reordering 也是 RRC 配的,单位毫秒,配小了会频繁超时递交、增加乱序,配大了空洞等待变长、时延上去。
3.2 AM 接收窗口:字节段级别的重复判定
AM 比 UM 多一层复杂度:它的重传可能把原 PDU 重分段,所以接收侧判重不能只看 SN,要看“字节段序号 y 到 z”。协议 5.1.3.2.2 写得很明确:收到包含 SN=x、字节段序号 y 到 z 的 AMD PDU 时,若 x 落在接收窗口外,或该字节段之前已收过,丢弃;否则入缓存,且若该 AMD PDU 的部分字节段之前已收,丢掉重复的那部分。
接收窗口的边界是VR(R) <= SN < VR(MR),其中 VR(MR) = VR(R) + AM_Window_Size。入缓存后的更新(5.1.3.2.3)比 UM 多两个变量:VR(MS) 和 VR(H)。VR(MS) 是“还没收全的第一个 SN”,用于 STATUS 上报;VR(H) 是收到的最高 SN 加一。核心分支是:若 x = VR(R) 且该 PDU 所有字节段收全,则推进 VR(R) 到下一个没收全的 SN,同步更新 VR(MR),然后重组递交;若 x >= VR(H),更新 VR(H) 为 x+1。
# AM 接收侧:SN=x 的 AMD PDU(字节段 y..z)入缓存后的关键更新 def on_amd_buffered(x, y, z, state): if all_segments_received(state, state.VR_MS): state.VR_MS = first_incomplete_sn_above(state.VR_MS) if x == state.VR_R and all_segments_received(state, x): state.VR_R = first_incomplete_sn_above(state.VR_R) state.VR_MR = state.VR_R + AM_WINDOW_SIZE reassemble_and_deliver(state, upto=state.VR_R) if x >= state.VR_H: state.VR_H = x + 1 if state.t_reordering_running: if state.VR_X == state.VR_R or \ (state.VR_X < state.VR_R or state.VR_X >= state.VR_MR) and state.VR_X != state.VR_MR: stop_and_reset(state.t_reordering) if not state.t_reordering_running and state.VR_H > state.VR_R: start(state.t_reordering) state.VR_X = state.VR_H这里最容易翻车的是“字节段重复判定”和“VR(MS) 更新时机”。协议注 2 特别强调:t_Reordering 超时触发 VR(MS) 更新,但 STATUS 报告要在 VR(MS) 更新之后才触发。实现时如果先发 STATUS 再更新 VR(MS),对端收到的 NACK 范围就是旧的,重传回来的包还是对不上,表现为“一直重传、一直收不全”的死循环。
3.3 重建时的收尾:能重组就递交,不能就丢
协议在 UM 和 AM 的接收部分都写了 RLC 重建时的行为:若可能,从乱序接收的 PDU 重组 SDU 并递交;丢弃任何不能重组进 SDU 的剩余 PDU;初始化相关状态变量并停止相关定时器。落地时这一步常被忽略,重建(比如切换、重配)时缓存里还压着一堆半截包,不清理就会带到新配置里,SN 空间一变全乱。我的习惯是重建入口先 stop 所有定时器,再按“能重组就递交、剩下的丢”走一遍,最后把 VR/状态变量按新配置重置。
4. AM 发送侧与 ARQ:轮询、STATUS 与重传阈值怎么配
AM 的可靠性全靠发送侧和接收侧的一问一答:发送侧发数据、按条件轮询,接收侧收数据、发 STATUS,发送侧据 STATUS 推进窗口或重传。协议 5.1.3.1 和 5.2 把这条链路拆得很细,参数配错就是“窗口卡死”或“无脑重传”。
4.1 发送窗口与 VT 变量:什么时候不能发
发送侧维护发送窗口VT(A) <= SN < VT(MS),VT(A) 是最早未被肯定应答的 SN,VT(MS) = VT(A) + AM_Window_Size,VT(S) 是下一个新 PDU 要用的 SN。协议 5.1.3.1.1 明确:不应向下层分发任何 SN 落在发送窗口之外的 RLC 数据 PDU。这就是“窗口延迟(window stalling)”的根源——对端迟迟不回 STATUS,VT(A) 推不动,VT(S) 顶到 VT(MS),新数据就发不出去了。
发送优先级协议也定了:控制 PDU 优先于数据 PDU,重传数据 PDU 优先于新数据 PDU。实现时调度器要按这个顺序取包,否则 STATUS 被新数据挤在后面,对端等不到确认,窗口越卡越死。
4.2 轮询触发:pollPDU、pollByte 与 t_PollRetransmit
轮询是发送侧主动要 STATUS 的手段。协议 5.2.2.1 给了几个触发条件,我按实现顺序列一下:
- 每组装一个新 AMD PDU,
PDU_WITHOUT_POLL加 1,BYTE_WITHOUT_POLL加上该 PDU 数据域的新字节数; - 若
PDU_WITHOUT_POLL >= pollPDU或BYTE_WITHOUT_POLL >= pollByte,在该 PDU 里带轮询; - 若该 PDU 发完后发送缓存和重传缓存都空(排除等确认的已发 PDU),或没有新 PDU 能发(比如窗口延迟),也带轮询。
带轮询的动作是:P 域置 1,两个计数器清零;PDU 分发到下层、VT(S) 增加后,置POLL_SN = VT(S) – 1,启动或重启 t_PollRetransmit。
# AM 发送侧:组装新 AMD PDU 时的轮询判定 def maybe_poll(pdu, state, cfg): state.PDU_WITHOUT_POLL += 1 state.BYTE_WITHOUT_POLL += len(pdu.data_field) # 只算数据域新字节 need_poll = (state.PDU_WITHOUT_POLL >= cfg.pollPDU or state.BYTE_WITHOUT_POLL >= cfg.pollByte) if buffers_empty_except_pending(state) or cannot_send_new(state): need_poll = True if need_poll: pdu.header.P = 1 state.PDU_WITHOUT_POLL = 0 state.BYTE_WITHOUT_POLL = 0 state.POLL_SN = state.VT_S - 1 # 注意在 VT(S) 增加之后 restart(state.t_PollRetransmit)参数上,pollPDU 和 pollByte 都是 RRC 配的,pollPDU 常见几十到几百,pollByte 常见几十万字节量级,具体看承载。配小了轮询太频繁,STATUS 挤占空口;配大了对端迟迟不回,窗口卡死。t_PollRetransmit 是轮询重传定时器,超时后(5.2.2.3)若缓存空或发不出新包,就把SN = VT(S)-1的 PDU 或任何未被肯定应答的 PDU 标记重传,并再带一次轮询。
4.3 STATUS 报告与禁止定时器:别把确认打成风暴
接收侧发 STATUS 的触发(5.2.3)主要有两个:收到 P 域为 1 的 PDU(轮询),或 t_Reordering 超时检测到丢包。触发后不是立刻发,要看 t_StatusProhibit:没在跑就在下一个传输机会构造并下发;在跑就等它超时后的首个传输机会,且期间多次触发只发一个 STATUS。STATUS 下发后启动 t_StatusProhibit。
这个禁止定时器是防止 STATUS 风暴的关键。配小了,每个乱序包都触发一次 STATUS,空口上全是确认包;配大了,对端等确认等到窗口卡死。常见做法是让它略大于一个 RTT,具体值跟承载和调度周期走。构造 STATUS 时(5.2.3 末尾),对VR(R) <= SN < VR(MS)范围内未收全的 AMD PDU 按序填 NACK,这个范围就是“还缺什么”的精确表达。
4.4 重传阈值 maxRetxThreshold:什么时候上报失败
协议 5.2.1 定义了 RETX_COUNT 的维护:首次重传时置 0,之后每次“已不再等待重传”时加 1,当RETX_COUNT = maxRetxThreshold时向上层指示已达到最大重传。这个阈值是 RLC 把“我尽力了”告诉 RRC 的出口,配得太小,稍微有点空口抖动就报失败、触发重建;配得太大,一个包在 RLC 里反复重传,时延早就爆了上层还不知道。落地时这个值要和上层(比如 PDCP 的丢弃定时器)对齐,别让 RLC 还在死磕、PDCP 那边已经超时丢了。
5. 避坑与排查:RLC 调试里最常见的五个翻车现场
协议读得再熟,落到实现和抓包还是会有各种“看起来对、跑起来不对”。这一章是我踩过的几个典型坑,按“现象 → 原因 → 解决”记下来,你对着 log 排查能少走弯路。
5.1 现象:UM 业务时延忽高忽低,log 里 t_Reordering 频繁超时
原因:t_Reordering 配得过小,或者 UM_Window_Size 配得和承载不匹配。定时器太小,一个正常的空口乱序就被判成丢包,提前递交导致上层收到乱序数据;窗口太小,SN 回绕快,老包和新包在窗口边界打架。
解决:先确认 t_Reordering 是否大于一个 HARQ RTT 加调度余量,再核对 UM_Window_Size 是否覆盖得住最大在途包数。调参时一次只动一个,抓 t_Reordering 超时次数和上层乱序率两个指标一起看。
5.2 现象:AM 一直重传同一个包,STATUS 里 NACK 范围不变
原因:接收侧 VR(MS) 更新和 STATUS 构造的顺序反了。协议注 2 要求 t_Reordering 超时先更新 VR(MS) 再触发 STATUS,如果实现里先构造 STATUS 再更新,NACK 范围就是旧的,对端重传回来的还是对不上。
解决:把 STATUS 构造放到 VR(MS) 更新之后,抓包看 STATUS 里的 NACK SN 是否随 VR(MS) 推进。另外检查字节段重复判定,重分段后的字节段序号 y 到 z 有没有正确比对。
5.3 现象:发送窗口卡死,VT(S) 顶到 VT(MS),新数据发不出去
原因:对端迟迟不回 STATUS,或轮询根本没发出去。常见是 pollPDU/pollByte 配得太大,加上 t_PollRetransmit 没启动或超时后没重新带轮询,发送侧一直在等一个永远不会来的确认。
解决:抓发送侧 P 域为 1 的 PDU 频率,确认轮询按 pollPDU/pollByte 正常触发;再看 t_PollRetransmit 超时后有没有把未确认 PDU 标记重传并再带轮询。窗口卡死时优先怀疑轮询链路,而不是空口质量。
5.4 现象:STATUS 风暴,空口上确认包占比异常高
原因:t_StatusProhibit 配得太小,或者没生效。每个乱序包都触发 STATUS,接收侧不停地发确认,挤占了数据调度。
解决:确认 t_StatusProhibit 在 STATUS 下发后确实启动了,且值大于一个 RTT;检查“期间多次触发只发一个 STATUS”的逻辑有没有实现。抓包统计 STATUS PDU 占比,正常应该远低于数据 PDU。
5.5 现象:RLC 重建后业务异常,SN 对不上
原因:重建时没按协议清理缓存和状态变量。UM/AM 接收部分都要求重建时“能重组就递交、不能就丢、初始化状态变量并停止定时器”,漏了这步,旧缓存的半截包带着旧 SN 进了新配置。
解决:重建入口统一走一遍清理流程:stop 所有定时器、按序重组可重组部分并递交、丢弃剩余、重置 VR/VT 变量。抓重建前后的 SN 连续性,确认没有旧包混入。
6. 把协议变成可验证的检查清单:从状态变量到一致性测试
协议读到最后,真正的价值不是“看懂了”,而是“能拿它去验证一个实现对不对”。这份技术报告的结构本身就适合做检查清单:第 4 章给结构和功能,第 5 章给过程,第 6 章给 PDU 格式和参数。我一般按“状态变量 → 定时器 → PDU 字段”三层去对实现。
第一层对状态变量。UM 侧重点看 VR(UH)/VR(UR)/VR(UX) 的更新顺序,尤其是入缓存后“先更新 VR(UH)、再判下边界、再判定时器”这个次序;AM 侧重点看 VR(R)/VR(MR)/VR(MS)/VR(H)/VR(X) 五个变量的联动,特别是 VR(MS) 和 STATUS 构造的先后。可以写个小脚本,把抓到的 SN 序列喂进去,模拟状态机跑一遍,和实际 log 里的变量值比对,对不上就是实现有偏差。
第二层对定时器。t_Reordering、t_PollRetransmit、t_StatusProhibit 三个定时器的启动、停止、重置条件,协议里都是“若…则启动/停止/重启”的句式,逐条抄成检查项。比如 t_PollRetransmit 的停止条件是“收到包含 POLL_SN 确认或否定的 STATUS”,重启条件是“带轮询的 PDU 分发后”,这两条如果实现里只做了启动没做重启,轮询就会漏。
第三层对 PDU 字段。第 6 章的 PDU 格式里,UMD PDU 的 FI(成帧信息)、E(扩展比特)、SN、LSF(末段标志)、SO(段偏移)这些字段,每个都有明确的置位规则。抓包时逐字段核对,尤其是重分段场景下 SO 和 LSF 的组合,错了对端就重组不出来。
| 检查层 | 重点对象 | 验证手段 | 常见偏差 |
|---|---|---|---|
| 状态变量 | UM 三变量 / AM 五变量 | 脚本模拟状态机比对 log | 更新顺序反、边界判定漏 |
| 定时器 | t_Reordering / t_PollRetransmit / t_StatusProhibit | 逐条抄启动停止条件 | 只启动不重启、超时后不重带轮询 |
| PDU 字段 | FI / E / SN / LSF / SO | 抓包逐字段核对 | 重分段时 SO/LSF 组合错 |
这套清单我每次接手一个新的 RLC 实现都会走一遍,血泪经验是:别信“看起来能跑”,状态变量的更新顺序错一步,短时间业务正常,一到切换或高负载就原形毕露。从那以后我每次做 RLC 相关改动,都强制把这三层检查走一遍再上测试环境,后悔药没处买。希望帮到你。
本文还有配套的精品资源,点击获取