4.8 SMP 多核支持
2026/7/19大约 5 分钟内核组件内核SMP多核CPC自旋锁
4.8 SMP 多核支持
📚 本节导读
学习时长: 约 35 分钟
难度级别: ⭐⭐⭐⭐⭐
前置知识: 第 4.2 节任务管理、4.3.6 自旋锁、SMP 多核基础概念
🎯 学习目标
- 理解 OneOS SMP 对称多处理架构
- 掌握 CPC 核间调用的原理与使用
- 理解自旋锁在 SMP 场景下的关键作用
- 了解 CPU 亲和性(affinity)配置
- 掌握 SMP 环境下的编程注意事项
一、概述
SMP(Symmetric Multi-Processing)允许多个 CPU 核心同时运行 OneOS 内核,共享同一内存空间。OneOS 通过 OS_USING_SMP 宏启用 SMP 支持,核心实现在 kernel/source/os_cpc.c(Cross Processor Call,核间调用)和 kernel/source/os_spinlock.c(自旋锁)中。
SMP 的关键组件:
| 组件 | 源码 | 说明 |
|---|---|---|
| CPC 核间调用 | os_cpc.c / os_cpc.h | 向其他 CPU 发送函数调用请求 |
| 自旋锁 | os_spinlock.c / os_spinlock.h | SMP 环境下的临界区保护 |
| CPU 亲和性 | 通过 os_task_t 的 cpu_affinity 字段 | 指定任务运行的 CPU 核心 |
| SMP 初始化 | os_startup.c 中 k_mp_init() | 启动从核 |
二、CPC 核间调用
2.1 原理
CPC(Cross Processor Call)允许一个 CPU 核心向其他核心发送"远程函数调用"请求。当 CPU A 需要 CPU B 执行某个操作时,CPU A 将请求放入 CPC 队列,并向 CPU B 发送 IPI(Inter-Processor Interrupt),CPU B 在中断中处理 CPC 请求。
┌──────────────┐ IPI 中断 ┌──────────────┐
│ CPU 0 │ ──────────────────→ │ CPU 1 │
│ │ │ │
│ os_cpc_send()│ │ cpc_irq_handler()
│ ↓ │ │ ↓ │
│ 放入 CPC 队列 │ │ 从队列取回并执行│
└──────────────┘ └──────────────┘2.2 核心数据结构
/* CPC 请求结构体 */
struct os_cpc_request
{
void (*func)(void *arg); /* 待执行的函数 */
void *arg; /* 函数参数 */
os_list_node_t node; /* 链表节点 */
};
/* 每个 CPU 的 CPC 管理结构 */
struct os_cpc_item
{
os_list_node_t pending_list; /* 待处理的 CPC 请求队列 */
os_spinlock_t lock; /* 保护队列的自旋锁 */
os_atomic_t irq_cnt; /* 中断计数 */
uint32_t ipi_irq; /* IPI 中断号 */
uint32_t cpu_id; /* 当前 CPU ID */
char name[OS_NAME_MAX]; /* 名称 */
};2.3 API 接口
/* 向指定 CPU 发送 CPC 请求 */
os_err_t os_cpc_send(uint32_t cpu_id, void (*func)(void *arg), void *arg);
/* 向所有 CPU 广播 CPC 请求 */
os_err_t os_cpc_send_all(void (*func)(void *arg), void *arg);
/* 向除自己外的所有 CPU 发送 CPC 请求 */
os_err_t os_cpc_send_others(void (*func)(void *arg), void *arg);2.4 使用示例
#include <os_cpc.h>
/* 远程函数:在目标 CPU 上执行 */
void remote_print(void *arg)
{
uint32_t cpu_id = os_hw_cpu_id();
os_kprintf("Hello from CPU %d! Message: %s\r\n", cpu_id, (char *)arg);
}
void cpc_example(void)
{
/* 向 CPU 1 发送远程调用 */
os_cpc_send(1, remote_print, "CPC test message");
/* 向所有 CPU 发送广播 */
os_cpc_send_all(remote_print, "Broadcast to all CPUs");
}三、SMP 下的自旋锁
在 SMP 环境中,仅禁用本地 CPU 中断不足以保护临界区(其他 CPU 仍可访问共享数据)。自旋锁在 SMP 环境下使用硬件原子指令(如 ARM 的 LDREX/STREX)实现跨核互斥。
#include <os_spinlock.h>
static os_spinlock_t g_lock;
static int g_shared_counter = 0;
void smp_safe_increment(void)
{
os_spin_lock(&g_lock);
g_shared_counter++; /* 临界区:多核安全 */
os_spin_unlock(&g_lock);
}自旋锁的详细说明见 4.3.6 自旋锁。
四、CPU 亲和性(Affinity)
通过设置任务的 CPU 亲和性,可以将任务绑定到特定核心:
os_task_t *task;
os_task_init(&task, "affinity_task", task_func, NULL, 1024, 5, 10);
/* 设置 CPU 亲和性:只允许在 CPU 0 上运行 */
task->cpu_affinity = (1 << 0); /* bit 0 对应 CPU 0 */
/* 允许在 CPU 0 或 CPU 1 上运行 */
task->cpu_affinity = (1 << 0) | (1 << 1);
os_task_startup(task);五、SMP 调度
SMP 环境下的调度器需考虑:
- 负载均衡:将就绪任务分配到不同 CPU 核心
- 任务迁移:将任务从繁忙 CPU 迁移到空闲 CPU
- Per-CPU 变量:每个 CPU 拥有独立的
os_sched_t结构体
┌─────────────────────────────────────────────────┐
│ SMP 调度器 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ CPU 0 │ │ CPU 1 │ │ CPU N │ │
│ │ 就绪队列 │ │ 就绪队列 │ │ 就绪队列 │ │
│ │ [A→B→C] │ │ [D→E] │ │ [F→G→H] │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ ↑ 任务迁移/负载均衡 ↑ │
└─────────────────────────────────────────────────┘六、SMP 编程注意事项
6.1 必须使用自旋锁保护共享数据
/* ❌ 错误:SMP 环境下不安全 */
void bad_example(void)
{
os_irq_lock(); /* 仅禁用本地 CPU 中断 */
shared_data++; /* 其他 CPU 仍可访问! */
os_irq_unlock();
}
/* ✅ 正确:使用自旋锁 */
void good_example(void)
{
os_spin_lock(&lock);
shared_data++;
os_spin_unlock(&lock);
}6.2 避免死锁
os_spinlock_t lock_a, lock_b;
/* ❌ 危险:不同 CPU 以不同顺序获取锁导致死锁 */
/* CPU 0: lock(A) → lock(B) */
/* CPU 1: lock(B) → lock(A) ← 死锁! */
/* ✅ 正确:所有 CPU 以相同顺序获取锁 */
/* CPU 0: lock(A) → lock(B) */
/* CPU 1: lock(A) → lock(B) */6.3 持锁时间尽量短
自旋锁持有期间会禁用抢占,持锁时间过长会影响系统实时性:
/* ❌ 不好:持锁时间过长 */
os_spin_lock(&lock);
do_heavy_computation(); /* 长时间操作 */
os_spin_unlock(&lock);
/* ✅ 好:仅在访问共享数据时持锁 */
do_heavy_computation(); /* 先完成耗时操作 */
os_spin_lock(&lock);
shared_data = result; /* 仅保护共享数据写入 */
os_spin_unlock(&lock);📝 本节小结
- SMP 允许 OneOS 在多个 CPU 核心上同时运行,通过
OS_USING_SMP宏启用 - CPC 核间调用实现 CPU 间的远程函数调用,通过 IPI 中断触发
- 自旋锁是 SMP 环境下保护临界区的核心机制,替代单核的关中断方式
- CPU 亲和性允许将任务绑定到指定核心,优化缓存利用率和性能
- SMP 编程需注意死锁、持锁时间、内存屏障等问题