我可以在 Linux 上创建一个循环缓冲区吗? (当前代码段错误)
Can I create a circular buffer on Linux? (current code segfaults)
受 this example 启发 Windows。简而言之,他们创建一个文件句柄(使用 CreateFileMapping
)然后创建 2 个指向同一内存的不同指针(MapViewOfFileEx
或 MapViewOfFile3
)
所以我尝试对 shm_open
、ftruncate
和 mmap
做同样的事情。我过去曾几次使用 mmap
作为内存和文件,但我从未将其与 shm_open
混合使用或使用 shm_open
.
我的代码在第二个 mmap
上失败并出现段错误。我尝试直接在两个 mmap 上执行系统调用,但它仍然出现段错误 :( 我该如何正确执行此操作?我的想法是我可以 memcpy(p+len-10, src, 20)
并让 src 的前 10 个字节位于内存的末尾,最后 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;
}
Linux 通常选择地址 space 用于从某个点开始的映射,并随着每次保留而降低。因此,您的第二次 mmap
调用替换了以前的文件映射之一(可能 libc.so
),这导致 SIGSEGV
和 SEGV_ACCERR
- 无效的访问权限。您正在用 non-executable 数据覆盖 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
可以在没有事先保留内存的情况下使用。要正确执行此操作,您需要:
- 通过使用
len * 2
大小、PROT_NONE
和 MAP_ANONYMOUS | MAP_PRIVATE
(没有文件)调用 mmap
来保留内存
- 将
mmap
与 MAP_FIXED
结合使用,用您需要的内容覆盖该映射的部分内容
此外,您应该更喜欢在 Linux 上使用 memfd_create
而不是 shm_open
以避免共享内存文件停留在周围。如果您的程序崩溃,用 shm_unlink
取消链接也无济于事。这也为您提供了一个程序实例专用的文件。
受 this example 启发 Windows。简而言之,他们创建一个文件句柄(使用 CreateFileMapping
)然后创建 2 个指向同一内存的不同指针(MapViewOfFileEx
或 MapViewOfFile3
)
所以我尝试对 shm_open
、ftruncate
和 mmap
做同样的事情。我过去曾几次使用 mmap
作为内存和文件,但我从未将其与 shm_open
混合使用或使用 shm_open
.
我的代码在第二个 mmap
上失败并出现段错误。我尝试直接在两个 mmap 上执行系统调用,但它仍然出现段错误 :( 我该如何正确执行此操作?我的想法是我可以 memcpy(p+len-10, src, 20)
并让 src 的前 10 个字节位于内存的末尾,最后 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;
}
Linux 通常选择地址 space 用于从某个点开始的映射,并随着每次保留而降低。因此,您的第二次 mmap
调用替换了以前的文件映射之一(可能 libc.so
),这导致 SIGSEGV
和 SEGV_ACCERR
- 无效的访问权限。您正在用 non-executable 数据覆盖 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
可以在没有事先保留内存的情况下使用。要正确执行此操作,您需要:
- 通过使用
len * 2
大小、PROT_NONE
和MAP_ANONYMOUS | MAP_PRIVATE
(没有文件)调用mmap
来保留内存 - 将
mmap
与MAP_FIXED
结合使用,用您需要的内容覆盖该映射的部分内容
此外,您应该更喜欢在 Linux 上使用 memfd_create
而不是 shm_open
以避免共享内存文件停留在周围。如果您的程序崩溃,用 shm_unlink
取消链接也无济于事。这也为您提供了一个程序实例专用的文件。