首页/目录/全部文章

全部文章

八个专题的源码、算法与协议笔记都在这里。

笔记列表

PeeringState 状态管理机制与关联关系分析

PeeringState 状态管理机制与关联关系分析

1. 概述

PeeringState 是 Ceph OSD 中 PG(Placement Group)对等(Peering)过程的核心状态机实现。它使用 boost::statechart 库实现了一个层次化的状态机,负责管理 PG 从初始化到激活、从 Peering 到 Active 的完整生命周期。

1.1 核心职责

  • 状态转换管理:管理 PG 在不同状态间的转换
  • Peering 协调:协调主副本和副本之间的信息交换
  • 恢复触发:触发和协调恢复(Recovery)和回填(Backfill)流程
  • 事件处理:处理 OSDMap 变化、消息接收等事件

1.2 设计特点

  • 层次化状态:使用 boost::statechart 的层次状态机
  • 事件驱动:通过事件触发状态转换
  • 回调机制:通过 PeeringListener 与上层(PG)交互

2. 状态机架构

2.1 状态机层次结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
PeeringMachine (状态机根)

├── Initial (初始状态)
│ └── 转换到 Reset

├── Reset (重置状态)
│ └── 转换到 Started

├── Started (已启动状态)
│ ├── Start (启动子状态)
│ │ ├── 转换到 Primary (主副本)
│ │ └── 转换到 Stray (游离副本)
│ │
│ ├── Primary (主副本状态)
│ │ ├── WaitActingChange (等待 Acting 变更)
│ │ ├── Peering (对等状态)
│ │ │ ├── GetInfo (获取信息)
│ │ │ ├── GetLog (获取日志)
│ │ │ ├── GetMissing (获取缺失对象)
│ │ │ ├── WaitUpThru (等待 UpThru)
│ │ │ └── Incomplete (不完整)
│ │ │
│ │ └── Active (激活状态)
│ │ ├── Activating (激活中)
│ │ ├── Clean (干净状态)
│ │ ├── Recovered (已恢复)
│ │ ├── Backfilling (回填中)
│ │ ├── WaitRemoteBackfillReserved (等待远程回填预留)
│ │ ├── WaitLocalBackfillReserved (等待本地回填预留)
│ │ ├── NotBackfilling (未回填)
│ │ ├── NotRecovering (未恢复)
│ │ ├── Recovering (恢复中)
│ │ ├── WaitRemoteRecoveryReserved (等待远程恢复预留)
│ │ └── WaitLocalRecoveryReserved (等待本地恢复预留)
│ │
│ ├── ReplicaActive (副本激活状态)
│ │ ├── RepNotRecovering (副本未恢复)
│ │ ├── RepRecovering (副本恢复中)
│ │ ├── RepWaitBackfillReserved (副本等待回填预留)
│ │ └── RepWaitRecoveryReserved (副本等待恢复预留)
│ │
│ ├── Stray (游离状态)
│ │
│ └── ToDelete (待删除状态)
│ ├── WaitDeleteReserved (等待删除预留)
│ └── Deleting (删除中)

└── Crashed (崩溃状态)

2.2 状态机类定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class PeeringMachine : public boost::statechart::state_machine< 
PeeringMachine,
Initial // 初始状态
> {
PeeringState *state; // 关联的 PeeringState
PGStateHistory *state_history; // 状态历史记录
CephContext *cct; // Ceph 上下文
spg_t spgid; // PG ID
DoutPrefixProvider *dpp; // 日志前缀提供者
PeeringListener *pl; // 监听器(通常是 PG)

utime_t event_time; // 事件处理时间
uint64_t event_count; // 事件计数
};

3. 状态定义与转换

3.1 主要状态说明

Initial(初始状态)

  • 作用:状态机的起始状态
  • 转换
    • InitializeReset
    • MNotifyRecPrimary(如果收到 Notify 消息)
    • MInfoRec / MLogRecStray(如果收到 Info/Log 消息)

Reset(重置状态)

  • 作用:重置 PG 状态,准备新的 Peering 过程
  • 转换
    • AdvMap → 可能触发新的 Peering
    • ActMap → 激活 Map

Started(已启动状态)

  • 作用:PG 已启动,等待确定角色(主副本/副本/游离)
  • 子状态Start
  • 转换
    • MakePrimaryPrimary
    • MakeStrayStray

Primary(主副本状态)

  • 作用:当前 OSD 是主副本
  • 子状态PeeringActive
  • 处理事件
    • ActMap:激活 Map
    • MNotifyRec:处理副本 Notify 消息

Peering(对等状态)

  • 作用:主副本正在与副本进行对等,交换信息
  • 子状态
    • GetInfo:获取副本信息
    • GetLog:获取副本日志
    • GetMissing:获取缺失对象信息
    • WaitUpThru:等待 UpThru
    • Incomplete:不完整状态
  • 转换
    • ActivateActive(对等完成,激活)

Active(激活状态)

  • 作用:PG 已激活,可以处理客户端请求
  • 子状态
    • Activating:激活中
    • Clean:干净状态(所有数据一致)
    • Recovered:已恢复
    • Backfilling:回填中
    • Recovering:恢复中
    • 各种等待预留的状态
  • 处理事件
    • ActMap:激活 Map
    • AdvMap:推进 Map
    • MInfoRec:处理 Info 消息
    • MLogRec:处理 Log 消息
    • DoRecovery:开始恢复
    • Backfilled:回填完成

ReplicaActive(副本激活状态)

  • 作用:副本已激活
  • 子状态
    • RepNotRecovering:副本未恢复
    • RepRecovering:副本恢复中
    • RepWaitBackfillReserved:等待回填预留
    • RepWaitRecoveryReserved:等待恢复预留

Stray(游离状态)

  • 作用:PG 不在 acting/up set 中,等待删除

ToDelete(待删除状态)

  • 作用:PG 标记为待删除
  • 子状态
    • WaitDeleteReserved:等待删除预留
    • Deleting:删除中

3.2 状态转换示例

正常启动流程

1
2
3
4
5
6
7
8
9
10
Initial
→ (Initialize) → Reset
→ (AdvMap) → Started/Start
→ (MakePrimary) → Primary/Peering/GetInfo
→ (GotInfo) → GetLog
→ (GotLog) → GetMissing
→ (GotMissing) → Active/Activating
→ (ActivateCommitted) → Active/Recovering
→ (RecoveryDone) → Active/Recovered
→ (GoClean) → Active/Clean

恢复流程

1
2
3
4
5
6
Active/Clean
→ (DoRecovery) → Active/WaitLocalRecoveryReserved
→ (LocalRecoveryReserved) → Active/WaitRemoteRecoveryReserved
→ (AllRecoveryReserved) → Active/Recovering
→ (RecoveryDone) → Active/Recovered
→ (GoClean) → Active/Clean

4. 事件系统

4.1 事件类型

Map 相关事件

  • AdvMap:OSDMap 推进事件

    • 触发条件:OSDMap epoch 增加
    • 处理:更新 up/acting set,可能触发新的 Peering
  • ActMap:OSDMap 激活事件

    • 触发条件:OSDMap 激活
    • 处理:激活 Map,更新状态

Peering 相关事件

  • MNotifyRec:收到 Notify 消息

    • 来源:副本发送
    • 处理:更新副本信息
  • MInfoRec:收到 Info 消息

    • 来源:副本发送
    • 处理:更新副本 PG 信息
  • MLogRec:收到 Log 消息

    • 来源:副本发送
    • 处理:更新副本日志
  • MQuery:收到 Query 消息

    • 来源:主副本查询
    • 处理:响应查询请求

激活相关事件

  • Activate:激活事件

    • 触发条件:Peering 完成
    • 处理:激活 PG
  • ActivateCommitted:激活已提交

    • 触发条件:激活事务已提交
    • 处理:完成激活流程
  • AllReplicasActivated:所有副本已激活

    • 触发条件:所有副本都激活
    • 处理:进入 Clean 状态

恢复相关事件

  • DoRecovery:开始恢复

    • 触发条件:检测到需要恢复的对象
    • 处理:请求恢复资源,开始恢复
  • RecoveryDone:恢复完成

    • 触发条件:所有对象恢复完成
    • 处理:进入 Recovered 状态
  • Backfilled:回填完成

    • 触发条件:回填完成
    • 处理:进入 Recovered 状态

其他事件

  • QueryState:查询状态
  • QueryUnfound:查询未找到的对象
  • IntervalFlush:区间刷新
  • RenewLease:续约租约
  • CheckReadable:检查可读性

4.2 事件处理机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 事件处理示例
boost::statechart::result PeeringState::Active::react(const AdvMap& advmap) {
// 1. 检查是否需要重启 Peering
if (should_restart_peering(...)) {
return forward_event(); // 转发事件到父状态
}

// 2. 处理 Map 变化
pl->on_active_advmap(advmap.osdmap);

// 3. 更新状态
pl->publish_stats_to_osd();

return forward_event(); // 继续处理
}

5. 与外部组件的关联

5.1 与 PG 的关联

PeeringState 通过 PeeringListener 接口与 PG 交互:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct PeeringListener {
// 准备写入
virtual void prepare_write(...) = 0;

// 恢复回调
virtual void on_local_recover(...) = 0;
virtual void on_global_recover(...) = 0;
virtual void on_peer_recover(...) = 0;

// 状态变更回调
virtual void on_activate(...) = 0;
virtual void on_active_advmap(...) = 0;

// 消息发送
virtual void send_cluster_message(...) = 0;

// 事务提交
virtual void queue_transaction(...) = 0;

// 统计发布
virtual void publish_stats_to_osd() = 0;
};

关联方式

  • PG 实现 PeeringListener 接口
  • PeeringState 通过 pl 指针调用回调
  • 所有回调在 PG 锁保护下执行

5.2 与 OSDMap 的关联

1
2
3
4
5
6
7
8
9
10
11
12
class PeeringState {
OSDMapRef osdmap_ref; // 当前 OSDMap 引用

// 更新 OSDMap
void update_osdmap_ref(OSDMapRef newmap);

// 处理 Map 推进
void advance_map(OSDMapRef osdmap, ...);

// 处理 Map 激活
void activate_map(PeeringCtx &rctx);
};

关联方式

  • PeeringState 持有 OSDMapRef
  • AdvMap / ActMap 事件携带新的 OSDMap
  • 状态转换时更新 OSDMap 引用

5.3 与 PGLog 的关联

1
2
3
4
5
6
7
8
9
class PeeringState {
PGLog pg_log; // PG 日志

// 日志操作
void merge_log(...); // 合并日志
void rewind_divergent_log(...); // 回退分歧日志
void append_log(...); // 追加日志
void add_log_entry(...); // 添加日志条目
};

关联方式

  • PeeringState 直接管理 pg_log
  • Peering 过程中比对和合并日志
  • 日志用于确定缺失对象

5.4 与 MissingLoc 的关联

1
2
3
4
5
6
7
class PeeringState : public MissingLoc::MappingInfo {
MissingLoc missing_loc; // 缺失对象定位器

// 缺失对象管理
void build_might_have_unfound(); // 构建可能包含未找到对象的集合
void discover_all_missing(...); // 发现所有缺失对象
};

关联方式

  • PeeringState 继承 MissingLoc::MappingInfo
  • missing_loc 用于定位缺失对象的位置
  • 恢复流程使用 missing_loc 确定恢复源

5.5 与 PeeringCtx 的关联

1
2
3
4
5
6
7
8
9
struct PeeringCtx : BufferedRecoveryMessages {
ObjectStore::Transaction transaction; // 事务
HBHandle* handle; // 心跳句柄

// 消息缓冲
void send_notify(...);
void send_query(...);
void send_info(...);
};

关联方式

  • 每个状态转换使用 PeeringCtx 管理上下文
  • transaction 用于批量提交状态变更
  • BufferedRecoveryMessages 用于缓冲消息

5.6 与 OSDService 的关联

通过 PeeringListener 间接关联:

1
2
3
4
5
6
7
8
9
10
11
12
// PG 实现 PeeringListener
class PG : public PeeringState::PeeringListener {
OSDService *osd; // OSD 服务

void send_cluster_message(...) {
osd->send_message_osd_cluster(...);
}

void queue_transaction(...) {
osd->store->queue_transaction(...);
}
};

6. 状态管理机制

6.1 状态进入/退出

每个状态都有 enter()exit() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct Active : boost::statechart::state< Active, Primary, Activating > {
explicit Active(my_context ctx) {
// 进入状态时的初始化
context< PeeringMachine >().log_enter(state_name);

// 激活 PG
ps->activate(transaction, epoch, ctx);

// 初始化恢复状态
ps->blocked_by.clear();
}

void exit() {
// 退出状态时的清理
context< PeeringMachine >().log_exit(state_name, enter_time);

// 清除状态标志
ps->state_clear(PG_STATE_ACTIVE);
}
};

6.2 状态标志管理

PeeringState 使用位标志管理 PG 状态:

1
2
3
4
5
6
7
8
9
10
11
12
class PeeringState {
pg_state_t state; // PG 状态标志

// 设置状态标志
void state_set(pg_state_t s) { state |= s; }

// 清除状态标志
void state_clear(pg_state_t s) { state &= ~s; }

// 检查状态标志
bool state_test(pg_state_t s) const { return state & s; }
};

状态标志包括

  • PG_STATE_ACTIVE:激活
  • PG_STATE_PEERED:已对等
  • PG_STATE_CLEAN:干净
  • PG_STATE_DEGRADED:降级
  • PG_STATE_RECOVERING:恢复中
  • PG_STATE_BACKFILLING:回填中
  • PG_STATE_UNDERSIZED:大小不足
  • 等等

6.3 状态历史记录

1
2
3
4
5
6
7
8
9
10
11
12
class PGStateHistory {
struct StateEntry {
const char *state_name;
utime_t enter_time;
utime_t exit_time;
};

std::list<StateEntry> history;

void log_enter(const char *name);
void log_exit(const char *name, utime_t duration);
};

用途

  • 记录状态转换历史
  • 性能统计(每个状态的停留时间)
  • 调试和问题排查

6.4 事件处理流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 1. 接收事件
void PeeringState::handle_event(const Event &evt, PeeringCtx *ctx) {
start_handle(ctx);
machine.process_event(evt); // 状态机处理事件
end_handle();
}

// 2. 状态机路由事件
boost::statechart::result State::react(const Event &evt) {
// 处理事件
// 返回转换结果
return transit<NextState>(); // 转换到下一状态
// 或
return discard_event(); // 丢弃事件
// 或
return forward_event(); // 转发到父状态
}

7. 关键状态转换场景

7.1 OSDMap 变化场景

1
2
3
4
5
6
7
8
9
10
11
12
1. OSDMap 更新(AdvMap 事件)

2. 检查是否需要重启 Peering

3. 如果需要:
- 转换到 Reset
- 开始新的 Peering 流程

4. 如果不需要:
- 更新对等节点信息
- 移除已下线的节点
- 继续当前状态

7.2 Peering 完成场景

1
2
3
4
5
6
7
8
9
10
11
1. 收集所有副本信息(GetInfo)

2. 获取权威日志(GetLog)

3. 计算缺失对象(GetMissing)

4. 选择 Acting Set(choose_acting)

5. 发送 Activate 事件

6. 转换到 Active 状态

7.3 恢复触发场景

1
2
3
4
5
6
7
8
9
10
11
12
13
1. 检测到缺失对象(needs_recovery)

2. 发送 DoRecovery 事件

3. 请求恢复资源(WaitLocalRecoveryReserved)

4. 等待远程资源(WaitRemoteRecoveryReserved)

5. 开始恢复(Recovering)

6. 恢复完成(RecoveryDone)

7. 转换到 Recovered 状态

7.4 回填触发场景

1
2
3
4
5
6
7
8
9
10
11
12
13
1. 检测到需要回填(needs_backfill)

2. 发送 RequestBackfill 事件

3. 请求本地资源(WaitLocalBackfillReserved)

4. 等待远程资源(WaitRemoteBackfillReserved)

5. 开始回填(Backfilling)

6. 回填完成(Backfilled)

7. 转换到 Recovered 状态

8. 状态同步机制

8.1 主副本与副本的同步

主副本

  • 发送 MOSDPGInfo2 消息(包含 PG 信息)
  • 发送 MOSDPGLog 消息(包含日志)
  • 接收副本的 MOSDPGNotify2 消息

副本

  • 发送 MOSDPGNotify2 消息(通知主副本)
  • 接收主副本的 MOSDPGInfo2MOSDPGLog 消息
  • 根据主副本的信息更新本地状态

8.2 状态一致性保证

  1. 版本控制

    • 使用 eversion_t 管理对象版本
    • 日志条目包含版本信息
    • 通过版本比对确定一致性
  2. 事务提交

    • 状态变更通过事务提交
    • 事务提交后才真正生效
    • 支持回滚机制
  3. 消息顺序

    • 使用 epoch 确保消息顺序
    • 丢弃过期的消息
    • 保证状态转换的原子性

9. 性能优化

9.1 状态转换优化

  • 批量处理:多个状态变更批量提交
  • 延迟激活:某些状态转换延迟执行
  • 资源预留:提前预留恢复/回填资源

9.2 消息缓冲

1
2
3
4
5
6
struct BufferedRecoveryMessages {
std::map<int, std::vector<MessageRef>> message_map;

// 缓冲消息,等待事务提交后发送
void send_osd_message(int target, MessageRef m);
};

优势

  • 减少消息发送次数
  • 保证消息与状态的一致性
  • 提高性能

9.3 状态历史统计

1
2
3
4
5
6
class PGStateHistory {
// 记录每个状态的停留时间
// 用于性能分析和优化
void log_enter(const char *name);
void log_exit(const char *name, utime_t duration);
};

10. 错误处理

10.1 状态机错误

  • Crashed 状态:处理无法恢复的错误
  • 事件丢弃:丢弃无法处理的事件
  • 状态回退:某些错误可能导致状态回退

10.2 超时处理

  • Peering 超时:如果 Peering 长时间未完成,可能触发超时
  • 恢复超时:恢复操作超时处理
  • 消息超时:等待消息超时处理

10.3 异常恢复

  • 状态不一致:检测并修复状态不一致
  • 日志损坏:处理日志损坏情况
  • 数据丢失:处理数据丢失情况

11. 总结

11.1 核心机制

  1. 层次化状态机:使用 boost::statechart 实现
  2. 事件驱动:通过事件触发状态转换
  3. 回调机制:通过 PeeringListener 与上层交互
  4. 事务管理:状态变更通过事务提交
  5. 消息缓冲:优化消息发送

11.2 关键关联

  • PG:通过 PeeringListener 接口关联
  • OSDMap:持有引用,响应 Map 变化
  • PGLog:直接管理,用于 Peering
  • MissingLoc:继承 MappingInfo,定位缺失对象
  • PeeringCtx:管理状态转换上下文
  • OSDService:通过 PG 间接关联

11.3 设计优势

  1. 清晰的状态管理:层次化状态机使状态转换清晰
  2. 解耦设计:通过接口与上层解耦
  3. 可扩展性:易于添加新状态和事件
  4. 可维护性:状态转换逻辑集中管理
  5. 可调试性:状态历史记录便于调试

11.4 相关文件

  • PeeringState.h/cc:状态机实现
  • PGPeeringEvent.h/cc:事件定义
  • PGStateUtils.h/cc:状态工具函数
  • PG.cc:PG 实现 PeeringListener
  • OSD.cc:OSD 触发状态机事件

Intel-IPSec-MB子模块作用与设计原理分析文档

Intel-IPSec-MB子模块作用与设计原理分析文档

目录

  1. 概述
  2. 模块架构
  3. 核心功能模块
  4. 多缓冲区设计原理
  5. 性能优化策略
  6. 在SPDK中的应用

概述

Intel-IPSec-MB简介

Intel Multi-Buffer Crypto for IPsec Library (Intel-IPSec-MB) 是Intel开发的高性能IPSec加密库,专门针对Intel处理器优化的软件实现。该库提供了IPSec核心加密处理功能,在Intel处理器上提供业界领先的性能。

核心特性

  • 多缓冲区处理: 同时处理多个加密作业,提高吞吐量
  • SIMD优化: 充分利用SSE、AVX、AVX2、AVX512指令集
  • 硬件加速: 利用AES-NI、PCLMULQDQ等硬件指令
  • 乱序执行: 支持乱序(out-of-order)调度,提高CPU利用率
  • 多种算法: 支持多种加密和完整性算法

主要功能

  1. 加密算法: AES-GCM, AES-CBC, AES-CTR, AES-ECB, DES, 3DES等
  2. 完整性算法: HMAC-SHA1/224/256/384/512, AES-XCBC, AES-CMAC, AES-GMAC等
  3. 组合模式: 加密+完整性验证的组合操作
  4. 无线算法: KASUMI, ZUC, SNOW3G (3GPP标准)

模块架构

目录结构

1
2
3
4
5
6
7
8
9
10
11
intel-ipsec-mb/
├── sse/ # SSE优化实现
├── avx/ # AVX优化实现
├── avx2/ # AVX2优化实现
├── avx512/ # AVX512优化实现
├── no-aesni/ # 无AES-NI的软件实现
├── include/ # 内部头文件
├── LibTestApp/ # 测试应用
├── LibPerfApp/ # 性能测试应用
├── intel-ipsec-mb.h # 公共API头文件
└── mb_mgr_code.h # 多缓冲区管理器代码

架构设计特点

  1. 多版本实现: 每个算法提供多个SIMD版本

    • SSE版本: 使用SSE4.1指令集
    • AVX版本: 使用AVX指令集
    • AVX2版本: 使用AVX2指令集
    • AVX512版本: 使用AVX512指令集
    • VAES版本: 使用VAES和VPCLMULQDQ扩展(AVX512)
  2. 运行时选择: 根据CPU特性自动选择最优实现

  3. 汇编优化: 关键路径使用汇编语言实现


核心功能模块

1. 多缓冲区管理器 (Multi-Buffer Manager)

作用

多缓冲区管理器是Intel-IPSec-MB的核心组件,负责管理多个加密作业的调度和执行。

核心概念

Lane (通道): 每个lane可以处理一个独立的加密作业

  • SSE: 4个lanes (HMAC-SHA1/256)
  • AVX: 4-8个lanes
  • AVX2: 8-16个lanes
  • AVX512: 16-32个lanes

Job (作业): 表示一个加密/完整性验证任务

关键数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 多缓冲区管理器
struct MB_MGR {
// AES乱序调度器
MB_MGR_AES_OOO aes_ooo;

// HMAC乱序调度器
MB_MGR_HMAC_SHA_1_OOO hmac_sha_1_ooo;
MB_MGR_HMAC_SHA_256_OOO hmac_sha_256_ooo;
MB_MGR_HMAC_SHA_512_OOO hmac_sha_512_ooo;

// 作业数组
JOB_AES_HMAC jobs[MAX_JOBS];

// 函数指针(根据CPU特性设置)
get_next_job_t get_next_job;
submit_job_t submit_job;
flush_job_t flush_job;
get_completed_job_t get_completed_job;
};

// AES乱序调度器
struct MB_MGR_AES_OOO {
AES_ARGS args; // AES参数
uint16_t lens[16]; // 每个lane的数据长度
uint64_t unused_lanes; // 未使用的lane列表
JOB_AES_HMAC *job_in_lane[16]; // 每个lane的作业指针
uint64_t num_lanes_inuse; // 正在使用的lane数量
};

// 作业结构
struct JOB_AES_HMAC {
const void *aes_enc_key_expanded; // 加密密钥(已扩展)
const void *aes_dec_key_expanded; // 解密密钥(已扩展)
const uint8_t *src; // 源数据
uint8_t *dst; // 目标数据
uint64_t msg_len_to_cipher_in_bytes; // 加密数据长度
uint64_t msg_len_to_hash_in_bytes; // 哈希数据长度
const uint8_t *iv; // 初始化向量
uint8_t *auth_tag_output; // 认证标签输出
JOB_STS status; // 作业状态
JOB_CIPHER_MODE cipher_mode; // 加密模式
JOB_HASH_ALG hash_alg; // 哈希算法
// ...
};

关键API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 分配多缓冲区管理器
MB_MGR *alloc_mb_mgr(uint64_t flags);

// 获取下一个可用作业
JOB_AES_HMAC *get_next_job(MB_MGR *state);

// 提交作业
JOB_AES_HMAC *submit_job(MB_MGR *state);

// 刷新作业(强制完成)
JOB_AES_HMAC *flush_job(MB_MGR *state);

