__atomic_test_and_set блокирует всю программу в конфигурации AMP с общей памятью - PullRequest
0 голосов
/ 03 мая 2019

У меня есть плата разработки ZYBO Zynq 7000 (двухъядерный ARM Cortex-A9), которую я использую в конфигурации Asymmetric MultiProcessing: CPU0 работает под управлением Linux, а CPU1 запускает приложение с открытым исходным кодом, написанное на C ++. Я настроил область общей памяти между двумя процессорами в соответствии с этим примечанием к приложению . Я перенастроил кэш на CPU1 и mmap редактировал общую память в моей программе Linux, и это прекрасно работает: я могу читать и записывать в общую память из обоих приложений.

Следующее, чего я пытаюсь добиться, - это использовать атомарные блокировки общих данных. Я использую std::atomic_flag.
Проблема, с которой я сталкиваюсь, заключается в том, что всякий раз, когда я вызываю atomic_flag::lock.test_and_set() (с любого ЦП), вся программа на этом ЦП просто зависает в этой строке.

Это код для блокировки, которую я использую:

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, если я отключу блокировку. Если я включаю блокировку, она кажется зависшей навсегда, поэтому она никогда не добьется приращения переменной.

Я закомментировал ScopedLock lock(test_lock); в строках 63 и 72, чтобы отключить блокировку.

TL; DR кода на 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 я использую установку GCC-8.3, которую я собрал с помощью crosstool-NG: arm-cortexa9_neon-linux-gnueabihf-g++, а для ядра с голым железом я использую GCC 8 gcc-arm-none-eabi-8-2018-q4-major, который я получил с сайта ARM , (Гугл снова заставляет меня усомниться в том, что это правильный компилятор для Cortex-A9, но, похоже, он работает нормально). Этот набор инструментов используется Xilinx Vivado SDK для генерации образа загрузки.
Я не знаю, какие флаги или настройки могут вызывать эту проблему, поэтому, если мне нужно будет публиковать более подробную информацию о параметрах моего компилятора и т. Д., Сообщите мне об этом в комментарии, и я добавлю его в свой пост.


Редактировать
После еще нескольких исследований это кажется гораздо более сложным, чем я думал.

Из Технического справочного руководства Zynq-7000 (с.144):

В кеше APU L1 есть эксклюзивные мониторы, но не в кеше уровня L2. Это означает, что эксклюзивный адрес доступа должен заканчиваться либо в кеше L1, либо в памяти L3, но не в L2.
Чтобы использовать эксклюзивный монитор L1, адресная область MMU должна быть установлена ​​как внутренняя кешируемая и обратная запись во внутренний кэш с записью-выделением. Это позволяет адрес, предназначенный для конкретного эксклюзивного доступ к кэшу L1 всегда будет выделен.
Чтобы использовать эксклюзивный монитор L3, доступ не должен заканчиваться в кеше L2 APU. От ARM В перспективе CPU это означает, что адрес должен быть общим, обычным и не кэшируемым. Кроме того, L2 Опция общего переопределения контроллера кэша (бит 22 в регистре вспомогательного управления L2) должна быть установлена ​​в вспомогательный регистр управления. По умолчанию в контроллере кэша L2 APU все не кешируемые общие чтения обрабатываются как кешируемые, не выделяемые, в то время как не кешируемые общие записи рассматриваются как кешируемые сквозная запись / нет записи-выделить. Опция общего переопределения контроллера кэша L2 в PL310 вспомогательный регистр управления отменяет это поведение и предотвращает размещение в кэш-памяти второго уровня.

Это моя лучшая попытка, но она все еще не работает.

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();
}

Я также установил бит 22 вспомогательного регистра управления в boot.S:

.set L2CCAuxControl,    0x72760000

Согласно этой теме . Используемые адреса находятся в памяти DDR (?) (0x00000000-0x3FFFFFFF), а мои - нет (0xFFFF0000-0xFFFFFFFF).
Этот диапазон адресов использовался в примечании к приложению, о котором я упоминал ранее, но я не понимаю, как он сопоставлен или почему он был сопоставлен таким образом.

...