c语言 - 共享 mmap 内存的部分/区域



我有几个进程执行特定大小(0x8000(的mmap()。我只想在这些进程之间共享一部分内存空间,如下图所示:

0x0             0x2000-0x3000           0x8000
p1:   [MEM. PRIVATE]  [MEM. SHARING]  [MEM. PRIVATE]
p2:   [MEM. PRIVATE]  [MEM. SHARING]  [MEM. PRIVATE]

在这种情况下,mmap()分配的内存必须仅在0x2000-0x3000范围内共享。其他部分是私有的(MEM.private(。

在调用mmap()之后,是否有系统调用来执行共享?我事先尝试过shm_open(),但整个范围都是共享的。

听起来你想要3个映射,只有中间一个是MAP_SHARED。

但是不要进行3个单独的mmap调用,而是进行两个首先是整个长度的MAP_PRIVATE,然后是偏移0x2000的共享区域的MAP_FIXED|MAP_SHARED,替换第一个映射的中间。第一个映射可能是MAP_ANONYMOUS,根本没有文件支持,因为你没有共享它。

如果它应该为零初始化,则不需要磁盘文件或shm区域的一部分。由于是私有的,对它的写入不会在任何地方持续存在,但如果您以其他方式将数据写入文件,则可以从非零初始值设定项开始。

对于MAP_FIXED,mmap的第一个arg不仅仅是一个提示,它肯定是映射的位置,即使这意味着用munmap替换该虚拟地址上的先前映射(部分(。

void *setup_mapping(int fd)
{
const size_t total_len = 0x8000;
const size_t shared_mem_offset = 0x2000, shared_len = 0x1000;
const size_t shared_file_offset = 0;  // the file *only* holds the shared part
// unless you have some non-zero data for the private mapping to start with
// Private anonymous non-shared mapping, like malloc.
// Letting the kernel pick an address
char *base = mmap(NULL, total_len, PROT_READ | PROT_WRITE, 
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
// or without MAP_ANONYMOUS, using fd, 0 
if (base == MAP_FAILED)
return MAP_FAILED;
// replace the middle of that mapping with our shared file mapping
void *shared = mmap(base+shared_mem_offset, shared_len, PROT_READ|PROT_WRITE, 
MAP_SHARED|MAP_FIXED, fd, shared_file_offset);
if (shared == MAP_FAILED){
munmap(base, total_len);   // if munmap returns error, still just return
return MAP_FAILED;
}
return base;
}

我使用了char *base,所以它的数学定义会很明确。GNU C确实将void*上的指针数学定义为与char*上的工作方式类似。

顺便说一句,完成后,一个munmap可以分解整个区域,而不管它是2个还是3个映射。当然,也可以直接退出流程。


以整个区域的mmap开始意味着你不需要寻找可用的虚拟地址范围作为提示;第一个mmap会找到并声明一个,这样你就可以安全地将_FIXED映射到它的一部分。即使其他线程同时分配内存,这也是安全的;他们不能在两个mmap调用之间随机选择冲突的地址,就像你只使用提示地址或使用MAP_FIXED_NOREPLACE进行3个单独的映射一样。这在实践中不太可能成为一个问题。

此外,这只会进行2次系统调用,而不是3次,因此效率更高。(映射更多页面和替换映射的内部工作应该是次要的,尤其是当你还没有接触到内存来实际故障时。(

它还减少了要编写的代码,减少了要检查的错误返回值,从而减少了部分故障的角落情况。

您需要三个映射:

  1. MAP_PRIVATE
  2. 映射共享
  3. 映射私人

诀窍是为addr参数使用不同的值,而为每个参数使用mmap。第一个是NULL,然后对于剩余的每个,使用旧的返回值+段大小(即,对于后两个,使用显式映射地址(。


这是代码。注释:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/mman.h>
#include <sys/wait.h>
#define P1OFF       0x0000
#define P1LEN       0x1000
#define SHROFF      0x2000
#define SHRLEN      0x1000
#define P2OFF       (SHROFF + SHRLEN)
#define P2LEN       (0x8000 - SHROFF)
const char *shrfile = "share.mem";
typedef unsigned char u8;
typedef unsigned int u32;
typedef struct {
int seg_idx;                        // segment index
u32 seg_off;                        // segment offset
u32 seg_len;                        // segment length
void *seg_base;                     // segment pointer
int seg_shr;                        // 1=segment is shared
int seg_fd;                         // segment fd (unused)
char seg_file[100];                 // file for given segment (unused)
} seg_t;
seg_t seglist[] = {
[0] = {
.seg_idx = 0,
.seg_off = P1OFF,
.seg_len = P1LEN,
.seg_file = "p1_%d.mem",
},
[1] = {
.seg_idx = 1,
.seg_off = SHROFF,
.seg_len = SHRLEN,
.seg_shr = 1,
.seg_file = "share.mem",
},
[2] = {
.seg_idx = 2,
.seg_off = P2OFF,
.seg_len = P2LEN,
.seg_file = "p2_%d.mem",
},
};
#define SEGALL 
seg = &seglist[0]; 
seg < &seglist[sizeof(seglist) / sizeof(seglist[0])]; 
++seg
#define prt(_fmt...) 
do { 
prtx(); 
printf(_fmt); 
} while (0)
#define sysfault(_fmt...) 
do { 
prt(_fmt); 
exit(1); 
} while (0)
int pidxid;                             // processes's unique sequential ID
int fdshr;                              // file descriptor for buffer
u8 *shrbase;                            // pointer to shared segment
u8 *allbase;                            // pointer to full mapped area
u32 totlen;                             // length of area
double tsczero;
double
tscgetf(void)
{
struct timespec ts;
double sec;
clock_gettime(CLOCK_MONOTONIC,&ts);
sec = ts.tv_nsec;
sec /= 1e9;
sec += ts.tv_sec;
sec -= tsczero;
return sec;
}
void
prtx(void)
{
printf("[%.9f %d] ",tscgetf(),pidxid);
}
void
mkfile(const char *shrfile,u32 len)
{
prt("mkfile: shrfile='%s' len=%4.4Xn",shrfile,len);
int fd = open(shrfile,O_RDWR | O_TRUNC | O_CREAT,0644);
if (fd < 0)
sysfault("mkfile: open fault shrfile='%s' -- %sn",
shrfile,strerror(errno));
int err = ftruncate(fd,len);
if (err < 0)
sysfault("mkfile: open fault shrfile='%s' -- %sn",
shrfile,strerror(errno));
char buf[1];
buf[0] = 0;
for (u32 off = 0;  off < len;  ++off)
write(fd,buf,1);
close(fd);
}
void
mapall(void)
{
u32 curoff;
seg_t *seg;
void *oldbase = NULL;
fdshr = open(shrfile,O_RDWR);
shrbase = NULL;
allbase = NULL;
for (SEGALL) {
errno = 0;
seg->seg_base = mmap(oldbase,seg->seg_len,PROT_READ | PROT_WRITE,
seg->seg_shr ? MAP_SHARED : MAP_PRIVATE,fdshr,seg->seg_off);
prt("dochild: MAP seg_idx=%d oldbase=%p seg_base=%p seg_len=%4.4X -- %sn",
seg->seg_idx,oldbase,seg->seg_base,seg->seg_len,strerror(errno));
if (seg->seg_base == MAP_FAILED)
sysfault("dochild: FAILn");
oldbase = seg->seg_base + seg->seg_len;
// remember the shared address
if (seg->seg_shr)
shrbase = seg->seg_base;
// remember the start address
if (allbase == NULL)
allbase = seg->seg_base;
}
if (shrbase == NULL)
sysfault("mapall: null shrbasen");
// single main that our setup is done
*shrbase = 1;
}
void
dochild(int idx)
{
seg_t *seg;
pidxid = idx;
mapall();
sleep(2);
// fill the _entire_ buffer with our ID
prt("dochild: FILLn");
memset(allbase + 1,pidxid + 1,totlen - 1);
prt("dochild: UNMAPn");
for (SEGALL)
munmap(seg->seg_base,seg->seg_len);
close(fdshr);
}
int
main(void)
{
pid_t pid;
seg_t *seg;
tsczero = tscgetf();
setlinebuf(stdout);
pidxid = 0;
// get total size
totlen = 0;
for (SEGALL)
totlen += seg->seg_len;
// create the file
mkfile(shrfile,totlen);
// point to shared segment
seg = &seglist[1];
for (int idx = 0;  idx <= 3;  ++idx) {
if (idx == 0) {
mapall();
continue;
}
*shrbase = 0;
pid = fork();
if (pid == 0) {
dochild(idx);
continue;
}
// wait for child to complete setup
while (*shrbase == 0)
usleep(100);
}
while (1) {
pid = wait(NULL);
if (pid < 0)
break;
prt("main: reapedn");
}
return 0;
}

这是程序输出:

[0.000008308 0] mkfile: shrfile='share.mem' len=8000
[0.027699569 0] dochild: MAP seg_idx=0 oldbase=(nil) seg_base=0x7f8d7d41b000 seg_len=1000 -- Success
[0.027718086 0] dochild: MAP seg_idx=1 oldbase=0x7f8d7d41c000 seg_base=0x7f8d7d41a000 seg_len=1000 -- Success
[0.027725139 0] dochild: MAP seg_idx=2 oldbase=0x7f8d7d41b000 seg_base=0x7f8d7d414000 seg_len=6000 -- Success
[0.027908063 1] dochild: MAP seg_idx=0 oldbase=(nil) seg_base=0x7f8d7d413000 seg_len=1000 -- Success
[0.027958351 1] dochild: MAP seg_idx=1 oldbase=0x7f8d7d414000 seg_base=0x7f8d7d412000 seg_len=1000 -- Success
[0.027965133 1] dochild: MAP seg_idx=2 oldbase=0x7f8d7d413000 seg_base=0x7f8d7d40c000 seg_len=6000 -- Success
[0.028166477 2] dochild: MAP seg_idx=0 oldbase=(nil) seg_base=0x7f8d7d413000 seg_len=1000 -- Success
[0.028225279 2] dochild: MAP seg_idx=1 oldbase=0x7f8d7d414000 seg_base=0x7f8d7d412000 seg_len=1000 -- Success
[0.028235211 2] dochild: MAP seg_idx=2 oldbase=0x7f8d7d413000 seg_base=0x7f8d7d40c000 seg_len=6000 -- Success
[0.028478286 3] dochild: MAP seg_idx=0 oldbase=(nil) seg_base=0x7f8d7d413000 seg_len=1000 -- Success
[0.028522598 3] dochild: MAP seg_idx=1 oldbase=0x7f8d7d414000 seg_base=0x7f8d7d412000 seg_len=1000 -- Success
[0.028529647 3] dochild: MAP seg_idx=2 oldbase=0x7f8d7d413000 seg_base=0x7f8d7d40c000 seg_len=6000 -- Success
[2.028197225 1] dochild: FILL
[2.028475924 2] dochild: FILL
[2.028713112 3] dochild: FILL
[2.355625517 0] main: reaped
[2.355667716 0] main: reaped
[2.356288144 0] main: reaped

这是文件的转储。注意;私人的";区域保持为0,即使子进程在那里写入它们自己的非零ID。

00000000: 00000000 00000000 00000000 00000000  ................
*
00002000: 01000000 00000000 00000000 00000000  ................
00002010: 00000000 00000000 00000000 00000000  ................
*
00002fc0: 04040404 04040404 04040404 04040404  ................
00002fd0: 04040404 04040404 04040404 04040404  ................
00002fe0: 04040404 04040404 04040404 04040404  ................
00002ff0: 04040404 04040404 04040404 04040404  ................
00003000: 00000000 00000000 00000000 00000000  ................
*
00007ff0: 00000000 00000000 00000000 00000000  ................

最新更新