// 获取已完成的作业
JOB_AES_HMAC *get_completed_job(MB_MGR *state);

工作流程

1
2
3
4
5
6
1. 获取作业: job = get_next_job(mgr)
2. 填充作业: 设置job的各个字段(密钥、数据、IV等)
3. 提交作业: submit_job(mgr) // 将job添加到队列
4. 处理作业: 库内部使用SIMD指令并行处理多个作业
5. 获取结果: completed_job = get_completed_job(mgr)
6. 检查状态: if (completed_job->status == STS_COMPLETED)

2. 加密算法模块

支持的加密算法

AES系列:

  • AES-GCM: Galois/Counter Mode(认证加密)
  • AES-CBC: Cipher Block Chaining
  • AES-CTR: Counter Mode
  • AES-ECB: Electronic Codebook
  • AES-CCM: Counter with CBC-MAC

其他算法:

  • DES/3DES: 数据加密标准(遗留支持)
  • KASUMI-F8: 3GPP加密算法
  • ZUC-EEA3: 中国标准加密算法
  • SNOW3G-UEA2: 3GPP加密算法

AES-GCM实现原理

GCM模式特点:

  • 同时提供加密和认证
  • 使用CTR模式进行加密
  • 使用GHASH进行认证

关键数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// GCM密钥数据
struct gcm_key_data {
uint8_t expanded_keys[16 * 15]; // 扩展密钥
union {
// SSE/AVX版本
struct {
uint8_t shifted_hkey[16 * 8]; // 预计算的哈希密钥
uint8_t shifted_hkey_k[16 * 8]; // Karatsuba乘法密钥
} sse_avx;
// AVX2/AVX512版本
struct {
uint8_t shifted_hkey[16 * 8];
} avx2_avx512;
// VAES版本
struct {
uint8_t shifted_hkey[16 * 48]; // 更多预计算密钥
} vaes_avx512;
} ghash_keys;
};

// GCM上下文
struct gcm_context_data {
uint8_t aad_hash[16]; // AAD哈希值
uint64_t aad_length; // AAD长度
uint64_t in_length; // 输入长度
uint8_t current_counter[16]; // 当前计数器
// ...
};

GCM处理流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1. 密钥扩展: aes_gcm_pre_128(key, key_data)
- 扩展AES密钥
- 预计算GHASH密钥

2. 初始化: aes_gcm_init(key_data, ctx, iv, aad, aad_len)
- 初始化计数器
- 处理AAD(Additional Authenticated Data)

3. 加密/解密: aes_gcm_enc_dec_update(key_data, ctx, dst, src, len)
- CTR模式加密/解密
- 同时更新GHASH

4. 完成: aes_gcm_enc_dec_finalize(key_data, ctx, auth_tag, tag_len)
- 生成认证标签

性能优化:

  • 预计算: 预计算GHASH所需的密钥表
  • 并行处理: 一次处理多个块(by8表示一次8个块)
  • 硬件加速: 使用PCLMULQDQ指令加速GF(2^128)乘法

3. 完整性算法模块

支持的完整性算法

HMAC系列:

  • HMAC-SHA1-96: SHA1的HMAC,96位截断
  • HMAC-SHA2-224/256/384/512: SHA2系列的HMAC

AES系列:

  • AES-XCBC-96: AES扩展CBC-MAC
  • AES-CMAC-96: AES CMAC
  • AES-GMAC: GCM的认证部分

其他:

  • HMAC-MD5-96: MD5的HMAC(遗留支持)
  • KASUMI-F9: 3GPP完整性算法
  • ZUC-EIA3: 中国标准完整性算法
  • SNOW3G-UIA2: 3GPP完整性算法

HMAC实现原理

HMAC算法:

1
2
3
4
5
6
7
8
HMAC(K, m) = H((K ⊕ opad) || H((K ⊕ ipad) || m))

其中:
- K: 密钥
- m: 消息
- H: 哈希函数(SHA1/SHA2等)
- opad: 外部填充(0x5c重复)
- ipad: 内部填充(0x36重复)

多缓冲区HMAC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// HMAC-SHA1乱序调度器
struct MB_MGR_HMAC_SHA_1_OOO {
SHA1_ARGS args; // SHA1参数数组
uint16_t lens[16]; // 每个lane的长度
uint64_t unused_lanes; // 未使用的lane
HMAC_SHA1_LANE_DATA ldata[16]; // 每个lane的数据
uint32_t num_lanes_inuse; // 使用的lane数
};

// Lane数据
struct HMAC_SHA1_LANE_DATA {
uint8_t extra_block[2 * 64 + 8]; // 额外块(用于填充)
JOB_AES_HMAC *job_in_lane; // 作业指针
uint8_t outer_block[64]; // 外部块
uint32_t outer_done; // 外部块处理标志
// ...
};

处理流程:

1
2
3
4
5
6
7
8
9
10
11
1. 提交作业: submit_job_hmac_sha1(mgr)
- 将作业分配到可用lane
- 处理内部哈希(K ⊕ ipad || m)

2. 刷新作业: flush_job_hmac_sha1(mgr)
- 完成所有lane的处理
- 处理外部哈希(K ⊕ opad || H_internal)
- 生成认证标签

3. 获取结果: get_completed_job(mgr)
- 返回已完成的作业

性能优化:

  • 并行处理: 同时处理多个HMAC作业
  • 硬件加速: 使用SHA-NI指令(如果可用)
  • 批量处理: 一次处理多个块

4. 组合操作 (Chained Operations)

作用

支持加密和完整性验证的组合操作,这是IPSec的常见需求。

组合模式

CIPHER_HASH: 先加密后哈希

1
加密 → 完整性验证

HASH_CIPHER: 先哈希后加密

1
完整性验证 → 加密

实现原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 作业结构中的组合字段
struct JOB_AES_HMAC {
JOB_CHAIN_ORDER chain_order; // 组合顺序
uint64_t cipher_start_src_offset_in_bytes; // 加密起始偏移
uint64_t hash_start_src_offset_in_bytes; // 哈希起始偏移
// ...
};

// 处理流程
void process_chained_job(JOB_AES_HMAC *job)
{
if (job->chain_order == CIPHER_HASH) {
// 1. 先加密
aes_encrypt(job);
// 2. 后哈希(对加密后的数据)
hmac_compute(job);
} else {
// 1. 先哈希
hmac_compute(job);
// 2. 后加密
aes_encrypt(job);
}
}

多缓冲区设计原理

核心思想

多缓冲区技术通过同时处理多个独立的加密作业来提高CPU利用率,充分利用SIMD指令集的并行能力。

设计原理

1. Lane分配机制

原理: 将多个作业分配到不同的lane,使用SIMD指令并行处理。

实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 获取下一个可用lane
int get_unused_lane(MB_MGR_AES_OOO *mgr)
{
// unused_lanes是一个位图或列表
// 每个nibble/byte表示一个lane索引
int lane = extract_lane(mgr->unused_lanes);
mgr->unused_lanes = remove_lane(mgr->unused_lanes, lane);
return lane;
}

// 分配作业到lane
void assign_job_to_lane(MB_MGR_AES_OOO *mgr, JOB_AES_HMAC *job, int lane)
{
mgr->job_in_lane[lane] = job;
mgr->lens[lane] = job->msg_len_to_cipher_in_bytes;
mgr->num_lanes_inuse++;
}

2. 批量处理

原理: 当lane填满时,使用SIMD指令批量处理所有lane。

示例: AES-CBC加密(AVX版本,8个lane)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void aes_cbc_enc_128_x8(MB_MGR_AES_OOO *mgr)
{
// 加载8个lane的IV
__m256i iv0 = _mm256_loadu_si256((__m256i*)mgr->args.IV[0]);
__m256i iv1 = _mm256_loadu_si256((__m256i*)mgr->args.IV[1]);
// ...

// 加载8个lane的数据
__m256i data0 = _mm256_loadu_si256((__m256i*)mgr->args.in[0]);
// ...

// 并行加密(使用AES-NI指令)
__m256i encrypted0 = _mm256_aesenc_epi128(data0, key);
// ...

// 存储结果
_mm256_storeu_si256((__m256i*)mgr->args.out[0], encrypted0);
// ...
}

3. 乱序执行

原理: 作业可以乱序完成,不需要按照提交顺序。

优势:

  • 提高CPU利用率
  • 减少等待时间
  • 适应不同大小的作业

实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 提交作业(不立即执行)
JOB_AES_HMAC *submit_job(MB_MGR *mgr)
{
JOB_AES_HMAC *job = get_next_job(mgr);

// 分配lane
int lane = get_unused_lane(&mgr->aes_ooo);
assign_job_to_lane(&mgr->aes_ooo, job, lane);

// 如果lane满了,批量处理
if (mgr->aes_ooo.num_lanes_inuse >= NUM_LANES) {
process_batch(mgr);
}

return job;
}

// 处理批次
void process_batch(MB_MGR_AES_OOO *mgr)
{
// 使用SIMD指令并行处理所有lane
aes_cbc_enc_128_x8(mgr);

// 标记作业完成
for (int i = 0; i < NUM_LANES; i++) {
if (mgr->job_in_lane[i]) {
mgr->job_in_lane[i]->status = STS_COMPLETED_AES;
mgr->job_in_lane[i] = NULL;
}
}

// 清空lane
mgr->num_lanes_inuse = 0;
mgr->unused_lanes = ALL_LANES_FREE;
}

4. 刷新机制

原理: 强制处理所有待处理的作业,即使lane未满。

实现:

1
2
3
4
5
6
7
8
9
10
11
12
JOB_AES_HMAC *flush_job(MB_MGR *mgr)
{
// 处理所有部分填充的lane
if (mgr->aes_ooo.num_lanes_inuse > 0) {
// 用NULL填充空lane
fill_empty_lanes(mgr);
// 处理批次
process_batch(mgr);
}

return get_completed_job(mgr);
}

性能优化策略

1. SIMD向量化

原理

使用SIMD指令同时处理多个数据元素。

实现层次

SSE (4 lanes):

1
2
3
4
5
6
7
8
9
10
11
// 一次处理4个128位数据
__m128i data0 = _mm_loadu_si128((__m128i*)src[0]);
__m128i data1 = _mm_loadu_si128((__m128i*)src[1]);
__m128i data2 = _mm_loadu_si128((__m128i*)src[2]);
__m128i data3 = _mm_loadu_si128((__m128i*)src[3]);

// 并行AES加密
__m128i enc0 = _mm_aesenc_si128(data0, key);
__m128i enc1 = _mm_aesenc_si128(data1, key);
__m128i enc2 = _mm_aesenc_si128(data2, key);
__m128i enc3 = _mm_aesenc_si128(data3, key);

AVX2 (8 lanes):

1
2
3
4
5
6
7
8
// 一次处理8个128位数据(使用256位寄存器)
__m256i data01 = _mm256_loadu_si256((__m256i*)src[0]);
__m256i data23 = _mm256_loadu_si256((__m256i*)src[2]);
// ...

// 并行处理
__m256i enc01 = _mm256_aesenc_epi128(data01, key);
// ...

AVX512 (16 lanes):

1
2
3
4
5
6
7
// 一次处理16个128位数据(使用512位寄存器)
__m512i data0_3 = _mm512_loadu_si512(src);
// ...

// 并行处理
__m512i enc0_3 = _mm512_aesenc_epi128(data0_3, key);
// ...

2. 硬件指令利用

AES-NI指令

AESENC/AESDEC: AES加密/解密轮

1
2
// 单轮AES加密
__m128i aesenc(__m128i data, __m128i round_key);

AESKEYGENASSIST: AES密钥生成

1
2
// 生成下一轮密钥
__m128i aeskeygenassist(__m128i key, int round);

PCLMULQDQ指令

GF(2^128)乘法: 用于GCM的GHASH

1
2
// 64位×64位→128位结果(在GF(2^128)中)
__m128i _mm_clmulepi64_si128(__m128i a, __m128i b, int imm8);

SHA-NI指令(如果可用)

SHA1/SHA256加速: 硬件SHA指令

1
2
3
// SHA1消息调度
__m128i _mm_sha1msg1_epu32(__m128i a, __m128i b);
__m128i _mm_sha1msg2_epu32(__m128i a, __m128i b);

3. 预计算优化

GCM密钥预计算

原理: 预计算GHASH所需的所有密钥表。

实现:

1
2
3
4
5
6
7
8
9
10
11
12
void aes_gcm_precomp_128_sse(struct gcm_key_data *key_data)
{
// 预计算 HashKey, HashKey^2, ..., HashKey^8
// 用于加速GHASH计算
uint8_t hkey[16];
// ... 计算hkey ...

for (int i = 0; i < 8; i++) {
// 计算 HashKey^(i+1) << 1 mod poly
ghash_multiply(hkey, key_data->ghash_keys.sse_avx.shifted_hkey + i*16);
}
}

4. 内存对齐优化

对齐要求

  • 16字节对齐: SSE操作
  • 32字节对齐: AVX操作
  • 64字节对齐: AVX512操作

实现

1
2
3
4
5
6
7
8
// 对齐的数据结构
DECLARE_ALIGNED(uint8_t buffer[256], 32); // 32字节对齐

// 对齐检查
if ((uintptr_t)ptr & 0x1F) {
// 处理未对齐情况
// 或要求调用者提供对齐的缓冲区
}

5. 缓存优化

数据布局

  • 紧凑布局: 相关数据放在一起
  • 对齐到缓存行: 64字节对齐
  • 预取: 使用硬件预取指令

在SPDK中的应用

使用场景

  1. IPSec加密: 在NVMe over Fabrics等场景中提供IPSec加密支持
  2. 数据完整性: 提供数据完整性验证
  3. 高性能加密: 需要高吞吐量的加密场景

集成方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// SPDK中可能的集成示例
#include "intel-ipsec-mb.h"

// 初始化
MB_MGR *mgr = alloc_mb_mgr(0);

// 加密作业
JOB_AES_HMAC *job = get_next_job(mgr);
job->cipher_mode = GCM;
job->hash_alg = AES_GMAC;
job->aes_enc_key_expanded = expanded_key;
job->src = plaintext;
job->dst = ciphertext;
job->iv = iv;
job->msg_len_to_cipher_in_bytes = len;
job->msg_len_to_hash_in_bytes = len;

// 提交
submit_job(mgr);

// 获取结果
JOB_AES_HMAC *completed = get_completed_job(mgr);
if (completed->status == STS_COMPLETED) {
// 使用加密后的数据
}

性能影响

  • 高吞吐: 可达数十Gbps的加密吞吐量
  • 低延迟: 批量处理减少延迟
  • CPU效率: 充分利用SIMD指令,提高CPU利用率

算法支持矩阵

加密算法

算法 SSE AVX AVX2 AVX512 VAES
AES128-GCM by8 by8 by8 by8 by48
AES256-GCM by8 by8 by8 by8 by48
AES128-CBC x4 x8 - - x16
AES256-CBC x4 x8 - - x16
AES128-CTR by4 by8 - - by16
AES256-CTR by4 by8 - - by16
DES/3DES - - - x16 -

完整性算法

算法 SSE AVX AVX2 AVX512
HMAC-SHA1-96 x4 x4 x8 x16
HMAC-SHA256-128 x4 x4 x8 x16
HMAC-SHA512-256 x2 x2 x4 x8
AES-XCBC-96 x4 x8 - -
AES-GMAC by8 by8 by8 by8

说明:

  • byY: 单缓冲区,一次处理Y个块
  • xY: 多缓冲区,同时处理Y个缓冲区

安全考虑

安全选项

  1. SAFE_DATA: 清除敏感数据(密钥、IV等)
  2. SAFE_PARAM: 参数验证
  3. SAFE_LOOKUP: 常量时间查找(防止时序攻击)

编译选项

1
2
# 启用安全选项
make SAFE_DATA=y SAFE_PARAM=y SAFE_LOOKUP=y

算法建议

  • 避免使用: DES, 3DES, HMAC-MD5(遗留算法)
  • 推荐使用: AES-GCM, HMAC-SHA256/512

总结

Intel-IPSec-MB是SPDK中重要的加密加速组件,通过以下方式提供高性能:

  1. 多缓冲区技术: 同时处理多个作业,提高吞吐量
  2. SIMD优化: 充分利用现代CPU的并行计算能力
  3. 硬件加速: 利用AES-NI、PCLMULQDQ等硬件指令
  4. 乱序执行: 提高CPU利用率
  5. 预计算优化: 减少运行时计算开销

这些优化使得Intel-IPSec-MB能够提供比标准实现高数倍到数十倍的性能,是SPDK实现高性能加密的关键组件之一。


文档版本: 1.0
最后更新: 2024年

SPDK模块目录分析文档

SPDK模块目录分析文档

目录

  1. 概述
  2. 模块架构
  3. 块设备模块(BDEV)
  4. 加速器模块(ACCEL)
  5. Blob存储模块
  6. Blob文件系统模块
  7. 事件子系统模块
  8. Socket模块
  9. 环境模块
  10. 模块注册机制
  11. 模块依赖关系

概述

SPDK模块目录的作用

module 目录是SPDK的模块化实现目录,包含了各种功能模块的实现。这些模块通过SPDK的模块注册机制动态加载和集成到系统中。

模块分类

根据 module/Makefile 的定义,主要模块包括:

  • bdev: 块设备模块(最多样化的模块集合)
  • blob: Blob存储模块
  • blobfs: Blob文件系统模块
  • accel: 加速器模块
  • event: 事件子系统模块
  • sock: Socket抽象模块
  • env_dpdk: DPDK环境模块(条件编译)

模块依赖关系

1
2
3
event → bdev, blob
blobfs → blob
bdev → blob

模块架构

模块结构

每个模块通常包含以下组件:

  1. 核心实现文件 (.c): 模块的主要功能实现
  2. 头文件 (.h): 模块的接口定义
  3. RPC文件 (*_rpc.c): JSON-RPC接口实现
  4. Makefile: 构建配置

模块注册机制

SPDK使用模块注册机制来管理各种功能模块:

1
2
3
4
5
6
7
8
9
10
// 块设备模块注册
static struct spdk_bdev_module bdev_module = {
.name = "module_name",
.module_init = module_init_fn,
.module_fini = module_fini_fn,
.config_text = module_config_fn,
.get_ctx_size = module_get_ctx_size,
};

SPDK_BDEV_MODULE_REGISTER(bdev_module)

块设备模块(BDEV)

块设备模块是SPDK中最丰富的模块集合,提供了各种块设备的实现和虚拟化功能。

核心块设备模块

1. NVMe块设备模块 (bdev/nvme)

功能: 将NVMe设备暴露为SPDK块设备

主要文件:

  • bdev_nvme.c: NVMe块设备实现
  • bdev_nvme.h: 接口定义
  • bdev_nvme_rpc.c: RPC接口
  • bdev_ocssd.c: Open Channel SSD支持
  • bdev_opal.c: Opal安全功能支持

关键特性:

  • 支持标准NVMe命名空间
  • 支持Open Channel SSD (OCSSD)
  • 支持NVMe-oF (网络NVMe)
  • 支持热插拔监控
  • 支持超时处理和重试
  • 支持优先级仲裁
  • 支持保护信息(PI)验证

数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct spdk_bdev_nvme_opts {
enum spdk_bdev_timeout_action action_on_timeout;
uint64_t timeout_us;
uint32_t retry_count;
uint32_t arbitration_burst;
uint32_t low_priority_weight;
uint32_t medium_priority_weight;
uint32_t high_priority_weight;
uint64_t nvme_adminq_poll_period_us;
uint64_t nvme_ioq_poll_period_us;
uint32_t io_queue_requests;
bool delay_cmd_submit;
};

主要函数:

  • bdev_nvme_create(): 创建NVMe块设备
  • bdev_nvme_delete(): 删除NVMe控制器
  • bdev_nvme_get_io_qpair(): 获取IO队列对
  • bdev_nvme_set_hotplug(): 设置热插拔监控

2. Malloc块设备模块 (bdev/malloc)

功能: 在内存中创建虚拟块设备,用于测试和开发

主要文件:

  • bdev_malloc.c: Malloc块设备实现
  • bdev_malloc.h: 接口定义
  • bdev_malloc_rpc.c: RPC接口

关键特性:

  • 纯内存块设备
  • 支持动态创建和删除
  • 支持UUID
  • 用于性能测试和功能验证

主要函数:

  • create_malloc_disk(): 创建malloc磁盘
  • delete_malloc_disk(): 删除malloc磁盘

3. Null块设备模块 (bdev/null)

功能: 空块设备,丢弃所有写入,读取返回零

主要文件:

  • bdev_null.c: Null块设备实现
  • bdev_null.h: 接口定义
  • bdev_null_rpc.c: RPC接口

关键特性:

  • 丢弃所有写入操作
  • 读取操作返回零
  • 用于性能基准测试
  • 支持可配置的块大小和块数

4. AIO块设备模块 (bdev/aio) [Linux only]

功能: 将Linux AIO块设备暴露为SPDK块设备

主要文件:

  • bdev_aio.c: AIO块设备实现
  • bdev_aio.h: 接口定义
  • bdev_aio_rpc.c: RPC接口

