У меня есть плата разработки 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).
Этот диапазон адресов использовался в примечании к приложению, о котором я упоминал ранее, но я не понимаю, как он сопоставлен или почему он был сопоставлен таким образом.