mmap函数

1.内存映射方式来修改文件中的记录

1)解释部分

这段代码主要演示了如何使用内存映射方式来修改文件中的记录。具体来说,它的实现过程如下:

  1. 定义了一个存放记录的结构体 RECORD,其中包含了记录的编号和内容。
  2. 创建了一个文件 records.dat,并在其中写入测试数据。这些数据包含了50条记录,每条记录都有一个编号和内容,编号从0到49,内容为 No.0No.49
  3. 使用传统的方式来修改文件中的记录。具体来说,先打开文件,并将文件指针定位到第10条记录的位置。然后从文件中读取该记录的内容,修改它的编号和内容,并将修改后的内容写回到文件中。最后关闭文件。
  4. 使用内存映射方式来修改文件中的记录。具体来说,先打开文件,并使用 mmap 函数将文件映射到内存中。然后直接在内存中修改第10条记录的内容,并使用 msync 函数将修改后的内容异步写回到文件中。最后使用 munmap 函数释放映射的内存段,并关闭文件。

需要注意的是,内存映射方式的修改过程相对来说更加高效,因为它不需要频繁地进行文件读写操作,而是直接在内存中修改数据。此外,由于内存映射方式可以将文件映射到多个进程的内存空间中,因此多个进程可以同时访问同一个文件,从而实现进程间的通信。

2)代码区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdlib.h>

//定义存放记录的结构体

typedef struct
{
int index; //编号

char text[10]; //内容

} RECORD;

#define SIZE (50)
#define EDIT_INDEX (10)

int main(void)
{
RECORD record, *p_mapped_memory_addr;
int i, fd;
FILE *fp;

//创建文件并写入测试数据

fp = fopen("records.dat", "w+");
for (i = 0; i < SIZE; i++)
{
record.index = i;
sprintf(record.text, "No.%d", i);
fwrite(&record, sizeof(record), 1, fp);//因为字节序对齐,在32位机上,sizeof(record)=16,并不是14。

}
fclose(fp);
printf("Ok, write %d records to the file: records.dat ./n", SIZE);

//将第一30条记录编号修改为300,并相应地修改其内容。

//采用传统方式

fp = fopen("records.dat", "r+");
fseek(fp, EDIT_INDEX * sizeof(record), SEEK_SET);
fread(&record, sizeof(record), 1, fp);

record.index = EDIT_INDEX*10;
sprintf(record.text, "No.%d", record.index);

fseek(fp, EDIT_INDEX * sizeof(record), SEEK_SET);
fwrite(&record, sizeof(record), 1, fp);
fclose(fp);
printf("Ok, edit the file of records.dat using traditional method./n");

//同样的修改,这次使用内存映射方式。

//将记录映射到内存中

fd = open("records.dat", O_RDWR);
p_mapped_memory_addr = (RECORD *)mmap(0, SIZE * sizeof(record), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
//修改数据

p_mapped_memory_addr[EDIT_INDEX].index = EDIT_INDEX*10;
sprintf(p_mapped_memory_addr[EDIT_INDEX].text, "No.%d",
p_mapped_memory_addr[EDIT_INDEX].index);

/* Synchronize the region starting at ADDR and extending LEN bytes with the
file it maps. Filesystem operations on a file being mapped are
unpredictable before this is done. Flags are from the MS_* set.

This function is a cancellation point and therefore not marked with
__THROW. extern int msync (void *__addr, size_t __len, int __flags);
*/
//将修改写回映射文件中(采用异步写方式)

msync((void *)p_mapped_memory_addr, SIZE * sizeof(record), MS_ASYNC);
/* Deallocate any mapping for the region starting at ADDR and extending LEN
bytes. Returns 0 if successful, -1 for errors (and sets errno).
extern int munmap (void *__addr, size_t __len) __THROW;
*/
//释放内存段

munmap((void *)p_mapped_memory_addr, SIZE * sizeof(record));
printf("Ok, edit the file of records.dat using mmap method./n");

//关闭文件
close(fd);
return 0;
}

3)运行结果

image-20230424152151058


2.使用内存映射的方式来操作一个文件

1)解释部分

这段代码主要演示了如何使用内存映射的方式来操作一个文件,将文件映射到内存中,然后读取或写入数据。