关键特性:

  • 支持Linux异步I/O
  • 可以访问传统块设备(如 /dev/sda
  • 用于与传统存储系统集成

5. PMEM块设备模块 (bdev/pmem) [Linux only]

功能: 将持久化内存(PMEM)设备暴露为SPDK块设备

主要文件:

  • bdev_pmem.c: PMEM块设备实现
  • bdev_pmem.h: 接口定义
  • bdev_pmem_rpc.c: RPC接口

关键特性:

  • 支持Intel Optane持久化内存
  • 字节可寻址的持久化存储
  • 低延迟访问

6. iSCSI Initiator块设备模块 (bdev/iscsi)

功能: 将iSCSI目标暴露为SPDK块设备

主要文件:

  • bdev_iscsi.c: iSCSI块设备实现
  • bdev_iscsi.h: 接口定义
  • bdev_iscsi_rpc.c: RPC接口

关键特性:

  • iSCSI协议支持
  • 网络存储访问
  • 支持CHAP认证

7. Virtio块设备模块 (bdev/virtio) [Linux only]

功能: 将Virtio设备暴露为SPDK块设备

主要文件:

  • bdev_virtio_blk.c: Virtio BLK设备
  • bdev_virtio_scsi.c: Virtio SCSI设备
  • bdev_virtio.h: 接口定义
  • bdev_virtio_rpc.c: RPC接口

关键特性:

  • 支持Virtio BLK
  • 支持Virtio SCSI
  • 用于虚拟化环境

8. RBD块设备模块 (bdev/rbd) [条件编译]

功能: 将Ceph RBD设备暴露为SPDK块设备

主要文件:

  • bdev_rbd.c: RBD块设备实现
  • bdev_rbd.h: 接口定义
  • bdev_rbd_rpc.c: RPC接口

关键特性:

  • Ceph RBD集成
  • 分布式存储支持

9. io_uring块设备模块 (bdev/uring) [条件编译]

功能: 使用Linux io_uring接口访问块设备

主要文件:

  • bdev_uring.c: io_uring块设备实现
  • bdev_uring.h: 接口定义
  • bdev_uring_rpc.c: RPC接口

关键特性:

  • 使用Linux io_uring高性能接口
  • 异步I/O支持

虚拟块设备模块

1. 逻辑卷模块 (bdev/lvol)

功能: 在Blobstore上创建逻辑卷

主要文件:

  • vbdev_lvol.c: 逻辑卷实现
  • vbdev_lvol.h: 接口定义
  • vbdev_lvol_rpc.c: RPC接口

关键特性:

  • 基于Blobstore的逻辑卷管理
  • 支持快照和克隆
  • 支持精简配置(Thin Provisioning)
  • 支持在线调整大小
  • 支持只读模式

主要函数:

  • vbdev_lvs_create(): 创建逻辑卷存储
  • vbdev_lvol_create(): 创建逻辑卷
  • vbdev_lvol_create_snapshot(): 创建快照
  • vbdev_lvol_create_clone(): 创建克隆
  • vbdev_lvol_resize(): 调整逻辑卷大小
  • vbdev_lvol_set_read_only(): 设置只读模式

2. RAID模块 (bdev/raid)

功能: 在多个块设备上实现RAID功能

主要文件:

  • bdev_raid.c: RAID块设备实现
  • bdev_raid.h: 接口定义
  • bdev_raid_rpc.c: RPC接口
  • raid0.c: RAID0实现
  • raid5.c: RAID5实现

关键特性:

  • 支持RAID0 (条带化)
  • 支持RAID5 (带奇偶校验的条带化)
  • 可扩展的RAID模块架构
  • 支持热插拔
  • 支持在线重建

数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
enum raid_level {
INVALID_RAID_LEVEL = -1,
RAID0 = 0,
RAID5 = 5,
};

enum raid_bdev_state {
RAID_BDEV_STATE_ONLINE, // 在线状态
RAID_BDEV_STATE_CONFIGURING, // 配置中
RAID_BDEV_STATE_OFFLINE, // 离线状态
};

struct raid_bdev {
struct spdk_bdev bdev;
struct raid_base_bdev_info *base_bdev_info;
uint32_t strip_size;
uint32_t strip_size_kb;
enum raid_bdev_state state;
uint8_t num_base_bdevs;
enum raid_level level;
struct raid_bdev_module *module;
void *module_private;
};

RAID模块接口:

1
2
3
4
5
6
7
8
9
struct raid_bdev_module {
enum raid_level level;
uint8_t base_bdevs_min;
uint8_t base_bdevs_max_degraded;
int (*start)(struct raid_bdev *raid_bdev);
void (*stop)(struct raid_bdev *raid_bdev);
void (*submit_rw_request)(struct raid_bdev_io *raid_io);
void (*submit_null_payload_request)(struct raid_bdev_io *raid_io);
};

3. Split模块 (bdev/split)

功能: 将单个块设备分割成多个虚拟块设备

主要文件:

  • vbdev_split.c: Split块设备实现
  • vbdev_split.h: 接口定义
  • vbdev_split_rpc.c: RPC接口

关键特性:

  • 将一个块设备分割成多个部分
  • 支持可配置的分割数量和大小
  • 用于测试和资源隔离

主要函数:

  • create_vbdev_split(): 创建分割块设备
  • vbdev_split_destruct(): 删除分割块设备

4. Delay模块 (bdev/delay)

功能: 在块设备I/O中添加可配置的延迟

主要文件:

  • vbdev_delay.c: Delay块设备实现
  • vbdev_delay.h: 接口定义
  • vbdev_delay_rpc.c: RPC接口

关键特性:

  • 可配置的读取延迟(平均和P99)
  • 可配置的写入延迟(平均和P99)
  • 用于性能测试和故障模拟
  • 支持运行时更新延迟值

主要函数:

  • create_delay_disk(): 创建延迟块设备
  • delete_delay_disk(): 删除延迟块设备
  • vbdev_delay_update_latency_value(): 更新延迟值

5. Error注入模块 (bdev/error)

功能: 在块设备I/O中注入错误

主要文件:

  • vbdev_error.c: Error块设备实现
  • vbdev_error.h: 接口定义
  • vbdev_error_rpc.c: RPC接口

关键特性:

  • 可配置的错误注入
  • 支持IO失败和IO挂起
  • 用于测试错误处理逻辑
  • 支持指定错误数量

主要函数:

  • vbdev_error_create(): 创建错误注入块设备
  • vbdev_error_delete(): 删除错误注入块设备
  • vbdev_error_inject_error(): 注入错误

6. Zone Block模块 (bdev/zone_block)

功能: 将常规块设备转换为Zoned Block设备

主要文件:

  • vbdev_zone_block.c: Zone Block实现
  • vbdev_zone_block.h: 接口定义
  • vbdev_zone_block_rpc.c: RPC接口

关键特性:

  • 模拟Zoned Block设备
  • 支持Zone管理命令
  • 用于测试Zoned Block设备功能

7. GPT模块 (bdev/gpt)

功能: 解析GPT分区表并创建分区块设备

主要文件:

  • vbdev_gpt.c: GPT实现
  • gpt.c: GPT解析
  • gpt.h: GPT定义

关键特性:

  • 自动检测GPT分区
  • 为每个分区创建块设备
  • 支持标准GPT格式

8. Passthru模块 (bdev/passthru)

功能: 透传块设备,用于添加自定义功能

主要文件:

  • vbdev_passthru.c: Passthru实现
  • vbdev_passthru.h: 接口定义
  • vbdev_passthru_rpc.c: RPC接口

关键特性:

  • 透传所有I/O操作
  • 可用于添加中间层功能
  • 用于开发和测试

9. Crypto模块 (bdev/crypto) [条件编译]

功能: 在块设备上提供加密/解密功能

主要文件:

  • vbdev_crypto.c: Crypto实现
  • vbdev_crypto.h: 接口定义
  • vbdev_crypto_rpc.c: RPC接口

关键特性:

  • 透明加密/解密
  • 支持多种加密算法
  • 数据安全保护

10. Compress模块 (bdev/compress) [条件编译]

功能: 在块设备上提供压缩/解压缩功能

主要文件:

  • vbdev_compress.c: Compress实现
  • vbdev_compress.h: 接口定义
  • vbdev_compress_rpc.c: RPC接口

关键特性:

  • 透明压缩/解压缩
  • 节省存储空间
  • 使用ISA-L库

11. OCF模块 (bdev/ocf) [条件编译]

功能: Open CAS Framework集成,提供缓存功能

主要文件:

  • vbdev_ocf.c: OCF实现
  • vbdev_ocf.h: 接口定义
  • vbdev_ocf_rpc.c: RPC接口
  • ctx.c, data.c, volume.c, stats.c, utils.c: 辅助实现

关键特性:

  • 缓存加速
  • 支持多种缓存策略
  • 统计信息收集

12. FTL模块 (bdev/ftl) [Linux only]

功能: Flash Translation Layer,用于SSD管理

主要文件:

  • bdev_ftl.c: FTL实现
  • bdev_ftl.h: 接口定义
  • bdev_ftl_rpc.c: RPC接口

关键特性:

  • 磨损均衡
  • 地址转换
  • 垃圾回收

13. RPC模块 (bdev/rpc)

功能: 块设备的通用RPC接口

主要文件:

  • bdev_rpc.c: RPC实现

关键特性:

  • 统一的块设备RPC接口
  • 块设备查询和管理

加速器模块(ACCEL)

加速器模块提供了硬件加速功能的抽象。

1. IDXD加速器模块 (accel/idxd)

功能: Intel Data Streaming Accelerator (DSA) 支持

主要文件:

  • accel_engine_idxd.c: IDXD加速器实现
  • accel_engine_idxd.h: 接口定义
  • accel_engine_idxd_rpc.c: RPC接口

关键特性:

  • 硬件加速的数据移动
  • 硬件加速的CRC计算
  • 硬件加速的压缩/解压缩
  • 支持多个IDXD设备

主要函数:

  • accel_engine_idxd_enable_probe(): 启用IDXD探测

2. IOAT加速器模块 (accel/ioat)

功能: Intel I/O Acceleration Technology (IOAT) 支持

主要文件:

  • accel_engine_ioat.c: IOAT加速器实现
  • accel_engine_ioat.h: 接口定义
  • accel_engine_ioat_rpc.c: RPC接口

关键特性:

  • DMA拷贝加速
  • 内存移动优化
  • 卸载CPU负载

Blob存储模块

Blob模块 (blob)

功能: Blob存储的基础模块

主要文件:

  • blob_bdev.c: Blob与块设备的集成

关键特性:

  • 提供Blob存储的基础功能
  • 与块设备层集成

Blob文件系统模块

BlobFS模块 (blobfs)

功能: 在Blobstore上提供POSIX兼容的文件系统

主要文件:

  • blobfs_bdev.c: BlobFS与块设备的集成
  • blobfs_fuse.c: FUSE接口实现
  • blobfs_fuse.h: FUSE接口定义
  • blobfs_bdev_rpc.c: RPC接口

关键特性:

  • POSIX兼容的文件系统接口
  • FUSE支持,可挂载到文件系统
  • 基于Blobstore的高性能存储
  • 支持文件系统操作(创建、删除、读写等)

主要函数:

  • spdk_blobfs_bdev_detect(): 检测BlobFS
  • spdk_blobfs_bdev_mount(): 挂载BlobFS
  • spdk_blobfs_bdev_unmount(): 卸载BlobFS

事件子系统模块

Event模块 (event)

功能: 事件子系统的模块化实现

主要子模块:

1. BDEV子系统 (event/subsystems/bdev)

功能: 块设备子系统的事件处理

主要文件:

  • bdev.c: 块设备子系统实现

2. NVMf子系统 (event/subsystems/nvmf)

功能: NVMe over Fabrics目标子系统

主要文件:

  • nvmf_tgt.c: NVMf目标实现
  • nvmf_rpc.c: RPC接口
  • conf.c: 配置管理
  • event_nvmf.h: 事件定义

关键特性:

  • NVMe-oF目标实现
  • 支持RDMA、TCP、FC传输
  • 子系统管理
  • 命名空间管理

3. iSCSI子系统 (event/subsystems/iscsi)

功能: iSCSI目标子系统

主要文件:

  • iscsi.c: iSCSI目标实现

关键特性:

  • iSCSI协议支持
  • 目标端实现

4. Vhost子系统 (event/subsystems/vhost)

功能: Vhost用户空间实现

主要文件:

  • vhost.c: Vhost实现

关键特性:

  • 与QEMU/KVM集成
  • 高性能虚拟化I/O

5. SCSI子系统 (event/subsystems/scsi)

功能: SCSI协议支持

主要文件:

  • scsi.c: SCSI实现

6. NBD子系统 (event/subsystems/nbd)

功能: Network Block Device支持

主要文件:

  • nbd.c: NBD实现

关键特性:

  • 网络块设备导出
  • 远程块设备访问

7. VMD子系统 (event/subsystems/vmd)

功能: Volume Management Device支持

主要文件:

  • vmd.c: VMD实现
  • vmd_rpc.c: RPC接口
  • event_vmd.h: 事件定义

关键特性:

  • Intel VMD设备管理
  • 热插拔支持

8. Net子系统 (event/subsystems/net)

功能: 网络子系统

主要文件:

  • net.c: 网络实现

9. Sock子系统 (event/subsystems/sock)

功能: Socket子系统

主要文件:

  • sock.c: Socket实现

10. Accel子系统 (event/subsystems/accel)

功能: 加速器子系统

主要文件:

  • accel.c: 加速器实现

11. RPC模块 (event/rpc)

功能: 事件子系统的RPC接口

主要文件:

  • subsystem_rpc.c: 子系统RPC
  • app_rpc.c: 应用RPC

Socket模块

Sock模块 (sock)

功能: 提供可插拔的Socket实现

主要子模块:

1. POSIX Socket (sock/posix)

功能: 标准POSIX Socket实现

主要文件:

  • posix.c: POSIX Socket实现

关键特性:

  • 标准BSD Socket接口
  • 跨平台支持

2. io_uring Socket (sock/uring) [条件编译]

功能: 基于io_uring的高性能Socket实现

主要文件:

  • uring.c: io_uring Socket实现

关键特性:

  • 使用Linux io_uring接口
  • 高性能异步I/O
  • 减少系统调用开销

3. VPP Socket (sock/vpp) [条件编译]

功能: 基于VPP (Vector Packet Processing) 的Socket实现

主要文件:

  • vpp.c: VPP Socket实现

关键特性:

  • 用户空间网络栈
  • 高性能数据包处理

环境模块

env_dpdk模块 (env_dpdk)

功能: DPDK环境的RPC接口

主要文件:

  • env_dpdk_rpc.c: DPDK环境RPC实现

关键特性:

  • DPDK环境配置的RPC接口
  • 内存管理RPC
  • CPU核心管理RPC

模块注册机制

块设备模块注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 模块定义
static struct spdk_bdev_module bdev_module = {
.name = "module_name",
.module_init = module_init_fn,
.module_fini = module_fini_fn,
.config_text = module_config_fn,
.get_ctx_size = module_get_ctx_size,
.examine_config = module_examine_config,
.examine_disk = module_examine_disk,
.claim_opts = module_claim_opts,
};

// 注册宏
SPDK_BDEV_MODULE_REGISTER(bdev_module)

RAID模块注册

1
2
3
4
5
6
7
8
9
10
11
12
13
// RAID模块定义
static struct raid_bdev_module raid_module = {
.level = RAID0,
.base_bdevs_min = 2,
.base_bdevs_max_degraded = 0,
.start = raid0_start,
.stop = raid0_stop,
.submit_rw_request = raid0_submit_rw_request,
.submit_null_payload_request = raid0_submit_null_payload_request,
};

// 注册宏
RAID_MODULE_REGISTER(&raid_module)

加速器模块注册

1
2
3
4
5
6
7
8
9
10
// 加速器引擎注册
static struct spdk_accel_module_if g_accel_idxd_module = {
.name = "idxd",
.get_ctx_size = accel_idxd_get_ctx_size,
.init = accel_idxd_init,
.fini = accel_idxd_fini,
.submit_tasks = accel_idxd_submit_tasks,
};

SPDK_ACCEL_MODULE_REGISTER(idxd, &g_accel_idxd_module)

模块依赖关系

编译时依赖

根据 module/Makefile:

1
2
3
4
5
6
7
DEPDIRS-blob :=
DEPDIRS-accel :=
DEPDIRS-env_dpdk :=
DEPDIRS-sock :=
DEPDIRS-bdev := blob
DEPDIRS-blobfs := blob
DEPDIRS-event := bdev blob

运行时依赖

  1. bdev模块 → 依赖 blob模块

    • 某些虚拟块设备(如lvol)需要blob支持
  2. blobfs模块 → 依赖 blob模块

    • BlobFS基于Blobstore实现
  3. event模块 → 依赖 bdev模块blob模块

    • 事件子系统需要块设备和blob支持

条件编译依赖

  • CONFIG_CRYPTO: crypto模块
  • CONFIG_OCF: ocf模块
  • CONFIG_REDUCE: compress模块
  • CONFIG_URING: uring模块(bdev和sock)
  • CONFIG_ISCSI_INITIATOR: iscsi模块
  • CONFIG_VIRTIO: virtio模块
  • CONFIG_PMDK: pmem模块
  • CONFIG_RBD: rbd模块
  • CONFIG_VPP: vpp模块

模块工作流程

块设备模块工作流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1. 模块初始化
└─> module_init_fn()
└─> 注册模块到bdev子系统

2. 配置解析
└─> module_config_fn()
└─> 解析配置文件

3. 设备检测
└─> module_examine_disk()
└─> 检测并创建块设备

4. I/O处理
└─> bdev_module->submit_request()
└─> 处理I/O请求

5. 模块清理
└─> module_fini_fn()
└─> 清理资源

RAID模块工作流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1. RAID配置
└─> raid_bdev_config_add()
└─> 创建RAID配置

2. RAID创建
└─> raid_bdev_create()
└─> 创建RAID块设备

3. 添加基础设备
└─> raid_bdev_add_base_devices()
└─> 添加成员设备

4. RAID启动
└─> raid_module->start()
└─> 初始化RAID状态

5. I/O分发
└─> raid_module->submit_rw_request()
└─> 分发I/O到成员设备

6. I/O完成
└─> raid_bdev_io_complete()
└─> 聚合完成状态

模块设计原则

1. 模块化设计

  • 每个模块独立实现
  • 通过标准接口集成
  • 支持动态加载

2. 可扩展性

  • 模块注册机制
  • 插件式架构
  • 易于添加新模块

3. 统一接口

  • 块设备模块统一接口
  • RPC接口标准化
  • 事件处理统一

4. 性能优化

  • 零拷贝设计
  • 异步I/O
  • 硬件加速支持

5. 可测试性

  • 虚拟块设备用于测试
  • 错误注入支持
  • 延迟模拟

总结

SPDK的 module 目录提供了丰富的模块化功能实现:

  1. 块设备模块: 提供了从物理设备到虚拟设备的完整支持
  2. 加速器模块: 利用硬件加速提升性能
  3. 存储模块: Blob和BlobFS提供对象存储和文件系统
  4. 事件子系统: 模块化的事件处理框架
  5. 网络模块: 可插拔的Socket实现
  6. 环境模块: DPDK环境集成

这些模块通过统一的注册机制和接口规范,实现了高度的模块化和可扩展性,使得SPDK能够适应各种存储场景和性能需求。


文档版本: 1.0
最后更新: 2024年

SPDK库模块分析文档

SPDK库模块分析文档

目录

  1. 概述
  2. 核心基础设施模块
  3. 存储设备模块
  4. 网络协议模块
  5. 加速器模块
  6. 文件系统模块
  7. 工具与支持模块
  8. 模块依赖关系

概述

SPDK (Storage Performance Development Kit) 是一个用于编写高性能存储应用程序的工具包。本文档详细分析了 src/spdk/lib 目录下各个模块的作用和工作原理。

SPDK架构特点

  • 用户态驱动: 绕过内核,直接在用户空间操作硬件
  • 异步I/O: 基于事件驱动的异步I/O模型
  • 零拷贝: 直接使用用户缓冲区,避免数据拷贝
  • 无锁设计: 通过线程绑定和消息传递实现无锁并发

核心基础设施模块

1. thread (线程管理)

作用: 提供SPDK的线程抽象和管理机制

核心功能:

  • 线程创建、销毁和管理
  • 线程本地存储(TLS)支持
  • 消息传递机制(spdk_msg)
  • I/O设备注册和I/O通道管理
  • 线程间通信

工作原理:

1
2
3
4
5
6
7
8
9
10
11
// 线程结构
struct spdk_thread {
uint64_t id; // 线程ID
struct spdk_io_channel *channels; // I/O通道链表
struct spdk_msg_queue msg_queue; // 消息队列
// ...
};

// 消息处理
spdk_thread_send_msg() // 发送消息到指定线程
spdk_thread_poll() // 轮询处理消息

关键特性:

  • 每个线程维护独立的I/O通道
  • 消息批处理机制(SPDK_MSG_BATCH_SIZE = 8)
  • 线程退出超时保护(5秒)

文件: thread/thread.c


2. event (事件框架)

作用: 提供SPDK应用程序的事件驱动框架

核心功能:

  • 应用程序生命周期管理
  • Reactor模式实现
  • 子系统初始化和销毁
  • JSON配置文件解析
  • RPC服务集成

工作原理:

1
2
3
4
5
6
7
8
9
// 应用程序结构
struct spdk_app {
struct spdk_conf *config; // 配置文件
spdk_app_shutdown_cb shutdown_cb; // 关闭回调
// ...
};

// Reactor轮询
spdk_reactor_run() // 运行reactor事件循环

关键特性:

  • 支持命令行参数解析
  • 支持JSON和文本配置文件
  • 优雅关闭机制
  • 多核支持(CPU亲和性绑定)

文件: event/app.c, event/reactor.c, event/subsystem.c


3. env_dpdk (DPDK环境抽象)

作用: 提供基于DPDK的环境抽象层

核心功能:

  • 内存管理(大页内存分配)
  • PCI设备枚举和管理
  • CPU核心管理
  • 线程管理
  • 中断处理

工作原理:

1
2
3
4
5
6
7
// 内存分配
spdk_malloc() // 分配内存(使用大页)
spdk_free() // 释放内存

// PCI设备
spdk_pci_enumerate() // 枚举PCI设备
spdk_pci_device_map_bar() // 映射BAR空间

关键特性:

  • 大页内存支持(2MB/1GB)
  • 物理地址到虚拟地址转换
  • NUMA感知的内存分配
  • PCI设备热插拔支持

文件: env_dpdk/env.c, env_dpdk/memory.c, env_dpdk/pci.c


4. util (工具函数库)

作用: 提供各种通用工具函数

核心功能:

  • 字符串处理
  • CRC校验(CRC32, CRC32C, CRC16)
  • 位数组操作
  • CPU集合管理
  • 数学运算
  • 文件操作
  • UUID生成
  • Base64编解码

关键函数:

1
2
3
4
spdk_crc32c_update()    // CRC32C计算
spdk_bit_array_set() // 位数组操作
spdk_cpuset_parse() // CPU集合解析
spdk_uuid_generate() // UUID生成

文件: util/string.c, util/crc32c.c, util/bit_array.c, util/cpuset.c


存储设备模块

5. nvme (NVMe驱动)

作用: 提供NVMe设备的用户态驱动

核心功能:

  • NVMe控制器管理
  • 命名空间管理
  • 队列对(QPair)管理
  • 数据传输(读写命令)
  • 多种传输方式支持(PCIe, RDMA, TCP, FC)

工作原理:

  • 命令提交: 构建NVMe命令 → 提交到SQ → 敲响doorbell
  • 完成处理: 轮询CQ → 处理完成项 → 调用回调函数
  • 地址描述: 支持PRP和SGL两种方式描述数据缓冲区

关键数据结构:

1
2
3
4
struct spdk_nvme_ctrlr    // NVMe控制器
struct spdk_nvme_ns // 命名空间
struct spdk_nvme_qpair // 队列对
struct nvme_request // I/O请求

传输层:

  • PCIe: nvme_pcie.c - 直接访问PCIe设备
  • RDMA: nvme_rdma.c - 基于InfiniBand/RoCE
  • TCP: nvme_tcp.c - 基于TCP/IP
  • Fabric: nvme_fabric.c - NVMe over Fabrics

文件: nvme/nvme.c, nvme/nvme_pcie.c, nvme/nvme_qpair.c, nvme/nvme_ns_cmd.c


6. bdev (块设备抽象层)

作用: 提供统一的块设备抽象接口

核心功能:

  • 块设备注册和管理
  • I/O请求处理
  • QoS限流
  • 设备热插拔
  • 分区支持
  • Zone设备支持

工作原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 块设备结构
struct spdk_bdev {
char *name; // 设备名
uint64_t blockcnt; // 块数量
uint32_t blocklen; // 块大小
spdk_bdev_io_fn submit_request; // I/O提交函数
// ...
};

// I/O请求
struct spdk_bdev_io {
struct spdk_bdev *bdev; // 目标设备
void *buf; // 数据缓冲区
uint64_t offset; // 偏移量
uint64_t num_blocks; // 块数量
// ...
};

关键特性:

  • 支持多种后端设备(NVMe, AIO, Malloc等)
  • I/O池化减少内存分配开销
  • QoS支持(IOPS和带宽限制)
  • 零拷贝I/O

文件: bdev/bdev.c, bdev/bdev_internal.h


7. blob (Blob存储)

作用: 提供对象存储抽象,用于构建更高级的存储系统

核心功能:

  • Blob创建、删除、打开、关闭
  • 数据读写
  • 快照和克隆
  • 扩展属性(xattr)
  • 元数据管理

工作原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Blob存储结构
struct spdk_blob_store {
struct spdk_bdev *bdev; // 底层块设备
uint32_t cluster_size; // 簇大小
uint32_t page_size; // 页大小
// ...
};

// Blob结构
struct spdk_blob {
spdk_blob_id id; // Blob ID
uint64_t num_clusters; // 簇数量
struct spdk_blob_store *bs; // 所属存储
// ...
};

关键特性:

  • 基于簇的存储管理
  • 元数据和数据分离
  • 支持快照和克隆
  • 支持扩展属性

文件: blob/blobstore.c, blob/blobstore.h


8. lvol (逻辑卷管理)

作用: 在Blob存储上提供逻辑卷管理功能

核心功能:

  • 逻辑卷存储(LVS)创建和管理
  • 逻辑卷(LVOL)创建、删除、调整大小
  • 快照和克隆
  • 精简配置(Thin Provisioning)

工作原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 逻辑卷存储
struct spdk_lvol_store {
struct spdk_blob_store *bs; // 底层Blob存储
char name[SPDK_LVS_NAME_MAX]; // 存储名称
// ...
};

// 逻辑卷
struct spdk_lvol {
struct spdk_blob *blob; // 底层Blob
char name[SPDK_LVOL_NAME_MAX]; // 卷名称
uint64_t size_in_clusters; // 大小(簇)
// ...
};

关键特性:

  • 基于Blob存储构建
  • 支持动态扩展
  • 快照和克隆支持
  • 精简配置

文件: lvol/lvol.c


9. ftl (Flash Translation Layer)

作用: 提供闪存转换层,将块设备接口转换为SSD接口

核心功能:

  • 地址转换(逻辑地址到物理地址)
  • 磨损均衡
  • 垃圾回收
  • 坏块管理
  • 数据恢复

工作原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// FTL设备
struct spdk_ftl_dev {
struct spdk_bdev *base_bdev; // 基础块设备
struct ftl_band *bands; // Band数组
struct ftl_wptr *write_ptr; // 写指针
// ...
};

// Band管理
struct ftl_band {
uint32_t id; // Band ID
enum ftl_band_state state; // 状态
// ...
};

关键特性:

  • 支持Open Channel SSD
  • 磨损均衡算法
  • 垃圾回收策略
  • 数据持久化

文件: ftl/ftl_core.c, ftl/ftl_band.c, ftl/ftl_io.c


网络协议模块

10. nvmf (NVMe over Fabrics Target)

作用: 实现NVMe over Fabrics目标端,通过网络提供NVMe存储

核心功能:

  • 子系统管理
  • 控制器管理
  • 队列对管理
  • 多种传输方式(RDMA, TCP, FC)
  • Discovery服务

工作原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// NVMe-oF目标
struct spdk_nvmf_tgt {
struct spdk_nvmf_subsystem *subsystems; // 子系统列表
// ...
};

// 子系统
struct spdk_nvmf_subsystem {
char nqn[SPDK_NVMF_NQN_MAX_LEN]; // NQN
struct spdk_nvmf_ns *ns; // 命名空间列表
// ...
};

// 轮询组
struct spdk_nvmf_poll_group {
struct spdk_thread *thread; // 绑定线程
// ...
};

传输方式:

  • RDMA: nvmf/rdma.c - 基于InfiniBand/RoCE
  • TCP: nvmf/tcp.c - 基于TCP/IP
  • FC: nvmf/fc.c - 基于Fibre Channel

关键特性:

  • 支持多子系统
  • 异步I/O处理
  • 多路径支持
  • Discovery服务

文件: nvmf/nvmf.c, nvmf/subsystem.c, nvmf/ctrlr.c


11. iscsi (iSCSI Target)

作用: 实现iSCSI目标端,提供基于IP的SCSI存储

核心功能:

  • iSCSI会话管理
  • 连接管理
  • 任务处理
  • 目标节点管理
  • 门户组管理

工作原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
// iSCSI目标节点
struct spdk_iscsi_tgt_node {
char name[SPDK_ISCSI_NODE_MAXLEN]; // 节点名
struct spdk_scsi_lun *lun; // LUN列表
// ...
};

// iSCSI连接
struct spdk_iscsi_conn {
int socket; // 套接字
struct spdk_iscsi_session *session; // 会话
// ...
};

关键特性:

  • 支持CHAP认证
  • 支持多会话
  • 支持多路径
  • 支持快照

文件: iscsi/iscsi.c, iscsi/conn.c, iscsi/task.c


12. vhost (Vhost用户态实现)

作用: 实现Vhost协议,为虚拟机提供高性能存储

核心功能:

  • Vhost设备管理(SCSI, BLK, NVMe)
  • 队列管理
  • 内存区域管理
  • 设备热插拔

工作原理:

1
2
3
4
5
6
7
8
9
10
11
12
// Vhost设备
struct spdk_vhost_dev {
char name[64]; // 设备名
struct spdk_bdev *bdev; // 后端块设备
// ...
};

// Vhost会话
struct spdk_vhost_session {
struct spdk_vhost_dev *dev; // 所属设备
// ...
};

关键特性:

  • 支持Vhost-user协议
  • 零拷贝I/O
  • 多队列支持
  • 设备热插拔

文件: vhost/vhost.c, vhost/vhost_scsi.c, vhost/vhost_blk.c


13. rte_vhost (DPDK Vhost集成)

作用: 集成DPDK的Vhost实现

核心功能:

  • 与DPDK Vhost库集成
  • 套接字管理
  • 文件描述符管理

文件: rte_vhost/vhost_user.c, rte_vhost/socket.c


14. sock (套接字抽象)

作用: 提供统一的套接字抽象接口

核心功能:

  • 套接字创建和管理
  • 多种实现(Posix, DPDK等)
  • 网络框架集成

文件: sock/sock.c, sock/net_framework.c


15. net (网络接口管理)

作用: 提供网络接口管理功能

核心功能:

  • 网络接口枚举
  • 接口配置
  • 地址管理

文件: net/interface.c


16. rdma (RDMA支持)

作用: 提供RDMA功能支持

核心功能:

  • Verbs API封装
  • 队列对管理
  • 内存注册

文件: rdma/rdma_verbs.c, rdma/rdma_mlx5_dv.c


加速器模块

17. accel (加速器框架)

作用: 提供统一的加速器抽象框架

核心功能:

  • 加速器引擎注册
  • 硬件/软件加速器选择
  • 支持的操作:拷贝、填充、CRC32C、压缩、加密等

工作原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 加速器引擎
struct spdk_accel_engine {
spdk_accel_submit_copy_fn submit_copy; // 拷贝操作
spdk_accel_submit_fill_fn submit_fill; // 填充操作
spdk_accel_submit_crc32c_fn submit_crc32c; // CRC32C计算
// ...
};

// 任务
struct spdk_accel_task {
spdk_accel_completion_cb cb_fn; // 完成回调
void *cb_arg; // 回调参数
// ...
};

关键特性:

  • 支持硬件加速(IOAT, IDXD)
  • 软件回退实现
  • 批处理支持
  • 异步操作

文件: accel/accel_engine.c


18. idxd (Intel Data Streaming Accelerator)

作用: 提供Intel DSA硬件加速支持

核心功能:

  • DSA设备管理
  • 工作队列管理
  • 批量操作支持

工作原理:

  • 使用Intel DSA硬件加速数据移动和转换操作
  • 支持批量提交操作以提高效率

文件: idxd/idxd.c, idxd/idxd.h


19. ioat (Intel I/O Acceleration Technology)

作用: 提供Intel IOAT DMA引擎支持

核心功能:

  • IOAT设备管理
  • DMA操作
  • 拷贝和填充操作

工作原理:

  • 使用Intel IOAT硬件加速内存拷贝操作
  • 支持零拷贝数据传输

文件: ioat/ioat.c, ioat/ioat_internal.h


文件系统模块

20. blobfs (Blob文件系统)

作用: 在Blob存储上提供POSIX兼容的文件系统

核心功能:

  • 文件创建、删除、读写
  • 目录操作
  • 文件系统挂载
  • 与RocksDB集成

工作原理:

1
2
3
4
5
6
7
8
9
10
11
// 文件系统
struct spdk_filesystem {
struct spdk_blob_store *bs; // 底层Blob存储
// ...
};

// 文件
struct spdk_file {
struct spdk_blob *blob; // 底层Blob
// ...
};

关键特性:

  • 基于Blob存储
  • POSIX兼容接口
  • 支持RocksDB

文件: blobfs/blobfs.c, blobfs/tree.c


工具与支持模块

21. json (JSON处理)

作用: 提供JSON解析和生成功能

核心功能:

  • JSON解析
  • JSON生成
  • JSON对象操作

文件: json/json_parse.c, json/json_write.c, json/json_util.c


22. jsonrpc (JSON-RPC)

作用: 实现JSON-RPC 2.0协议

核心功能:

  • JSON-RPC请求解析
  • JSON-RPC响应生成
  • 方法注册和调用
  • 客户端和服务器实现

工作原理:

1
2
3
4
5
6
7
8
9
// JSON-RPC请求
struct spdk_jsonrpc_request {
const char *method; // 方法名
const struct spdk_json_val *params; // 参数
// ...
};

// 方法注册
SPDK_RPC_REGISTER("method_name", handler_fn, flags)

文件: jsonrpc/jsonrpc_server.c, jsonrpc/jsonrpc_client.c


23. rpc (RPC框架)

作用: 提供RPC框架基础

核心功能:

  • RPC方法注册
  • 参数解析
  • 响应生成

文件: rpc/rpc.c


24. log (日志系统)

作用: 提供日志记录功能

核心功能:

  • 日志级别管理
  • 日志输出
  • 日志标志管理

文件: log/log.c, log/log_flags.c


25. trace (跟踪系统)

作用: 提供性能跟踪功能

核心功能:

  • 跟踪点定义
  • 跟踪数据收集
  • 跟踪数据解析

文件: trace/trace.c, trace/trace_flags.c


26. conf (配置管理)

作用: 提供配置文件解析功能

核心功能:

  • 配置文件解析
  • 配置项访问
  • 配置验证

文件: conf/conf.c


27. notify (通知机制)

作用: 提供事件通知机制

核心功能:

  • 事件注册
  • 事件通知
  • RPC接口

文件: notify/notify.c, notify/notify_rpc.c


28. scsi (SCSI协议支持)

作用: 提供SCSI协议支持

核心功能:

  • SCSI设备管理
  • SCSI命令处理
  • LUN管理
  • 持久保留

文件: scsi/scsi.c, scsi/dev.c, scsi/lun.c


29. nbd (Network Block Device)

作用: 提供NBD支持

核心功能:

  • NBD设备创建
  • 块设备导出

文件: nbd/nbd.c, nbd/nbd_rpc.c


30. virtio (Virtio支持)

作用: 提供Virtio设备支持

核心功能:

  • Virtio设备管理
  • PCI和用户态实现

文件: virtio/virtio.c, virtio/virtio_pci.c, virtio/virtio_user.c


31. vmd (Volume Management Device)

作用: 提供Intel VMD (Volume Management Device) 支持

核心功能:

  • VMD设备管理
  • LED控制

文件: vmd/vmd.c, vmd/led.c


32. reduce (数据缩减)

作用: 提供数据压缩和去重功能

核心功能:

  • 数据压缩
  • 数据去重

文件: reduce/reduce.c


33. ut_mock (单元测试Mock)

作用: 提供单元测试Mock支持

核心功能:

  • Mock函数注册
  • 测试辅助函数

文件: ut_mock/mock.c


34. env_ocf (OCF环境)

作用: 提供Open CAS Framework环境支持

核心功能:

  • OCF集成
  • 缓存管理

文件: env_ocf/ocf_env.c


模块依赖关系

核心依赖层次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
应用层
├── event (事件框架)
│ ├── thread (线程管理)
│ ├── env_dpdk (环境抽象)
│ └── util (工具函数)

存储层
├── bdev (块设备)
│ ├── nvme (NVMe驱动)
│ ├── blob (Blob存储)
│ └── util

├── blob (Blob存储)
│ ├── bdev
│ └── thread

├── lvol (逻辑卷)
│ └── blob

└── ftl (FTL)
├── bdev
└── nvme

网络层
├── nvmf (NVMe-oF)
│ ├── nvme
│ ├── bdev
│ └── sock

├── iscsi (iSCSI)
│ ├── scsi
│ └── bdev

└── vhost (Vhost)
├── bdev
└── rte_vhost

加速层
├── accel (加速器框架)
│ ├── idxd
│ └── ioat

├── idxd (Intel DSA)
└── ioat (Intel IOAT)

支持层
├── json (JSON处理)
├── jsonrpc (JSON-RPC)
├── rpc (RPC框架)
├── log (日志)
├── trace (跟踪)
└── conf (配置)

关键依赖说明

  1. thread 是几乎所有模块的基础,提供线程和I/O通道管理
  2. env_dpdk 提供底层环境抽象(内存、PCI等)
  3. bdev 是存储抽象的核心,被blob、lvol等模块依赖
  4. nvme 是底层存储驱动,被bdev和nvmf使用
  5. json/jsonrpc 提供RPC通信能力,被多个模块使用

总结

SPDK库采用模块化设计,各模块职责清晰:

  • 基础设施层: thread, event, env_dpdk, util - 提供基础能力
  • 存储层: nvme, bdev, blob, lvol, ftl - 提供存储功能
  • 网络层: nvmf, iscsi, vhost - 提供网络存储协议
  • 加速层: accel, idxd, ioat - 提供硬件加速
  • 支持层: json, rpc, log, trace - 提供工具支持

这种分层设计使得SPDK具有良好的可扩展性和可维护性,同时通过用户态驱动和异步I/O实现了极高的性能。


文档版本: 1.0
最后更新: 2024年

SPDK整体架构分析文档

SPDK整体架构分析文档

1. SPDK概述

1.1 什么是SPDK

SPDK(Storage Performance Development Kit)是Intel开发的高性能存储开发工具包,旨在提供用户态、轮询模式(Polling Mode)的存储应用开发框架。

1.2 核心设计理念

  • 用户态驱动:将所有必要的驱动程序移到用户空间,避免内核上下文切换
  • 轮询模式:使用轮询而非中断,消除中断处理开销
  • 零拷贝:最小化数据拷贝操作
  • NUMA感知:支持NUMA架构的内存和CPU管理
  • 事件驱动:基于事件驱动的异步I/O模型

2. SPDK整体架构

2.1 架构层次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
┌─────────────────────────────────────────────────────────┐
│ 应用程序层 (Applications) │
│ (spdk_tgt, nvmf_tgt, vhost, iscsi_tgt等) │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│ 子系统层 (Subsystems) │
│ (BDEV, NVMe, SCSI, Vhost, iSCSI, NVMf等) │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│ 模块层 (Modules) │
│ (BDEV模块、Accel模块、Blob模块、BlobFS模块等) │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│ 核心库层 (Core Libraries) │
│ (NVMe驱动、BDEV抽象、SCSI、Virtio、Vhost等) │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│ 环境抽象层 (Environment Abstraction) │
│ (env_dpdk: DPDK环境封装) │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│ DPDK层 (DPDK Framework) │
│ (EAL、内存管理、PCI设备管理、线程管理等) │
└─────────────────────────────────────────────────────────┘

2.2 目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
spdk/
├── app/ # 应用程序
│ ├── spdk_tgt/ # SPDK通用目标应用
│ ├── nvmf_tgt/ # NVMe over Fabrics目标应用
│ └── vhost/ # Vhost目标应用
├── lib/ # 核心库
│ ├── env_dpdk/ # DPDK环境抽象层
│ ├── bdev/ # 块设备抽象层
│ ├── nvme/ # NVMe驱动
│ ├── scsi/ # SCSI协议实现
│ ├── vhost/ # Vhost实现
│ ├── virtio/ # Virtio驱动
│ ├── blob/ # Blob存储
│ ├── blobfs/ # Blob文件系统
│ ├── accel/ # 加速引擎
│ ├── rdma/ # RDMA支持
│ └── util/ # 工具函数
├── module/ # 模块实现
│ ├── bdev/ # BDEV模块
│ ├── accel/ # 加速模块
│ ├── blob/ # Blob模块
│ ├── blobfs/ # BlobFS模块
│ ├── event/ # 事件子系统
│ └── sock/ # Socket抽象
├── include/ # 头文件
├── examples/ # 示例代码
└── dpdk/ # DPDK子模块

3. SPDK核心模块详解

3.1 环境抽象层 (env_dpdk)

位置: lib/env_dpdk/

功能: SPDK与DPDK之间的抽象层,封装DPDK功能为SPDK接口

主要文件:

  • init.c: DPDK EAL初始化
  • memory.c: 内存管理(基于DPDK hugepages)
  • threads.c: 线程管理(基于DPDK lcore)
  • pci.c: PCI设备管理(基于DPDK PCI bus)
  • env.c: 环境抽象接口实现

关键功能:

  • DPDK EAL初始化封装
  • 大页内存管理
  • NUMA感知的内存分配
  • 线程/核心管理
  • PCI设备发现和绑定

3.2 块设备抽象层 (BDEV)

位置: lib/bdev/

功能: 提供统一的块设备抽象接口,支持多种后端存储

核心概念:

  • BDEV: 块设备抽象,提供统一的I/O接口
  • BDEV模块: 实现具体存储后端的模块
  • BDEV栈: 支持BDEV的叠加(如加密、压缩、RAID等)

支持的BDEV类型:

  • NVMe BDEV
  • Malloc BDEV(内存)
  • AIO BDEV(Linux异步I/O)
  • PMEM BDEV(持久化内存)
  • Virtio BDEV
  • iSCSI Initiator BDEV
  • RBD BDEV(Ceph)
  • Lvol BDEV(逻辑卷)
  • RAID BDEV
  • 等等

3.3 NVMe驱动 (nvme)

位置: lib/nvme/

功能: 用户态NVMe驱动实现

主要组件:

  • NVMe控制器管理: nvme_ctrlr.c
  • 命名空间管理: nvme_ns.c
  • 队列对管理: nvme_qpair.c
  • PCIe传输: nvme_pcie.c
  • RDMA传输: nvme_rdma.c
  • TCP传输: nvme_tcp.c
  • Fabric传输: nvme_fabric.c

特性:

  • 支持NVMe 1.3/1.4规范
  • 支持OCSSD(Open Channel SSD)
  • 支持Opal安全功能
  • 支持热插拔
  • 支持多路径

3.4 SCSI子系统 (scsi)

位置: lib/scsi/

功能: SCSI协议实现,用于iSCSI Target

主要组件:

  • SCSI设备管理
  • SCSI LUN管理
  • SCSI端口管理
  • SCSI任务处理
  • SCSI持久保留(PR)

3.5 Vhost子系统 (vhost)

位置: lib/vhost/

功能: 实现Vhost协议,支持QEMU/KVM虚拟化

特性:

  • Vhost SCSI
  • Vhost Blk
  • Vhost User协议
  • 与QEMU集成

3.6 Blob存储 (blob)

位置: lib/blob/

功能: 提供对象存储抽象,支持元数据管理

核心概念:

  • Blobstore: 底层存储管理器
  • Blob: 可变大小的对象
  • Cluster: 存储单元
  • Page: 元数据页

3.7 BlobFS (blobfs)

位置: lib/blobfs/

功能: 在Blobstore上实现POSIX兼容的文件系统

特性:

  • POSIX API兼容
  • FUSE支持
  • 高性能元数据操作

3.8 加速引擎 (accel)

位置: lib/accel/

功能: 提供硬件加速抽象,支持IOAT、IDXD等

支持的加速器:

  • IOAT(Intel I/O Acceleration Technology)
  • IDXD(Intel Data Streaming Accelerator)

3.9 事件框架 (event)

位置: module/event/

功能: 提供事件驱动的编程模型

核心组件:

  • 事件子系统管理
  • Reactor模式实现
  • 线程调度
  • 应用生命周期管理

4. SPDK与DPDK的集成关系

4.1 DPDK在SPDK中的作用

SPDK使用DPDK作为底层环境抽象层,主要利用DPDK的以下功能:

4.1.1 DPDK EAL (Environment Abstraction Layer)

使用位置: lib/env_dpdk/init.c

功能:

  • 系统初始化
  • 大页内存管理
  • CPU核心管理
  • 设备发现

关键调用:

1
2
3
rte_eal_init()              // 初始化EAL
rte_eal_hotplug_add() // 热插拔添加设备
rte_eal_hotplug_remove() // 热插拔移除设备

4.1.2 DPDK内存管理

使用位置: lib/env_dpdk/memory.c, lib/env_dpdk/env.c

使用的DPDK模块:

  • librte_malloc: 内存分配
  • librte_mempool: 内存池
  • librte_memzone: 内存区域
  • librte_memory: 内存配置

关键API:

1
2
3
4
rte_malloc_socket()         // NUMA感知的内存分配
rte_malloc_virt2iova() // 虚拟地址到IOVA转换
rte_mempool_create() // 创建内存池
rte_memzone_reserve() // 保留内存区域

4.1.3 DPDK线程/核心管理

使用位置: lib/env_dpdk/threads.c

使用的DPDK模块:

  • librte_lcore: 逻辑核心管理

关键API:

1
2
3
4
5
6
rte_lcore_count()           // 获取核心数量
rte_lcore_id() // 获取当前核心ID
rte_lcore_to_socket_id() // 核心到NUMA节点映射
rte_eal_remote_launch() // 在指定核心启动函数
rte_eal_mp_wait_lcore() // 等待所有核心完成
rte_get_next_lcore() // 获取下一个核心

4.1.4 DPDK PCI设备管理

使用位置: lib/env_dpdk/pci.c

使用的DPDK模块:

  • librte_bus_pci: PCI总线
  • librte_pci: PCI设备
  • librte_dev: 设备管理

关键API:

1
2
3
4
5
6
rte_pci_read_config()       // 读取PCI配置空间
rte_pci_write_config() // 写入PCI配置空间
rte_eal_hotplug_add() // 热插拔添加
rte_eal_hotplug_remove() // 热插拔移除
rte_dev_probe() // 探测设备
rte_dev_remove() // 移除设备

4.1.5 DPDK VFIO支持

使用位置: lib/env_dpdk/memory.c, lib/env_dpdk/init.c

使用的DPDK模块:

  • librte_vfio: VFIO支持

关键API:

1
2
rte_vfio_enable()           // 启用VFIO
rte_vfio_setup_device() // 设置VFIO设备

4.1.6 DPDK告警机制

使用位置: lib/env_dpdk/pci.c

使用的DPDK模块:

  • librte_alarm: 告警/定时器

关键API:

1
rte_eal_alarm_set()         // 设置告警

4.2 SPDK对DPDK的封装

SPDK通过env_dpdk模块将DPDK功能封装为统一的SPDK接口:

4.2.1 内存管理封装

1
2
3
4
5
6
7
8
9
10
// SPDK接口
void *spdk_malloc(size_t size, size_t align, uint64_t *phys_addr,
int socket_id, uint32_t flags);
void *spdk_zmalloc(...);
void spdk_free(void *buf);

// 内部实现(基于DPDK)
rte_malloc_socket()
rte_malloc_virt2iova()
rte_free()

4.2.2 线程管理封装

1
2
3
4
5
6
7
8
9
10
11
12
// SPDK接口
uint32_t spdk_env_get_core_count(void);
uint32_t spdk_env_get_current_core(void);
int spdk_env_thread_launch_pinned(uint32_t core,
thread_start_fn fn, void *arg);
void spdk_env_thread_wait_all(void);

// 内部实现(基于DPDK)
rte_lcore_count()
rte_lcore_id()
rte_eal_remote_launch()
rte_eal_mp_wait_lcore()

4.2.3 环境初始化封装

1
2
3
4
5
// SPDK接口
int spdk_env_init(const struct spdk_env_opts *opts);

// 内部实现(基于DPDK)
rte_eal_init()

4.3 DPDK模块使用总结

DPDK模块 SPDK使用位置 主要功能
librte_eal env_dpdk/init.c EAL初始化、系统配置
librte_malloc env_dpdk/env.c 内存分配
librte_mempool env_dpdk/env.c 内存池管理
librte_memzone env_dpdk/env.c 内存区域管理
librte_memory env_dpdk/memory.c 内存配置
librte_lcore env_dpdk/threads.c 逻辑核心管理
librte_bus_pci env_dpdk/pci.c PCI总线
librte_pci env_dpdk/pci.c PCI设备操作
librte_dev env_dpdk/pci.c 设备管理
librte_vfio env_dpdk/memory.c, env_dpdk/init.c VFIO支持
librte_alarm env_dpdk/pci.c 告警/定时器
librte_cycles env_dpdk/env.c 时间戳/周期计数

5. SPDK应用模式

5.1 SPDK Target (spdk_tgt)

位置: app/spdk_tgt/

功能: 通用的SPDK目标应用,支持多种存储协议

特性:

  • 支持BDEV管理
  • 支持NVMe over Fabrics Target
  • 支持iSCSI Target
  • 支持Vhost
  • JSON-RPC接口

5.2 NVMe over Fabrics Target (nvmf_tgt)

位置: app/nvmf_tgt/

功能: 专门的NVMe over Fabrics目标应用

支持的传输:

  • RDMA
  • TCP
  • FC(Fibre Channel)

5.3 Vhost Target (vhost)

位置: app/vhost/

功能: Vhost目标应用,用于虚拟化场景

特性:

  • 与QEMU集成
  • 支持Vhost SCSI和Vhost Blk

6. SPDK模块系统

6.1 模块注册机制

SPDK使用模块注册机制实现插件化架构:

1
2
3
4
5
// BDEV模块注册
SPDK_BDEV_MODULE_REGISTER(module_name, module_init_fn, module_fini_fn)

// Accel模块注册
SPDK_ACCEL_MODULE_REGISTER(module_name, module_init_fn, module_fini_fn)

6.2 主要模块类型

6.2.1 BDEV模块

位置: module/bdev/

主要模块:

  • nvme: NVMe BDEV
  • malloc: 内存BDEV
  • aio: AIO BDEV
  • pmem: PMEM BDEV
  • virtio: Virtio BDEV
  • lvol: 逻辑卷BDEV
  • raid: RAID BDEV
  • compress: 压缩BDEV
  • crypto: 加密BDEV
  • ocf: OCF缓存BDEV
  • 等等

6.2.2 Accel模块

位置: module/accel/

主要模块:

  • ioat: IOAT加速
  • idxd: IDXD加速

6.2.3 Socket模块

位置: module/sock/

主要模块:

  • posix: POSIX Socket
  • uring: io_uring Socket
  • vpp: VPP Socket

7. SPDK初始化流程

7.1 典型初始化序列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1. spdk_env_init()
└─> rte_eal_init() // 初始化DPDK EAL
├─> 大页内存分配
├─> CPU核心检测
└─> PCI设备扫描

2. spdk_app_start()
└─> 事件框架初始化
├─> Reactor创建
└─> 子系统初始化

3. 子系统初始化
├─> BDEV子系统
├─> NVMe子系统
├─> SCSI子系统
└─> 其他子系统

4. 模块加载
├─> BDEV模块加载
├─> Accel模块加载
└─> 其他模块加载

5. 应用启动
└─> 开始事件循环

8. SPDK性能优化特性

8.1 轮询模式

  • 不使用中断,避免上下文切换
  • 主动轮询设备完成队列
  • 零延迟响应

8.2 零拷贝

  • 直接内存访问(DMA)
  • 避免不必要的内存拷贝
  • 使用大页内存减少TLB缺失

8.3 NUMA感知

  • 内存分配考虑NUMA节点
  • CPU核心绑定到NUMA节点
  • 减少跨NUMA访问

8.4 无锁设计

  • 使用无锁数据结构
  • 每核心数据结构
  • 减少锁竞争

9. 总结

SPDK是一个高性能的存储开发框架,其核心优势在于:

  1. 用户态驱动: 避免内核上下文切换
  2. 轮询模式: 消除中断开销
  3. 模块化设计: 易于扩展和维护
  4. DPDK集成: 充分利用DPDK的底层能力
  5. 事件驱动: 高效的异步I/O模型

SPDK通过env_dpdk模块将DPDK的功能封装为统一的接口,主要使用DPDK的以下模块:

  • EAL(环境抽象层)
  • 内存管理(malloc、mempool、memzone)
  • 线程/核心管理(lcore)
  • PCI设备管理(bus_pci、pci)
  • VFIO支持
  • 告警机制

这种设计使得SPDK既能充分利用DPDK的底层能力,又能保持接口的统一性和可移植性。

SPDK 硬盘管理总体架构分析

SPDK 硬盘管理总体架构分析

1. 概述

SPDK(Storage Performance Development Kit)通过多层架构管理硬盘设备,从底层的物理设备发现到上层的块设备抽象,实现了高性能、用户态的存储管理方案。

1.1 核心设计理念

  • 用户态驱动:避免内核上下文切换开销
  • 轮询模式:消除中断延迟,实现零延迟响应
  • 模块化设计:通过 BDEV 抽象层统一管理不同类型的存储设备
  • 热插拔支持:动态发现和管理设备

1.2 核心存储栈架构

根据 SPDK 的实际实现,硬盘管理的核心架构如下:

1
2
3
4
5
6
7
8
9
10
11
上层协议和应用 (NVMe-oF / iSCSI / vhost / 应用)

lvol (逻辑卷管理)

Blobstore (真正的磁盘空间管理核心)

bdev (统一块设备抽象层)

NVMe Driver (用户态,轮询模式)

NVMe SSD (物理硬件)

关键要点

  1. Blobstore 是核心:Blobstore 是真正的磁盘空间管理核心,负责:

    • 磁盘空间的分配和回收
    • 元数据的持久化和管理
    • Blob(可变大小对象)的生命周期管理
    • Cluster(存储单元)的分配
  2. lvol 基于 Blobstore:逻辑卷(lvol)是建立在 Blobstore 之上的高级抽象:

    • 每个逻辑卷对应 Blobstore 上的一个 Blob
    • 提供快照、克隆等高级功能
    • 提供块设备接口给上层协议使用
  3. bdev 提供统一接口:bdev 抽象层提供统一的块设备接口,支持多种后端:

    • 可以是直接访问(如 NVMe BDEV)
    • 也可以是基于 Blobstore(如 lvol BDEV)

2. 架构总览

2.1 核心存储栈架构图

根据 SPDK 的实际实现,核心存储栈如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
┌─────────────────────────────────────────────────────────────────┐
│ 上层协议和应用层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │NVMe-oF │ │ iSCSI │ │ vhost │ │ 应用 │ │
│ │ Target │ │ Target │ │ │ │ │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ lvol (逻辑卷管理) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ lib/lvol/lvol.c │ │
│ │ - 逻辑卷存储 (lvol store) 管理 │ │
│ │ - 逻辑卷 (lvol) 生命周期管理 │ │
│ │ - 快照和克隆支持 │ │
│ │ - 基于 Blobstore 的 Blob │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ Blobstore (真正的磁盘空间管理核心) ⭐ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ lib/blob/blobstore.c │ │
│ │ - 磁盘空间分配和回收 │ │
│ │ - 元数据持久化和管理 │ │
│ │ - Blob (可变大小对象) 管理 │ │
│ │ - Cluster (存储单元) 分配 │ │
│ │ - Page (元数据页) 管理 │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ bdev (统一块设备抽象层) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ lib/bdev/bdev.c │ │
│ │ - 设备注册/注销 │ │
│ │ - I/O 请求路由 │ │
│ │ - QoS 控制 │ │
│ │ - Channel 管理 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ BDEV 模块: │ │
│ │ - NVMe BDEV: 直接访问 NVMe 设备 │ │
│ │ - Lvol BDEV: 基于 Blobstore 的逻辑卷 │ │
│ │ - AIO/Malloc/PMEM/RBD/RAID 等 │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ NVMe Driver (用户态,轮询模式) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ lib/nvme/nvme_ctrlr.c, nvme_pcie.c │ │
│ │ - 控制器管理 │ │
│ │ - 命名空间管理 │ │
│ │ - 队列对管理 │ │
│ │ - 轮询模式 I/O │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ NVMe SSD (物理硬件) │
│ PCIe NVMe 固态硬盘 │
└─────────────────────────────────────────────────────────────────┘

2.2 关键路径说明

路径 1: 直接访问路径(不使用 Blobstore)

1
应用 → bdev → NVMe BDEV → NVMe Driver → NVMe SSD

适用于:直接访问物理设备,无需高级存储管理功能

路径 2: 存储管理路径(使用 Blobstore + lvol)⭐

1
应用 → lvol → Blobstore → bdev → NVMe BDEV → NVMe Driver → NVMe SSD

适用于:需要逻辑卷管理、快照、克隆等高级功能

3. 核心组件详解

3.1 BDEV 抽象层 (Block Device Abstraction Layer)

位置: lib/bdev/bdev.c

功能: SPDK 的块设备抽象层核心,提供统一的块设备接口。

主要职责:

  1. 设备管理

    • 设备的注册和注销
    • 设备查询和枚举
    • 设备生命周期管理
  2. I/O 管理

    • I/O 请求的路由和分发
    • I/O 缓冲区的分配和管理
    • I/O 完成回调处理
  3. QoS 控制

    • I/O 速率限制(IOPS、带宽)
    • 读写分离的速率控制
    • 时间片(timeslice)机制
  4. Channel 管理

    • 每线程的 I/O channel 创建
    • Channel 和共享资源的关联
    • 多 channel 共享管理

关键数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// BDEV 管理器(全局单例)
struct spdk_bdev_mgr {
struct spdk_mempool *bdev_io_pool; // I/O 对象内存池
struct spdk_mempool *buf_small_pool; // 小缓冲区内存池
struct spdk_mempool *buf_large_pool; // 大缓冲区内存池
void *zero_buffer; // 零缓冲区

TAILQ_HEAD(, spdk_bdev_module) bdev_modules; // BDEV 模块链表
struct spdk_bdev_list bdevs; // 已注册的 BDEV 设备链表

bool init_complete; // 初始化完成标志
pthread_mutex_t mutex; // 互斥锁
};

// BDEV 设备结构
struct spdk_bdev {
char *name; // 设备名称
struct spdk_bdev_module *module; // 所属模块
struct spdk_bdev_fn_table *fn_table; // 函数表(读、写、刷盘等)

uint64_t blockcnt; // 总块数
uint32_t blocklen; // 块大小
uint32_t optimal_io_boundary; // 最优 I/O 边界

struct spdk_bdev_qos *qos; // QoS 配置

TAILQ_ENTRY(spdk_bdev) link; // 链表节点
};

3.2 Blobstore (真正的磁盘空间管理核心) ⭐

位置: lib/blob/blobstore.c

功能: Blobstore 是 SPDK 中真正的磁盘空间管理核心,负责磁盘空间的分配、回收和元数据管理。

核心职责:

  1. 磁盘空间管理

    • Cluster(存储单元,默认 1MB)的分配和回收
    • 空闲空间的管理和跟踪
    • 空间碎片整理
  2. 元数据管理

    • 元数据的持久化(存储在设备上)
    • 元数据页(Page)的管理
    • 元数据的同步和恢复
  3. Blob 对象管理

    • Blob(可变大小对象)的创建、删除、打开、关闭
    • Blob 数据的读、写、擦除操作
    • Blob 快照和克隆支持
  4. I/O 路径优化

    • 元数据操作和 I/O 操作分离
    • 元数据线程和 I/O 线程分离
    • 无锁设计的 I/O 路径

关键数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Blobstore 结构
struct spdk_blob_store {
struct spdk_bs_dev *dev; // 底层块设备
uint64_t total_clusters; // 总集群数
uint64_t free_clusters; // 空闲集群数
uint32_t cluster_sz; // 集群大小(默认 1MB)
uint32_t page_size; // 元数据页大小
uint32_t num_md_pages; // 元数据页数量

// 元数据管理
struct spdk_bs_md_mask *used_clusters; // 已使用集群位图
struct spdk_bs_md_mask *used_pages; // 已使用页位图

// Blob 管理
struct spdk_blob *open_blob[SPDK_BLOBSTORE_TYPE_LENGTH];
uint64_t next_blobid; // 下一个 Blob ID
};

// Blob 结构
struct spdk_blob {
struct spdk_blob_store *bs; // 所属的 blobstore
spdk_blob_id id; // Blob ID
spdk_blob_id parent_id; // 父 Blob ID(用于克隆)

// 可变数据(会持久化到磁盘)
struct spdk_blob_mut_data clean; // 干净副本(与磁盘一致)
struct spdk_blob_mut_data active; // 活动副本(当前状态)

uint64_t num_clusters; // 集群数量
uint64_t *clusters; // 集群 LBA 数组

enum spdk_blob_state state; // Blob 状态
uint32_t open_ref; // 打开引用计数
};

Blobstore 与 BDEV 的关系:

1
2
3
4
5
6
// Blobstore 通过 bdev 访问底层存储
struct spdk_bs_dev *spdk_bdev_create_bs_dev_from_desc(struct spdk_bdev_desc *desc);

// Blobstore 在 bdev 上初始化
int spdk_bs_init(struct spdk_bs_dev *dev, struct spdk_blob_store_opts *opts,
spdk_bs_op_with_handle_complete cb_fn, void *cb_arg);

关键特性:

  • 持久化: 所有元数据都持久化到磁盘,支持恢复
  • 高性能: I/O 路径无锁,元数据和数据分离
  • 可扩展: 支持可变大小的 Blob,最大可达设备容量
  • 快照支持: 支持 Blob 快照和克隆

3.3 lvol (逻辑卷管理)

位置: lib/lvol/lvol.c

功能: lvol 是建立在 Blobstore 之上的逻辑卷管理系统,将 Blobstore 的 Blob 封装为逻辑卷(logical volume)。

核心职责:

  1. 逻辑卷存储(lvol store)管理

    • 在 bdev 上创建 Blobstore
    • 管理 lvol store 的生命周期
    • lvol store 的元数据管理
  2. 逻辑卷(lvol)管理

    • 创建、删除、打开、关闭逻辑卷
    • 每个逻辑卷对应 Blobstore 上的一个 Blob
    • 逻辑卷的快照和克隆
  3. BDEV 封装

    • 将逻辑卷封装为 BDEV 设备
    • 提供块设备接口给上层协议使用
    • I/O 请求的路由和处理

关键数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 逻辑卷存储结构
struct spdk_lvol_store {
char name[SPDK_LVS_NAME_MAX]; // 名称
struct spdk_blob_store *blobstore; // 底层的 Blobstore
struct spdk_bdev *bdev; // 底层块设备
struct spdk_bdev_desc *bdev_desc; // 块设备描述符

// 逻辑卷列表
TAILQ_HEAD(, spdk_lvol) lvols; // 逻辑卷链表

uint32_t cluster_sz; // 集群大小
uint64_t total_data_clusters; // 总数据集群数
uint64_t free_clusters; // 空闲集群数
};

// 逻辑卷结构
struct spdk_lvol {
char name[SPDK_LVOL_NAME_MAX]; // 名称
char unique_id[SPDK_LVOL_NAME_MAX]; // 唯一标识符

struct spdk_lvol_store *lvs; // 所属的 lvol store
struct spdk_blob *blob; // 对应的 Blob

struct spdk_bdev *bdev; // 对应的 BDEV 设备
uint32_t ref_count; // 引用计数

uint64_t size_in_bytes; // 大小(字节)
uint64_t num_clusters; // 集群数量

bool action_in_progress; // 是否有操作正在进行
};

lvol 与 Blobstore 的关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1. 创建 lvol store
spdk_lvs_init()
→ 在 bdev 上创建 Blobstore
→ 初始化 lvol store 元数据

2. 创建逻辑卷
spdk_lvol_create()
→ 在 Blobstore 上创建 Blob
→ 将 Blob 封装为 lvol
→ 创建对应的 BDEV 设备

3. 打开逻辑卷
spdk_lvol_open()
→ 打开对应的 Blob
→ 增加引用计数

4. I/O 操作
应用 → BDEV → lvol → Blob → Blobstore → bdev → NVMe Driver

关键功能:

  • 快照: 基于 Blob 快照实现
  • 克隆: 基于 Blob 克隆实现
  • 动态扩展: 逻辑卷可以动态扩展
  • 元数据持久化: 所有元数据都持久化到 Blobstore

3.4 NVMe BDEV 模块

位置: module/bdev/nvme/bdev_nvme.c

功能: 将 NVMe 控制器和命名空间封装为 BDEV 设备。

主要流程:

1
2
3
4
5
6
7
8
9
1. NVMe 控制器探测 (Probe)

2. NVMe 控制器附加 (Attach)

3. 命名空间识别 (Identify Namespace)

4. 创建 BDEV 设备

5. 注册到 BDEV 管理器

关键函数:

  • bdev_nvme_create(): 创建 NVMe BDEV
  • bdev_nvme_delete(): 删除 NVMe BDEV
  • nvme_bdev_create(): 从命名空间创建 BDEV
  • nvme_bdev_destruct(): 销毁 BDEV

3.3 NVMe 驱动层

位置: lib/nvme/nvme_ctrlr.c, lib/nvme/nvme_pcie.c

功能: 用户态 NVMe 驱动,管理 NVMe 控制器、命名空间和队列对。

主要组件:

3.3.1 NVMe 控制器管理

1
2
3
4
5
6
7
8
9
10
11
12
13
// 控制器结构
struct spdk_nvme_ctrlr {
struct spdk_nvme_transport_id trid; // 传输ID
struct spdk_nvme_ctrlr_opts opts; // 控制器选项

struct spdk_nvme_ns *ns[SPDK_NVME_MAX_NS]; // 命名空间数组
uint32_t num_ns; // 命名空间数量

struct spdk_nvme_qpair *adminq; // 管理队列
struct spdk_nvme_qpair **io_qpairs; // I/O 队列数组

struct nvme_async_event_request *aer_list; // 异步事件请求列表
};

关键流程:

  1. 探测阶段 (spdk_nvme_probe())

    • 扫描 PCI 总线或网络地址
    • 识别 NVMe 控制器
    • 调用用户回调函数
  2. 附加阶段 (spdk_nvme_ctrlr_attach())

    • 初始化控制器寄存器
    • 创建管理队列对 (Admin Queue Pair)
    • 识别控制器能力
    • 识别命名空间列表
  3. 命名空间识别 (nvme_ctrlr_identify_active_ns())

    • 获取活动命名空间列表
    • 对每个命名空间进行识别
    • 创建命名空间对象
  4. 队列对管理 (spdk_nvme_ctrlr_alloc_io_qpair())

    • 创建 I/O 队列对
    • 分配队列内存
    • 配置队列参数

3.3.2 传输层

SPDK 支持多种传输类型:

  • PCIe 传输 (nvme_pcie.c)

    • 直接访问 PCIe 设备
    • 使用 VFIO 或 UIO 绑定设备
    • MMIO 寄存器访问
  • RDMA 传输 (nvme_rdma.c)

    • 基于 InfiniBand 或 RoCE
    • 支持 NVMe over Fabrics
  • TCP 传输 (nvme_tcp.c)

    • 基于 TCP/IP 网络
    • NVMe/TCP 协议实现
  • FC 传输 (nvme_fc.c)

    • 光纤通道传输
    • NVMe over FC 协议

3.4 环境抽象层 (env_dpdk)

位置: lib/env_dpdk/

功能: 封装 DPDK 功能,为 SPDK 提供统一的环境接口。

主要功能:

  1. 内存管理 (memory.c)

    • 大页内存分配
    • NUMA 感知的内存分配
    • IOVA (IO Virtual Address) 管理
  2. 线程管理 (threads.c)

    • 逻辑核心 (lcore) 管理
    • 线程绑定
    • CPU 亲和性设置
  3. PCI 设备管理 (pci.c)

    • PCI 总线扫描
    • 设备发现和枚举
    • VFIO/UIO 绑定

4. 硬盘发现与管理流程

4.1 PCIe NVMe 设备发现流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
┌─────────────────────────────────────────────────────────────┐
│ 1. 系统初始化 │
│ spdk_env_init() │
│ └─> rte_eal_init() (DPDK EAL 初始化) │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ 2. PCI 总线扫描 │
│ DPDK PCI 总线扫描 │
│ └─> 发现 NVMe 控制器 │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ 3. NVMe 控制器探测 │
│ spdk_nvme_probe() │
│ ├─> 扫描 PCI 设备 │
│ ├─> 匹配 NVMe 设备 │
│ └─> 调用用户探测回调 │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ 4. NVMe 控制器附加 │
│ nvme_ctrlr_attach() │
│ ├─> 初始化 PCIe 传输 │
│ ├─> 读取控制器寄存器 (CAP, VS) │
│ ├─> 创建管理队列对 (Admin Queue Pair) │
│ ├─> 识别控制器 (Identify Controller) │
│ └─> 识别命名空间 (Identify Namespaces) │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ 5. 命名空间识别 │
│ nvme_ctrlr_identify_active_ns() │
│ ├─> 获取活动命名空间列表 │
│ └─> 对每个命名空间: │
│ ├─> Identify Namespace │
│ ├─> Identify Namespace Descriptor List │
│ └─> 创建 spdk_nvme_ns 对象 │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ 6. 创建 BDEV 设备 │
│ bdev_nvme_create() │
│ ├─> 为每个命名空间创建 nvme_bdev │
│ ├─> 注册到 BDEV 管理器 │
│ └─> 触发设备就绪事件 │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ 7. 设备可用 │
│ - BDEV 设备已注册 │
│ - 可以通过 RPC 或应用程序访问 │
└─────────────────────────────────────────────────────────────┘

4.2 NVMe over Fabrics 设备发现流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
┌─────────────────────────────────────────────────────────────┐
│ 1. 配置传输ID │
│ 指定 trtype (RDMA/TCP/FC) │
│ 指定 traddr (目标地址) │
│ 指定 trsvcid (端口号) │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ 2. 网络连接建立 │
│ - RDMA: 建立 QP (Queue Pair) │
│ - TCP: 建立 TCP 连接 │
│ - FC: 建立 FC 连接 │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ 3. NVMe over Fabrics 握手 │
│ - Fabric Connect 命令 │
│ - 获取控制器 ID │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ 4. 后续流程同 PCIe │
│ (附加控制器 → 识别命名空间 → 创建 BDEV) │
└─────────────────────────────────────────────────────────────┘

4.3 热插拔支持

SPDK 支持设备热插拔,通过以下机制实现:

  1. 热插拔轮询 (bdev_nvme_set_hotplug())

    • 定期扫描 PCI 总线
    • 检测新设备或设备移除
    • 触发相应的回调函数
  2. 热插拔事件处理

    • 设备添加:执行探测和附加流程
    • 设备移除:清理资源和注销设备

5. I/O 路径

5.1 I/O 请求流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
应用程序

spdk_bdev_read/write()

BDEV 抽象层 (bdev.c)
├─> 创建 bdev_io 结构
├─> QoS 检查
├─> 路由到相应的 BDEV 模块
└─> 提交 I/O 请求

BDEV 模块 (如 bdev_nvme.c)
├─> 获取 NVMe 队列对
├─> 构建 NVMe 命令
└─> 提交到 NVMe 队列

NVMe 传输层 (如 nvme_pcie.c)
├─> 写入提交队列 (SQ)
├─> 更新门铃寄存器
└─> 轮询完成队列 (CQ)

硬件设备
├─> 执行 I/O 操作
└─> 写入完成队列

NVMe 传输层
├─> 检测完成队列更新
├─> 处理完成事件
└─> 调用完成回调

BDEV 模块
└─> 调用 bdev_io 完成回调

应用程序
└─> I/O 完成回调执行

5.2 轮询模式 I/O

SPDK 使用轮询模式而非中断模式:

1
2
3
4
5
6
7
8
9
10
// 典型的轮询 I/O 模式
while (!io_completed) {
// 提交 I/O
spdk_bdev_read();

// 轮询完成队列
while (pending_ios > 0) {
spdk_nvme_qpair_process_completions(qpair, 0);
}
}

优势:

  • 零中断延迟
  • 可预测的延迟
  • 更高的吞吐量

6. BDEV 模块注册机制

6.1 模块注册

每个 BDEV 模块通过以下方式注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 模块注册宏
SPDK_BDEV_MODULE_REGISTER(nvme, nvme_module_init, nvme_module_fini)

// 模块结构
struct spdk_bdev_module {
const char *name; // 模块名称

// 初始化回调
int (*module_init)(void);

// 退出回调
void (*module_fini)(void);

// 异步初始化回调
void (*async_init)(void);

// 异步退出回调
void (*async_fini)(void);

// 检查设备回调
void (*examine)(struct spdk_bdev *bdev);

// 获取配置回调
int (*config_json)(struct spdk_json_write_ctx *w);

TAILQ_ENTRY(spdk_bdev_module) tailq;
};

6.2 模块初始化顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
1. 静态模块列表构建
(通过 SPDK_BDEV_MODULE_REGISTER 宏)

2. 模块异步初始化阶段
- 调用每个模块的 async_init()
- 例如:NVMe 模块扫描 PCI 设备

3. 模块同步初始化阶段
- 调用每个模块的 module_init()

4. 设备检查阶段
- 调用每个模块的 examine()
- 模块可以创建或修改 BDEV 设备

7. 设备配置与管理

7.1 JSON-RPC 接口

SPDK 通过 JSON-RPC 提供设备管理接口:

主要 RPC 命令:

  • bdev_nvme_attach_controller: 附加 NVMe 控制器
  • bdev_nvme_detach_controller: 分离 NVMe 控制器
  • bdev_nvme_get_controllers: 获取控制器列表
  • bdev_get_bdevs: 获取所有 BDEV 设备列表
  • bdev_get_bdevs: 获取 BDEV 统计信息

7.2 配置文件

SPDK 支持通过配置文件定义设备:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"subsystems": [
{
"subsystem": "bdev",
"config": [
{
"method": "bdev_nvme_attach_controller",
"params": {
"name": "Nvme0",
"trtype": "PCIe",
"traddr": "0000:01:00.0"
}
}
]
}
]
}

