+3
- 01
- 02
- 03
- 04
- 05
- 06
- 07
- 08
- 09
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
// https://deadlockempire.github.io/
// Игра, где надо играть за планировщик чтоб вызвать дедлок
// https://deadlockempire.github.io/#2-flags
// First Army
while (true) {
while (flag != false) {
;
}
flag = true;
critical_section();
flag = false;
}
// Second Army
while (true) {
while (flag != false) {
;
}
flag = true;
critical_section();
flag = false;
}
The day finally came. The Deadlock Empire opened its gates and from them surged massive amounts of soldiers, loyal servants of the evil Parallel Wizard. The Wizard has many strengths - his armies are fast, and he can do a lot of stuff that we can't. But now he set out to conquer the world, and we cannot have that.
You are our best Scheduler, commander! We have fewer troops and simpler ones, so we will need your help. Already two armies of the Deadlock Empire are approaching our border keeps. They are poorly equipped and poorly trained, however. You might be able to desync them and break their morale.
Запостил:
j123123,
20 Февраля 2021
армия:строка
1:9
2:20
1:12
2:23
1:13
2:24
еблысь
если во круг флага нет заборов, то он по идее может остаться в буфере ЦПУ, и в теории армия1 вообще никогда не узнает, что его изменила армия2.
Да и flag = true с flag = false может слипнуться ещё во время конпеляции.
З.Ы. Угу, gcc от всей этой хуйни только внешний while (1) оставил.
volatile нужно пхать, да?
я думал, что это volatile.
Срать может как и другое ядро, так и железо. просто в случае с другим ядром этого НЕДОСТАТОЧНО, потому что код-то он не выкинет, но гонка останется
или нет?
Атомик втыкает барьеры, но не запрещает оптимизировать обращения. Они могут слипнуться если это никому не ломает happens before.
Я имел ввиду, что volatile хоть и не сделает код корректным, но гарантирует, что его хотя-бы не выкинут.
Потому что без волатайла тут написано ``while(true){}``
Разумеется, обращение к переменной из двух потоков нужно синхронизировать барьером.
Допустим есть микроконтроллер с одним ядром, есть некая глобальная volatile переменная, которая может читаться/писаться в обработчике прерывания и в обычном коде. Допустим, переменная там 64-битная, а инструкции записи байтиков в память есть максимум на 32 бит, и вот в коде мы наполовину перезаписали volatile переменную новым значением, и тут хуяк - обработчик прерывания. А в переменной вообще хуйня какая-то непредусмотренная
Выходит, что запись в такую переменную это критическая секция, и нужно маскировать прерыввания на время записи, не?
У нас в жабке эти две концепции слиты воедино, нам проще
Но, насколько я помню, для x.store(42); x.store(100500) конпелятор и проц имеют право выкинуть первую инструкцию. Порядок это не ломает, сторонний наблюдатель просто подумает, что он пиздец невезучий, раз никогда не видит там 42.
> ... That is all we need for what volatile is intended for: manipulating I/O registers or memory-mapped hardware, but it doesn't help us in multithreaded code where the volatile object is often only used to synchronize access to non-volatile data. Those accesses can still be reordered relative to the volatile ones.
volatile вообще нехуй так использовать, компилятор имеет право всякое говно между этими volatile переставлять
https://godbolt.org/z/Pad3n5
- вот например оно два инкремента не-volatile глобальной переменной спихнуло в одну инструкцию после всей волатильной питушни
например
volatile_crap++
нельзя свернуть в volatile_crap += 2
а регулярный можно.
И потом дальше делать как обычно.
interrupt_safe_atomic<uint64_t> x;
x = 42;
А если у CPU есть атомарная инструкция для этого?
В жабке есть AtomicInteger например
это std::atomic дает такую гарантию?
Потому что если я просто использую атомарную иструацию цепеу, то мне это не поможет.
PetuhCountAtomicIncrement()
SendDataToIoPort()
PetuhCountAtomicIncrement()
если питух не волатилен, то что мешает компилятору сделать
PetuhCountAtomicIncrement()
PetuhCountAtomicIncrement()
SendDataToIoPort()
?
> 8.1.1 Guaranteed Atomic Operations
>
> The Intel486 processor (and newer processors since) guarantees that the following basic memory operations will always be carried out atomically:
>
> •Reading or writing a byte
> •Reading or writing a word aligned on a 16-bit boundary
> •Reading or writing a doubleword aligned on a 32-bit boundary
>
> The Pentium processor (and newer processors since) guarantees that the following additional memory operations will always be carried out atomically:
>
> •Reading or writing a quadword aligned on a 64-bit boundary
> •16-bit accesses to uncached memory locations that fit within a 32-bit data bus
>
> The P6 family processors (and newer processors since) guarantee that the following additional memory operation will always be carried out atomically:
>
> •Unaligned 16-, 32-, and 64-bit accesses to cached memory that fit within a cache line
т.е. никакого xadd не надо для атомарности инкремента, если адрес записи выровнен по нужной границе
Емнип, на практике не атомарный add ещё как плющит из нескольких потоков.
Понятное дело, что обычный поток кода не может выполняться, пока выполняется прерывание в одноядерной системе) Это я еще со времен доса помню.
А как именно код и обработчик прерывания достукиваются до этой глобалки?
Если это просто какое-то место в памяти, то разве не нужно его поменчать как volatile? компилятор же выкинет иначе нафиг всё, нет?
По адресу памяти, очевидно.
> Если это просто какое-то место в памяти, то разве не нужно его поменчать как volatile?
Нет, зачем?
> компилятор же выкинет иначе нафиг всё, нет?
С чего б ему выкидывать что-то?
*p = 1;
он видит, что автматическая переменная p нигде не испльзуется, и выкидывает ее
или нет?
то запись байтика по адресу 0xAABBCCDD у тебя нихуя выкидываться не будет
то если в вызов shit3 заинлайнивается вся хуйня и компилятор это заоптимизирует, то запись в glob_var там будет только одна, ибо нехуй лишний раз ворошить память. И если shit3() это какой-то обработчик прерываний, то "применить" изменения глобалок надо тогда, когда весь этот обработчик завершится, так что по итогу там будет 1337 и никакие предыдущие записи в глобалку нахуй не нужны, раз обработчик никто на полпути не прервет. Но если glob_var это какая-то хуйня типа MMIO и там особая хуйня должна происходить при записи говна по таким-то адресам, то тогда вот можно volatile юзать
автоматические переменные никто не видит, но так как память-то у нас не в стеке, то запись в нее нельзя выкидывать
выкинуть
*a = 51;
он может только если потом идет
*a = 52
и тут нужен волатил
Если насрать в глобалку и выйти -- конпелятор обязан сохранить такой эффект. Он даже отложить его на попозже его не может т.к. ISR никуда не инлайнится.
запись в глобалку нельзя отменить, можно только схлопнуть две записи
Оно вполне может вытеснять, важно чтоб оно не конфликтовало, т.е. не ворошило те же самые глобалки, с которыми то прерывание имеет дело.
В том же "uefi" таймерное прерывание может само себя прервать 4-5 раз. Такие вот дела.
понятно
Congratulations, you have triggered deadlocks in same way normal CPUs do!