第2章 通信协议栈概览:物理层、MAC层、网络层在多核上的映射关系
做通信DSP这些年,我见过不少团队把协议栈跑在多核上,结果性能反而比单核还差。为啥?说白了就是没搞清楚每一层到底该往哪个核上放。
今天咱们就聊聊这个映射关系。我会结合自己踩过的坑,把物理层、MAC层、网络层在多核上的分工讲明白。
2.1 协议栈的三层结构
通信协议栈,我习惯把它看成三个独立的"车间":
- 物理层:负责信号的收发、调制解调、编解码。这是最"重"的活儿,计算量最大。
- MAC层:负责调度、重传、资源分配。实时性要求高,但计算量相对小。
- 网络层:负责路由、寻址、数据包转发。处理的是控制面,对时延没那么敏感。
你想想看,这三层的特性完全不同。物理层是"体力活",MAC层是"精细活",网络层是"管理活"。把它们硬塞到同一个核上,那肯定出问题。
核心观点:多核映射的关键,是把计算密集的任务和实时性敏感的任务分开。物理层占计算,MAC层占实时,网络层占吞吐。
2.2 物理层:独占一个核,甚至多个核
我在项目中遇到过,物理层的处理量能占到整个协议栈的70%以上。尤其是OFDM调制、LDPC译码这些模块,动不动就是几百万次乘加运算。
我的建议是:物理层最好独占一个DSP核。如果带宽高,比如5G NR的100MHz带宽,那得用两个核甚至四个核来分担。
举个例子,一个典型的物理层处理流程:
// 伪代码:物理层接收处理
void phy_rx_process(core_id) {
while(1) {
// 步骤1:FFT解调(计算密集)
fft_demod(rx_buffer, freq_domain);
// 步骤2:信道估计(需要查表)
channel_estimation(freq_domain, channel_matrix);
// 步骤3:LDPC译码(迭代计算)
ldpc_decode(channel_matrix, decoded_bits);
// 步骤4:传给MAC层(通过共享内存)
send_to_mac(decoded_bits);
}
}
你看,每个步骤都是计算密集型。如果这时候MAC层来抢核,那FFT还没算完就被打断了,整个链路就卡住了。
避坑指南:我曾经把一个LTE的物理层和MAC层放在同一个核上,结果吞吐量直接掉了40%。后来把物理层单独放到一个核上,问题就解决了。记住,物理层不要跟别人共享核。
2.3 MAC层:放在控制核上,但要靠近物理层
MAC层的活儿,说白了就是"调度"。它要决定:
- 什么时候发数据
- 用哪个资源块发
- 发错了要不要重传
这些决策对实时性要求极高。比如5G里的HARQ重传,必须在几个毫秒内完成。如果MAC层离物理层太远,数据传来传去就超时了。
我个人的做法是:把MAC层放在一个独立的控制核上,但这个核跟物理层的核之间要有高速互联。比如共享L2缓存,或者用硬件队列通信。
来看一个MAC层的调度流程:
// 伪代码:MAC层调度
void mac_scheduler(core_id) {
while(1) {
// 从物理层拿数据(通过共享内存)
rx_data = get_from_phy();
// 判断是否需要重传
if (rx_data.crc_error) {
schedule_retransmission(rx_data.ue_id);
}
// 分配新资源
allocate_resource(next_tti);
// 把调度结果发给物理层
send_to_phy(schedule_info);
}
}
注意:MAC层和物理层之间的通信延迟,最好控制在微秒级。如果超过10微秒,HARQ的时序就可能出问题。我见过一个项目,就是因为共享内存的访问延迟太大,导致重传总是超时。
2.4 网络层:放在通用核上,或者单独的控制核
网络层处理的是IP包的路由和转发。这个活儿的特点是:
- 计算量不大
- 对实时性要求不高
- 但需要处理复杂的协议逻辑
所以,网络层最适合放在通用处理器核上,比如ARM核或者RISC-V核。如果DSP芯片里没有通用核,那就放在一个专门的控制核上,优先级设低一点。
嗯,这里要注意:网络层的数据包可能很大,比如一个IP包有1500字节。如果频繁地在核之间搬运这些数据,会浪费大量带宽。我建议用DMA来搬运,别让CPU去拷贝。
2.5 多核映射的典型架构
说了这么多,咱们画个图来总结一下。下面这张SVG图,展示了我个人最常用的三层映射架构:
这张图里,物理层在最下面,独占DSP核0。MAC层在中间,用控制核1。网络层在最上面,用通用核2。每一层之间通过共享内存或消息队列通信。
2.6 实际项目中的映射策略
我参与过一个5G小基站的项目,用的是4核DSP。当时我们是这样分配的:
| 核编号 | 负责模块 | 优先级 | 备注 |
|---|---|---|---|
| 核0 | 物理层接收(FFT + 信道估计 + 译码) | 最高 | 独占,不允许被抢占 |
| 核1 | 物理层发送(编码 + 调制 + IFFT) | 高 | 与核0并行工作 |
| 核2 | MAC层调度 + HARQ | 中 | 与核0/1通过硬件队列通信 |
| 核3 | 网络层 + 控制面处理 | 低 | 可被其他任务抢占 |
你看,物理层用了两个核(一个收一个发),MAC层用一个核,网络层用一个核。这样分工明确,每个核的负载都比较均衡。
小技巧:如果物理层的计算量特别大,比如做64QAM的LDPC译码,我建议把译码模块单独放到一个核上。这样其他物理层处理(比如FFT)可以跟译码并行,整体吞吐量能提升不少。
2.7 常见问题与避坑
最后,我总结几个常见的坑,都是我在项目中亲身经历过的:
- 坑一:物理层和MAC层共用核。我曾经这么干过,结果MAC层的调度延迟导致HARQ总是超时。后来分开就好了。
- 坑二:核间通信用全局变量。多核环境下,全局变量不加锁会导致数据错乱。我建议用硬件队列或者原子操作。
- 坑三:网络层数据直接拷贝。大包拷贝会占用大量CPU时间。用DMA搬运,或者用零拷贝技术。
- 坑四:优先级设置不合理。物理层优先级一定要最高,MAC层次之,网络层最低。否则高优先级任务会被低优先级任务阻塞。
嗯,今天就聊到这儿。记住一句话:物理层占计算,MAC层占实时,网络层占吞吐。把这个原则记牢了,多核映射就不会出大问题。
公众号:蓝海资料掘金营,微信deep3321