8. 性能优化特性

8.1 零拷贝

  • 直接内存访问 (DMA)
  • 避免不必要的内存拷贝
  • 使用大页内存减少 TLB 缺失

8.2 NUMA 感知

  • 内存分配考虑 NUMA 节点
  • CPU 核心绑定到 NUMA 节点
  • 减少跨 NUMA 访问

8.3 无锁设计

  • 每核心数据结构
  • 无锁队列
  • 减少锁竞争

8.4 I/O 批处理

  • 批量提交 I/O 请求
  • 减少系统调用开销
  • 提高吞吐量

9. Blobstore + lvol 完整工作流程

9.1 初始化流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
┌─────────────────────────────────────────────────────────────┐
│ 1. 发现 NVMe 设备 │
│ NVMe Driver 扫描 PCI 总线 │
│ 发现 NVMe SSD │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ 2. 创建 NVMe BDEV │
│ bdev_nvme_create() │
│ 将 NVMe 命名空间封装为 BDEV │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ 3. 创建 Blobstore (可选) │
│ spdk_lvs_init() │
│ → spdk_bs_init() 在 BDEV 上创建 Blobstore │
│ → 初始化超级块和元数据区域 │
│ → 创建逻辑卷存储 (lvol store) │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ 4. 创建逻辑卷 (可选) │
│ spdk_lvol_create() │
│ → 在 Blobstore 上创建 Blob │
│ → 将 Blob 封装为逻辑卷 (lvol) │
│ → 创建 lvol BDEV 设备 │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ 5. 上层协议使用 │
│ NVMe-oF / iSCSI / vhost 等协议使用 lvol BDEV │
└─────────────────────────────────────────────────────────────┘

