__atomic_test_and_set使用共享内存阻止AMP配置中的整个程序

时间:2019-05-03 14:50:29

标签: c++ arm multiprocessing shared-memory atomic

我有一个用于非对称多处理配置的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)。
我在前面提到的应用笔记中使用了该地址范围,但我不明白它是如何映射的,或者为什么以这种方式映射它。

0 个答案:

没有答案