创建仓库

This commit is contained in:
2025-12-11 07:24:36 +08:00
commit 0d81c1792d
128 changed files with 15104 additions and 0 deletions

View File

@@ -0,0 +1,68 @@
---
tags: []
date created: 星期三, 十一月 19日 2025, 9:58:46 晚上
date modified: 星期三, 十一月 19日 2025, 9:59:01 晚上
---
### 一、 核心概念MTU 与网络开销
| 概念 | 定义 (专业) | 默认值 (行业标准) |
| :--- | :--- | :--- |
| **MTU** (Maximum Transmission Unit, 最大传输单元) | 网络通信中单个数据包或帧在不被分片Fragmentation的情况下**链路层可承载的最大数据净载荷**Payload尺寸。 | **1500 字节** |
| **开销** (Overhead) | 每个数据包除了净载荷外还必须包含的固定长度的网络协议头如以太网头、IP 头、UDP 头等)。 | **约 42 - 54 字节** |
### 二、 默认 MTU (1500) 在雷达高吞吐场景的局限性
在雷达数据采集(高速、大容量的 UDP 数据流)场景中,使用默认的 MTU 1500 字节会产生两个致命的性能问题:
#### 1. 吞吐效率低下 (Efficiency)
- 在 MTU 1500 的情况下,每个数据包中,实际用于传输雷达数据的净载荷仅占 $1500 / (1500 + \text{Headers})$。
- 如果雷达数据流的速率是 $1000 \text{Mbps}$ (1GbE 的理论上限),其中有高达 $3\%-5\%$ 的带宽会被固定协议头开销占据,实际用于净数据的带宽进一步降低。
#### 2. CPU 中断风暴 (The Interrupt Storm)
这是实时系统中最关键的问题。
- 为了传输大量数据,操作系统和网卡必须将数据流切割成无数个 1500 字节的小块。
- 每接收一个数据包,网卡通常会触发一次**硬件中断IRQ**来通知 CPU 内核数据已到达。
- 在 1GbE 链路满负荷运行时CPU 需要在**每秒处理数十万次**的网卡中断。
- **后果:** 频繁的中断处理会导致 CPU 大量时间花费在**上下文切换 (Context Switching)** 和中断服务例程上,而不是执行您的核心信号处理算法。这将显著推高系统 CPU 占用率(`sys cpu`),破坏实时性。
### 三、 JUMBO Frame (MTU 9000) 的引入与价值
“JUMBO Frame”是一种非标准的、通过配置将 MTU **放大到 9000 字节左右**的技术。它不是一种新协议,而是对现有以太网协议参数的扩展。
#### 1. 核心价值:极大减少 CPU 中断频率
将 MTU 从 1500 提升到 9000 字节,意味着:
- **数据量不变,中断次数减少 6 倍。** 传输相同的数据量,现在只需要发送六分之一的数据包数量。
- **结果:** CPU 从每秒处理数十万次中断,降低到每秒处理数万次中断。这极大地减轻了内核的压力,将 CPU 资源释放回用户态,保障了您的雷达实时处理线程能够获得更稳定的调度时间。
#### 2. 吞吐效率提升 (Header Compression)
- 在 MTU 9000 下,协议头开销在整个帧中的占比被稀释到**小于 $0.5\%$**,几乎所有的带宽都用于传输雷达净载荷。
- **结论:** 在您当前**受限于 1GbE 硬件** 的情况下MTU 9000 是确保 1GbE 链路能跑满**最接近 $1 \text{Gb/s}$ 的有效吞吐量**的**P1 级软件优化**。
### 四、 技术实施要求与基线确立
MTU 9000 不是一个自动生效的配置,它是一个**端到端End-to-End**的协商机制。
| 环节 | 要求 (基线) |
| :--- | :--- |
| **Host NIC** (您的麒麟服务器) | **必须**通过 `ip link set dev ens4f1 mtu 9000` 命令将 NIC 接口 (例如 `ens4f1`) 的 MTU 设为 9000。 |
| **DPU/ADC** (雷达前端) | **必须**将其发送端的 MTU 设为 **9000 字节**或相同的数值。 |
| **网络设备** (交换机/路由器) | 如果数据路径中有交换机等中间设备,**所有中间设备**的端口也必须开启并配置 MTU 9000否则将导致数据包被丢弃或分片后者会重新引入 CPU 开销)。 |
**最终基线结论:**
鉴于硬件瓶颈,采用 **UDP/IP + JUMBO Frame (MTU 9000 字节)** 是在当前 Feiteng/Kylin 平台上实现雷达数据采集链路性能最大化的**核心基线**。
---
**下一步讨论:**
如果您确认了 MTU 9000 的基线,我建议我们进入下一阶段,讨论如何从内核层面配合这一高吞吐策略,即 `2.1.3 NIC 硬件资源与队列管理``2.1.5 DMA 与内核旁路策略`
**提问:** 在确认 JUMBO Frame 基线后,您对 **NIC 环形缓冲区 (Ring Buffer)** 的深度配置是否有初步的性能或资源偏好?(这是一个直接影响丢包率的关键参数)

