В типичной ОС с семафором связан список ожидания, в который
становятся процессы ожидающие срабатывания данного семафора. И
будится один из процессов, первый в очереди. А у тебя -- spinlock,
совсем другая история. И да, там бы с атомарностью что-то
придумать: обычно достаточно примитива test and set (атомарного
инкремента, compare and swap...) Без атомарности не взлетит, т.к.
условие "!busy" может сработать сразу в двух потоках одновременно и
будет глюкодром. Нужна атомарная операция ЧТЕНИЯ-И-МОДИФИКАЦИИ. Без этого -- никак или очень сложно (есть теоретически разработки, например алгоритм Петерсона, практически трудно применимые). Поэтому все микропроцессоры начиная с 80-х начинают получать атомарные инструкции. Вариант "test and set" есть практически у всех. У многих CISC был атомарный инкремент. У RISC хуже, т.к. все инструкции или чтение, или запись, но не то и другое одновременно. У MIPS появилась пара инструкций LL/SC, которой можно обрамить некоторую последовательность инструкций, которая должна быть выполнена без прерывания (если будет прерывание, то на финальном SC это можно заметить и нужно повторять). У ARM test-and-set был и больше ничего, только начиная с ARMv7 (прошу уточнить) появились LDREX/STREX работающие примерно как в MIPS. Поэтому атомики в Linux на ARM работают очень странным способом, аж с поддержкой ядра.