我有一个用于非对称多处理配置的ZYBO Zynq 7000(双核ARM Cortex-A9)开发板:CPU0运行Linux,CPU1运行以C ++编写的裸机应用程序。我已经按照this application note在两个CPU之间配置了一个共享内存区域。我已经在CPU1上重新配置了缓存,并在我的Linux程序中mmap
添加了共享内存,并且工作正常:我可以从两个应用程序读取和写入共享内存。
我接下来要实现的是对共享数据使用原子锁。我正在使用std::atomic_flag
。
我面临的问题是,每当我从任一CPU调用atomic_flag::lock.test_and_set()
时,该CPU上的整个程序都将挂在那一行。
这是我正在使用的锁的代码:
class ScopedLock {
public:
ScopedLock(volatile std::atomic_flag &lock) : lock{lock} {
bool locked = true;
for (size_t i = 0; i < NUM_RETRIES; ++i) {
locked = lock.test_and_set(std::memory_order_acquire);
std::cout << "locked = " << locked << std::endl;
if (locked)
usleep(WAIT_TIME);
else
break;
}
if (locked)
throw std::runtime_error("Timeout: Could not acquire lock");
}
~ScopedLock() {
lock.clear(std::memory_order_release);
std::cout << "released" << std::endl;
}
private:
volatile std::atomic_flag &lock;
constexpr static size_t NUM_RETRIES = 10;
constexpr static useconds_t WAIT_TIME = 50;
};
我在这里做什么错了?
我将测试程序及其主要依赖项上传到GitHub:
它仅使每个内核的共享变量增加1000倍。期望的结果将是2000,但是按预期,如果禁用锁定,结果将小于2000。如果启用了锁,它似乎会永远挂起,因此永远不会增加变量。
我在第63和72行上注释了ScopedLock lock(test_lock);
以禁用该锁定。
TL; GitHub上的代码的灾难恢复:
#define atomic_flag32 std::atomic_flag __attribute__((aligned(4)))
struct TestStruct {
mutable atomic_flag32 test_lock = ATOMIC_FLAG_INIT;
uint32_t counter = 0;
void increment() volatile {
{
ScopedLock lock(test_lock);
uint32_t tmp = counter;
usleep(40);
counter = tmp + 1;
}
usleep(10);
}
};
// In bare-metal, I initialize the shared struct instance:
volatile TestStruct *sm = new ((void *) addressInSharedMem) TestStruct();
// On Linux, I use mmap, and I don't initialize the memory, I just use it immediately
for (size_t i = 0; i < 1'000; ++i)
sm->increment();
在Linux上,我使用的是使用crosstool-NG构建的GCC-8.3安装:arm-cortexa9_neon-linux-gnueabihf-g++
,对于裸机内核,我使用的是GCC 8 gcc-arm-none-eabi-8-2018-q4-major
从ARM网站。 (再次搜索它使我怀疑它是否是适合Cortex-A9的编译器,但似乎工作正常)Xilinx Vivado SDK使用此工具链来生成启动映像。
我不知道什么标志或设置可能导致此问题,因此,如果我应该发布有关我的编译器选项等的更多详细信息,请在评论中通知我,并将其添加到我的帖子中。
修改
经过更多研究后,似乎比我想像的要复杂得多。
来自Zynq-7000 Technical Reference Manual(第144页):
APU L1缓存中有专用监视器,但L2级别缓存中没有。这意味着独家 访问地址必须终止于L1高速缓存或L3存储器中,但不得终止于L2。
若要使用L1专用监视器,必须将寻址的MMU区域设置为内部可缓存,并且 具有写分配的内部缓存写回。这允许特定排他对象定位的地址 始终将访问权限分配给L1缓存。
要使用L3专用监视器,访问不得终止于APU L2高速缓存。从ARM 从CPU角度来看,这意味着地址必须是可共享的,正常的且不可缓存的。另外,L2 高速缓存控制器共享优先选项(L2辅助控制寄存器中的位22)必须在 辅助控制寄存器。默认情况下,在APU L2缓存控制器中,所有不可缓存的共享读取 被视为可缓存的不可分配,而不可缓存的共享写被视为可缓存 直写/无写分配。 PL310中的L2缓存控制器共享优先选项 辅助控制寄存器将覆盖此行为,并阻止分配到L2缓存中。
这是我的最佳尝试,但仍然无效。
void eagle_setup_ipc(void) {
// Original: 0b100110111100010 = 0x04de2
// Configuration of the Level 1 Page Table
// =======================================
//
// See Figure 3-5 on p.78 of the Zynq-7000 Technical Reference Manual
// https://www.xilinx.com/support/documentation/user_guides/ug585-Zynq-7000-TRM.pdf
//
// [31:20] → base address of section
// [19] 0 → NS
// [18] 0 → 1 MiB "sections"
// [17] 0 → Global
// [16] 1 → Shareable
// [15] 0 → Access Permission [2]
// [14:12] 100 → TEX → Normal memory, non-cacheable
// [11:10] 11 → Access Permission [1:0] → Full Access
// [9] 0
// [8:5] 1111 → Domain
// [4] 1 → Execute Never
// [3:2] 00 → CB → non-cacheable
// [1:0] 10 → 1 MiB "sections"
eagle_SetTlbAttributes(0xFFFF0000, 0b1'0'100'11'0'1111'1'00'10);
}
void eagle_DCacheFlush(void) {
Xil_L1DCacheFlush();
//Xil_L2CacheFlush();
}
void eagle_SetTlbAttributes(u32 addr, u32 attrib) {
u32 *ptr;
u32 section;
mtcp(XREG_CP15_INVAL_UTLB_UNLOCKED, 0);
dsb();
mtcp(XREG_CP15_INVAL_BRANCH_ARRAY, 0);
dsb();
eagle_DCacheFlush();
section = addr / 0x100000;
ptr = &MMUTable + section;
*ptr = (addr & 0xFFF00000) | attrib;
dsb();
}
我还在boot.S中设置了辅助控制寄存器的第22位:
.set L2CCAuxControl, 0x72760000
按照this thread。他们使用的地址在DDR内存(?)(0x00000000-0x3FFFFFFF)中,而我的地址不在(0xFFFF0000-0xFFFFFFFF)。
我在前面提到的应用笔记中使用了该地址范围,但我不明白它是如何映射的,或者为什么以这种方式映射它。