map_normalfile1.c:

  1. 定义了一个结构体 people,它包含一个名字和一个年龄。
  2. 打开一个文件,使用 lseek 函数将文件大小扩展到 people 结构体的大小的 5 倍,然后将指针移到文件末尾,写入一个空字符,这样就创建了一个大小为 people 结构体大小的 5 倍的文件。
  3. 使用 mmap 函数将该文件映射到内存中,映射的大小为 people 结构体大小的 10 倍。
  4. 使用 memcpy 函数将每个 people 结构体的名字设置为一个字母,然后将年龄依次设置为 20 到 29。
  5. 程序等待 10 秒钟,然后使用 munmap 函数释放内存映射,并输出提示信息。

map_normalfile2.c:

  1. 定义了一个结构体 people,它包含一个名字和一个年龄。
  2. 打开一个文件,使用 mmap 函数将该文件映射到内存中,映射的大小为 people 结构体大小的 10 倍。
  3. 循环遍历每个 people 结构体,输出它的名字和年龄。
  4. 使用 munmap 函数释放内存映射。

这两段代码可以一起使用,先运行 map_normalfile1.c 将数据写入文件并映射到内存中,然后运行 map_normalfile2.c 从内存中读取数据并输出。通过这种方式,可以实现进程间的数据共享和通信。

2)代码区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/*-------------map_normalfile1.c-----------*/
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
char name[4];
int age;
} people;

int main(int argc, char** argv) {
int i;
people *p_map;
char temp;
p_map = (people*) mmap(NULL, sizeof(people)*10, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (fork() == 0) {
sleep(2);
for (i = 0; i < 5; i++) {
printf("child read: the %d people's age is %d\n", i + 1, (*(p_map + i)).age);
}
(*p_map).age = 100;
munmap(p_map, sizeof(people)*10);
exit(0);
}
temp = 'a';
for (i = 0; i < 5; i++) {
temp += 1;
memcpy((*(p_map + i)).name, &temp, 1);
(*(p_map + i)).age = 20 + i;
}

sleep(5);
printf("parent read: the first people's age is %d\n", (*p_map).age);
printf("unmap\n");
munmap(p_map, sizeof(people)*10);
printf("unmap ok\n");

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/-------------map_normalfile2.c-----------/
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h> // Added for printf()

typedef struct {
char name[4];
int age; // Added missing type specifier
} people;

int main(int argc, char** argv) {
int fd, i;
people *p_map;

fd = open(argv[1], O_CREAT | O_RDWR, 00777);
p_map = (people*) mmap(NULL, sizeof(people) * 10, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

for (i = 0; i < 10; i++) { // Fixed loop condition
printf("name: %s age %d;\n", (*(p_map+i)).name, (*(p_map+i)).age);
}

munmap(p_map, sizeof(people) * 10); // Fixed munmap() call

return 0;
}


3)运行结果

image-20230424152237008


3.父子进程通过匿名映射实现共享内存

1)解释部分

这段代码演示了如何使用内存映射实现进程间通信,父进程和子进程可以通过共享内存来交换数据。

  1. 定义了一个结构体 people,它包含一个名字和一个年龄。
  2. 使用 mmap 函数将一块共享内存映射到进程的地址空间中。这个共享内存的大小为 people 结构体大小的 10 倍,标识为 MAP_SHARED | MAP_ANONYMOUS,表示该内存区域是可共享的,并且没有关联到任何文件。
  3. 创建一个子进程,子进程在两秒钟后读取共享内存中的前五个 people 结构体数据,并更新第一个 people 结构体的年龄。然后释放共享内存并退出子进程。
  4. 父进程循环遍历前五个 people 结构体,将每个 people 的名字设置为递增的字母,并将年龄设置为 20 到 24。然后等待 5 秒钟后读取共享内存中的第一个 people 结构体,并输出它的年龄。最后释放共享内存并退出父进程。

通过共享内存来传递数据,可以避免复制数据的开销,提高程序的效率。但需要注意的是,在共享内存中操作数据时需要考虑同步和互斥,否则可能会发生数据竞争和死锁等问题。

2)代码区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h> // Added for printf()
#include <stdlib.h> // Added for exit()
#include <string.h> // Added for memcpy()

