flapenguin.me

ringbuffer даром

|

Оригинал https://t.me/bits_and_bicycles/25

Кольцевой буфер (он же ring buffer) – это структура данных #datastructure, которая позволяет тебе писать с одного конца (write) и читать с другого (read). Она классно подходит, когда тебе нужны FIFO очереди, потоки и всякие системы, где читатели с писателями работают отдельно друг от друга.

                        v read
|kcheburek..............topke|
          ^ write

Но есть проблема. Ты лишаешься простоты последовательных блоков памяти, как при записи, так и при чтении. Теперь у тебя два куска памяти. Ну или всё таки один. Как получится, зависит от текущего положения внутри буфера. Приходится писать какие-то проверки, данные копировать в непрерывную память снаружи. Неудобно.

Ну и вроде ничего не поделаешь. Память ведь одним куском идёт.

Но доступ к памяти устроен чуть хитрее. Отдельный кусок процессора, MMU, транслирует адреса по табличке (для простоты давай считать, что блоками по 4KiB). И, что главное, разные адреса до трансляции могут вести в одно и тот же адрес в итоге. Работаешь ты с виртуальными адресами, а в итоге получешь адрес в физической памяти.

virtual            mmu     physical
0x00001???
0x00002??? -  ---\    /-->0x00002???
0x00003???        \------>0x00003???
0x00004??? -  ------/
...

Я думаю, ты уже понял куда я клоню. Ты можешь отобразить одни и те же 4KiB (или сколько там тебе надо) друг за другом. И получишь ring buffer даром! В #POSIX эта штука контроллируется, как и большинство настроек виртуальной памяти, через mmap. Физических адресов тебе, конечно, в user-space никто не даст, но они тебе и не нужны.

// Ищем место под 2 блока подряд в виртуальной памяти
base = mmap(0, 2 * size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
// Создаём файл в памяти (технический момент, полноценный файл нам не нужен)
int fd = memfd_create("/oh/my/ringbuffer", 0);
ftruncate(fd, size);
// Отображаем блоки в файл
mmap(base       , size, PROT_xxx, MAP_FIXED | MAP_SHARED, fd, 0);
mmap(base + size, size, PROT_xxx, MAP_FIXED | MAP_SHARED, fd, 0);
read = write = base;

Всё, можешь читать свои 17 байт в read или писать во write. И не париться какая сейчас позиция в буфере: начало, середина, или вообще предпоследний байт.