我可以在 Linux 上创建一个循环缓冲区吗? (当前代码段错误)

Can I create a circular buffer on Linux? (current code segfaults)

this example 启发 Windows。简而言之,他们创建一个文件句柄(使用 CreateFileMapping)然后创建 2 个指向同一内存的不同指针(MapViewOfFileExMapViewOfFile3

所以我尝试对 shm_openftruncatemmap 做同样的事情。我过去曾几次使用 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),这导致 SIGSEGVSEGV_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_NONEMAP_ANONYMOUS | MAP_PRIVATE(没有文件)调用 mmap 来保留内存
  • mmapMAP_FIXED 结合使用,用您需要的内容覆盖该映射的部分内容

此外,您应该更喜欢在 Linux 上使用 memfd_create 而不是 shm_open 以避免共享内存文件停留在周围。如果您的程序崩溃,用 shm_unlink 取消链接也无济于事。这也为您提供了一个程序实例专用的文件。