typedef struct {
char name[4];
int age;
} people;

int main(int argc, char** argv) {
int i;
people *p_map;
char temp = 'a';

p_map = (people*) mmap(NULL, sizeof(people) * 10, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (p_map == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}

pid_t pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
} else if (pid == 0) { // child process
sleep(2);
for (i = 0; i < 5; i++) {
printf("child read: the %d people's age is %d\n", i + 1, (*(p_map + i)).age);
}
(*p_map).age = 100;
munmap(p_map, sizeof(people) * 10);
exit(EXIT_SUCCESS);
} else { // parent process
for (i = 0; i < 5; i++) {
temp++;
memcpy((*(p_map + i)).name, &temp, 1);
(*(p_map + i)).age = 20 + i;
}
sleep(5);
printf("parent read: the first people's age is %d\n", (*p_map).age);
printf("umap\n");
munmap(p_map, sizeof(people) * 10);
printf("umap ok\n");
exit(EXIT_SUCCESS);
}
}

3)运行结果

image-20230424152029852

4.对mmap返回地址访问

1)解释部分

这段代码演示了如何使用 mmap() 函数创建一个映射文件,并在其中进行页级别的内存操作。

  1. 定义了一个结构体 people,它包含一个名字和一个年龄。
  2. 使用 sysconf() 函数获取操作系统的页面大小,并输出。
  3. 使用 open() 函数创建一个新的文件,设置 O_CREAT 标志表示如果文件不存在就创建它,设置 O_RDWR 标志表示文件可读可写,设置 O_TRUNC 标志表示清空文件内容。
  4. 使用 lseek() 函数设置文件偏移量为两页大小减去 100,然后使用 write() 函数写入一个字节的空字符,以扩展文件大小为两页。
  5. off 的值设置为一个页大小,表示将映射文件的第一个页保留不使用,而将后面的两页用于内存映射。
  6. 使用 mmap() 函数将映射文件的第二个页和第三个页映射到当前进程的地址空间中,并返回一个指向 people 结构体数组的指针 p_map。设置 MAP_SHARED 标志表示这是一个共享映射,即多个进程可以共同访问该映射区域。
  7. 在一个循环中,对于每两个页,分别访问该页的倒数第二个元素、倒数第一个元素和下一个页的第一个元素,并将它们的年龄设置为 100。在访问每个元素之后,程序会输出一条提示信息。
  8. 最后,使用 munmap() 函数释放映射内存,并关闭文件句柄。

该程序展示了如何使用 mmap() 函数将一个文件映射到内存中,并对映射内存进行操作。在这个例子中,程序通过映射文件的第二个页和第三个页,实现了页级别的内存操作,可以方便地进行内存分页相关的实验和研究。

2)代码区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

typedef struct {
char name[4];
int age;
} people;

int main(int argc, char** argv) {
int fd, i, pagesize, off;
people *p_map;

pagesize = sysconf(_SC_PAGESIZE);
printf("pagesize is %d\n", pagesize);

fd = open(argv[1], O_CREAT | O_RDWR | O_TRUNC, 00777);
lseek(fd, pagesize * 2 - 100, SEEK_SET);
write(fd, "", 1);
off = pagesize; // 将 off 的值设置为 pagesize,使其指向第二个页的开始位置,即第一个页的末尾

p_map = (people*) mmap(NULL, pagesize * 3, PROT_READ | PROT_WRITE, MAP_SHARED, fd, off);
close(fd);

for (i = 0; i < 9; i += 2) {
int index1 = (pagesize / sizeof(people)) * i + pagesize / sizeof(people) - 2;
int index2 = (pagesize / sizeof(people)) * i + pagesize / sizeof(people) - 1;
int index3 = (pagesize / sizeof(people)) * (i + 1);
p_map[index1].age = 100;
printf("access page %d over\n", i + 1);
p_map[index2].age = 100;
printf("access page %d edge over, now begin to access page %d\n", i + 1, i + 2);
p_map[index3].age = 100;
printf("access page %d over\n", i + 2);
}

munmap(p_map, pagesize * 3 / sizeof(people));
return 0;
}

mmap函数
https://ahaostillcoding.github.io/2023/04/24/mmap函数/
作者
a_hao
发布于
2023年4月24日
许可协议