Могу ли я создать круговой буфер в Linux? (текущие ошибки кода)

Вдохновлен этот пример для Windows. Короче говоря, они создают дескриптор файла (с CreateFileMapping), а затем создают 2 разных указателя на одну и ту же память (MapViewOfFileEx или MapViewOfFile3).

Поэтому я попытался сделать то же самое с shm_open, ftruncate и mmap. Я использовал mmap несколько раз в прошлом для памяти и файлов, но я никогда не смешивал его с shm_open и не использовал shm_open.

Мой код дает сбой на второй mmap с segfault. Я попытался сделать системный вызов непосредственно на обоих mmap, и он все еще segfaults :( Как мне это сделать правильно? Идея в том, что я могу сделать memcpy(p+len-10, src, 20) и сделать так, чтобы первые 10 байт src были в конце памяти, а последние 10 записаны в начало (отсюда круговой)

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
int main()
{
    write(2, "Start\n", 6); //prints this
    int len = 1024*1024*2;
    int fd = shm_open("example", O_RDWR | O_CREAT, 0777);
    assert(fd > 0); //ok
    int r1 = ftruncate(fd, len);
    assert(r1 == 0); //ok
    char*p = (char*)mmap(0, len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
    assert((long long)p>0); //ok
    //Segfaults on next line
    char*p2 = (char*)mmap(p+len, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED, fd, 0); //segfaults
    write(2, "Finish\n", 7); //doesn't print this
    return 0;
}
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
82
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вам не нужно снова вызывать mmap, чтобы сгенерировать новый указатель. (Вы даже не должны этого делать.) Просто увеличьте его.

Указатель p2 не будет указывать на адрес только после выделенного блока памяти.

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
int main()
{
    write(2, "Start\n", 6); //prints this
    int len = 1024*1024*2;
    int fd = shm_open("example", O_RDWR | O_CREAT, 0777);
    assert(fd > 0); //ok
    int r1 = ftruncate(fd, len);
    assert(r1 == 0); //ok
    char*p = (char*)mmap(0, len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
    assert((long long)p>0); //ok
    char*p2 = p+len;
    write(2, "Finish\n", 7); //doesn't print this
    return 0;
}

Я пытаюсь создать круговой буфер, поэтому, если я напишу p[len+1], он запишет изменение на p[1]. Или, что более реалистично, memcpy(p+len-10, src, 20), который скопирует 10 байт в конце и 10 байт назад в начале.

Eric Stotch 22.03.2022 17:07

Вы можете преобразовать буфер в массив целых чисел, структур и т. д. Это просто другая область памяти, но не выделенная с помощью malloc (даже malloc может вызывать mmap в своей реализации). Теперь у вас есть массив или, если хотите, область памяти, начинающаяся с адреса p и заканчивающаяся p + len. Когда вы разыгрываете его (например, int *shm_array = p, struct foo_struct *shm_array = p, …), вы получаете обычный (но поддерживаемый диском/shm) массив с len / sizeof(array type) элементами.

jiwopene 22.03.2022 17:09
Ответ принят как подходящий

Linux обычно выбирает адресное пространство для отображений, начиная с определенной точки и с каждым резервированием опускаясь ниже. Таким образом, ваш второй вызов mmap заменяет одно из предыдущих сопоставлений файлов (вероятно libc.so), что приводит к SIGSEGV с SEGV_ACCERR — недействительными правами доступа. Вы перезаписываете исполняемый раздел libc.so (который выполняется прямо сейчас) неисполняемыми данными.

Используйте strace, чтобы проверить, что происходит внутри:

$ strace ./a.out 
...
openat(AT_FDCWD, "/dev/shm/example", O_RDWR|O_CREAT|O_NOFOLLOW|O_CLOEXEC, 0777) = 3
ftruncate(3, 2097152)                   = 0
mmap(NULL, 2097152, PROT_READ|PROT_WRITE, MAP_PRIVATE, 3, 0) = 0x7f134c1bf000
mmap(0x7f134c3bf000, 2097152, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0) = 0x7f134c3bf000
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_ACCERR, si_addr=0x7f134c4ccc37} ---
+++ killed by SIGSEGV +++

Сравните адреса, которые вы передаете, с файлом /proc/$pid/maps, и вы увидите, что вы перезаписываете.

Ваша ошибка заключалась в том, что вы предположили, что MAP_FIXED можно использовать без предварительного резервирования памяти. Чтобы сделать это правильно, вам нужно:

  • Зарезервируйте память, вызвав mmap с размером len * 2, PROT_NONE и MAP_ANONYMOUS | MAP_PRIVATE (и без файла)
  • Используйте mmap с MAP_FIXED, чтобы перезаписать части этого сопоставления нужным вам контентом.

Кроме того, вы должны предпочесть использовать memfd_create вместо shm_open в Linux, чтобы файлы общей памяти не оставались без дела. Отключение их с помощью shm_unlink не поможет, если ваша программа выйдет из строя. Это также дает вам файл, который является частным для вашего экземпляра программы.

Благодарю вас! Я подумал, что это странно, что системный вызов у ​​меня вылетает, а не выдает какую-то ошибку. Следующая проблема заключается в том, что значение не отражается. Если я сделаю и p, и p2 volatile char* и напишу p[123] = 12; p2[123] = 45;, то printf я получу 12 для p и 45 для p2. memfd_create и shm_open оба делают это

Eric Stotch 22.03.2022 18:42

@EricStoch Вы должны использовать MAP_SHARED вместо MAP_PRIVATE. Прямо сейчас вы получаете поведение копирования при записи, например. каждое из двух сопоставлений имеет собственное представление данных. Кроме того, если вы используете shm_open, с MAP_SHARED каждый экземпляр вашей программы будет делиться этими данными, чего вы, вероятно, не ожидаете. Использование memfd_create в основном обязательно на данном этапе, так как это ближайший эквивалент CreateFileMapping

StaceyGirl 22.03.2022 18:43

Это полностью работает. Спасибо. Я пробовал писать 5 и 4 на разные указатели и ожидал, что оптимизатор сделает ошибку в O2. Он правильно показал мне второе значение, когда я прочитал оба указателя. Мне не нужен volatile? Я немного беспокоюсь, что оптимизатор вставит проверку, чтобы убедиться, что указатели разные, и выдаст неправильный (но быстрый) код. Я сильно беспокоюсь или мне стоит использовать volatile?

Eric Stotch 22.03.2022 18:58

В целом да, но это еще зависит от того, как вы работаете с буфером. Компилятор может оптимизировать доступ только тогда, когда они близки друг к другу, поэтому вы можете не использовать volatile, получая при этом более качественный код. Не использовать volatile должно быть хорошо, например, если вы записываете элементы только на одной стороне и читаете на другой стороне.

StaceyGirl 22.03.2022 19:16

Другие вопросы по теме