Вдохновлен этот пример для 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;
}
Вам не нужно снова вызывать 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;
}
Вы можете преобразовать буфер в массив целых чисел, структур и т. д. Это просто другая область памяти, но не выделенная с помощью malloc
(даже malloc
может вызывать mmap
в своей реализации). Теперь у вас есть массив или, если хотите, область памяти, начинающаяся с адреса p
и заканчивающаяся p + len
. Когда вы разыгрываете его (например, int *shm_array = p
, struct foo_struct *shm_array = p
, …), вы получаете обычный (но поддерживаемый диском/shm) массив с len / sizeof(array type)
элементами.
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
оба делают это
@EricStoch Вы должны использовать MAP_SHARED
вместо MAP_PRIVATE
. Прямо сейчас вы получаете поведение копирования при записи, например. каждое из двух сопоставлений имеет собственное представление данных. Кроме того, если вы используете shm_open
, с MAP_SHARED
каждый экземпляр вашей программы будет делиться этими данными, чего вы, вероятно, не ожидаете. Использование memfd_create
в основном обязательно на данном этапе, так как это ближайший эквивалент CreateFileMapping
Это полностью работает. Спасибо. Я пробовал писать 5 и 4 на разные указатели и ожидал, что оптимизатор сделает ошибку в O2. Он правильно показал мне второе значение, когда я прочитал оба указателя. Мне не нужен volatile? Я немного беспокоюсь, что оптимизатор вставит проверку, чтобы убедиться, что указатели разные, и выдаст неправильный (но быстрый) код. Я сильно беспокоюсь или мне стоит использовать volatile
?
В целом да, но это еще зависит от того, как вы работаете с буфером. Компилятор может оптимизировать доступ только тогда, когда они близки друг к другу, поэтому вы можете не использовать volatile
, получая при этом более качественный код. Не использовать volatile должно быть хорошо, например, если вы записываете элементы только на одной стороне и читаете на другой стороне.
Я пытаюсь создать круговой буфер, поэтому, если я напишу
p[len+1]
, он запишет изменение наp[1]
. Или, что более реалистично,memcpy(p+len-10, src, 20)
, который скопирует 10 байт в конце и 10 байт назад в начале.