9.2 I/O 请求流程(使用 Blobstore + lvol)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
应用层发起 I/O 请求

spdk_bdev_read/write() (通过 lvol BDEV)

lvol 模块 (lib/lvol/lvol.c)
├─> 将 BDEV I/O 转换为 Blob I/O
└─> 调用 Blobstore I/O 接口

Blobstore (lib/blob/blobstore.c)
├─> 元数据查找:LBA → Cluster 映射
├─> Cluster 查找:Cluster ID → 物理 LBA
└─> 转换为底层 BDEV I/O

BDEV 抽象层 (lib/bdev/bdev.c)
└─> 路由到 NVMe BDEV 模块

NVMe BDEV 模块 (module/bdev/nvme/bdev_nvme.c)
├─> 获取 NVMe 队列对
├─> 构建 NVMe 命令
└─> 提交到 NVMe 队列

NVMe Driver (lib/nvme/nvme_pcie.c)
├─> 写入提交队列 (SQ)
├─> 更新门铃寄存器
└─> 轮询完成队列 (CQ)

NVMe SSD 硬件
├─> 执行 I/O 操作
└─> 写入完成队列

9.3 元数据管理流程

Blobstore 元数据组织:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
设备布局:
┌─────────────────────────────────────────────────────────┐
│ 超级块 (Super Block) │
│ - Blobstore 标识 │
│ - 版本信息 │
│ - 集群大小 │
│ - 元数据页数量 │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 元数据区域 (Metadata Pages) │
│ - 集群使用位图 │
│ - 元数据页使用位图 │
│ - Blob 元数据页 │
│ - Blob ID │
│ - 父 Blob ID │
│ - Cluster 列表 (LBA 数组) │
│ - 扩展属性 │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 数据区域 (Data Clusters) │
│ - Cluster 0 (1MB) │
│ - Cluster 1 (1MB) │
│ - ... │
└─────────────────────────────────────────────────────────┘

