第2章 共享内存配置:VxWorks内存布局、MMU配置与页表设置、共享内存区域划分策略
好,咱们进入正题。共享内存这东西,说白了就是让多个核能看到同一块物理内存。但VxWorks默认情况下,每个核跑自己的地址空间,互不干扰。你想让它们共享数据?那就得手动配置。这一章,我带你走一遍完整的配置流程。
2.1 VxWorks内存布局:你得先知道家底
在动手之前,先搞清楚VxWorks的内存是怎么排布的。我个人习惯把内存布局想象成一张地图——你得知道哪些区域是系统保留的,哪些是应用可用的,哪些是留给共享内存的。
典型的VxWorks内存布局长这样:
+------------------+ <-- 高地址
| Local Memory | (每个核私有)
+------------------+
| Shared Memory | (所有核可见)
+------------------+
| Kernel Image | (代码段、数据段)
+------------------+
| Boot ROM | (启动代码)
+------------------+ <-- 低地址
嗯,这里要注意:共享内存区域必须放在所有核都能访问的物理地址空间内。如果某个核的MMU映射没覆盖到这块区域,那它根本看不到共享内存。
2.2 MMU配置与页表设置:让每个核都“看见”共享内存
VxWorks的MMU配置,说白了就是告诉CPU:这块物理内存,你该怎么映射。对于共享内存,我们需要确保每个核的页表里都有一项,指向同一块物理地址。
我建议在系统启动的早期阶段就完成MMU配置。具体来说,在sysLib.c或者config.h里定义共享内存的物理基地址和大小,然后在sysMmuInit()里添加映射。
看个例子:
/* 在 config.h 中定义共享内存区域 */
#define SHARED_MEM_BASE 0x80000000 /* 物理基地址 */
#define SHARED_MEM_SIZE 0x00100000 /* 1MB */
/* 在 sysLib.c 的 sysMmuInit() 中添加映射 */
void sysMmuInit(void)
{
/* ... 其他MMU初始化代码 ... */
/* 为每个核添加共享内存映射 */
/* 注意:这里假设所有核共享同一个页表基址 */
MMU_ATTRIBUTE attr;
attr.cacheable = FALSE; /* 共享内存通常关闭cache */
attr.bufferable = FALSE;
attr.permission = MMU_READ_WRITE;
mmuMapPage(SHARED_MEM_BASE, /* 虚拟地址 = 物理地址(1:1映射) */
SHARED_MEM_BASE,
SHARED_MEM_SIZE,
&attr);
/* ... 其他代码 ... */
}
你想想看,为什么这里要关闭cache?因为多核环境下,cache一致性是个大坑。如果Core0写数据到共享内存,数据还在cache里没刷出去,Core1去读物理内存,读到的是旧数据。所以,共享内存区域我建议一律配置为non-cacheable。
cacheFlush()和cacheInvalidate()来保证数据同步。但说实话,能不开就不开,省心。
2.3 共享内存区域划分策略:别把鸡蛋放一个篮子里
共享内存区域划分,是门艺术。我见过不少新手,直接把整个共享内存当成一个大数组用。结果呢?数据覆盖、同步混乱、调试困难。我的建议是:把共享内存划分成若干个功能区域,每个区域有明确的用途。
常用的划分策略如下:
| 区域名称 | 偏移地址 | 大小 | 用途 |
|---|---|---|---|
| 控制区 | 0x0000 - 0x00FF | 256字节 | 存放状态标志、锁、信号量等控制信息 |
| 数据区A | 0x0100 - 0x1FFF | 7.5KB | Core0 → Core1 的数据通道 |
| 数据区B | 0x2000 - 0x3FFF | 8KB | Core1 → Core0 的数据通道 |
| 日志区 | 0x4000 - 0x4FFF | 4KB | 多核共享的日志缓冲区 |
| 保留区 | 0x5000 - 0xFFFF | 44KB | 预留,用于未来扩展 |
我个人习惯在代码里用宏定义来管理这些偏移:
/* 共享内存区域偏移定义 */
#define SHM_CTRL_OFFSET 0x0000
#define SHM_DATA_A_OFFSET 0x0100
#define SHM_DATA_B_OFFSET 0x2000
#define SHM_LOG_OFFSET 0x4000
#define SHM_RESERVED_OFFSET 0x5000
/* 访问宏 */
#define SHM_CTRL_BASE (SHARED_MEM_BASE + SHM_CTRL_OFFSET)
#define SHM_DATA_A_BASE (SHARED_MEM_BASE + SHM_DATA_A_OFFSET)
#define SHM_DATA_B_BASE (SHARED_MEM_BASE + SHM_DATA_B_OFFSET)
#define SHM_LOG_BASE (SHARED_MEM_BASE + SHM_LOG_OFFSET)
为什么要留保留区?因为项目后期总会加需求。我曾经有个项目,一开始只规划了数据区A和B,结果后来要加一个共享状态表,只能硬挤空间。从那以后,我每次都会留20%以上的保留区。
2.4 避坑指南:我踩过的那些坑
嗯,说到避坑,我经验可不少。这里列几个最常见的:
- 对齐问题: 共享内存里的数据结构,一定要考虑对齐。不同核的编译器可能对齐方式不同。我建议用
#pragma pack(1)强制1字节对齐,或者手动填充padding。 - 初始化顺序: 共享内存的初始化必须在所有核访问之前完成。我一般让Core0负责初始化,然后通过硬件中断通知其他核。
- 调试困难: 共享内存的问题很难复现。我的做法是在共享内存的控制区里放一个“魔数”(magic number),每次访问前检查魔数是否被破坏。如果魔数变了,说明有核写越界了。
好了,这一章的内容就到这里。下一章我们讲共享内存的同步机制——怎么用信号量和自旋锁来保护共享数据。到时候我会分享一个我实际项目中用过的环形缓冲区实现,保证让你眼前一亮。