简介:SDAP(Service Data Adaptation Protocol)是第五代移动通信新空口(5G NR)系统中的关键协议,负责服务数据的自适应处理,以及QoS流到数据无线承载(DRB)的映射。文档内容依据3GPP 37324-g20规范编写,对SDAP协议进行了系统讲解,适合5G协议开发工程师、网络优化人员以及通信专业学习者参考。资源包内仅包含1个docx文件,文件结构完整,章节划分清晰,便于逐节研读,压缩包大小约242KB,非常轻量。目前已有582人学习,具备不错的实用价值。文档覆盖了SDAP子层架构、SDAP实体的建立与释放、向上层提供的用户面数据传输服务,并详细介绍了上下行之间QoS流与DRB的映射、在数据包中标记QoS流ID、反射QoS流到DRB映射等关键功能。与此同时,文档结合具体的上下行数据传输流程,讲解了UL与DL的SDAP数据协议数据单元的构造和解析,并针对反射映射中的RDI、RQI标识处理逻辑给出了细致说明,能够帮助读者快速理清SDAP在5G无线协议栈中的位置和运作机制,尤其适合在5G NR协议学习、规范对照以及项目排障中使用。
1. 从调度器视角看 SDAP:它凭什么决定 5G QoS 流的走向
在 5G NR 的用户面协议栈里,SDAP(Service Data Adaptation Protocol)是最容易被低估的一层。PDCP 负责加解密和重排序,RLC 负责分段重传,MAC 负责调度复用,而 SDAP 只做两件事:把 QoS 流映射到 DRB,以及在数据包里打上 QFI 标记。但恰恰是这个看似简单的映射关系,决定了空口资源的分配效率。gNB 侧如果不知道某个 QoS 流属于哪个 DRB,就无法为其选择正确的 RLC 模式和调度优先级;UE 侧如果配置错了映射规则,轻则吞吐量下降,重则 QoS 流被错误地路由到默认 DRB,导致时延和丢包率完全失控。
这份 3GPP 37324-g20 版本的 SDAP 规范,是目前 5G 协议栈开发、协议一致性测试和网络优化绕不开的基准文档。它定义了 SDAP 实体的建链、释放、UL/DL 数据传输流程、反射映射机制以及端标记控制 PDU 的格式。无论你是在做协议栈软件、做 RAN 侧的 QoE 优化,还是在搞终端侧的日志分析,这份文档里的条款 5.2、5.3、6.2 都是必须吃透的硬骨头。接下来我按功能拆解这份规范,把每条流程背后的设计意图和实现要点讲清楚。
2. SDAP 实体与架构:为什么每个 PDU 会话都要独立建一个 SDAP 实体
2.1 SDAP 在用户面协议栈中的位置
SDAP 子层位于 IP 层和 PDCP 层之间,直接承载用户面数据。规范 4.2.1 节给出的结构视图基于 TS 38.300 定义的无线接口协议架构,但明确说明这只是参考实现,不限制具体实现方式。实际开发时,SDAP 实体大多数时候不是独立的进程或模块,而是作为 PDCP 实体内部的一个功能子模块存在——尤其是在 UE 侧芯片的 L2 协议栈里,SDAP 的处理逻辑往往被直接揉进 PDCP 的收发路径中,减少一次跨层调用带来的开销。
SDAP 子层由 RRC 配置。RRC 层通过信令为每个 PDU 会话配置一个 SDAP 实体。这意味着一个终端如果有多个 PDU 会话,就会对应多个 SDAP 实体。每个实体彼此独立,各自维护自己的 QoS 流到 DRB 映射关系、各自的默认 DRB 配置、各自的反射映射规则表。这个设计保证了不同 PDU 会话之间的 QoS 策略互不干扰。
协议栈位置(UE 侧视角): IP/SDAP 层 -> PDCP (加密、重排序) -> RLC (分段、重传) -> MAC (复用、HARQ) -> PHY (编码、调制)SDAP 实体向上层提供用户面数据传输服务,向下层期望有序交付。规范 4.3.2 节里特别提到「有序交付,除非 RRC 配置了乱序交付」——这实际上是指 PDCP 层可以配置 out-of-order delivery 给 SDAP 使用。在实现时,如果 PDCP 配置了乱序交付,SDAP 就不需要对收到的数据包做任何顺序假设,直接按到达顺序处理反射映射逻辑。这会影响到反射映射时的时序一致性:如果 PDCP 乱序递交,某个 QoS 流的 DL PDU 可能先到达,而包含映射关系更新的 PDCP PDU 后到达,处理时必须容忍这种情况。
2.2 SDAP 实体的建链和释放流程
SDAP 实体的建立和释放都由 RRC 信令触发,规范 5.1.1 和 5.1.2 节的描述非常简洁。建立时 UE 只需要创建一个实体并遵循 5.2.1 和 5.2.2 的数据传输流程;释放时直接销毁实体即可。这里真正有实现细节的地方是:SDAP 实体建立时不带任何 QoS 流映射规则,所有规则都是后续通过 RRC 重配置或反射映射逐步建立的。
实际协议栈里,SDAP 实体建立后需要立刻处理一种特殊情况:当 UE 有一个 QoS 流需要发送 UL 数据,但还没有收到任何映射规则时,规范 5.2.1 节要求把 SDU 映射到默认 DRB。如果此时默认 DRB 也没有配置,那么行为未定义。规范用注释的方式明说了这一点。所以在实现 SDAP 模块时,必须对「有数据无默认 DRB、无映射规则」这种情况做防御性处理,否则实际网络中 UE 会在注册完成后短时间内收到大量应用层数据,而映射规则尚未下发,SDAP 的 UL 数据路径可能直接报错。
| 建链/释放触发条件 | UE 行为 | 关键依赖 |
|---|---|---|
| RRC 请求建立 SDAP 实体 | 创建实体,初始化映射表为空 | 等待 RRC 重配置下发映射规则 |
| RRC 请求释放 SDAP 实体 | 销毁实体,清除所有映射规则 | PDU 会话释放流程 |
| RRC 释放某个 DRB | 移除该 DRB 关联的所有 QoS 流映射 | 5.3.3 节 DRB release |
2.3 与 NR SL 通信的差异
规范 4.2.2 节提到,对于 NR sidelink(SL)通信,SDAP 实体不支持 PC5 QoS 流到 SL-DRB 的反射映射。这是一个容易忽略的边界条件。做 V2X 或公共安全场景协议栈时,如果照搬 NR Uu 的反射映射逻辑到 SL-DRB 上,就会出现规范未定义的行为。我在实现中一般会在 SDAP 实体配置里加一个 mode 字段区分 Uu 和 SL:Uu 模式下启用反射映射处理和 RQI 处理,SL 模式下只做基础的用户面传输和 QFI/PQFI 标记,反射映射相关分支直接跳过。这样既符合规范,也避免在 SL 场景下做出越界行为。
3. QoS 流到 DRB 的映射机制:静态配置、默认 DRB 与端标记的配合
3.1 UL 方向的数据传输流程
UL 数据传输是 SDAP 最核心的路径。规范 5.2.1 节给出了完整的处理逻辑。当 SDAP 实体从上层收到一个 SDU 时,先查存储的 QoS 流到 DRB 映射规则。如果没有规则,就用默认 DRB;有规则,就按规则映射。映射完成后还需要根据该 DRB 是否配置了 SDAP 头来决定构造带头的 PDU 还是不带头的 PDU。
先看默认 DRB 的使用条件。在 5G 网络中,默认 DRB 在 PDU 会话建立时由 gNB 通过 RRC 信令配置,它承载所有没有显式映射规则的 QoS 流。注意规范 5.2.1 的注释 2 明确写了一句「默认 DRB 总是配置 UL SDAP 头」。原因是默认 DRB 承载的 QoS 流不固定,接收端必须依靠 SDAP 头里的 QFI 字段来区分不同的业务流,否则所有业务混在一起无法区分 QoS。
而带 SDAP 头的 UL PDU 格式是 D/C(1 bit)+ RQI(1 bit)+ RDI(1 bit)+ QFI(6 bit),总共 1 或 2 字节。对于非默认 DRB,如果 gNB 没有显式配置 SDAP 头,那么 UL PDU 就只有纯粹的 SDU 数据,没有头。SDAP 头配置属于 RRC 层 DRB 配置的一部分,在 TS 38.331 里有对应的 IE:pdu-SessionID、sdap-HeaderUL、sdap-HeaderDL。
3.2 UL 映射规则的配置、更新与释放
RRC 层会向 SDAP 下发 UL QoS 流到 DRB 的映射规则。规范 5.3.1 节要求在两种情况下构造端标记控制 PDU:
第一种情况,SDAP 实体已建立,当前没有存储该 QoS 流的映射规则,并且配置了默认 DRB。此时新规则下发意味着该 QoS 流从默认 DRB 迁移到目标 DRB。SDAP 必须在上行方向通知接收端:此 QoS 流的数据不再从原 DRB 过来了。这就是端标记控制 PDU 的作用——它不带任何 SDU 数据,只有 SDAP 头里的 QFI 字段标识是哪个 QoS 流发生了迁移。
第二种情况,存储的映射规则与刚配置的规则不同,且新规则对应的 DRB 配置了 UL SDAP 头。此时需要把这个 QoS 流从旧 DRB 上「端标记」一次,让接收端知道旧 DRB 上该 QoS 流的数据已经结束。接收端收到端标记后,会理解到后续该 QoS 流的数据将出现在新 DRB 上。
实现时注意时序:端标记 PDU 必须排在旧 DRB 里最后一条该 QoS 流的数据之后,新映射规则才生效。如果在端标记之前就把后续数据切换到了新 DRB,接收端会短暂丢弃一段时间的数据。所以代码逻辑应当先发送端标记到旧 DRB,再切换映射规则并发送后续数据到新 DRB。
收到 RRC 重配置映射规则(QoS flow 3 -> DRB 2): 1. 查映射表:QoS flow 3 当前是否已映射? - 未映射:构造 End-Marker PDU(QFI=3),提交到默认 DRB - 已映射到 DRB 1:构造 End-Marker PDU(QFI=3),提交到 DRB 1 2. 存储新映射:QoS flow 3 -> DRB 2 3. 后续 UL SDU(QFI 3)直接映射到 DRB 2这段逻辑的本质是让对端 SDAP 实体的接收状态机同步迁移。如果跳过端标记,接收端缓存队列里旧 DRB 上尚未递交的数据与新 DRB 上已经开始递交的数据之间可能出现空洞,而上层业务(如 TCP)感知到乱序或丢包。
3.3 DL 方向的数据传输流程
DL 方向的处理相对简单:UE 侧 SDAP 实体收到下层递交的数据 PDU 后,先看该 DRB 是否配置了 SDAP 头。配置了,就解析 SDAP 头、执行反射映射和 RQI 处理,然后提取出 SDU 递交给上层;没配置,直接整包提取 SDU。
这里有个细节:DL SDAP 头的解析包含 RDI 和 RQI 两个标志位。规范要求先做反射映射(RDI 逻辑)再做 RQI 处理,顺序不能反过来。原因后续展开解释。接收侧还要注意 D/C 位的判断——如果收到了 Control PDU(D/C=0),要按控制 PDU 格式解析而不是当作数据 PDU 解析。当然,DL 方向实际很少收到来自 gNB 的控制 PDU,因为端标记控制 PDU 是 UE 在 UL 方向发给 gNB 的。但代码上还是要留这个分支,避免出现协议异常。
4. 反射映射与 RQI:下行方向如何反向配置上行映射
4.1 反射映射的完整流程
反射映射机制解决的是上行映射规则如何快速建立的问题。传统做法是 gNB 每建立一个 UL 映射规则都需要单独的 RRC 信令,开销较大。反射映射的思路是:gNB 在 DL 数据 PDU 的 SDAP 头里携带 RDI=1 和 QFI 字段,UE 收到后把这条 DL 上的 DRB 与 QoS 流的对应关系,直接映射成 UL 方向该 QoS 流到对应 DRB 的映射规则。
规范 5.3.2 节给出了四步流程。第一步解析 QFI;第二步检查当前是否没有存储的映射规则,且配置了默认 DRB,若是则向默认 DRB 发送端标记;第三步检查存储的映射规则是否与本次 DL 映射不同,若是则向旧 DRB 发送端标记;第四步把 DL 的映射保存为 UL 的映射规则。注意第四步是最终目的,前面三步都是为了清理旧状态。
收到 DL SDAP Data PDU(RDI=1, QFI=7, 从 DRB 3 到达): 1. 解析 QFI=7 2. 若当前无 QoS flow 7 的映射规则且配置了默认 DRB: 构造 End-Marker PDU 到默认 DRB 提交给下层(UL 方向) 3. 若当前 QoS flow 7 已映射到 DRB 2(与 DL 的 DRB 3 不同): 构造 End-Marker PDU 到 DRB 2 提交给下层(UL 方向) 4. 存储映射规则: QoS flow 7 -> DRB 3(UL 方向)这个机制的巧妙之处在于,DL 数据对 UE 而言是「免费的信令」,不需要额外的 RRC 信令开销。但代价是 UE 侧需要能够下发端标记来控制 PDU——这本身也是规范允许的 UL 控制面传输路径的一部分。在实际网络中,gNB 通常在业务初建或 QoS 流迁移时设置 RDI=1,后续稳定期会清掉 RDI 以减少头开销。
4.2 反射映射需要考虑的时序问题
反射映射的实现要小心一个问题:RDI=1 的 DL PDU 可能到达在端标记之前还是之后?在正常网络里,gNB 先在 DL 方向用 RDI=1 标记某个 DRB 上的 QoS 流映射,之后才在 UL 方向真正使用这个映射关系。如果终端收到一个 RDI=1 的 PDU,但此刻 UL 侧还有其他数据正排队发送到旧 DRB,那么必须先发端标记到旧 DRB,再切换后续数据到新 DRB。规范 5.3.2 的注释没有明确端标记和切映射的先后顺序,按工程惯例,应先发送端标记,即在上层的映射表里先加入「该 QoS 流在旧 DRB 的终止」逻辑,确保旧 DRB 上不再有新的数据排队。
另一个实现细节是 QoS 流的判定只能靠 QFI。SDAP 头部只有 6 位 QFI,有效值范围是 1 到 63(0 保留)。如果 QFI 非法(比如 0 或者超过配置的最大值),接收端应该丢弃数据还是把数据递交给上层?规范没有定义此类异常处理。我的做法是丢弃 PDU 并打印错误日志,因为产生非法 QFI 说明上下行映射配置出现了严重错位,继续递交可能把业务数据错误地路由到错误的 QoS 上下文,造成上层解码失败甚至内存破坏。
4.3 RQI 标志的作用与 NAS 层交互
RQI(Reflective QoS Indication)用于触发 UE 的 NAS 层发起 QoS 流更新流程。当 DL SDAP 头里 RQI=1,UE 需要通知 NAS 层「RQI 和 QFI」信息,NAS 层根据这些信息决定是否执行 QoS 流规则的更新。这与反射映射不同:反射映射是直接在 SDAP 层做 UL 映射规则的镜像,而 RQI 要触发 NAS 层的上层决策,可能最终通过 PDU 会话修改流程来更新 QoS 规则。
实现时要注意,RQI=1 的 PDU 可能非常频繁。如果每次都把 RQI 和 QFI 通过接口抛给 NAS 层,NAS 层可能来不及处理,造成信令积压。因此规范只定义了「通知 NAS」,而没有定义通知限速。工程上常见做法是引入一个去重逻辑:对于同一个 QoS 流,如果上一个 RQI=1 的通知尚未被 NAS 处理完,就不再重复通知。这样可以在未明确违反规范的前提下避免无意义的信令风暴。
5. SDAP PDU 格式逐位分析:从 D/C 位到端标记控制 PDU
5.1 UL/DL 数据 PDU 的字段布局
SDAP 数据 PDU 分为三类:不带 SDAP 头的纯数据 PDU、带 SDAP 头的 UL 格式、带 SDAP 头的 DL 格式。UL 和 DL 格式有区别:UL 带头的格式是 D/C + RQI + RDI + QFI(1 字节,正好 9 bits,但实际是按字节对齐的,所以整体是 2 字节);DL 带头的格式则是 D/C + RQI + RDI + QFI(1 字节)+ 额外的 2 bits 保留位,整体也是 2 字节。差别在于 UL 头严格是 8 bits 内,DL 头在规范里刻意多加了一个值扩展的准备空间,以兼容未来可能出现的 QoS 流 ID 扩展。
各字段含义对照如下:
| 字段 | 长度 | 含义 | 取值说明 |
|---|---|---|---|
| D/C | 1 bit | 数据类型 | 0=Control PDU,1=Data PDU |
| RQI | 1 bit | 反射 QoS 指示 | 1=通知 NAS 更新规则,0=不通知 |
| RDI | 1 bit | 反射映射指示 | 1=存储 QoS 流到 DRB 映射,0=不存储 |
| QFI | 6 bit | QoS 流 ID | 1-63 有效,0 保留 |
| R | 1 bit(DL 头) | 保留 | 置 0,接收端忽略 |
字段的解释顺序是 MSB(最左)到 LSB(最右),整数用无符号二进制编码。实际抓包时,如果遇到 0x80 开头的 SDAP PDU,D/C 就是 1(最高位),代表 Data PDU;0x00 开头则 D/C=0,是 Control PDU。比如 UL 常见的 SDAP 头0x1A,解析出来是 D/C=1、RQI=0、RDI=0、QFI=26,即 QoS 流 26 的数据 PDU。
5.2 端标记控制 PDU 的格式与行为
端标记控制 PDU 的格式非常简单:D/C=0 + R + RQI(1) + RDI(1) + QFI(6)。控制 PDU 不携带任何用户面数据,仅用于向对端表示某个 QoS 流的映射关系已经终止。发送端会在以下场景触发:RRC 配置了新映射规则,或 RDI=1 的 DL PDU 触发了映射更新,且需要清理旧 DRB 上的数据流状态。
收到端标记控制 PDU 的一方应如何处理?规范没有明确要求接收端必须做什么。实际实现中,接收端一般把它当作一个信号,据此调整本地的接收分类索引。比如 gNB 侧收到 UE 发来的端标记,可以确认旧 DRB 上该 QoS 流的数据边界,从而安全释放旧的 PDCP/RLC 缓存。如果接收端忽略端标记,最坏情况是旧 DRB 上残存的数据与新映射的数据混在一起,造成上层业务乱序。
端标记控制 PDU 构造示例(C 语言伪码): uint8_t pdu[2]; pdu[0] = 0x00; // D/C=0 Control PDU pdu[1] = (qfi & 0x3F); // QFI 低 6 位有效 // 提交到下层(PDCP/RLC)进行传输构造时注意 QFI 只取低 6 位。如果上层传入的 QFI 超过 63,必须做断言或容错处理。我在多个协议栈实现里见过直接把 QFI 强转成 uint8_t 导致 QFI=64 变成 0 从而被对端当成非法值的 bug。
5.3 保留位的处理约定
规范 6.3.5 明确指出保留位在当前版本里必须置 0,且接收方应该忽略保留位。这给了前向兼容的空间。实现时注意两点:发送侧一定把保留位置 0;接收侧不要因为保留位非 0 就报错或丢弃。实际网络中,不同厂商的协议栈版本不一致,可能存在某种实现把保留位写成 0xFF 的极端情况。只要不涉及安全漏洞,保留位非 0 的 PDU 在调试日志里打 warning 即可,不建议直接在协议栈里丢包。
6. 协议一致性测试与日志分析:验证 SDAP 行为是否合规的方法
6.1 构造 SDAP PDU 进行单元测试
协议栈开发最耗时的环节就是验证 SDAP 在边界条件下是否合规。我一般先用 Python 脚本构造一组 SDAP PDU,把规范里的流程用测试用例覆盖掉。下面是一个构造 UL 带头 SDAP PDU 的示例:
def build_ul_sdap_pdu(qfi, rqi=0, rdi=0, payload=b'\x00\x01\x02'): # D/C=1, RQI, RDI, QFI header = 0x80 | (rqi << 6) | (rdi << 5) | (qfi & 0x3F) return bytes([header]) + payload def parse_sdap_header(byte): dc = (byte >> 7) & 0x1 rqi = (byte >> 6) & 0x1 rdi = (byte >> 5) & 0x1 qfi = byte & 0x3F return dc, rqi, rdi, qfi test_pdu = build_ul_sdap_pdu(7, rdi=1) print(hex(test_pdu[0])) # 0xA7这段脚本的构造逻辑严格对应规范的位序:最高位 D/C,接下来 RQI,再接下来 RDI,最后 6 位 QFI。测试时要特别注意 QFI 的值不能超过 63——如果传入 64,qfi & 0x3F会把它截断为 0,生成一个 QFI=0 的非法规格数据包。所以我实际项目里会在构造函数入口加断言,从源头避免这种测试误用。
解包函数常用于日志分析。当你在终端 dump 文件里看到 SDAP PDU 的原始字节时,先用 DC/RQI/RDI/QFI 解析确认 PDU 格式,再对照 RRC 配置的映射表判断该 PDU 的路由是否正确。比如某条 UL 日志里 QFI 对应的 DRB 和预期不符,基本可以定位是 SDAP 映射规则更新逻辑出错了。
6.2 抓包验证反射映射是否生效
验证反射映射不能只看 SDAP 层的单包,要结合 RRC 信令验证。步骤是这样的:
bash 命令示例(抓取空口日志并过滤 SDAP 头): tcpdump -i any -s 0 -w sdap_capture.pcap # 用 Wireshark 打开后设置过滤条件: # sdap 协议过滤器: sdap # 观察 SDAP 头中的 RDI/RQI/QFI 字段 # 结合 RRC 重配置消息确认映射变化先找到一条 RDI=1 的 DL SDAP PDU,记录 QFI 与 DRB ID;随后在接下来的 UL 方向数据中查找相同 QFI 是否出现在该 DRB 上。如果出现,说明反射映射流程生效。如果没有,则检查是否配置了默认 DRB,以及映射规则表是否被后续 RRC 重配置覆盖。
规范 5.2.1 提到「如果既没有默认 DRB,也没有存储映射规则,那么 UE 行为不定义」。在终端日志中遇到此类场景,需要特别小心区分:到底是 UE 由于没有映射规则而丢弃了 UL SDU,还是 UE 在等待网络侧配置反射映射规则后补发?由于规范未做约束,各厂商实际行为可能不同。测试时不要在日志预期里写死「不丢包」,否则终端协议栈的容错设计会被误判为 bug。
6.3 空口抓包分析中常见的几个误判
排查实际网络问题时,有几个 SDAP 相关的坑需要特别注意,这些也是协议测试和网络优化中反复出现的误判源:
第一个是 QFI 与 5QI 混淆。SDAP 头里的 QFI 是 0 到 63 的数值,5QI 是标准化的 QoS 等级标识(1 到 9)。QFI 是一个流级别 ID,由 gNB 分配,和 5QI 不是一回事。经常有人把 QFI=9 当成 5QI=9(比如语音业务),导致业务识别错误。
第二个是 SDAP 头在 PDCP 加密后的表现。SDAP 层在 PDCP 之下,但 SDAP 头随 PDCP SDU 一起加密。这意味着空口抓包的 PDCP 层里很可能看不到明文 SDAP 头。要看 SDAP 头必须在上层暴露的调试接口(伪基站测试、协议栈内部日志)或 gNB 侧抓包才能看到。如果你的抓包工具按标准 3GPP 接口只看到密文,不要以为 SDAP 头没被添加,那只是被加密了。
第三个是默认 DRB 的 UL SDAP 头。规范注释明确「默认 DRB 总是配置 UL SDAP 头」。所以遇到默认 DRB 却不带头的 UL 数据,必是配置或实现错误。反过来说,非默认 DRB 上如果收到了带头的数据,正常——只要这条 DRB 被配置了 SDAP 头。
第四个常见误判是把反射映射和 NAS 的 QoS 流程混在一起排查。反射映射与 RQI 是 SDAP 内的独立触发源,而 RRC 配置的映射规则是优先级最高的来源。当二者冲突时,以 RRC 显式配置为准。比如反射映射刚建立了 QFI 5 -> DRB 2,随后 RRC 重配置又下发 QFI 5 -> DRB 3,则最终映射是 DRB 3。此时不能只拿反射映射日志去定位,要拉全 RRC 信令时间线。
本文还有配套的精品资源,点击获取