元数据操作流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1. 创建 Blob
spdk_blob_create()
→ 分配 Blob ID
→ 分配元数据页
→ 初始化 Blob 元数据
→ 同步元数据到磁盘

2. 写入数据
spdk_blob_write()
→ 查找或分配 Cluster
→ 更新 Cluster 映射表
→ 执行数据写入
→ 同步元数据(如果需要)

3. 读取数据
spdk_blob_read()
→ 查找 Cluster 映射
→ 执行数据读取

9.4 快照和克隆流程

基于 Blobstore 的快照:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1. 创建快照
spdk_lvol_create_snapshot()
→ spdk_blob_clone()
→ 在 Blobstore 上克隆 Blob
→ 创建新的 Blob ID
→ 共享父 Blob 的 Cluster(Copy-on-Write)
→ 创建新的 lvol

2. 写入快照
如果 Cluster 被共享:
→ 分配新的 Cluster
→ 复制旧数据到新 Cluster
→ 更新快照的 Cluster 映射
→ 执行写入操作

10. 总结

SPDK 的硬盘管理架构通过多层抽象实现了:

  1. 统一接口: BDEV 抽象层提供统一的块设备接口

  2. Blobstore 是核心: Blobstore 是真正的磁盘空间管理核心,负责:

    • 磁盘空间的分配和回收
    • 元数据的持久化和管理
    • Blob 对象的生命周期管理
  3. lvol 提供高级功能: 逻辑卷管理基于 Blobstore,提供:

    • 快照和克隆
    • 动态扩展
    • 元数据持久化
  4. 模块化设计: 不同类型的存储设备通过模块化方式管理

  5. 高性能: 用户态驱动、轮询模式、零拷贝等技术实现高性能

  6. 灵活性: 支持多种传输类型(PCIe、RDMA、TCP、FC)

  7. 可扩展性: 易于添加新的 BDEV 模块

核心架构路径:

1
2
3
4
5
6
7
8
9
10
11
NVMe SSD

NVMe Driver (用户态,轮询模式)

bdev (统一块设备抽象层)

Blobstore (真正的磁盘空间管理核心) ⭐

lvol (逻辑卷管理)

上层协议 (NVMe-oF / iSCSI / vhost / 应用)

这种架构使得 SPDK 能够高效管理各种类型的存储设备,为上层应用提供高性能的存储服务。Blobstore 作为磁盘空间管理的核心,为逻辑卷管理等高级功能提供了坚实的基础。

SPDK调用DPDK流程分析文档

SPDK调用DPDK流程分析文档

目录

  1. 概述
  2. 初始化流程
  3. 主要调用点
  4. 环境抽象层
  5. 关键调用流程详解
  6. DPDK功能映射

概述

SPDK与DPDK的关系

SPDK (Storage Performance Development Kit) 使用DPDK (Data Plane Development Kit) 作为其底层环境抽象层。SPDK通过 env_dpdk 模块封装了DPDK的功能,为上层存储应用提供统一的环境抽象接口。

设计目的

  1. 内存管理: 使用DPDK的大页内存管理
  2. CPU核心管理: 利用DPDK的CPU亲和性功能
  3. PCI设备访问: 通过DPDK的PCI总线访问硬件
  4. 线程管理: 使用DPDK的轻量级线程模型
  5. 中断处理: 利用DPDK的中断机制

初始化流程

完整初始化流程图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
应用程序启动

spdk_app_start()

app_setup_env() [event/app.c]

spdk_env_init() [env_dpdk/init.c]

build_eal_cmdline() [构建DPDK EAL参数]

rte_eal_init() [DPDK EAL初始化]

spdk_env_dpdk_post_init() [SPDK环境后初始化]

pci_env_init() [PCI环境初始化]
mem_map_init() [内存映射初始化]
vtophys_init() [虚拟地址到物理地址映射初始化]

详细步骤

1. 应用程序入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// event/app.c
int spdk_app_start(struct spdk_app_opts *opts, spdk_msg_fn start_fn, void *arg1)
{
// ...

// 1. 设置环境
rc = app_setup_env(opts);
if (rc < 0) {
return rc;
}

// 2. 创建reactor
rc = spdk_reactors_init();

// 3. 启动应用
// ...
}

2. 环境设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// event/app.c
static int app_setup_env(struct spdk_app_opts *opts)
{
struct spdk_env_opts env_opts = {};

// 1. 初始化环境选项
spdk_env_opts_init(&env_opts);

// 2. 填充选项
env_opts.name = opts->name;
env_opts.core_mask = opts->reactor_mask;
env_opts.shm_id = opts->shm_id;
env_opts.mem_channel = opts->mem_channel;
env_opts.master_core = opts->master_core;
env_opts.mem_size = opts->mem_size;
// ...

// 3. 初始化环境
rc = spdk_env_init(&env_opts);

return rc;
}

3. DPDK EAL初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// env_dpdk/init.c
int spdk_env_init(const struct spdk_env_opts *opts)
{
char **dpdk_args = NULL;
int rc;

// 1. 构建DPDK EAL命令行参数
rc = build_eal_cmdline(opts);
if (rc < 0) {
return -EINVAL;
}

// 2. 复制参数数组(DPDK会修改)
dpdk_args = calloc(g_eal_cmdline_argcount, sizeof(char *));
memcpy(dpdk_args, g_eal_cmdline, sizeof(char *) * g_eal_cmdline_argcount);

// 3. 调用DPDK EAL初始化
optind = 1; // 重置getopt
rc = rte_eal_init(g_eal_cmdline_argcount, dpdk_args);
optind = orig_optind;

if (rc < 0) {
return -rte_errno;
}

// 4. SPDK后初始化
rc = spdk_env_dpdk_post_init(legacy_mem);

return rc;
}

主要调用点

1. EAL初始化 (rte_eal_init)

调用位置: env_dpdk/init.c:567

作用: 初始化DPDK环境抽象层(Environment Abstraction Layer)

参数构建过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// env_dpdk/init.c
static int build_eal_cmdline(const struct spdk_env_opts *opts)
{
char **args = NULL;
int argcount = 0;

// 程序名
args = push_arg(args, &argcount, _sprintf_alloc("%s", opts->name));

// CPU核心掩码
args = push_arg(args, &argcount, _sprintf_alloc("-c %s", opts->core_mask));

// 内存通道数
if (opts->mem_channel > 0) {
args = push_arg(args, &argcount, _sprintf_alloc("-n %d", opts->mem_channel));
}

// 内存大小
if (opts->mem_size >= 0) {
args = push_arg(args, &argcount, _sprintf_alloc("-m %d", opts->mem_size));
}

// Master核心
if (opts->master_core > 0) {
args = push_arg(args, &argcount, _sprintf_alloc("--master-lcore=%d", opts->master_core));
}

// PCI黑名单/白名单
if (opts->num_pci_addr > 0) {
// ... 添加PCI地址列表
}

// IOMMU模式
if (opts->iova_mode) {
args = push_arg(args, &argcount, _sprintf_alloc("--iova-mode=%s", opts->iova_mode));
}

// 基础虚拟地址
args = push_arg(args, &argcount, _sprintf_alloc("--base-virtaddr=0x%" PRIx64, opts->base_virtaddr));

// 其他选项...

g_eal_cmdline = args;
g_eal_cmdline_argcount = argcount;
return argcount;
}

构建的参数示例:

1
2
3
["spdk", "-c", "0x1", "-n", "4", "-m", "1024", 
"--master-lcore=0", "--base-virtaddr=0x200000000000",
"--file-prefix=spdk_pid1234", "--log-level=lib.eal:6", ...]

2. 内存管理调用

内存分配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// env_dpdk/env.c
void *spdk_malloc(size_t size, size_t align, uint64_t *phys_addr,
int socket_id, uint32_t flags)
{
void *buf;

// 使用DPDK的内存分配
align = spdk_max(align, RTE_CACHE_LINE_SIZE);
buf = rte_malloc_socket(NULL, size, align, socket_id);

// 获取物理地址
if (buf && phys_addr) {
*phys_addr = virt_to_phys(buf);
}

return buf;
}

虚拟地址到物理地址转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// env_dpdk/env.c
static uint64_t virt_to_phys(void *vaddr)
{
uint64_t ret;

// 首先尝试使用DPDK的转换
ret = rte_malloc_virt2iova(vaddr);
if (ret != RTE_BAD_IOVA) {
return ret;
}

// 回退到SPDK自己的实现
return spdk_vtophys(vaddr, NULL);
}

Memzone管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// env_dpdk/env.c
void *spdk_memzone_reserve_aligned(const char *name, size_t len,
int socket_id, unsigned flags, unsigned align)
{
const struct rte_memzone *mz;
unsigned dpdk_flags = 0;

// 转换SPDK标志到DPDK标志
if ((flags & SPDK_MEMZONE_NO_IOVA_CONTIG) == 0) {
dpdk_flags |= RTE_MEMZONE_IOVA_CONTIG;
}

// 使用DPDK的memzone分配
mz = rte_memzone_reserve_aligned(name, len, socket_id, dpdk_flags, align);

if (mz != NULL) {
memset(mz->addr, 0, len);
return mz->addr;
}

return NULL;
}

3. CPU核心管理调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// env_dpdk/threads.c
uint32_t spdk_env_get_current_core(void)
{
// 直接调用DPDK API
return rte_lcore_id();
}

uint32_t spdk_env_get_core_count(void)
{
return rte_lcore_count();
}

uint32_t spdk_env_get_next_core(uint32_t prev_core)
{
unsigned lcore = rte_get_next_lcore(prev_core, 0, 0);
if (lcore == RTE_MAX_LCORE) {
return UINT32_MAX;
}
return lcore;
}

int spdk_env_thread_launch_pinned(uint32_t core, thread_start_fn fn, void *arg)
{
// 使用DPDK的远程启动功能
return rte_eal_remote_launch(fn, arg, core);
}

void spdk_env_thread_wait_all(void)
{
rte_eal_mp_wait_lcore();
}

4. PCI设备管理调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// env_dpdk/pci.c
static int map_bar_rte(struct spdk_pci_device *device, uint32_t bar,
void **mapped_addr, uint64_t *phys_addr, uint64_t *size)
{
struct rte_pci_device *dev = device->dev_handle;

// 直接使用DPDK PCI设备的信息
*mapped_addr = dev->mem_resource[bar].addr;
*phys_addr = (uint64_t)dev->mem_resource[bar].phys_addr;
*size = (uint64_t)dev->mem_resource[bar].len;

return 0;
}

static int cfg_read_rte(struct spdk_pci_device *dev, void *value,
uint32_t len, uint32_t offset)
{
// 使用DPDK的PCI配置空间读取
int rc = rte_pci_read_config(dev->dev_handle, value, len, offset);
return (rc > 0 && (uint32_t) rc == len) ? 0 : -1;
}

static int cfg_write_rte(struct spdk_pci_device *dev, void *value,
uint32_t len, uint32_t offset)
{
// 使用DPDK的PCI配置空间写入
int rc = rte_pci_write_config(dev->dev_handle, value, len, offset);
return (rc > 0 && (uint32_t) rc == len) ? 0 : -1;
}

环境抽象层

SPDK环境抽象接口

SPDK定义了一组环境抽象接口,env_dpdk模块实现了这些接口:

内存管理接口

1
2
3
4
5
6
7
// spdk/env.h
void *spdk_malloc(size_t size, size_t align, uint64_t *phys_addr,
int socket_id, uint32_t flags);
void spdk_free(void *buf);
void *spdk_dma_malloc(size_t size, size_t align, uint64_t *phys_addr);
void spdk_dma_free(void *buf);
void *spdk_memzone_reserve(const char *name, size_t len, int socket_id, unsigned flags);

CPU核心接口

1
2
3
4
5
uint32_t spdk_env_get_current_core(void);
uint32_t spdk_env_get_core_count(void);
uint32_t spdk_env_get_next_core(uint32_t prev_core);
int spdk_env_thread_launch_pinned(uint32_t core, thread_start_fn fn, void *arg);
void spdk_env_thread_wait_all(void);

PCI设备接口

1
2
3
4
5
6
int spdk_pci_device_map_bar(struct spdk_pci_device *dev, uint32_t bar, 
void **mapped_addr, uint64_t *phys_addr, uint64_t *size);
int spdk_pci_device_cfg_read(struct spdk_pci_device *dev, void *value,
uint32_t len, uint32_t offset);
int spdk_pci_device_cfg_write(struct spdk_pci_device *dev, void *value,
uint32_t len, uint32_t offset);

关键调用流程详解

流程1: DPDK初始化流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1. 应用程序调用 spdk_app_start()

2. app_setup_env() 设置环境选项

3. spdk_env_init() 开始初始化

4. build_eal_cmdline() 构建DPDK命令行参数
├── 添加程序名
├── 添加CPU核心掩码 (-c)
├── 添加内存通道数 (-n)
├── 添加内存大小 (-m)
├── 添加master核心 (--master-lcore)
├── 添加PCI黑名单/白名单
├── 添加IOMMU模式 (--iova-mode)
├── 添加基础虚拟地址 (--base-virtaddr)
├── 添加文件前缀 (--file-prefix)
└── 添加日志级别 (--log-level)

5. rte_eal_init() 调用DPDK EAL初始化
├── DPDK解析命令行参数
├── DPDK分配大页内存
├── DPDK初始化PCI总线
├── DPDK设置CPU亲和性
└── DPDK初始化其他子系统

6. spdk_env_dpdk_post_init() SPDK后初始化
├── pci_env_init() PCI环境初始化
├── mem_map_init() 内存映射初始化
└── vtophys_init() 虚拟地址转换初始化

7. 初始化完成

流程2: 内存分配流程

1
2
3
4
5
6
7
8
9
应用程序调用 spdk_malloc()

spdk_malloc() [env_dpdk/env.c]
├── 对齐到缓存行
├── 调用 rte_malloc_socket() [DPDK API]
├── 调用 virt_to_phys()
│ ├── 尝试 rte_malloc_virt2iova() [DPDK API]
│ └── 回退到 spdk_vtophys()
└── 返回分配的缓冲区

流程3: PCI设备枚举流程

1
2
3
4
5
6
7
8
9
10
11
12
13
spdk_pci_enumerate()

DPDK PCI总线枚举
├── rte_eal_init() 时已经初始化PCI总线
├── SPDK注册PCI驱动
│ └── rte_pci_register() [DPDK API]
├── DPDK扫描PCI设备
│ └── rte_eal_scan_proc() [内部调用]
├── DPDK匹配驱动和设备
└── 调用SPDK的驱动probe函数
└── pci_device_init()
├── map_bar_rte() [使用DPDK PCI设备信息]
└── vtophys_pci_device_added() [注册到地址转换]

流程4: 线程启动流程

1
2
3
4
5
6
7
8
spdk_env_thread_launch_pinned()

rte_eal_remote_launch() [DPDK API]
├── DPDK检查核心是否有效
├── DPDK检查核心是否已启动
├── 通过管道发送启动消息
└── 目标核心接收消息并启动线程
└── 在目标核心上执行函数

DPDK功能映射

内存管理映射

SPDK函数 DPDK函数 说明
spdk_malloc() rte_malloc_socket() 内存分配
spdk_free() rte_free() 内存释放
spdk_realloc() rte_realloc() 内存重分配
spdk_memzone_reserve() rte_memzone_reserve_aligned() 大页内存预留
spdk_vtophys() rte_malloc_virt2iova() 虚拟地址转物理地址

CPU核心管理映射

SPDK函数 DPDK函数 说明
spdk_env_get_current_core() rte_lcore_id() 获取当前核心ID
spdk_env_get_core_count() rte_lcore_count() 获取核心数量
spdk_env_get_next_core() rte_get_next_lcore() 获取下一个核心
spdk_env_get_socket_id() rte_lcore_to_socket_id() 获取Socket ID
spdk_env_thread_launch_pinned() rte_eal_remote_launch() 启动绑定核心的线程
spdk_env_thread_wait_all() rte_eal_mp_wait_lcore() 等待所有线程

PCI设备管理映射

SPDK函数 DPDK函数/结构 说明
spdk_pci_device_map_bar() rte_pci_device.mem_resource[] 映射BAR空间
spdk_pci_device_cfg_read() rte_pci_read_config() 读取PCI配置空间
spdk_pci_device_cfg_write() rte_pci_write_config() 写入PCI配置空间
spdk_pci_enumerate() rte_bus_scan(), rte_bus_probe() 枚举PCI设备

其他功能映射

SPDK函数 DPDK函数 说明
spdk_get_ticks() rte_rdtsc() 获取时间戳计数器
spdk_delay_us() rte_delay_us() 微秒延迟
spdk_delay_ms() rte_delay_ms() 毫秒延迟

DPDK初始化参数详解

核心参数构建过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// env_dpdk/init.c: build_eal_cmdline()

// 1. CPU核心掩码
if (opts->core_mask[0] == '[') {
// 核心列表格式: [0,1,2-4]
args = push_arg(args, &argcount, _sprintf_alloc("-l %s", opts->core_mask + 1));
} else {
// 十六进制掩码: 0x1
args = push_arg(args, &argcount, _sprintf_alloc("-c %s", opts->core_mask));
}

// 2. 内存通道数(NUMA节点数)
if (opts->mem_channel > 0) {
args = push_arg(args, &argcount, _sprintf_alloc("-n %d", opts->mem_channel));
}

// 3. 内存大小(MB)
if (opts->mem_size >= 0) {
args = push_arg(args, &argcount, _sprintf_alloc("-m %d", opts->mem_size));
}

// 4. Master核心
if (opts->master_core > 0) {
args = push_arg(args, &argcount, _sprintf_alloc("--master-lcore=%d", opts->master_core));
}

// 5. IOMMU模式
if (opts->iova_mode) {
args = push_arg(args, &argcount, _sprintf_alloc("--iova-mode=%s", opts->iova_mode));
} else {
// 自动检测IOMMU能力
if (get_iommu_width() < SPDK_IOMMU_VA_REQUIRED_WIDTH) {
args = push_arg(args, &argcount, _sprintf_alloc("--iova-mode=pa"));
}
}

// 6. 基础虚拟地址
args = push_arg(args, &argcount, _sprintf_alloc("--base-virtaddr=0x%" PRIx64, opts->base_virtaddr));

// 7. 文件前缀(用于共享内存)
if (opts->shm_id < 0) {
args = push_arg(args, &argcount, _sprintf_alloc("--file-prefix=spdk_pid%d", getpid()));
} else {
args = push_arg(args, &argcount, _sprintf_alloc("--file-prefix=spdk%d", opts->shm_id));
args = push_arg(args, &argcount, strdup("--proc-type=auto"));
}

// 8. 内存分配匹配
#if RTE_VERSION >= RTE_VERSION_NUM(19, 02, 0, 0)
args = push_arg(args, &argcount, strdup("--match-allocations"));
#endif

// 9. PCI黑名单/白名单
if (opts->num_pci_addr > 0) {
for (i = 0; i < opts->num_pci_addr; i++) {
spdk_pci_addr_fmt(bdf, 32, &pci_addr[i]);
args = push_arg(args, &argcount, _sprintf_alloc("%s=%s",
(opts->pci_blacklist ? "--pci-blacklist" : "--pci-whitelist"), bdf));
}
}

// 10. 日志级别
args = push_arg(args, &argcount, strdup("--log-level=lib.eal:6"));
args = push_arg(args, &argcount, strdup("--log-level=lib.cryptodev:5"));
args = push_arg(args, &argcount, strdup("--log-level=user1:6"));

// 11. 用户自定义参数
if (opts->env_context) {
args = push_arg(args, &argcount, strdup(opts->env_context));
}

DPDK后初始化流程

spdk_env_dpdk_post_init()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// env_dpdk/init.c
int spdk_env_dpdk_post_init(bool legacy_mem)
{
int rc;

// 1. PCI环境初始化
pci_env_init();

// 2. 内存映射初始化
rc = mem_map_init(legacy_mem);
if (rc < 0) {
return rc;
}

// 3. 虚拟地址到物理地址转换初始化
rc = vtophys_init();
if (rc < 0) {
return rc;
}

return 0;
}

PCI环境初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
// env_dpdk/pci.c
void pci_env_init(void)
{
// 注册PCI设备热插拔回调
rte_pci_register_driver(&g_spdk_pci_driver);

// 扫描PCI设备
rte_bus_scan();
rte_bus_probe();

// 设置热插拔回调
rte_eal_alarm_set(...);
}

内存映射初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// env_dpdk/memory.c
int mem_map_init(bool legacy_mem)
{
// 创建内存映射结构
// 用于虚拟地址到物理地址转换
g_mem_reg_map = spdk_mem_map_alloc(0, ...);

// 注册映射操作
spdk_mem_map_register(&g_mem_reg_map, ...);

// 初始化VFIO(如果启用)
#if VFIO_ENABLED
vfio_init();
#endif
}

运行时调用示例

示例1: 内存分配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 应用程序代码
void *buf;
uint64_t phys_addr;

// SPDK内存分配
buf = spdk_dma_malloc(4096, 64, &phys_addr);

// 实际调用链:
// spdk_dma_malloc()
// └─> spdk_malloc(size, align, phys_addr, socket_id, flags)
// └─> rte_malloc_socket(NULL, size, align, socket_id) [DPDK]
// └─> DPDK从大页内存池分配
// └─> rte_malloc_virt2iova(buf) [DPDK]
// └─> 返回IOVA地址

示例2: 线程启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 应用程序代码
void worker_thread(void *arg)
{
// 在工作核心上执行
uint32_t core = spdk_env_get_current_core();
printf("Running on core %u\n", core);
}

// 启动线程
spdk_env_thread_launch_pinned(2, worker_thread, NULL);
spdk_env_thread_wait_all();

// 实际调用链:
// spdk_env_thread_launch_pinned()
// └─> rte_eal_remote_launch(fn, arg, core) [DPDK]
// └─> DPDK通过管道发送消息到目标核心
// └─> 目标核心接收消息
// └─> 在目标核心上执行函数

示例3: PCI设备访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// SPDK PCI驱动注册
static struct spdk_pci_driver nvme_driver = {
.name = "spdk_nvme",
.id_table = nvme_id_table,
};

spdk_pci_register_driver(&nvme_driver);