View File

@@ -0,0 +1,148 @@
---
tags: []
aliases:
- TL;DR
date created: 星期四, 十一月 27日 2025, 7:22:37 晚上
date modified: 星期五, 十一月 28日 2025, 12:47:41 凌晨
---
# 大端序和小端序
## TL;DR
**端序 (Endianness)** 指的是**多字节数据**(如 `int`, `float`)在内存中存储的**字节排列顺序**。
- **大端序 (Big-Endian)** 高位字节存放在低地址(符合人类阅读习惯,从左到右)。**网络传输标准**。
- **小端序 (Little-Endian)** 低位字节存放在低地址(高低位颠倒)。**x86/x64 架构标准**。
- **核心影响:** 在进行网络编程Socket或跨平台文件解析时必须进行字节序转换否则读出的数值会完全错误。
---
## 1. 核心概念与可视化
计算机内存就像一条长长的街道每个“门牌号”内存地址只能住一个人1 个字节8 bits
当我们存储一个需要占多个门牌号的“大家伙”(比如 4 字节的整数 0x12345678就产生了一个问题应该把头高位放在小编号还是把脚低位放在小编号
假设内存地址从 `0x100` 开始增长,数据是十六进制的 `0x12345678`
- **高位 (MSB)** `0x12` (数值最大的部分)
- **低位 (LSB)** `0x78` (数值最小的部分)
### 可视化对比
|**内存地址**|**大端序 (Big-Endian)**|**小端序 (Little-Endian)**|
|---|---|---|
|**0x100 (低地址)**|**12** (高位 MSB)|**78** (低位 LSB)|
|**0x101**|34|56|
|**0x102**|56|34|
|**0x103 (高地址)**|**78** (低位 LSB)|**12** (高位 MSB)|
|**人类阅读视角**|`12 34 56 78` (顺眼)|`78 56 34 12` (反人类)|
---
## 2. 为什么会有两种标准?(底层原理)
这并非单纯的“习惯不同”,而是基于不同的工程权衡:
### 大端序 (Big-Endian) 的逻辑
- **直观性:** 内存中的顺序与人类手写数字的顺序一致。`123` 就是先写百位,再写个位。
- **符号判断快:** 正负号符号位总是在第一个字节低地址。CPU 只要读第一个字节就能判断正负,无需读完整个数。
- **应用场景:** **网络协议 (TCP/IP)**、Java 虚拟机、早期的 Motorola 68k 处理器。
### 小端序 (Little-Endian) 的逻辑
- **计算优势:** 计算机做加法是从低位开始算的需要进位。CPU 读取数据时,先读到低位(低地址),可以直接开始运算,无需等待高位读取完成。
- **类型转换零开销:** 强制转换数据类型(如 `int32``int8`)时,**内存地址不需要变**。因为低位都在 `0x100`,只要把读取长度截断即可。而在大端序中,转成 `int8` 需要将地址偏移到 `0x103` 才能拿到低位。
- **应用场景:** **Intel x86/x64 架构**、现代大部分 ARM 芯片(虽然 ARM 支持双端序,但在 Android/iOS 上默认配置为小端)。
---
## 3. 工程中的“坑”:网络字节序 Vs 主机字节序
在网络开发中,这是最容易出错的地方。
- **主机字节序 (Host Byte Order)** 取决于 CPU 架构。Intel CPU 是小端序。
- **网络字节序 (Network Byte Order)** **强制规定为大端序**
**典型故障流程:**
1. 你的 x86 服务器(小端)发送整数 `1` (`0x00000001`)。
2. 如果不转换直接发,网线上跑的数据是 `01 00 00 00`(小端首字节)。
3. 接收端(假设也是 x86按照网络标准大端解析认为收到的是 `0x01000000`(十进制 16,777,216
4. **结果:** 发送了 1对方收到了 1600 多万。
解决方案:
使用标准库函数进行显式转换(代码具备可移植性,若架构相同会编译为空操作):
- `htonl()`: Host to Network Long (32-bit)
- `htons()`: Host to Network Short (16-bit)
- `ntohl()`: Network to Host Long
- `ntohs()`: Network to Host Short
---
## 4. 代码检测与验证 (C/C++)
这是一个经典面试题,也是检测当前环境端序的最简单方法。
```C
#include <stdio.h>
#include <stdint.h>
// 检查当前系统是否为小端序
int is_little_endian() {
uint32_t num = 1; // 0x00000001
// 将 int 指针强转为 char 指针,只读取内存中第一个字节(低地址)
char *byte_ptr = (char*)&num;
// 如果低地址存的是 1说明低位在前 -> 小端序
// 如果低地址存的是 0说明高位在前 -> 大端序
return (*byte_ptr == 1);
}
int main() {
uint32_t data = 0x12345678;
uint8_t *p = (uint8_t*)&data;
printf("Current System: %s\n", is_little_endian() ? "Little-Endian (小端)" : "Big-Endian (大端)");
printf("Memory Dump of 0x12345678:\n");
for(int i = 0; i < 4; i++) {
printf("Address +%d: 0x%02x\n", i, p[i]);
}
return 0;
}
```
**x86 机器上的输出:**
```Plaintext
Current System: Little-Endian (小端)
Memory Dump of 0x12345678:
Address +0: 0x78
Address +1: 0x56
Address +2: 0x34
Address +3: 0x12
```
---
## 5. 自我反驳与局限性 (Self-Rebuttal)
虽然“网络是大端x86 是小端”是共识,但以下情况需要注意:
- **单字节数据无关性:** ASCII 字符串(如 "Hello")不受端序影响,因为每个字符只占 1 字节,不存在“内部顺序”问题。只有 `int``short``long``float` 等多字节类型才受影响。
- **位序 (Bit Endianness)** 我们讨论的是**字节序**。在极少数底层协议(如某些旧的串行通信协议)中,**一个字节内部的 8 个 bit** 传输顺序也分大端和小端。但在现代 CPU 和标准网络编程中,通常不需要关心位序,硬件会处理好。
- **双端序架构 (Bi-endian)** ARM 和 PowerPC 架构实际上是可配置的。虽然安卓和 iOS 运行在 ARM 上通常配置为小端但在某些基站或路由器设备上ARM 可能运行在大端模式。**不能假设 ARM 永远是小端。**
---
## 6. 总结
1. **大端序 (Big-Endian)** 顺撇子。高位在低地址。**网络标准。**
2. **小端序 (Little-Endian)** 逆撇子。低位在低地址。**主机 (x86) 标准。**
3. **铁律:** 只要涉及**跨机器通信**(网络)或**跨系统文件交换**,必须显式调用 `ntohl` / `htonl` 系列函数,严禁依赖默认行为。

View File

@@ -0,0 +1,78 @@
---
tags: []
date created: 星期三, 十一月 19日 2025, 10:00:12 晚上
date modified: 星期三, 十一月 19日 2025, 10:03:20 晚上
---
### 一、 环形缓冲区Ring Buffer的原理与实时性作用
#### 1\. 概念定位
环形缓冲区Ring Buffer常称为 RX/TX 队列是位于网络接口卡NIC硬件和操作系统内核网络堆栈之间的 **FIFO 队列**。它由内核驱动在系统物理内存中分配和映射。
#### 2\. 实时性功能:数据减震器
在雷达高速数据流中Ring Buffer 充当了关键的**数据减震器Shock Absorber**。
- **工作机制:** 当 NIC 硬件通过 **DMA** (直接内存访问) 将接收到的数据包写入缓冲区时CPU 尚未开始处理。当 CPU 核心因执行您的信号处理算法或其他高优先级任务而产生瞬时延迟JitterRing Buffer 可以暂存 NIC 涌入的数据。
- **关键价值:** 它提供了**时间容忍度**。如果内核调度或中断处理短暂延迟,只要 Ring Buffer 还有空间,数据就不会因为\*\* 缓冲区溢出Buffer Overrun\*\* 而被网卡硬件丢弃。
### 二、 深度配置的权衡分析Trade-off Matrix
对于雷达信号处理这类**对数据完整性有 P0 级要求**的业务,我们需要在**内存消耗**和**可靠性**之间进行权衡。
| 维度 | 小缓冲区配置 (e.g., 256) | 大缓冲区配置 (e.g., 4096) | 结论与建议 |
| :--- | :--- | :--- | :--- |
| **数据完整性/丢包风险 (P0)** | **高。** 突发流量或内核微秒级延迟即可能导致 Ring Buffer 满溢,引发丢包。 | **极低。** 可缓冲更长的系统瞬态延迟(数十毫秒),显著降低数据丢包率。 | **P0 级需求:** 必须使用大缓冲区。 |
| **CPU 负载/效率** | 每次中断处理的数据批次小CPU 频繁唤醒,上下文切换开销大。 | 每次中断可批量处理更多数据描述符,有利于**批量处理Batch Processing**,整体降低 CPU 负载。 | **性能优化:** 大缓冲区有助于降低 CPU 开销。 |
| **内存开销与锁定** | 低。对系统内存消耗小。 | 高。Ring Buffer 空间需要使用**连续的物理内存**,并需要 `mlock()` 锁定以保证实时性。 | **系统资源充裕:** 系统总内存高达 62GiB且已解除了 `max locked memory` 限制,可承担大开销。|
| **平均延迟/抖动** | 理论平均延迟最低,但延迟抖动大(可能在零和丢包之间跳跃)。 | 略微增加平均延迟(数据包在 Buffer 中停留时间长),但**延迟抖动Jitter更小**。 | **实时性需求:** 相比绝对最低延迟,更需要低抖动和高可靠性。|
### 三、 基线确立与实施规范
基于上述权衡分析,为保障雷达数据采集的**可靠性**和**低丢包率**,应采取最大化配置策略。
#### 1\. 基线确定
- **RX Ring Buffer (接收):** **强制设定为 NIC 硬件支持的 RX 队列最大值。** (通常为 4096 或 8192)。
- **TX Ring Buffer (发送):** 保持默认或适度增加。雷达采集业务是接收主导,发送压力相对较小。
#### 2\. 关键实施命令
您需要使用 `ethtool` 工具来查询和配置 NIC 的 Ring Buffer 深度。
```bash
# 1. 查询当前 NIC 接口 (例如 ens4f1) 的 Ring Buffer 能力与当前值
# Max: 是硬件支持的最大深度Current: 是当前设置值
ethtool -g ens4f3
Ring parameters for ens4f3:
Pre-set maximums:
RX: 8192
RX Mini: 0
RX Jumbo: 0
TX: 8192
Current hardware settings:
RX: 512
RX Mini: 0
RX Jumbo: 0
TX: 512
# 2. 将 RX Ring Buffer 深度配置为最大值 (假设 Max 为 4096)
# 注意:配置值不能超过 ethtool -g 中显示的 Max: 值。
ethtool -G ens4f3 rx 8192
```
#### 3\. 额外提醒:内核内存
配置完成后,请确保您的内核已经通过 `sysctl` 优化了网络缓冲区大小,以匹配 JUMBO Frame 的需求。审计报告已确认该优化已实施:
```bash
sysctl net.core.rmem_max
net.core.rmem_max = 26214400 # 约 25MB足以支持 Ring Buffer 深度和 JUMBO Frame
```
**下一步讨论:**
在确立了 JUMBO Frame 和最大 Ring Buffer 的基线后,我们进入下一议题:`2.1.5 DMA 与内核旁路策略`
**提问:** 您是否倾向于使用**内核旁路Zero-copy技术**(如 DPDK 或 AF\_XDP以进一步降低 CPU 负载并消除内核开销,还是更倾向于使用**标准 UDP/IP 堆栈**以保证实现的简单和平台兼容性?(请考虑您当前 Kylin V10/GCC 7.3/Clang 18 的异构环境对新技术的兼容性挑战。)

View File

@@ -0,0 +1,166 @@
---
tags: []
aliases:
- TL;DR
date created: 星期三, 十一月 26日 2025, 9:31:51 晚上
date modified: 星期三, 十一月 26日 2025, 9:38:25 晚上
---
# 硬件 PTP 同步 + TSC 软时钟封装
## TL;DR
**硬件 PTP 同步 + TSC 软时钟封装** 是一种专为**微秒级低延迟系统**(如高频交易、雷达信号处理)设计的时间同步方案。
**核心逻辑**:利用 **硬件 PTP (精确时间协议)** 获取高精度的全球统一时间(解决“准”的问题),利用 CPU 的 **TSC (时间戳计数器)** 实现纳秒级极速读取(解决“快”的问题)。两者结合,消除了通过 PCIe 读取网卡时间的巨大延迟,使应用程序能在 **10-30 纳秒** 内获取误差小于 **1 微秒** 的绝对时间。
-----
## 1. 核心概念拆解
要理解这个方案,必须先理解它试图解决的矛盾:**精度与速度通常不可兼得**。
| 组件 | 全称 (中文) | 角色 | 优点 | 缺点 |
| :--- | :--- | :--- | :--- | :--- |
| **PTP** | Precision Time Protocol (精确时间协议) | **校准者** (类似于标准原子钟) | 精度极高 (硬件级可达亚微秒),全网统一。 | 读取慢。从 CPU 到网卡读取时间需要走 PCIe 总线,耗时 **\>500ns**,这在高频场景不可接受。 |
| **TSC** | Time Stamp Counter (时间戳计数器) | **计数者** (类似于手中的秒表) | 读取极快 (CPU 寄存器指令),耗时 **\~10ns**。 | 只有相对刻度 (开机后的 CPU 周期数),不知绝对时间;可能受 CPU 变频影响 (漂移)。 |
**封装 (Encapsulation)** 的本质就是:**用 PTP 定期校准 TSC应用程序只读 TSC。**
-----
## 2. 为什么需要这种方案?(底层原理)
通常获取时间使用操作系统提供的 `gettimeofday``clock_gettime`,但在极致性能场景下,这有两种开销:
1. **系统调用 (System Call) 开销:** 用户态切换到内核态,开销大。
2. **I/O 延迟:** 如果要获得最真实的 PTP 时间必须读取网卡上的寄存器。CPU 访问外设(网卡)必须经过 PCIe 总线,这比访问内存慢几个数量级。
**方案演进路线:**
NTP (毫秒级,软件同步) `->` 软件 PTP (微秒级,受 OS 抖动影响) `->` 硬件 PTP (亚微秒,但读取慢) `->` **硬件 PTP + TSC 软时钟 (亚微秒精度 + 纳秒级读取)**
-----
## 3. 实现流程与逻辑
该方案通常由一个后台守护进程Control Plane和一个前台高效接口Data Plane组成。
```mermaid
graph TD
A["GPS/北斗卫星"] -->|授时| B("PTP Master Server")
B -->|网络包| C["本地网卡 (NIC) PTP 硬件时钟"]
subgraph "操作系统内核/驱动"
C -->|定期读取/校准| D{"时钟同步算法"}
E["CPU TSC 寄存器"] -->|读取当前周期| D
D -->|计算转换参数 Scale & Offset| F["共享内存 (Shared Memory)"]
end
subgraph "用户态应用程序"
E -->|RDTSC 指令| G["读取 TSC"]
F -->|读取参数| G
G -->|公式计算| H["高精度绝对时间"]
end
```
1. **硬件层 (PTP)** 网卡硬件打标,确保获得的时间戳不包含操作系统调度的延迟。
2. **控制面 (Sync Driver)** 一个内核驱动或后台进程,每秒多次(如 10Hz同时读取 " 网卡 PTP 时间 " 和 "CPU TSC 计数值 "。
3. **计算面 (Calibration)** 计算线性关系 $T_{real} = TSC \times Scale + Offset$。
4. **数据面 (User App)** 应用程序直接通过汇编指令 `rdtsc` 读取寄存器,结合共享内存中的 $Scale$ 和 $Offset$ 计算时间。**全程无系统调用,无 I/O 操作。**
-----
## 4. 代码实现示例 (C++)
> **注意**:此代码仅为核心逻辑演示,生产环境需增加内存屏障 (Memory Barrier)、原子操作和 CPU 亲和性绑核处理。
```cpp
#include <cstdint>
#include <iostream>
#include <x86intrin.h> // for __rdtsc
// 模拟共享内存中的校准参数
struct ClockParams {
uint64_t base_ptp_ns; // 基准 PTP 时间 (纳秒)
uint64_t base_tsc; // 对应的 TSC 计数值
double mult; // 转换倍率 (1 TSC tick 对应多少 ns)
// 生产环境需要加入 sequence lock 避免读到更新中的数据
};
// 模拟:假设这是由后台同步线程更新的全局变量
volatile ClockParams g_params = { 1700000000000000000, 1000000, 0.4 };
class SoftClock {
public:
// 获取当前高精度时间 (纳秒)
static uint64_t NowNs() {
uint64_t current_tsc;
uint64_t current_time_ns;
// 1. 读取 CPU TSC 寄存器 (极快)
// 使用 __rdtscp 而非 __rdtsc 可以防止指令重排,保证测量准确性
unsigned int aux;
current_tsc = __rdtscp(&aux);
// 2. 线性变换: Time = BaseTime + (DeltaTSC * Multiplier)
// 实际工程中为避免浮点运算,通常使用定点数位移操作 (Shift)
uint64_t delta_tsc = 0;
// 简单的边界检查:防止 TSC 溢出或重置导致的巨大跳变
if (current_tsc >= g_params.base_tsc) {
delta_tsc = current_tsc - g_params.base_tsc;
} else {
// 错误处理TSC 回退(极少见,可能是多核不同步)
// 策略:返回上一次可信时间或降级调用系统时间
return 0; // 示例直接返回 0
}
current_time_ns = g_params.base_ptp_ns + (uint64_t)(delta_tsc * g_params.mult);
return current_time_ns;
}
};
int main() {
uint64_t t = SoftClock::NowNs();
if (t == 0) {
std::cerr << "Error: Clock instability detected." << std::endl;
return 1;
}
std::cout << "Current HW-Synced Time: " << t << " ns" << std::endl;
return 0;
}
```
-----
## 5. 方案对比
| 维度 | 仅用系统调用 (gettimeofday) | 纯硬件 PTP 读取 (Read NIC) | 硬件 PTP + TSC 封装 |
| :--- | :--- | :--- | :--- |
| **数据源** | OS 系统时间 (软) | 网卡寄存器 (硬) | **CPU 寄存器 (硬) + 算法校准** |
| **精度 (误差)** | 微秒级 (us) \~ 毫秒级 | 亚微秒 (\<1us) | **亚微秒 (\<1us)** |
| **读取耗时 (Latency)** | \~500 ns (系统调用开销) | \>500 ns (PCIe I/O 开销) | **\~10 - 20 ns (纯 CPU 计算)** |
| **性能损耗** | 中 (上下文切换) | 高 (阻塞总线) | **极低** |
| **典型场景** | 日志记录、普通业务 | 低频高精校准 | **高频交易、雷达信号处理** |
-----
## 6. 局限性与风险 (Self-Rebuttal)
虽然此方案是高性能领域的首选,但在以下场景会失效或需特殊处理:
- **TSC 漂移问题 (Non-Invariant TSC)** 在极老的 CPU 上TSC 频率会随 CPU 降频/超频而变化。
- *对策:* 必须确认 CPU 支持 `Invariant TSC` (现代 x86 CPU 基本都支持)。
- **多核不同步 (Core Sync)** 不同 CPU 核心的 TSC 寄存器初值可能不同。
- *对策:* 必须在 OS 启动时强制同步 TSC或在代码中计算每个核心的独立 Offset。
- **SMI (系统管理中断)** 硬件层面的中断(如散热控制)可能暂停 CPU导致 TSC 计数虽然在走,但实际业务逻辑停顿,造成“时间流逝但业务未动”的错觉。
- *对策:* 在 BIOS 中尽可能关闭所有省电和管理功能 (Performance Mode)。
- **虚拟机陷阱:** 在虚拟化环境 (VM) 中TSC 可能是模拟的,读取开销变大且精度下降。
- *对策:* 此方案主要适用于物理机 (Bare Metal) 或支持 `kvm-clock` 透传的环境。
## 7. 总结
- **痛点:** 网卡时间准但读得慢CPU 时间读得快但不准。
- **解法:** `PTP` 负责准,`TSC` 负责快,软件负责中间的 `转换逻辑`
- **核心路径:** 卫星 `->` 网卡 PTP `->` 驱动校准 `->` 共享内存 `->` 用户态 TSC 计算。

View File

@@ -0,0 +1,78 @@
---
tags:
aliases:
- 1. 宏观原理图:箱子与积木的错位
date created: 星期一, 十一月 24日 2025, 5:50:26 下午
date modified: 星期一, 十一月 24日 2025, 5:50:35 下午
---
# 1. 宏观原理图:箱子与积木的错位
想象 CPU 是一个强迫症收纳师,他手里有一排固定的收纳盒(缓存行),每个盒子长度固定是 64。
我们要存的数据(点迹) 是长度为 48 的积木条。
请看下面的图,展示了当我们把积木一条接一条紧挨着放进去时,发生了什么:
```mermaid
---
config:
theme: base
flowchart:
curve: linear
---
graph LR
%% 样式定义
classDef box fill:#e6f7ff,stroke:#1890ff,stroke-width:2px,stroke-dasharray: 5 5
classDef block1 fill:#ffccc7,stroke:#f5222d,stroke-width:2px
classDef block2 fill:#d9f7be,stroke:#52c41a,stroke-width:2px
subgraph Memory["内存空间 (连续摆放)"]
direction LR
subgraph Box1["收纳盒 1 (容量 64)"]
direction LR
A1["积木A (48)"]:::block1
B1["积木B 的头 (16)"]:::block2
end
subgraph Box2["收纳盒 2 (容量 64)"]
direction LR
B2["积木B 的身子 (32)"]:::block2
C1["…"]:::white
end
end
%% 解释连接
A1 -- 紧挨着 --> B1
B1 -- "⚠️ 惨遭腰斩 ⚠️" --> B2
```
# 2. 细节文字表述:为什么这很糟糕?
**场景还原:**
1. **强迫症规则**CPU 每次读取数据,必须**连盒带盖**端走整整一个“收纳盒”64 字节),不能只捏走里面的某一块。
2. **读取积木 A红色**
- CPU 伸手端走 **收纳盒 1**
- 积木 A 完整地在盒子里。
- **耗时**1 次搬运。**(快)**
3. **读取积木 B绿色**
- CPU 端走 **收纳盒 1**,拿到了积木 B 的**头**。
- CPU 发现身子没了,只能再去端走 **收纳盒 2**,拿到积木 B 的**身子**。
- 然后 CPU 还得在手里把这两段拼起来。
- **耗时**2 次搬运 + 拼接时间。**(慢!)**
# 3. 结论与解决方案
- **问题核心**因为数据的尺寸48不能被盒子的尺寸64整除导致后续的数据像“跨栏”一样骑在两个盒子的边界上。这叫**跨缓存行Cache Line Split**。
- **我们的方案(填充 Padding**
- 既然 48 放不进 64 很尴尬,我们就在每个积木后面**硬塞 16 块没用的泡沫Padding**。
- 把积木强行撑大到 **64**
- **结果**虽然浪费了空间但现在每个盒子正好放一个积木。CPU 拿任何积木都只需要搬 **1 次**盒子。
这就是我们为了极致性能所做的妥协:**用空间换时间**。