// 实际调用链:
// spdk_pci_register_driver()
// └─> rte_pci_register(&driver->driver) [DPDK]
// └─> DPDK将驱动添加到驱动列表
// └─> DPDK扫描PCI设备
// └─> rte_bus_scan() [DPDK]
// └─> DPDK匹配驱动和设备
// └─> rte_bus_probe() [DPDK]
// └─> 调用驱动的probe函数
// └─> pci_device_init()
// └─> 使用DPDK的PCI设备信息

DPDK与SPDK的交互点

1. 内存池 (Mempool)

1
2
3
4
5
6
7
8
9
10
11
12
13
// env_dpdk/env.c
void *spdk_mempool_create(const char *name, unsigned count, unsigned ele_size,
unsigned cache_size, int socket_id)
{
struct rte_mempool *mp;

// 使用DPDK的mempool
mp = rte_mempool_create(name, count, ele_size, cache_size,
0, NULL, NULL, obj_init, obj_init_arg,
NULL, socket_id, 0);

return mp;
}

2. 中断处理

1
2
3
4
// 使用DPDK的alarm机制
rte_eal_alarm_set(us, callback, arg);

// 使用DPDK的管道进行线程间通信

3. 时间管理

1
2
3
4
5
6
7
// env_dpdk/env.c (间接使用)
uint64_t spdk_get_ticks(void)
{
// SPDK可能使用DPDK的rte_rdtsc()
// 或自己的实现
return __rdtsc();
}

关键数据结构

SPDK环境选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// spdk/env.h
struct spdk_env_opts {
const char *name; // 进程名
const char *core_mask; // CPU核心掩码
int shm_id; // 共享内存ID
int mem_channel; // 内存通道数
int master_core; // Master核心
int mem_size; // 内存大小(MB)
bool hugepage_single_segments; // 单文件段
bool unlink_hugepage; // 卸载时删除大页
const char *hugedir; // 大页目录
bool no_pci; // 禁用PCI
size_t num_pci_addr; // PCI地址数量
struct spdk_pci_addr *pci_blacklist; // PCI黑名单
struct spdk_pci_addr *pci_whitelist; // PCI白名单
const char *env_context; // 自定义环境上下文
const char *iova_mode; // IOMMU模式
uint64_t base_virtaddr; // 基础虚拟地址
};

DPDK PCI设备结构

1
2
3
4
5
6
7
8
// SPDK通过DPDK的PCI设备结构访问硬件
struct rte_pci_device {
struct rte_device device;
struct rte_pci_addr addr; // PCI地址
struct rte_mem_resource mem_resource[PCI_MAX_RESOURCE]; // BAR资源
uint16_t id; // 设备ID
// ...
};

调用流程时序图

初始化时序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
应用程序          event/app.c        env_dpdk/init.c         DPDK
| | | |
|-- spdk_app_start()| | |
| | | |
|-- app_setup_env()| | |
| |-- spdk_env_init()--| |
| | | |
| |-- build_eal_cmdline() |
| | | |
| |-- rte_eal_init()------------------>| |
| | | |-- 初始化EAL
| | | |-- 分配大页内存
| | | |-- 初始化PCI总线
| | | |-- 设置CPU亲和性
| |<-------------------| |
| | | |
| |-- spdk_env_dpdk_post_init() |
| | |-- pci_env_init() |
| | |-- mem_map_init() |
| | |-- vtophys_init() |
| | | |
|<------------------| | |

内存分配时序

1
2
3
4
5
6
7
8
9
10
应用程序          env_dpdk/env.c          DPDK
| | |
|-- spdk_malloc()--| |
| |-- rte_malloc_socket()------------>|
| | |-- 从大页内存分配
| |<-------------------|
| |-- virt_to_phys() |
| | |-- rte_malloc_virt2iova()--->|
| | |<----------------|
|<------------------| |

总结

SPDK调用DPDK的关键点

  1. 初始化入口: rte_eal_init() - DPDK EAL初始化
  2. 内存管理: rte_malloc_*(), rte_memzone_*() - 大页内存管理
  3. CPU管理: rte_lcore_*(), rte_eal_remote_launch() - CPU核心管理
  4. PCI管理: rte_pci_*(), rte_bus_*() - PCI设备管理
  5. 时间管理: rte_rdtsc(), rte_delay_*() - 时间相关功能

SPDK的封装策略

  1. 统一接口: 通过SPDK环境抽象层提供统一接口
  2. 参数转换: 将SPDK参数转换为DPDK参数
  3. 功能扩展: 在DPDK基础上添加SPDK特定功能(如vtophys)
  4. 错误处理: 封装DPDK错误码为SPDK错误码

调用特点

  1. 直接调用: SPDK直接调用DPDK API,无中间层
  2. 参数适配: SPDK参数经过转换后传递给DPDK
  3. 后处理: DPDK初始化后,SPDK进行额外的初始化
  4. 功能复用: SPDK充分利用DPDK的基础功能,避免重复实现

文档版本: 1.0
最后更新: 2024年

SPDK代码详细分析文档

SPDK代码详细分析文档

1. 概述

SPDK(Storage Performance Development Kit)是Intel开发的高性能存储开发工具包。在Ceph中,SPDK被集成作为可选的块设备后端,用于直接访问NVMe设备,提供比传统内核驱动更高的I/O性能。

1.1 SPDK核心特性

  • 用户空间驱动:驱动程序运行在用户空间,避免内核态切换开销
  • 轮询模式:使用轮询而非中断来检查I/O完成,减少延迟和抖动
  • 零拷贝:直接内存访问,减少数据拷贝
  • 多队列支持:充分利用NVMe的多队列特性
  • 基于DPDK:使用DPDK提供的内存管理和PCIe设备访问

1.2 在Ceph中的用途

在Ceph中,SPDK主要用于:

  • BlueStore后端:作为BlueStore的可选块设备后端(NVMEDevice
  • 高性能存储:为需要极致性能的场景提供用户空间NVMe驱动
  • 可选启用:通过编译时选项HAVE_SPDK控制是否启用

2. 目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
src/spdk/
├── include/spdk/ # 公共头文件
│ ├── nvme.h # NVMe驱动API
│ ├── env.h # 环境抽象层
│ ├── bdev.h # 块设备抽象层
│ └── ...
├── lib/ # 核心库
│ ├── nvme/ # NVMe驱动实现
│ ├── env_dpdk/ # DPDK环境实现
│ ├── bdev/ # 块设备抽象
│ ├── virtio/ # Virtio支持
│ └── vhost/ # vhost支持
├── module/ # 模块实现
│ ├── bdev/ # 块设备模块
│ └── event/ # 事件框架模块
├── app/ # 应用程序
│ ├── spdk_tgt/ # SPDK目标程序
│ ├── nvmf_tgt/ # NVMe-oF目标
│ └── vhost/ # vhost应用
├── dpdk/ # DPDK子模块
├── isa-l/ # Intel存储加速库
├── intel-ipsec-mb/ # Intel IPsec多缓冲区库
└── ocf/ # Open CAS Framework

3. 核心架构

3.1 用户空间驱动原理

3.1.1 设备绑定

SPDK使用UIO或VFIO驱动来绑定PCIe设备:

  1. 卸载内核驱动:将设备从内核驱动(如nvme驱动)解绑
  2. 绑定UIO/VFIO:将设备绑定到UIO或VFIO驱动
  3. 映射BAR空间:将PCIe设备的BAR(Base Address Register)映射到用户空间
  4. 直接MMIO访问:通过内存映射I/O直接访问设备寄存器

3.1.2 内存管理

SPDK使用DPDK提供的内存管理:

  • 大页内存:使用大页内存(hugepage)提高TLB效率
  • DMA安全内存:分配DMA安全的内存用于I/O缓冲区
  • NUMA感知:支持NUMA节点的内存分配

3.1.3 轮询模式

  • 无中断:不使用硬件中断
  • 主动轮询:应用程序主动轮询完成队列
  • 低延迟:避免中断处理的开销和上下文切换

3.2 环境抽象层(Environment)

文件位置: lib/env_dpdk/env.c

环境抽象层提供:

  • 内存管理spdk_malloc(), spdk_dma_malloc()
  • PCIe设备访问:设备枚举和绑定
  • 线程管理:线程抽象和CPU亲和性
  • 时间服务:高精度时间戳

关键函数

1
2
3
4
5
6
7
8
9
10
// 初始化环境
int spdk_env_init(struct spdk_env_opts *opts);

// DMA安全内存分配
void *spdk_dma_malloc(size_t size, size_t align, uint64_t *phys_addr);
void *spdk_dma_zmalloc(size_t size, size_t align, uint64_t *phys_addr);
void spdk_dma_free(void *buf);

// 虚拟地址到物理地址转换
uint64_t spdk_vtophys(void *buf, uint64_t *size);

3.3 NVMe驱动

文件位置: lib/nvme/nvme.c, lib/nvme/nvme_ctrlr_cmd.c, lib/nvme/nvme_ns_cmd.c

3.3.1 控制器管理

关键数据结构

1
2
3
struct spdk_nvme_ctrlr;  // NVMe控制器
struct spdk_nvme_ns; // 命名空间(Namespace)
struct spdk_nvme_qpair; // 队列对(Queue Pair)

控制器初始化流程

  1. 探测设备spdk_nvme_probe()
  2. 附加控制器attach_cb回调
  3. 获取命名空间spdk_nvme_ctrlr_get_ns()
  4. 创建队列对spdk_nvme_ctrlr_alloc_io_qpair()

3.3.2 I/O操作

读取操作

1
2
3
4
5
6
7
8
9
int spdk_nvme_ns_cmd_read(
struct spdk_nvme_ns *ns,
struct spdk_nvme_qpair *qpair,
void *buffer,
uint64_t lba,
uint32_t lba_count,
spdk_nvme_cmd_cb cb_fn,
void *cb_arg,
uint32_t io_flags);

写入操作

1
2
3
4
5
6
7
8
9
int spdk_nvme_ns_cmd_write(
struct spdk_nvme_ns *ns,
struct spdk_nvme_qpair *qpair,
void *buffer,
uint64_t lba,
uint32_t lba_count,
spdk_nvme_cmd_cb cb_fn,
void *cb_arg,
uint32_t io_flags);

完成处理

1
2
3
int spdk_nvme_qpair_process_completions(
struct spdk_nvme_qpair *qpair,
uint32_t max_completions);

3.3.3 传输类型

SPDK支持多种NVMe传输类型:

  • PCIe (SPDK_NVME_TRANSPORT_PCIE): 本地PCIe设备
  • RDMA (SPDK_NVME_TRANSPORT_RDMA): NVMe over Fabrics (RDMA)
  • TCP (SPDK_NVME_TRANSPORT_TCP): NVMe over Fabrics (TCP)
  • FC (SPDK_NVME_TRANSPORT_FC): NVMe over Fabrics (Fibre Channel)

3.4 块设备抽象层(BDEV)

文件位置: lib/bdev/bdev.c, include/spdk/bdev.h

BDEV(Block Device)提供统一的块设备抽象:

关键数据结构

1
2
3
struct spdk_bdev;        // 块设备
struct spdk_bdev_io; // I/O请求
struct spdk_bdev_desc; // 块设备描述符

BDEV模块类型

  • Malloc BDEV: 内存块设备
  • NVMe BDEV: NVMe块设备
  • Virtio BDEV: Virtio块设备
  • 压缩BDEV: 压缩块设备
  • 加密BDEV: 加密块设备
  • 逻辑卷BDEV: LVM逻辑卷

4. Ceph中的SPDK集成

4.1 NVMEDevice类

文件位置: src/blk/spdk/NVMEDevice.h, src/blk/spdk/NVMEDevice.cc

NVMEDevice是Ceph中SPDK NVMe驱动的封装类,继承自BlockDevice

4.1.1 类结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class NVMEDevice : public BlockDevice {
SharedDriverData *driver; // 共享驱动数据
std::string name; // 设备名称

public:
// 设备操作
int open(const std::string& path) override;
void close() override;

// I/O操作
int read(uint64_t off, uint64_t len, bufferlist *pbl, ...) override;
int write(uint64_t off, bufferlist& bl, ...) override;
int aio_read(uint64_t off, uint64_t len, bufferlist *pbl, ...) override;
int aio_write(uint64_t off, bufferlist& bl, ...) override;
void aio_submit(IOContext *ioc) override;

// 工具方法
static bool support(const std::string& path);
int collect_metadata(...) const override;
};

4.1.2 关键数据结构

SharedDriverData:

1
2
3
4
5
6
7
8
9
10
class SharedDriverData {
unsigned id;
spdk_nvme_transport_id trid; // 传输ID
spdk_nvme_ctrlr *ctrlr; // NVMe控制器
spdk_nvme_ns *ns; // 命名空间
uint32_t block_size;
uint64_t size;
std::thread admin_thread; // 管理线程(非PCIe设备)
std::vector<NVMEDevice*> registered_devices;
};

SharedDriverQueueData:

1
2
3
4
5
6
7
8
9
class SharedDriverQueueData {
NVMEDevice *bdev;
SharedDriverData *driver;
spdk_nvme_ctrlr *ctrlr;
spdk_nvme_ns *ns;
struct spdk_nvme_qpair *qpair; // 队列对
uint32_t current_queue_depth; // 当前队列深度
bi::slist<data_cache_buf> data_buf_list; // 数据缓冲区列表
};

Task:

1
2
3
4
5
6
7
8
9
10
11
12
struct Task {
NVMEDevice *device;
IOContext *ctx;
IOCommand command; // READ_COMMAND, WRITE_COMMAND, FLUSH_COMMAND
uint64_t offset;
uint64_t len;
bufferlist bl;
std::function<void()> fill_cb;
IORequest io_request; // I/O请求信息
SharedDriverQueueData *queue;
int ref; // 引用计数
};

4.1.3 设备打开流程

  1. 检查设备路径:通过SPDK_PREFIX(”spdk:”)前缀识别
  2. 解析传输ID:从文件读取spdk_nvme_transport_id
  3. 获取/创建驱动:通过NVMEManager::try_get()获取或创建驱动
  4. 初始化环境:如果是首次使用,初始化SPDK环境(DPDK线程)
  5. 探测设备:调用spdk_nvme_probe()探测NVMe设备
  6. 注册设备:将设备注册到SharedDriverData

关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int NVMEDevice::open(const string& p) {
// 1. 读取传输ID
std::ifstream ifs(p);
std::getline(ifs, val);
spdk_nvme_transport_id trid;
spdk_nvme_transport_id_parse(&trid, val.c_str());

// 2. 获取驱动
manager.try_get(trid, &driver);

// 3. 注册设备
driver->register_device(this);

// 4. 设置设备属性
block_size = driver->get_block_size();
size = driver->get_size();
name = trid.traddr;
}

4.1.4 I/O处理流程

写入流程

  1. 创建Taskwrite_split()将大的写入请求分割成多个Task
  2. 分配缓冲区:从数据缓冲区池分配DMA安全的内存
  3. 拷贝数据:将用户数据拷贝到DMA缓冲区
  4. 提交I/O:调用spdk_nvme_ns_cmd_writev()提交写入命令
  5. 轮询完成_aio_handle()轮询完成队列
  6. 回调处理:I/O完成时调用io_complete()回调

关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void SharedDriverQueueData::_aio_handle(Task *t, IOContext *ioc) {
while (ioc->num_running) {
// 轮询完成队列
if (current_queue_depth) {
r = spdk_nvme_qpair_process_completions(qpair, max_io_completion);
}

// 提交新的I/O
for (; t; t = t->next) {
if (t->command == IOCommand::WRITE_COMMAND) {
alloc_buf_from_pool(t, true); // 分配缓冲区
spdk_nvme_ns_cmd_writev(
ns, qpair, lba_off, lba_count, io_complete, t, 0,
data_buf_reset_sgl, data_buf_next_sge);
}
}
}
}

完成回调

1
2
3
4
5
6
7
8
9
10
11
12
static void io_complete(void *t, const struct spdk_nvme_cpl *completion) {
Task *task = static_cast<Task*>(t);
--queue->current_queue_depth;

if (task->command == IOCommand::WRITE_COMMAND) {
task->release_segs(queue); // 释放缓冲区
if (!--ctx->num_running) {
task->device->aio_callback(...); // 调用完成回调
}
delete task;
}
}

4.1.5 数据缓冲区管理

缓冲区池

  • 使用spdk_dma_zmalloc()分配DMA安全的内存
  • 默认池大小:1024个缓冲区,每个8KB
  • 使用boost::intrusive::slist管理空闲缓冲区

缓冲区分配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int SharedDriverQueueData::alloc_buf_from_pool(Task *t, bool write) {
uint64_t count = t->len / data_buffer_size;
// 从池中分配缓冲区
for (uint16_t i = 0; i < count; i++) {
segs[i] = &data_buf_list.front();
data_buf_list.pop_front();
}
// 如果是写入,拷贝数据到缓冲区
if (write) {
auto blp = t->bl.begin();
for (uint16_t i = 0; i < count; ++i) {
blp.copy(data_buffer_size, static_cast<char*>(segs[i]));
}
}
}

4.1.6 NVMEManager

作用:管理SPDK环境和NVMe设备

关键功能

  • 环境初始化:在独立线程中初始化SPDK环境
  • 设备管理:管理SharedDriverData列表
  • 设备探测:处理设备探测请求队列

初始化流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int NVMEManager::try_get(const spdk_nvme_transport_id& trid, ...) {
// 如果DPDK线程未启动,启动它
if (!dpdk_thread.joinable()) {
dpdk_thread = std::thread([this, ...]() {
struct spdk_env_opts opts;
spdk_env_opts_init(&opts);
opts.name = "nvme-device-manager";
opts.core_mask = coremask_arg.c_str();
opts.mem_size = mem_size_arg;
spdk_env_init(&opts); // 初始化环境

// 处理探测队列
while (!stopping) {
if (!probe_queue.empty()) {
spdk_nvme_probe(..., probe_cb, attach_cb, NULL);
}
}
});
}
}

4.2 BlueStore集成

文件位置: src/os/bluestore/BlueStore.cc

BlueStore通过设备路径前缀识别SPDK设备:

1
2
3
4
5
6
// 检查是否是SPDK设备
if (!epath.compare(0, strlen(SPDK_PREFIX), SPDK_PREFIX)) {
// 处理SPDK设备路径
string trid = epath.substr(strlen(SPDK_PREFIX));
// ...
}

设备路径格式

  • SPDK设备路径格式:spdk:<transport_id>
  • 例如:spdk:0000:01:00.0 (PCIe设备)
  • Transport ID存储在文件中,由BlueStore读取

5. 核心库详解

5.1 lib/nvme - NVMe驱动库

5.1.1 控制器管理

文件: lib/nvme/nvme.c

关键函数

  • spdk_nvme_probe(): 探测NVMe设备
  • spdk_nvme_ctrlr_alloc_io_qpair(): 分配I/O队列对
  • spdk_nvme_ctrlr_free_io_qpair(): 释放I/O队列对
  • spdk_nvme_ctrlr_process_admin_completions(): 处理管理命令完成

5.1.2 命名空间操作

文件: lib/nvme/nvme_ns_cmd.c

关键函数

  • spdk_nvme_ns_cmd_read(): 读取命令
  • spdk_nvme_ns_cmd_write(): 写入命令
  • spdk_nvme_ns_cmd_writev(): 分散/聚集写入
  • spdk_nvme_ns_cmd_readv(): 分散/聚集读取
  • spdk_nvme_ns_cmd_flush(): 刷新命令

命令拆分

  • 大I/O请求会被自动拆分成多个子请求
  • 支持PRP(Physical Region Page)和SGL(Scatter Gather List)描述符

5.1.3 队列对处理

轮询完成

1
2
3
int spdk_nvme_qpair_process_completions(
struct spdk_nvme_qpair *qpair,
uint32_t max_completions);

特点

  • 非阻塞轮询
  • 可以限制每次处理的最大完成数
  • 返回处理的完成数

5.2 lib/env_dpdk - DPDK环境实现

文件: lib/env_dpdk/env.c

内存管理

  • 基于DPDK的rte_malloc
  • 支持NUMA感知
  • 支持大页内存
  • DMA安全的内存分配

PCIe设备管理

  • 设备枚举
  • 设备绑定(UIO/VFIO)
  • BAR空间映射

5.3 lib/virtio - Virtio支持

文件: lib/virtio/virtio.c

提供Virtio设备支持:

  • Virtio PCI: PCIe上的Virtio设备
  • Virtio User: 用户空间Virtio设备(通过vhost-user)

5.4 lib/vhost - vhost支持

文件: lib/vhost/vhost.c

提供vhost支持:

  • vhost-scsi: SCSI设备模拟
  • vhost-blk: 块设备模拟
  • vhost-nvme: NVMe设备模拟

5.5 lib/bdev - 块设备抽象

文件: lib/bdev/bdev.c

提供统一的块设备接口:

  • BDEV模块系统:插件化的块设备后端
  • I/O队列:管理I/O请求队列
  • QoS控制:I/O限速

6. 模块系统

6.1 module/bdev - 块设备模块

各种块设备后端实现:

  • bdev_nvme: NVMe块设备
  • bdev_malloc: 内存块设备
  • bdev_virtio: Virtio块设备
  • bdev_compress: 压缩块设备
  • bdev_crypto: 加密块设备
  • bdev_raid: RAID块设备

6.2 module/event - 事件框架

事件驱动的应用框架:

  • 反应器模式:事件循环
  • 异步I/O:基于事件的异步I/O
  • 线程模型:每个线程一个反应器

7. 性能特性

7.1 零拷贝

  • 使用DMA安全的内存
  • 减少数据拷贝次数
  • 直接内存访问

7.2 多队列

  • 每个线程一个队列对
  • 无锁设计
  • 充分利用NVMe多队列特性

7.3 轮询模式

  • 无中断延迟
  • 可预测的延迟
  • 适合低延迟应用

7.4 CPU亲和性

  • 绑定CPU核心
  • 减少上下文切换
  • 提高缓存命中率

8. 配置和使用

8.1 编译配置

启用SPDK支持

  • 编译时选项:HAVE_SPDK
  • 需要DPDK库
  • 需要SPDK库

8.2 运行时配置

Ceph配置选项

  • bluestore_spdk_coremask: SPDK使用的CPU核心掩码
  • bluestore_spdk_mem: SPDK内存大小
  • bluestore_spdk_max_io_completion: 最大I/O完成数
  • bluestore_spdk_io_sleep: I/O轮询休眠时间(微秒)

8.3 设备路径

SPDK设备路径格式

1
spdk:<transport_id>

Transport ID格式

  • PCIe: PCIe:0000:01:00.0
  • RDMA: RDMA:192.168.1.1:4420
  • TCP: TCP:192.168.1.1:4420

示例

1
/dev/nvme0n1 -> spdk:PCIe:0000:01:00.0

9. 数据流程图

9.1 写入流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
应用程序

BlueStore::_do_write()

NVMEDevice::aio_write()

write_split() [创建Task]

NVMEDevice::aio_submit()

SharedDriverQueueData::_aio_handle()

alloc_buf_from_pool() [分配DMA缓冲区]
↓ [拷贝数据到缓冲区]
spdk_nvme_ns_cmd_writev() [提交写入命令]

spdk_nvme_qpair_process_completions() [轮询完成]

io_complete() [完成回调]

释放缓冲区,调用上层回调

9.2 读取流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
应用程序

BlueStore::_do_read()

NVMEDevice::aio_read()

make_read_tasks() [创建Task]

NVMEDevice::aio_submit()

SharedDriverQueueData::_aio_handle()

alloc_buf_from_pool() [分配DMA缓冲区]

spdk_nvme_ns_cmd_readv() [提交读取命令]

spdk_nvme_qpair_process_completions() [轮询完成]

io_complete() [完成回调]

fill_cb() [拷贝数据到用户缓冲区]

释放缓冲区,调用上层回调

10. 关键代码路径

10.1 设备初始化

1
2
3
4
5
6
7
8
9
NVMEDevice::open()
→ NVMEManager::try_get()
→ [启动DPDK线程]
→ spdk_env_init()
→ spdk_nvme_probe()
→ probe_cb()
→ attach_cb()
→ NVMEManager::register_ctrlr()
→ new SharedDriverData()

10.2 I/O提交

1
2
3
4
5
6
7
8
9
NVMEDevice::aio_write()
→ write_split()
→ new Task()
→ ioc_append_task()
→ NVMEDevice::aio_submit()
→ SharedDriverQueueData::_aio_handle()
→ alloc_buf_from_pool()
→ spdk_nvme_ns_cmd_writev()
→ [轮询完成队列]

10.3 I/O完成

1
2
3
4
5
[硬件完成I/O]
→ spdk_nvme_qpair_process_completions()
→ io_complete()
→ Task::release_segs()
→ [调用上层回调]

11. 内存管理

11.1 DMA安全内存

  • 分配spdk_dma_zmalloc()
  • 对齐:页对齐(4KB)
  • NUMA感知:根据CPU核心选择NUMA节点

11.2 缓冲区池

  • 预分配:启动时预分配缓冲区池
  • 复用:I/O完成后缓冲区回到池中
  • 大小:默认1024个缓冲区,每个8KB

11.3 大页内存

  • 配置:通过DPDK配置大页内存
  • 好处:减少TLB缺失,提高性能
  • 大小:通常2MB或1GB

12. 线程模型

12.1 DPDK线程

  • 独立线程:SPDK环境运行在独立线程中
  • CPU绑定:可以绑定到特定CPU核心
  • 轮询循环:持续轮询I/O完成

12.2 应用线程

  • 每个线程一个队列对:避免锁竞争
  • 线程本地存储thread_local SharedDriverQueueData
  • 异步I/O:非阻塞I/O操作

13. 错误处理

13.1 设备错误

  • 控制器错误:检测控制器状态
  • 命名空间错误:检测命名空间状态
  • 传输错误:处理网络传输错误

13.2 I/O错误

  • 完成状态检查spdk_nvme_cpl_is_error()
  • 错误代码:从完成结构体获取错误码
  • 重试机制:由上层应用处理重试

14. 性能优化

14.1 队列深度

  • 最大队列深度:队列大小-1(避免溢出)
  • 动态调整:根据负载调整
  • 背压机制:队列满时等待

14.2 I/O合并

  • 请求合并:合并相邻的I/O请求
  • 向量I/O:使用writev/readv减少系统调用

14.3 轮询优化

  • 批量处理:一次处理多个完成
  • 自适应休眠:队列空时短暂休眠
  • CPU占用控制:通过配置控制CPU使用

15. 限制和注意事项

15.1 设备独占

  • SPDK设备被绑定后,内核无法访问
  • 需要确保设备未被其他程序使用

15.2 内存要求

  • 需要大页内存
  • DMA缓冲区占用内存
  • 配置足够的内存大小

15.3 CPU使用

  • 轮询模式占用CPU
  • 需要专用CPU核心
  • 不适合CPU资源紧张的场景

15.4 兼容性

  • 需要特定硬件(NVMe设备)
  • 需要特定的内核版本和驱动
  • 不同传输类型有不同的要求

16. 调试和监控

16.1 日志

  • SPDK日志级别可配置
  • Ceph日志集成SPDK日志
  • 使用dout输出调试信息

16.2 性能统计

  • I/O延迟统计
  • 吞吐量统计
  • 队列深度统计

16.3 工具

  • spdk_tgt: SPDK目标程序
  • spdk_lspci: 列出PCIe设备
  • spdk_top: 性能监控工具

17. 总结

SPDK在Ceph中作为可选的高性能块设备后端,主要特点:

  1. 高性能:用户空间驱动,轮询模式,零拷贝
  2. 低延迟:无中断,直接设备访问
  3. 可扩展:多队列,多线程
  4. 可选:编译时和运行时可选
  5. 集成:通过NVMEDevice类与BlueStore集成

适用场景:

  • 需要极致性能的NVMe存储
  • 低延迟要求高的应用
  • CPU资源充足的系统
  • 专用的高性能存储节点

18. 参考代码位置

  • Ceph集成: cephMain/src/blk/spdk/NVMEDevice.h/cc
  • SPDK核心: cephMain/src/spdk/lib/nvme/
  • 环境抽象: cephMain/src/spdk/lib/env_dpdk/
  • 块设备抽象: cephMain/src/spdk/lib/bdev/
  • 头文件: cephMain/src/spdk/include/spdk/
  • BlueStore集成: cephMain/src/os/bluestore/BlueStore.cc

19. 相关文档

本地大模型栈总览与阅读路线

本地大模型栈总览与阅读路线

「本系列第 1/17 章」

本系列共十七章,编号从 00 到 16。它不是四个仓库各自再写一遍目录,而是按「训练 → 转换 → 推理 → 检索增强」把一条可在本机复现的本地大模型栈串起来。本章先画边界,再给出阅读顺序;从下一章起进入 GGML 0.15.3 的张量与计算图。

一条栈,四段职责

本地栈不要理解成「把 Hugging Face、llama.cpp、LlamaIndex 塞进同一个进程」。四段产物不同、进程不同、出了问题的排查入口也不同:

  1. 训练:LLaMA-Factory 0.9.6.dev0 在 Hugging Face 权重上做监督微调与偏好对齐,磁盘产物仍是 SafeTensors / PyTorch,不是 GGUF。
  2. 转换与量化convert_hf_to_gguf.py 把 HF 模型写成 GGUF;llama-quantize 再压到生产常用的 Q4_K_M。IQ 系列必须带 imatrix,否则质量会塌。
  3. 推理:llama.cpp 用 GGML 建图并在 CPU / CUDA / Metal / Vulkan 上执行;对外由 llama-server 提供 OpenAI 兼容 HTTP。
  4. RAG:LlamaIndex 0.14.23 不读 GGUF,也不管理 KV cache。它只用 OpenAILike / OpenAILikeEmbedding 打到 llama-server
HF 数据集与基座LLaMA-Factory 0.9.6.dev0convert_hf_to_ggufllama-quantizellama.cpp / llama-serverLlamaIndex 0.14.23

边界必须守住。LLaMA-Factory 不懂 GGUF 的 32 字节对齐,也不懂 vec_dot。GGML 不懂 chat template,也不决定采样策略。LlamaIndex 看不见量化 block,更不会去改 n_gpu_layers。日志里一旦同时出现「训练 loss」「GGUF magic」「retriever 空结果」,先判断卡在哪一段,再往下翻对应章节。

GGML 在栈中的位置

GGML 0.15.3 是 llama.cpp 的张量引擎,不是应用框架。库拆成三层链接:ggml-base 放张量、图、量化、GGUF、Backend 接口、调度器与分配器;ggml 负责 Backend 发现与注册;ggml-cpuggml-cudaggml-metalggml-vulkan 是各硬件实现。llama.cpp 的 libllama 同时链这三层。

每个 ggml_tensornenbtypeopsrc 描述,维度上限 GGML_MAX_DIMS=4nb 是字节 stride,因此 permute / view / transpose 可以不拷贝数据。ggml_mul_matggml_ropeggml_rms_norm 这类 API 只连边,不会立刻算矩阵。图要等 ggml_build_forward_expand 做拓扑排序并填 use_counts,再走三条执行路径之一:

路径 API 典型场景
纯 CPU ggml_graph_compute 示例与调试
单 Backend ggml_backend_graph_compute 单卡、单设备
多 Backend 调度 ggml_backend_sched_graph_compute_async llama.cpp 生产路径

权重容器是 GGUF:magic 为 GGUF,当前 version 为 3,默认 alignment 为 32。llama.cpp 用 gguf_init_from_fileno_alloc=true:先解析 KV 与张量元数据,再用 Backend buffer 承接 data,而不是让 Context 自己 malloc 整份权重。

推理热路径是 Q4 权重 × Q8 激活vec_dot,不是先反量化成 F32 再做通用 GEMM。生产量化优先 Q4_K_M。IQ 系列依赖 codebook,且 ggml_quantize_requires_imatrix 为真,没有校准矩阵就不要当成品。

内存分两套。Context 的 bump allocator 只管张量与图的元数据;真正的 data 由 gallocr 配合 dyn_tallocr 按拓扑分配。中间结果在 n_children==1ggml_op_can_inplace 时可以原地复用。标了 GGML_TENSOR_FLAG_OUTPUT 的张量永不覆盖,否则 logits 会在下一次 decode 里被写坏。

Backend 是五层 vtable。静态注册顺序是 CUDA → Metal → … → CPU 最后,CPU 作兜底。调度器对图做三遍切分,n_copies=4 用来做跨设备 pipeline。CUDA 按形状走 mmvq / mmq / cublas;macOS 默认开 Metal;Vulkan 用 shader-gen 生成各量化类型的 SPIR-V。构建时最常碰到的开关是 GGML_CUDAGGML_CPU_REPACKGGML_CUDA_FA

这些机制分别在第 2 到第 4 章展开。总览只要求你记住:算子 API 不计算,GGUF 只提供自描述权重,真正落地的是 Backend 与调度器。

训练侧停在 Hugging Face

LLaMA-Factory 0.9.6.dev0 默认走 v0:扁平 YAML 经 HfArgumentParser 变成 dataclass,再按 stage 选 workflow,最后落到 Hugging Face Trainer。它能做 SFT、DPO 一类对齐、导出 adapter 或合并后的 HF 权重,也能拉起自己的 chat / API / Board。那是另一条「HF 或 vLLM 推理」路径,不是本系列后续要接的 GGUF 路径。

要把训练结果送进 llama.cpp,必须承认一次格式断裂:HF 侧的 config.json、tokenizer 文件、SafeTensors 分片,与 GGUF 的 general.architecturetokenizer.ggml.tokens、按 blk.{i} 命名的张量,不是同一套元数据。转换桥负责翻译,而不是「改个后缀」。

转换桥:HF 写成 GGUF,再压到 Q4_K_M

1
2
python convert_hf_to_gguf.py /path/to/hf --outfile model-f16.gguf --outtype f16
llama-quantize model-f16.gguf model-q4_k_m.gguf Q4_K_M

第一行写出接近全精度的 GGUF,第二行用 ggml-quants.c 的参考实现做量化。量化工具不走 CUDA mmq,也不走 Metal shader。Q4_K_M 使用 K-quant 的 256 元素 super-block,体积与质量之间最稳,适合作为默认生产格式。若改 IQ,先用校准数据生成 imatrix,再交给 llama-quantize;否则 codebook 选点没有列重要性,生成会明显变差。

转换脚本还要写入架构 KV、词表和特殊 token。漏掉 bos / eos 或架构名写错,后面 llama.cpp 能打开文件,但建图会对不上层数或头数。分片 GGUF 用 split.count / split.no 描述,加载器会合成一张权重表。

推理侧:llama.cpp 建图,llama-server 对外

llama.cpp 读 GGUF,按架构建 ggml_mul_mat / ggml_rope / ggml_flash_attn_ext 等节点,管理 KV cache 与采样,经 ggml_backend_sched_graph_compute_async 执行。llama-server 把同一套能力暴露为 /v1/chat/completions/v1/embeddings。上下文长度、GPU offload、并发 slot、chat template 都在这个进程里,不在 RAG 框架里。

因此「模型很慢」首先看 Backend 是否真的注册到 CUDA / Metal,以及调度器有没有把 MUL_MAT 切到 GPU;「接口 404」才去看 server 路由。不要在 LlamaIndex 里找 ggml_type

应用侧:LlamaIndex 只认 HTTP

LlamaIndex 0.14.23 的规范接法是进程外 HTTP:生成模型一个 llama-server,embedding 模型另一个,LlamaIndex 用 OpenAILikeOpenAILikeEmbedding 访问。它负责切分文档、建索引、检索与响应合成。GGUF 路径、量化类型、n_copies 对它不可见。

不要把进程内的 LlamaCPP 绑定与这条 OpenAILike 路径混配。前者把推理塞进 Python 进程,后者把推理留给 C++ server。本系列后续只沿 HTTP 这条线讲 RAG,以免配置项对不上。

十七章怎么读

按依赖读,不要跳过 GGML 三章直接抄 server 命令。编号 00 到 16 对应阅读顺序:

编号 主题 读完应能回答
00 本栈总览 四段职责如何切开
01 GGML 张量与惰性图 为何 mul_mat 当时不算数
02 内存、量化、GGUF 权重如何进 buffer,Q4×Q8 如何算
03 Backend 与调度 图如何切到 CUDA / Metal / CPU
04 llama.cpp 加载与建图 GGUF 如何变成可执行图
05 Decode 与 KV Cache 逐 token 如何复用图
06 Batch 与采样 连续批处理停在哪一层
07 llama-server OpenAI 兼容 HTTP 如何落地
08 LLaMA-Factory 入口与配置 v0 训练如何启动
09 数据、模板与模型 样本如何变成 HF batch
10 训练与对齐流水线 SFT / 偏好阶段如何走
11 导出与转换桥 HF 如何变成 Q4_K_M
12 LlamaIndex 架构 Document 如何变成 Node
13 索引、检索与合成 查询如何拼出 prompt
14 OpenAILike 对接 如何接到 llama-server
15 端到端联调 训练到问答的检查点
16 性能、硬件与排错 如何选 Backend、看峰值

建议先读 00 到 03,把「惰性图 + 量化权重 + 可插拔 Backend」变成默认心智模型;再读 04 到 07,看 llama.cpp 如何把图变成服务;然后 08 到 11 把训练产物接进 GGUF;最后 12 到 16 做成可查询系统。

后文只写源码里能对上的 API 与常量,不编造一层「更友好」的封装。遇到「这个函数会立刻算出结果」的直觉,先回到张量与计算图那一章。遇到 OOM 或 logits 被覆盖,先回到内存分配与 OUTPUT 标志。遇到「明明编译了 CUDA 却在 CPU 上算」,先回到注册顺序与三遍调度。

读系列时要带着的三张检查表

第一张表问数据格式。训练完成时磁盘上应是 HF 目录。转换完成后应能用文件头四个字节 GGUF 认出容器,version 为 3。量化完成后 general.file_type 应指向 Q4_K_M 或你明确选择的类型;若是 IQ,旁边必须有 imatrix 来源。RAG 联调时,LlamaIndex 进程里不应出现 GGUF 路径,只应出现 http://127.0.0.1:8080 这类 base URL。

第二张表问进程边界。LLaMA-Factory 的 Trainer、convert_hf_to_gguf 的 Python 解释器、llama-quantize 的 C++ 工具、llama-server、LlamaIndex 应用,默认是五个进程、五份日志。把它们焊进一个 notebook 单元格,出了错无法判断是建图失败还是检索为空。本系列允许你在同一台机器上跑完全流程,但不鼓励把五段合成一个「一键脚本」再去调试。

第三张表问计算发生的位置。训练算力在 PyTorch。量化算力在 ggml-quants.c 参考实现,通常是 CPU。推理算力在 GGML Backend:CPU 的 vec_dot、CUDA 的 mmvq/mmq/cublas、macOS 的 Metal、跨平台的 Vulkan shader。RAG 侧几乎不占模型算力,它只拼 prompt、打 HTTP、写向量库。显存被占满时,先看 llama-server 而不是 LlamaIndex。

把三张表当作阅读锚点,十七章就不会读成互不相干的手册。你随时可以停下来问:此刻改的是格式、进程,还是计算位置?答案会直接指向后面某一章,而不是让你回到七十篇旧笔记里翻文件名。

下一章进入 GGML 的张量模型、计算图与惰性执行。

GGML:张量模型、计算图与惰性执行

GGML:张量模型、计算图与惰性执行

「本系列第 2/17 章」

上一章把本地栈切成训练、转换、推理、RAG 四段。从本章起进入推理引擎的底部:GGML 0.15.3 如何用一张最多四维的张量,加上惰性计算图,把一次前向表示出来。读完应能解释:为什么 ggml_mul_mat 返回时矩阵还没乘,以及 ggml_build_forward_expand 之后三条执行路径各自给谁用。

张量是五元组,不是「一块连续 float」

ggml_tensor 用五个要素就能完整描述一次计算节点:

字段 含义
ne[4] 各维元素个数,上限 GGML_MAX_DIMS=4
nb[4] 各维 stride,单位是字节
type F32、F16、Q4_K、Q8_0 等
op + src[] 产生该张量的算子及其输入
buffer + data 数据落在哪块 Backend 内存

LLM 权重大多是 [rows, cols, 1, 1]。第四维不是浪费:Attention 与 KV 会用到 batch、头数、序列长。nb 允许非连续布局,所以 view / permute / transpose 常常只改元数据。所有 Backend 必须尊重 stride;GPU kernel 若假定连续,调度器或算子实现会先 ggml_cont,否则结果 silently 错。

type 决定后面怎么算,而不是「先变成 F32 再算」。量化权重保持 block 布局,和 Q8 激活做 vec_dot。这一点下一章会展开;本章只需记住:type 是图的一部分,不是加载后的临时标签。

Context 只分配元数据

ggml_init 创建一个 Context。它内部是 bump allocator:张量对象、图对象、名字字符串从一块 mem_buffer 顺序切出去。Context 不能单独 free 某个 tensor。ggml_reset 只重置对象链表,不把整块内存还给系统;ggml_free 才拆掉整个池。

加载 GGUF 时必须把 no_alloc 设为 true。此时 Context 只建 ne / nb / type / 名字,data 为空,随后由 Backend buffer 绑定。llama.cpp 正是这条路径。若 no_alloc=false 且把 7B 权重量进 Context,元数据池会和权重抢同一块 bump 内存,既慢又容易一次就爆。

1
2
3
4
5
6
struct ggml_init_params params = {
.mem_size = 16 * 1024 * 1024,
.mem_buffer = NULL,
.no_alloc = true,
};
struct ggml_context * ctx = ggml_init(params);

这里的 16MB 是「图上有多少节点」的预算,不是「模型有多少 GB」。权重大小由 GGUF 与 gallocr 管,不要混为一谈。

算子 API 只建图

调用 ggml_mul_mat(ctx, w, x) 时,GGML 做的事情是:新分配一个结果张量,把它的 op 设成矩阵乘,把 src[0]src[1] 指到 wx,必要时在 op_params 里写入少量整数参数。没有 cuBLAS 调用,也没有 CPU 点积。ggml_ropeggml_rms_normggml_flash_attn_extggml_get_rowsggml_gluggml_mul_mat_id 同样如此。

这就是惰性执行。好处有三。第一,同一套 Python 或 C++ 建图代码可以在 CPU 调试、在 CUDA 跑生产,不必为每个设备写一套表达式。第二,调度器能在执行前看见整张图,才能做三遍 Backend 分配和跨设备 copy。第三,gallocr 需要拓扑序与引用计数,才能决定哪块中间激活可以原地复用。

对应的心智模型是:建图阶段在构造 DAG;ggml_build_forward_expand 把 DAG 拉成可执行的节点数组;graph_compute 一类 API 才沿着数组开火。

1
2
3
struct ggml_tensor * out = ggml_mul_mat(ctx, w, x);
struct ggml_cgraph * gf = ggml_new_graph(ctx);
ggml_build_forward_expand(gf, out);

build_forward_expand 会从根做 DFS,访问父节点,写入 nodes[] / leafs[],并维护 use_counts。叶子通常是权重与输入;中间节点才有 op。同一张量被两个下游使用时,use_counts 为 2,分配器就不能在第一个下游算完后立刻回收——这是下一章 in-place 条件的前置知识。

图还有 uid。调度器靠它判断「这次 decode 的图和上次是不是同一结构」。结构没变就可以复用已分配的 buffer;变了才 realloc。llama.cpp 之所以能在逐 token 路径上避免每步重新 malloc,前提就是建图稳定、uid 可复用。

三条执行路径

展开之后,计算仍不会自动开始。GGML 提供三条入口,不要混用:

1
2
3
4
5
6
7
8
ggml_graph_compute(ctx, cgraph, n_threads)
→ 纯 CPU,走 ggml-cpu 主循环

ggml_backend_graph_compute(backend, cgraph)
→ 单个 Backend,例如一张 CUDA 卡

ggml_backend_sched_graph_compute_async(sched, cgraph)
→ 多 Backend 调度,llama.cpp 生产路径

第一条适合 examples 和把某个 op 在 CPU 上对拍。第二条适合「所有张量已经在同一块设备内存」。第三条才会 split_graph、插入 copy 节点、按 n_copies=4 做 pipeline,并在返回后仍可能异步。异步路径必须 ggml_backend_sched_synchronize 之后才能读输出;pipeline 复用同一块 input 时尤其如此,否则下一 token 的写入会覆盖还没算完的上一批。

生产代码几乎总是:sched_reset → 建图 → build_forward_expandsched_alloc_graph → 填 input → sched_graph_compute_asyncsynchronize → 读带 OUTPUT 标志的 logits。

标志位如何改变图的命运

flags 不是调试装饰。GGML_TENSOR_FLAG_INPUT 提示调度器:这是用户写入的入口,通常落在优先级最低的 Backend(注册表里最后的 CPU),再按需拷到 GPU。GGML_TENSOR_FLAG_OUTPUT 告诉 gallocr:这块地址不能当中间激活回收,也不能被 in-place 算子覆盖。PARAMLOSS 给 ggml-opt 训练路径用;本系列推理主线用不到,但要知道反向图是 build_backward_expand,与前向 expand 成对,llama.cpp 解码不会走它。

一次最小前向长什么样

把上面拼成一条可读的时间线:

1
2
3
4
5
6
7
ggml_init(no_alloc=true)
→ 创建 weight / input 张量元数据
→ 绑定 GGUF 或 Backend buffer
→ out = ggml_mul_mat / rope / rms_norm / ...
→ ggml_build_forward_expand(gf, out)
→ 三条路径之一执行
→ 读取 OUTPUT 张量

中间任何一步都可能让人误以为「已经算完了」。最常见的错觉是打印 out->data 发生在 build_forward_expand 之后、graph_compute 之前:此时 data 要么是空,要么是上一次图留下的旧值。惰性库的调试顺序必须是「先问图有没有展开,再问 Backend 有没有 synchronize」。

和本栈其他层的接口

LLaMA-Factory 不会调用这些 API。它停在 HF。convert_hf_to_gguf 只负责把权重写成 GGUF,也不建推理图。真正逐层调用 ggml_mul_mat 的是 llama.cpp 的模型架构文件。LlamaIndex 连 GGML 头文件都看不到。因此本章的对象模型只在「推理进程内部」有效;对外仍然是 llama-server 的 HTTP。

若你从 PyTorch 过来,需要丢掉两个习惯。第一,x @ w 在 Python 里往往立刻算或至少立刻规划 GPU kernel;在 GGML 里它只是长出一条边。第二,PyTorch 的 tensor 自己拥有 storage 且可单独释放;GGML 的 tensor 元数据属于 Context,data 属于 Backend buffer,生命周期由 use_countsgallocr 共同决定。把这两点记住,下一章的内存复用和 GGUF no_alloc 才不会显得突兀。

建图时常见的三种误读

第一种误读是把 src[] 当成「函数参数的值」。src 存的是指针,指向图上已经存在的张量。改变某个叶子的 data 再重新 graph_compute,下游节点会看到新输入,不必重建整张图。llama.cpp 的 decode 正是靠这一点:权重叶子不变,input 与位置编码每步改内容,图结构复用。若每步都 ggml_new_graph 却忘记稳定 uid,调度器会认为来了一张新图,分配器跟着 realloc,延迟会从微秒跳到毫秒。

第二种误读是以为 GGML_MAX_DIMS=4 限制了「只能表示四维神经网络」。它限制的是单个张量的轴数。MoE 的专家维、Flash Attention 的头维,都挤在这四个轴里,靠 nenb 的约定来解释。超过四维的逻辑结构必须在建图代码里拆成多个张量或塞进 op_params,而不是幻想有第五个 ne[4]op_params 是一段固定长度的 int32 数组,ROPE 的 mode、归一化的近似开关都编码在这里,不是 C 结构体字段。读源码时不要去找 struct rope_params

第三种误读是把 ggml_build_forward_expand 当成「编译成某种字节码」。它不做算子融合,也不生成 PTX。它只做 DFS、去重、拓扑排序和引用计数。融合与 kernel 选择发生在各 Backend 的 graph_compute 内部:CUDA 在那里决定 mmvq 还是 mmq,CPU 在那里选 vec_dot 函数指针。因此「图长得一样」不等于「跑得一样快」——快慢属于下一章之后的 Backend 问题;本章只保证图是可调度的 DAG。

把三种误读排除后,惰性模型可以收成一句话:张量描述形状与来源,图描述依赖,执行器才碰硬件。本系列后面所有「为什么先 expand 再 compute」的句子,都建立在这一句上。调试时若结果不对,先数 cgraphn_nodesn_leafs 是否符合你手写的层数,再去怀疑 Backend。图都没展开完整,加速器再快也只是在算一张残缺的 DAG。

下一章讨论 GGML 的内存分配、量化体系与 GGUF。