Linux/AndroidOS中进程间的通信线程间的同步 - 内存映射
前言
如何使用 mmap()系统调用来创建内存映射。内存映射可用于 IPC 以及其他很多方面。
1 概述
mmap()系统调用在调用进程的虚拟地址空间中创建一个新内存映射。映射分为两种。
- 文件映射:文件映射将一个文件的一部分直接映射到调用进程的虚拟内存中。一旦一个文件被映射之后就可以通过在相应的内存区域中操作字节来访问文件内容了。映射的分页会在需要的时候从文件中(自动)加载。这种映射也被称为基于文件的映射或内存映射文件。
- 匿名映射:一个匿名映射没有对应的文件。相反,这种映射的分页会被初始化为 0。
一个进程的映射中的内存可以与其他进程中的映射共享(即各个进程的页表条目指向RAM 中相同分页)。这种行为会在两种情况下发生。
- 当两个进程映射了一个文件的同一个区域时它们会共享物理内存的相同分页。
- 通过 fork()创建的子进程会继承其父进程的映射的副本,并且这些映射所引用的物理内存分页与父进程中相应映射所引用的分页相同。
当两个或更多个进程共享相同分页时,每个进程都有可能会看到其他进程对分页内容做出的变更,当然这要取决于映射是私有的还是共享的。
- 私有映射(MAP_PRIVATE):在映射内容上发生的变更对其他进程不可见,对于文件映射来讲,变更将不会在底层文件上进行。尽管一个私有映射的分页在上面介绍的情况中初始时是共享的,但对映射内容所做出的变更对各个进程来讲则是私有的。内核使用了写时复制(copy-on-write)技术完成了这个任务。这意味着每当一个进程试图修改一个分页的内容时,内核首先会为该进程创建一个新分页并将需修改的分页中的内容复制到新分页中(以及调整进程的页表)。正因为这个原因,MAP_PRIVATE 映射有时候会被称为私有、写时复制映射。
- 共享映射(MAP_SHARED):在映射内容上发生的变更对所有共享同一个映射的其他进程都可见,对于文件映射来讲,变更将会发生在底层的文件上。
上面介绍的两个映射特性(文件与匿名以及私有和共享)可以以四种不同的方式加以组合,下表对此进行了总结。
这四种不同的内存映射的创建和使用方式如下所述。
- 私有文件映射:映射的内容被初始化为一个文件区域中的内容。多个映射同一个文件的进程初始时会共享同样的内存物理分页,但系统使用写时复制技术使得一个进程对映射所做出的变更对其他进程不可见。这种映射的主要用途是使用一个文件的内容来初始化一块内存区域。一些常见的例子包括根据二进制可执行文件或共享库文件的相应部分来初始化一个进程的文本和数据段。
- 私有匿名映射:每次调用 mmap()创建一个私有匿名映射时都会产生一个新映射,该映射与同一(或不同)进程创建的其他匿名映射是不同的(即不会共享物理分页)。尽管子进程会继承其父进程的映射,但写时复制语义确保在 fork()之后父进程和子进程不会看到其他进程对映射所做出的变更。私有匿名映射的主要用途是为一个进程分配新(用零填充)内存(如在分配大块内存时 malloc()会为此而使用mmap())。
- 共享文件映射:所有映射一个文件的同一区域的进程会共享同样的内存物理分页,这些分页的内容将被初始化为该文件区域。对映射内容的修改将直接在文件中进行。这种映射主要用于两个用途。第一种用途,它允许内存映射 I/O,这表示一个文件会被加载到进程的虚拟内存中的一个区域中并且对该块内容的变更会自动被写入到这个文件中。因此,内存映射 I/O 为使用 read()和 write()来执行文件 I/O 这种做法提供了一种替代方案。第二种用途是允许无关进程共享一块内容以便以一种共享内存段的方式来执行(快速)IPC。
- 共享匿名映射:与私有匿名映射一样,每次调用 mmap()创建一个共享匿名映射时都会产生一个新的、与任何其他映射不共享分页的截然不同的映射。这里的差别在于映射的分页不会被写时复制。这意味着当一个子进程在 fork()之后继承映射时,父进程和子进程共享同样的 RAM 分页,并且一个进程对映射内容所做出的变更会对其他进程可见。共享匿名映射允许以一种类似于 System V 共享内存段的方式来进行 IPC,但只有相关进程之间才能这么做。
2 创建一个映射:mmap()
2.1 函数参数
mmap()系统调用在调用进程的虚拟地址空间中创建一个新映射。
#include<sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
/* Returns starting address of mapping on success, Or MAP_FAILED on error */
-
addr 参数指定了映射被放置的虚拟地址。如果将 addr 指定为 NULL,那么内核会为映射选择一个合适的地址。这是创建映射的首选做法。或者在 addr 中指定一个非 NULL 值时,内核会在选择将映射放置在何处时将这个参数值作为一个提示信息来处理。在实践中,内核至少会将指定的地址舍入到最近的一个分页边界处。不管采用何种方式,内核会选择一个不与任何既有映射冲突的地址。(如果在 flags 包含了 MAP_FIXED,那么 addr 必须是分页对齐的。
成功时 mmap()会返回新映射的起始地址。发生错误时 mmap()会返回 MAP_FAILED。 -
length 参数指定了映射的字节数。尽管 length 无需是一个系统分页大小的倍数,但内核会以分页大小为单位来创建映射,因此实际上 length 会被向上提升为分页大小的下一个倍数。
-
prot 参数是一个位掩码,它指定了施加于映射之上的保护信息,其取值要么是PROT_NONE,要么是下表中列出的其他三个标记的组合(取 OR)。
-
flags 参数是一个控制映射操作各个方面的选项的位掩码。这个掩码必须只包含下列值中一个。
MAP_PRIVATE 创建一个私有映射。区域中内容上所发生的变更对使用同一映射的其他进程是不可见的,对于文件映射来讲,所发生的变更将不会反应在底层文件上。
MAP_SHARED 创建一个共享映射。区域中内容上所发生的变更对使用 MAP_SHARED 特性映射同一区域的进程是可见的,对于文件映射来讲,所发生的变更将直接反应在底层文件上。对文件的更新将无法确保立即生效,具体可参加 49.5 节中对 msync()系统调用的介绍。
除了 MAP_PRIVATE 和 MAP_SHARED 之外,在 flags 中还可以有选择地对其他标记取OR。在第6 和 10 节中将会对这些标记进行介绍。 -
剩余的参数 fd 和 offset 是用于文件映射的(匿名映射将忽略它们)。fd 参数是一个标识被映射的文件的文件描述符。offset 参数指定了映射在文件中的起点,它必须是系统分页大小的倍数。要映射整个文件就需要将 offset 指定为 0 并且将 length 指定为文件大小。在第5节中将会介绍更多有关文件映射的内容。
2.2 示例程序
程序中演示了如何使用 mmap()来创建一个私有文件映射。这个程序是一个简单版本的 cat(1),它将映射通过命令行参数指定的(整个)文件,然后将映射中的内容写入到标准输出中。
/*
mmap/mmcat.cUse mmap() plus write() to display the contents of a file (specifiedas a command-line argument) on standard output.
*/#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "tlpi_hdr.h"int
main(int argc, char *argv[])
{char *addr;int fd;struct stat sb;if (argc != 2 || strcmp(argv[1], "--help") == 0)usageErr("%s file\n", argv[0]);fd = open(argv[1], O_RDONLY);if (fd == -1)errExit("open");/* Obtain the size of the file and use it to specify the size ofthe mapping and the size of the buffer to be written */if (fstat(fd, &sb) == -1)errExit("fstat");/* Handle zero-length file specially, since specifying a size ofzero to mmap() will fail with the error EINVAL */if (sb.st_size == 0)exit(EXIT_SUCCESS);addr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);if (addr == MAP_FAILED)errExit("mmap");if (write(STDOUT_FILENO, addr, sb.st_size) != sb.st_size)fatal("partial/failed write");exit(EXIT_SUCCESS);
}
3 解除映射区域:munmap()
munmap()系统调用执行与 mmap()相反的操作,即从调用进程的虚拟地址空间中删除一个映射。
#include <sys/mman.h>
int munmap(void *addr, size_t length); //return 0 on success, or -1 on error.
- addr 参数是待解除映射的地址范围的起始地址,它必须与一个分页边界对齐。
- length 参数是一个非负整数,它指定了待解除映射区域的大小(字节数)。范围为系统分页大小的下一个倍数的地址空间将会被解除映射。
一般来讲通常会解除整个映射。因此可以将 addr 指定为上一个 mmap()调用返回的地址,并且 length 的值与 mmap()调用中使用的 length 的值一样。下面是一个例子。
addr = map(NULL, length, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
if(addr == MAP FAILED)errExit("mmap");
/* Code for working with mapped region */
if(munmap(addr,length)==-1)errExit(“munmap");
- 也可以解除一个映射中的部分映射,这样原来的映射要么会收缩,要么会被分成两个,这取决于在何处开始解除映射。还可以指定一个跨越多个映射的地址范围,这样的话所有在范围内的映射都会被解除。
- 如果在由 addr 和 length 指定的地址范围中不存在映射,那么 munmap()将不起任何作用并返回 0(表示成功)。
- 在解除映射期间,内核会删除进程持有的在指定地址范围内的所有内存锁。(内存锁是通过 mlock()或 mlockall()来建立的)
- 当一个进程终止或执行了一个 exec()之后进程中所有的映射会自动被解除。
- 为确保一个共享文件映射的内容会被写入到底层文件中,在使用 munmap()解除一个映射之前需要调用 msync()。
4 文件映射
要创建一个文件映射需要执行下面的步骤。
- 获取文件的一个描述符,通常通过调用 open()来完成。
- 将文件描述符作为 fd 参数传入 mmap()调用。
执行上述步骤之后 mmap()会将打开的文件的内容映射到调用进程的地址空间中。一旦mmap()被调用之后就能够关闭文件描述符了,而不会对映射产生任何影响。
除了普通的磁盘文件,使用 mmap()还能够映射各种真实和虚拟设备的内容,如硬盘、光盘以及/dev/mem。
在打开描述符 fd 引用的文件时必须要具备与 prot 和 flags 参数值匹配的权限。特别地,文件必须总是被打开以允许读取,并且如果在 flags 中指定了 PROT_WRITE 和 MAP_SHARED,那么文件必须总是被打开以允许读取和写入。
offset 参数指定了从文件区域中的哪个字节开始映射,它必须是系统分页大小的倍数。将offset 指定为 0 会导致从文件的起始位置开始映射。length 参数指定了映射的字节数。offset和 length 参数一起确定了文件的哪个区域会被映射进内存,如下图所示。
4.1 私有文件映射
私有文件映射最常见的两个用途如下所述。
- 允许多个执行同一个程序或使用同一个共享库的进程共享同样的(只读的)文本段,它是从底层可执行文件或库文件的相应部分映射而来的。
- 映射一个可执行文件或共享库的初始化数据段。这种映射会被处理成私有使得对映射数据段内容的变更不会发生在底层文件上。
mmap()的这两种用法通常对程序是不可见的,因为这些映射是由程序加载器和动态链接器创建的。可以在/proc/PID/maps 的输出中发现这两种映射。
4.2 共享文件映射
当多个进程创建了同一个文件区域的共享映射时,它们会共享同样的内存物理分页。此外,对映射内容的变更将会反应到文件上。实际上,这个文件被当成了该块内存区域的分页存储,如下图所示。(这幅图是简化过的,它并没有指出映射分页在物理内存中通常是不连续的这样一个事实。)
共享文件映射存在两个用途:内存映射 I/O 和 IPC。下面将分别介绍这两种用途。
4.2.1 内存映射I/O
由于共享文件映射中的内容是从文件初始化而来的,并且对映射内容所做出的变更都会自动反应到文件上,因此可以简单地通过访问内存中的字节来执行文件 I/O,而依靠内核来确保对内存的变更会被传递到映射文件中。(一般来讲,一个程序会定义一个结构化数据类型来与磁盘文件中的内容对应起来,然后使用该数据类型来转换映射的内容。)这项技术被称为内存映射 I/O,它是使用 read()和 write()来访问文件内容这种方法的替代方案。
内存映射 I/O 具备两个潜在的优势。
- 使用内存访问来取代 read()和 write()系统调用能够简化一些应用程序的逻辑。
- 在一些情况下,它能够比使用传统的 I/O 系统调用执行文件 I/O 这种做法提供更好的性能。
内存映射 I/O 之所以能够带来性能优势的原因如下。
- 正常的 read()或 write()需要两次传输:一次是在文件和内核高速缓冲区之间,另一次是在高速缓冲区和用户空间缓冲区之间。使用 mmap()就无需第二次传输了。对于输入来讲,一旦内核将相应的文件块映射进内存之后用户进程就能够使用这些数据了。对于输出来讲,用户进程仅仅需要修改内存中的内容,然后可以依靠内核内存管理器来自动更新底层的文件。
- mmap()还能够通过减少所需使用的内存来提升性能。当使用 read()或 write()时,数据将被保存在两个缓冲区中:一个位于用户空间,另一个位于内核空间。当使用 mmap()时,内核空间和用户空间会共享同一个缓冲区。此外,如果多个进程正在在同一个文件上执行 I/O,那么它们通过使用 mmap()就能够共享同一个内核缓冲区,从而又能够节省内存的消耗。
内存映射 I/O 所带来的性能优势在在大型文件中执行重复随机访问时最有可能体现出来。如果顺序地访问一个文件,并假设执行 I/O 时使用的缓冲区大小足够大以至于能够避免执行大量的 I/O 系统调用,那么与 read()和 write()相比,mmap()带来的性能上的提升就非常有限或者说根本就没有带来性能上的提升。性能提升的幅度之所以非常有限的原因是不管使用何种技术,整个文件的内容在磁盘和内存之间只传输一次,效率的提高主要得益于减少了用户空间和内核空间之间的一次数据传输,并且与磁盘 I/O 所需的时间相比,内存使用量的降低通常是可以忽略的。
内存映射 I/O 也有一些缺点。对于小数据量 I/O 来讲,内存映射 I/O 的开销(即映射、分页故障、解除映射以及更新硬件内存管理单元的超前转换缓冲器)实际上要比简单的read()或 write()大。此外,有些时候内核难以高效地处理可写入映射的回写(在这种情况下,使用 msync()或 sync_file_range()有助于提高效率)。
4.2.2 使用共享文件映射的 IPC
由于所有使用同样文件区域的共享映射的进程共享同样的内存物理分页,因此共享文件映射的第二个用途是作为一种(快速的)IPC 方法。这种特性对那些需要共享内存内容在应用程序或系统重启时能够持久化的应用程序来讲是非常有用的。
示例程序
程序清单 49-2 提供了一个简单的例子来演示如何使用 mmap()创建一个共享文件映射。这个程序首先映射一个名称通过第一个命令行参数指定的文件,然后打印出映射区域起始位置的字符串值。最后,如果提供了第二个命令行参数,那么该字符串会被复制进共享内存区域中。
下面的 shell 会话日志演示了如何使用这个程序。下面首先创建了一个大小为 1024 字节的文件并在其中填满零。
$ dd if=/dev/zero of=s.txt bs=1 count=1024
1024+0 records in
1024+0 records out
1024 bytes (1.0 kB, 1.0 KiB) copied, 0.00764544 s, 134 kB/s
然后使用程序映射这个文件并将一个字符串复制进映射区域中。
$ ./t_mmap s.txt hello
Current string=
Copied "hello" to shared memory
程序在打印当前字符串时不会显示任何内容,因为映射文件的初始值是以 null 字节打头的(即长度为零的字符串)。
接着再次使用程序映射这个文件并复制一个新字符串到映射区域中。
$ ./t_mmap s.txt goodbye
Current string=hello
Copied "goodbye" to shared memory
最后通过输出文件的内容来对其中的内容进行验证,每行显示了 8 个字符。
$ od -c -w8 s.txt
0000000 g o o d b y e \0
0000010 \0 \0 \0 \0 \0 \0 \0 \0
*
0002000
这个简单的程序没有使用任何机制来同步多个进程对映射文件的访问。但现实世界中的应用程序通常需要同步对共享映射的访问。这可以通过使用各种技术来完成,包括信号量和文件加锁。
/*
mmap/t_mmap.c
Demonstrate the use of mmap() to create a shared file mapping.
*/
#include <sys/mman.h>
#include <fcntl.h>
#include "tlpi_hdr.h"#define MEM_SIZE 10int
main(int argc, char *argv[])
{char *addr;int fd;if (argc < 2 || strcmp(argv[1], "--help") == 0)usageErr("%s file [new-value]\n", argv[0]);fd = open(argv[1], O_RDWR);if (fd == -1)errExit("open");addr = mmap(NULL, MEM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (addr == MAP_FAILED)errExit("mmap");if (close(fd) == -1) /* No longer need 'fd' */errExit("close");printf("Current string=%.*s\n", MEM_SIZE, addr);/* Secure practice: output at most MEM_SIZE bytes */if (argc > 2) { /* Update contents of region */if (strlen(argv[2]) >= MEM_SIZE)cmdLineErr("'new-value' too large\n");memset(addr, 0, MEM_SIZE); /* Zero out region */strncpy(addr, argv[2], MEM_SIZE - 1);if (msync(addr, MEM_SIZE, MS_SYNC) == -1)errExit("msync");printf("Copied \"%s\" to shared memory\n", argv[2]);}exit(EXIT_SUCCESS);
}
4.2.3 边界情况
在很多情况下,一个映射的大小是系统分页大小的整数倍,并且映射会完全落入映射文件的范围之内。下面来看一下当这些条件不满足时会发生什么事情。
下图描绘了映射完全落入映射文件的范围之内但区域的大小并不是系统分页大小的一个整数倍的情况(在这个讨论中假设分页大小为 4KB)。
由于映射的大小不是系统分页大小的整数倍,因此它会被向上舍入到系统分页大小的下一个整数倍。由于文件的大小要大于这个被向上舍入的大小,因此文件中对应字节会像图中那样被映射。
试图访问映射结尾之外的字节将会导致 SIGSEGV 信号的产生(假设在该位置处不存在其他映射)。这个信号的默认动作是终止进程并打印出一个 core dump。
当映射扩充过了底层文件的结尾处时(参见下图)情况就变得更加复杂了。与之前一样,由于映射的大小不是系统分页大小的整数倍,因此它会被向上舍入。但在这种情况下,虽然在向上舍入区域(即图中 2200 字节和 4095 字节)中的字节是可访问的,但它们不会被映射到底层文件上(由于在文件中不存在对应的字节),并且它们会被初始化为 0。当然,这些字节也不会与映射同一个文件的其他进程共享,即使它们指定了足够大的 length 参数。对这些字节做出的变更不会被写入到文件中。
如果映射中包含了超出向上舍入区域中(即上图中 4096 以及之后的字节)的分页,那么试图访问这些分页中的地址将会导致 SIGBUS 信号量的产生,即警告进程文件中没有区域与这些地址对应。与之前一样,试图访问超过映射结尾处的地址将会导致 SIGSEGV 信号的产生。
从上面的描述中可以看出,创建一个大小超过底层文件大小的映射可能是无意义的。但通过扩展文件的大小(如使用 ftruncate()或 write()),可以使得这种映射中之前不可访问的部分变得可用。
4.2.4 内存保护和文件访问模式交互
到目前为止还没有详细解释的一点是通过 mmap() prot 参数指定的内存保护与映射文件被打开的模式之间的交互。从一般原则来讲,PROT_READ 和 PROT_EXEC 保护要求被映射的文件使用 O_RDONLY 或 O_RDWR 打开,而 PROT_WRITE 保护要求被映射的文件使用O_WRONLY 或 O_RDWR 打开。
然而,由于一些硬件架构提供的内存保护粒度有限,因此情况会变得复杂起来。对于这种架构,下列结论是适用的。
- 所有内存保护组合与使用 O_RDWR 标记打开文件是兼容的。
- 没有内存保护组合——哪怕仅仅是 PROT_WRITE——与使用 O_WRONLY 标记打开的文件是兼容的(导致 EACCES 错误的发生)。这与一些硬件架构不允许对一个分页的只写访问这样一个事实是一致的。
- 使用 O_RDONLY 标记打开一个文件的结果依赖于在调用 mmap()时是否指定了MAP_PRIVATE 或 MAP_SHARED。对于一个 MAP_PRIVATE 映射来讲,在 mmap()中可以指定任意的内存保护组合——因为对MAP_PRIVATE分页内容做出的变更不会被写入到文件中,因此无法写入文件不会成为问题。对于一个 MAP_SHARED 映射来讲,唯一与 O_RDONLY 兼容的内存保护是 PROT_REA 和 (PROT_READ | PROT_EXEC)。这是符合逻辑的,因为一个 PROT_WRITE, MAP_SHARED 映射允许更新被映射的文件。
5 同步映射区域:msync()
内核会自动将发生在 MAP_SHARED 映射内容上的变更写入到底层文件中,但在默认情况下,内核不保证这种同步操作会在何时发生。
msync()系统调用让应用程序能够显式地控制何时完成共享映射与映射文件之间的同步。同步一个映射与底层文件在多种情况下都是非常有用的。如,为确保数据完整性,一个数据库应用程序可能会调用 msync()强制将数据写入到磁盘上。调用 msync() 还允许一个应用程序确保在可写入映射上发生的更新会对在该文件上执行 read()的其他进程可见。
#include <sys/mman.h>
int msync(void *addr, size_t length, int flags);
传给 msync()的 addr 和 length 参数指定了需同步的内存区域的起始地址和大小。在 addr中指定的地址必须是分页对齐的,length 会被向上舍入到系统分页大小的下一个整数倍。
flags 参数的可取值为下列值中的一个:
- MS_SYNC
执行一个同步的文件写入。这个调用会阻塞直到内存区域中所有被修改过的分页被写入到底盘为止。 - MS_ASYNC
执行一个异步的文件写入。内存区域中被修改过的分页会在后面某个时刻被写入磁盘并立即对在相应文件区域中执行 read()的其他进程可见。
另一种区分这两个值的方式可以表述为在 MS_SYNC 操作之后,内存区域会与磁盘同步,而在 MS_ASYNC 操作之后,内存区域仅仅是与内核高速缓冲区同步。 - MS_INVALIDATE
使映射数据的缓存副本失效。当内存区域中所有被修改过的分页被同步到文件中之后,内存区域中所有与底层文件不一致的分页会被标记为无效。当下次引用这些分页时会从文件的相应位置处复制相应的分页内容,其结果是其他进程对文件做出的所有更新将会在内存区域中可见。
与很多其他现代 UNIX 实现一样,Linux 提供了一个所谓的同一虚拟内存系统。这表示内存映射和高速缓冲区块会尽可能地共享同样的物理内存分页。因此通过映射获取的文件视图与通过 I/O 系统调用(read()、write()等)获得的文件视图总是一致的,而 msync()的唯一用途就是强制将一个映射区域中的内容写入到磁盘。
6 其他mmap()标记
除了 MAP_PRIVATE 和 MAP_SHARED 之外,Linux 允许在 mmap() flags 参数中包含其他一些值(取 OR)。表 49-3 对这些值进行了总结。除了 MAP_PRIVATE 和 MAP_SHARED 之外,在 SUSv3 中仅规定了 MAP_FIXED 标记。
7 匿名映射
匿名映射是没有对应文件的一种映射。本节将介绍如何创建匿名映射以及私有和共享匿名映射的用途。
7.1 MAP_ANONYMOUS 和/dev/zero
在 Linux 上,使用 mmap()创建匿名映射存在两种不同但等价的方法。
- 在 flags 中指定 MAP_ANONYMOUS 并将 fd 指定为−1。(在 Linux 上,当指定了MAP_ANONYMOUS 之后会忽略 fd 的值。)
- 打开/dev/zero 设备文件并将得到的文件描述符传递给 mmap()。
/dev/zero 是一个虚拟设备,当从中读取数据时它总是会返回 0,而写入到这个设备中的数据总会被丢弃。/dev/zero 的一个常见用途是使用 0 来组装一个文件。
不管是使用 MAP_ANONYMOUS 还是使用/dev/zero 技术,得到的映射中的字节会被初始化为 0。在两种技术中,offset 参数都会被忽略(因为没有底层文件,所以也无从指定偏移量)。
7.2 MAP_PRIVATE 匿名映射
MAP_PRIVATE 匿名映射用来分配进程私有的内存块并将其中的内容初始化为 0。下面的代码使用/dev/zero 技术创建了一个 MAP_PRIVATE 匿名映射。
fd = open("/dev/zero",O RDWR);
if(fd ==-1)errExit("open");
addr = mmap(NULL,length,PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, o);
if(addr == MAP_FAILED)errExit("mmap");
7.3 MAP_SHARED 匿名映射
MAP_SHARED 匿名映射允许相关进程(如父进程和子进程)共享一块内存区域而无需一个对应的映射文件。
下面的代码使用 MAP_ANONYMOUS 技术创建了一个 MAP_SHARED 匿名映射。
addr = mmap(NULL,length,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
if(addr == MAP_FAILED)errExit("mmap" );
如果在上面的代码之后加上一个对 fork()的调用,那么由于通过 fork()创建的子进程会继承映射,两个进程就会共享内存区域。
7.4 示例程序
程序演示了如何使用 MAP_ANONYMOUS 或/dev/zero 技术来在父进程和子进程之间共享一个映射区域。至于到底该选择何种技术则由在编译程序时是否定义了USE_MAP_ANON 来确定。父进程在调用 fork()之前将共享区域中的一个整数初始化为 1。然后子进程递增这个共享整数并退出,而父进程则等待子进程退出,然后打印出该整数的值。运行这个程序之后能看到下面这样的输出。
$ ./anon_mmap
Child started, value = 1
In parent, value = 2
/*在父进程和子进程之间共享一个匿名映射
mmap/anon_mmap.cDemonstrate how to share a region of mapped memory between a parent andchild process without having to create a mapped file, either through thecreation of an anonymous memory mapping or through the mapping of /dev/zero.
*/
#ifdef USE_MAP_ANON
#define _BSD_SOURCE /* Get MAP_ANONYMOUS definition */
#endif
#include <sys/wait.h>
#include <sys/mman.h>
#include <fcntl.h>
#include "tlpi_hdr.h"int
main(int argc, char *argv[])
{int *addr; /* Pointer to shared memory region *//* Parent creates mapped region prior to calling fork() */#ifdef USE_MAP_ANON /* Use MAP_ANONYMOUS */addr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE,MAP_SHARED | MAP_ANONYMOUS, -1, 0);if (addr == MAP_FAILED)errExit("mmap");#else /* Map /dev/zero */int fd;fd = open("/dev/zero", O_RDWR);if (fd == -1)errExit("open");addr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (addr == MAP_FAILED)errExit("mmap");if (close(fd) == -1) /* No longer needed */errExit("close");
#endif*addr = 1; /* Initialize integer in mapped region */switch (fork()) { /* Parent and child share mapping */case -1:errExit("fork");case 0: /* Child: increment shared integer and exit */printf("Child started, value = %d\n", *addr);(*addr)++;if (munmap(addr, sizeof(int)) == -1)errExit("munmap");exit(EXIT_SUCCESS);default: /* Parent: wait for child to terminate */if (wait(NULL) == -1)errExit("wait");printf("In parent, value = %d\n", *addr);if (munmap(addr, sizeof(int)) == -1)errExit("munmap");exit(EXIT_SUCCESS);}
}
8 重新映射一个映射区域:mremap()
在大多数 UNIX 实现上一旦映射被创建,其位置和大小就无法改变了。但 Linux 提供了mremap()系统调用来执行此类变更。
#define _GNU_SOURCE
#include <sys/mman.h>
void *mremap(void *old_address, size_t old_size, size_t new_size, int flags, ...);
old_address 和 old_size 参数指定了需扩展或收缩的既有映射的位置和大小。在 old_address中指定的地址必须是分页对齐的,并且通常是一个由之前的 mmap()调用返回的值。映射预期的新大小会通过 new_size 参数指定。在 old_size 和 new_size 中指定的值都会被向上舍入到系统分页大小的下一个整数倍。
在执行重映射的过程中内核可能会为映射在进程的虚拟地址空间中重新指定一个位置,而是否允许这种行为则是由 flags 参数来控制的。它是一个位掩码,其值要么是 0,要么包含下列几个值:
- MREMAP_MAYMOVE
如果指定了这个标记,那么根据空间要求的指令,内核可能会为映射在进程的虚拟地址空间中重新指定一个位置。如果没有指定这个标记,并且在当前位置处没有足够的空间来扩展这个映射,那么就返回 ENOMEM 错误。 - MREMAP_FIXED
这个标记只能与 MREMAP_MAYMOVE 一起使用。它在 mremap()中所起的作用与MAP_FIXED 在 mmap()(49.10 节)中所起的作用类似。如果指定了这个标记,那么 mremap()会接收一个额外的参数 void *new_address,该参数指定了一个分页对齐的地址,并且映射将会被迁移至该地址处。所有之前在由 new_address 和 new_size 确定的地址范围之内的映射将会被解除映射。
相关文章:
Linux/AndroidOS中进程间的通信线程间的同步 - 内存映射
前言 如何使用 mmap()系统调用来创建内存映射。内存映射可用于 IPC 以及其他很多方面。 1 概述 mmap()系统调用在调用进程的虚拟地址空间中创建一个新内存映射。映射分为两种。 文件映射:文件映射将一个文件的一部分直接映射到调用进程的虚拟内存中。一旦一个文…...
单例模式的实现方法
单例模式(Singleton Pattern)是一种常用的软件设计模式,用于确保一个类只有一个实例,并提供一个全局访问点。这种模式在需要控制对资源(如配置对象、线程池、缓存等)的访问时特别有用。 一、单例模…...
laravel 12 监听syslog消息,并将消息格式化后存入mongodb
在Laravel 12中实现监听Syslog消息并格式化存储到MongoDB,需结合日志通道配置、Syslog解析和MongoDB存储操作。以下是具体实现方案: 一、环境配置 安装MongoDB扩展包 执行以下命令安装必要的依赖: composer require jenssegers/mongodb ^4.0确…...
如何在使用 docker-compose 命令时指定 COMPOSE_PROJECT_NAME ?
1.默认值 COMPOSE_PROJECT_NAME 环境变量的默认值并非 docker。在没有显式设置 COMPOSE_PROJECT_NAME 时,其默认值是运行 docker-compose 命令所在目录的基础名称(也就是当前工作目录去掉路径后的文件夹名称)。 以下为你详细说明࿱…...
在命令行终端中快速打开npm包官网
命令 npm home 命令用于快速打开指定 npm 包的官网。例如,npm home react 会尝试打开 React 库的官方网站。 npm home PACKAGE_NAME 该命令会首先查找指定包的 package.json 文件中的 homepage 字段,如果存在,则打开该字段指定的网址。 {&…...
鸿蒙NEXT开发动画(风格的弹性缩放加载动画组件)
1.创建空白项目 2.Page文件夹下面新建Spin.ets文件,代码如下: // 接口定义(必须放在使用前) /*** 关键帧动画整体配置参数*/ interface KeyframeAnimationConfig {iterations: number;delay: number; }/*** 单个关键帧动画项*/…...
【MongoDB篇】MongoDB的事务操作!
目录 引言第一节:什么是事务? (ACID 原则)第二节:MongoDB 的演进:多文档 ACID 事务的到来!🎉第三节:事务的“玩法”——如何执行一个事务?💻🤝第四节…...
Android第六次面试总结之Java设计模式篇(一)
一、单例模式在 Android 面试中的核心考点 1. Android 中如何安全实现单例?需注意哪些坑?(字节跳动、美团面试真题) 解答: Android 中实现单例需重点关注 Context 泄漏、线程安全 和 反射 / 序列化攻击。 推荐实现&…...
关于论文中插入公式但是公式相对于段落的位置偏上应该如何调整备份
因为mythtype之前插入到word里面出现了一些问题就给删掉了,本来要是word里面内联mythtype的话直接,点击mythtype的格式化就可以了, 也就是这个佬的视频介绍链接 然后现在试了试普通word里面的方法,这个是比较有用的 然后看这个例…...
[java八股文][Java并发编程面试篇]并发安全
juc包下你常用的类? 线程池相关: ThreadPoolExecutor:最核心的线程池类,用于创建和管理线程池。通过它可以灵活地配置线程池的参数,如核心线程数、最大线程数、任务队列等,以满足不同的并发处理需求。Exe…...
【东枫科技】代理英伟达产品:智能网卡
文章目录 对比详细:NVIDIA ConnectX-7 适配器详细:NVIDIA ConnectX-6 Lx 以太网智能网卡详细:NVIDIA ConnectX-6 Dx 以太网智能网卡详细:NVIDIA ConnectX-6 InfiniBand 适配器 对比 详细:NVIDIA ConnectX-7 适配器 为最…...
eNSP中路由器OSPF协议配置完整实验和命令解释
本实验使用三台华为路由器(R1、R2和R3)相连,配置OSPF协议实现网络互通。拓扑结构如下: 实验IP规划 R1: GE0/0/0: 192.168.12.1/24 (Area 0)Loopback0: 1.1.1.1/32 (Area 0) R2: GE0/0/0: 192.168.12.2/24 (Area 0)GE0/0/1: 192.…...
解锁健康生活:全新养身指南
健康养身不是遥不可及的目标,而是由一个个小习惯编织成的生活方式。当我们将这些健康理念融入日常,就能为身体注入源源不断的活力。 从 “吃” 开始守护健康。尝试制作 “营养碗”,底层铺满羽衣甘蓝、生菜等绿叶蔬菜,中间搭配水…...
win11 怎样把D盘空间分给C盘一点
如下所示,我的C盘甚至已经爆红了,打算D盘清理一些空间给C盘。 首先附上链接,这是我在b站看的教程,虽然跟着视频没成功,但是结合评论区大神们的建议,尝试了好几种方法,最终自己摸索成功了。 【怎…...
Apache Doris与StarRocks对比
## 历史背景 Apache Doris源自百度的Palo项目,于2017年开源,2018年贡献给Apache基金会,并于2022年从Apache孵化器毕业成为顶级项目。StarRocks则是由原Apache Doris团队的一部分成员在2020年分支出来成立的独立项目,最初称为DorisDB,后更名为StarRocks。这两个项目虽然有…...
OSCP - Proving Grounds - NoName
主要知识点 linux命令注入SUID find提权 具体步骤 从nmap开始搜集信息,只开放了一个80端口 Nmap scan report for 192.168.171.15 Host is up (0.40s latency). Not shown: 65534 closed tcp ports (reset) PORT STATE SERVICE VERSION 80/tcp open http …...
2025年OpenAI重大架构调整:资本与使命的再平衡
目录 前言 一、调整核心:三重架构的重构 1.1 控制权的重新锚定 1.2 营利部门的角色转型 1.3 资金池的重新配置 二、调整动因:三重矛盾的破解 2.1 资金需求与融资限制的冲突 2.2 商业竞争与使命纯度的博弈 2.3 内部治理与外部监管的张力 三、产…...
【quantity】0 README.md文件
PhysUnits 物理单位库 Type-safe physical quantities with dimensional analysis 带量纲分析的类型安全物理量库 A Rust library for safe unit operations / Rust实现的类型安全单位计算库 Core Design / 核心设计 1. Dimension / 量纲 /// Base SI dimensions / 国际单…...
[python] str
一、移除字符串中所有非字母数字字符 使用正则表达式 import re string_value "alphanumeric123__" cleaned_string re.sub(r[\W_], , string_value) # 或 r[^a-zA-Z0-9] print(cleaned_string) # 输出: alphanumeric123使用**str.isalnum()**方法 string_v…...
iOS与HTTPS抓包调试小结
最近在做一个多端 SDK 网络请求兼容性的测试,期间遇到一些 HTTPS 请求抓不到、iOS 抓包失效等问题,趁机整理一下我平时抓包时用到的几个工具和技巧,也顺便记录一下对比体验。 一、传统工具的局限 最早用的是 Charles 和 Fiddler,…...
AI基础知识(02):机器学习的任务类型、学习方式、工作流程
03 机器学习(Machine Learning)的任务类型与学习方式 广义的机器学习主要是一个研究如何让计算机通过数据学习规律,并利用这些规律进行预测和决策的过程。这里的Machine并非物理意义上的机器,可以理解为计算机软硬件组织;Learning可以理解为一个系统或平台经历了某些过程…...
2025年大风灾害预警升级!疾风气象大模型如何筑起安全防线?
近年来,全球极端天气事件频发,大风灾害正成为威胁城市安全、交通运输和公共设施的重要隐患。据气象部门预测,2025年我国大风天气将更加频繁,局部地区可能出现超强阵风,对高空作业、电力设施、交通运输等领域构成严峻挑战。面对这一趋势,传统的气象预警方式已难以满足精准…...
Docker手动重构Nginx镜像,融入Lua、Redis功能
核心内容:Docker重构Nginx镜像,融入Lua、Redis功能 文章目录 前言一、准备工作1、说明2、下载模块3、Nginx配置文件3、Dockerfile配置文件3、准备工作全部结束 二、构建镜像三、基于镜像创建容器三、lua脚本的redis功能使用总结 前言 …...
Spring Boot Starter简介-笔记
1. Starter简介 Spring Boot Starter 是 Spring Boot 框架的核心组件之一,它通过预定义的依赖集合和自动化配置机制,极大简化了 Spring 应用的开发和部署。 Spring Boot Starter 的核心功能 自动化配置(Auto-Configuration) Spr…...
关系型数据库与非关系型数据库深度对比:从设计哲学到应用场景的全解析
关系型数据库与非关系型数据库深度对比:从设计哲学到应用场景的全解析 引言 在数字化浪潮中,数据库技术始终扮演着基础核心角色。本文将通过技术架构、应用场景等维度,深入剖析关系型数据库(RDBMS)与非关系型数据库(NoSQL)的本质差异。我们将以MySQL、MongoDB、Redis等…...
论文速读:《CoM:从多模态人类视频中学习机器人操作,助力视觉语言模型推理与执行》
论文链接:https://arxiv.org/pdf/2504.13351 项目链接:https://chain-of-modality.github.io/ 0. 简介 现代机器人教学的一个重要方向是让机器人通过观看人类的视频演示,自动学习并执行复杂的物理操作任务,比如拧瓶盖、插插头、打…...
系统思考:选择大于努力
在今年的伯克希尔哈撒韦股东大会上,94岁高龄的股神巴菲特再次以他的智慧和幽默,给年轻人留下了三句关于人生的黄金建议。让我印象最深刻的是:“选择和谁一起走,比怎么走更重要。” 这一句话让我反思了许多——人生的轨迹不单单是…...
【HTML5】显示-隐藏法 实现网页轮播图效果
【HTML5】显示-隐藏法 实现网页轮播图效果 实现思路:先将所有图片在页面中设置好,然后给放置图片的元素li添加display:none属性将其隐藏,然后通过js获取到放置图片的元素li,再一个一个的给li元素添加displayÿ…...
Jenkins 改完端口号启动不起来了
让我们将 Jenkins 恢复到默认的 8080 端口,确保它能正常启动: 1. 修改 Jenkins 的配置文件: sudo nano /etc/default/jenkins 将内容修改为: HTTP_PORT8080 JENKINS_ARGS"--webroot/var/cache/jenkins/war --httpPort8080…...
招标专家随机抽选——设计讲解—未来之窗智能编程——仙盟创梦IDE
招标专家系统 专家评标系统是服务于各类招标评标活动的数字化平台。它依托先进信息技术,集专家库管理、随机抽取专家、在线评标等功能于一体。系统依据项目需求设定筛选条件,从庞大专家库中精准抽取合适专家。评标时,专家可在线查阅投标文件…...
Os 库报错指南 路径处理常见陷阱
平台分隔符差异 Windows用\,Unix用/ → 使用os.path.join()自动处理 路径解析错误 os.path.abspath()解析相对路径时依赖当前工作目录 路径规范化缺失 ../等符号链接需用os.path.normpath()规范化 # 不推荐 path dir\\file.txt # Windows风格 path dir/file.…...
GD32/STM32 ADC/DMA使用指南
首先我们对ADC及DMA的基础知识作一下简单介绍。 一、 GD32/STM32 ADC模块的核心要点 一)、ADC基础特性 12位逐次逼近型 GD32/STM32 ADC为12位分辨率,最大量化值为4095(对应3.3V参考电压),支持0-3.3V模拟输入范…...
CSS Border 三角形阴影与多重边框的制作
CSS Border 三角形阴影与多重边框的制作 在现代网页设计中,CSS的强大功能让设计师和开发者能够创造出丰富多彩的视觉效果。本文将深入探讨如何利用CSS的border属性制作三角形阴影以及多重边框效果。这些技巧不仅能提升页面的美观度,还能增强用户体验。…...
ES6/ES11知识点 续五
迭代器【Iterator】 ES6 中的**迭代器(Iterator)**是 JavaScript 的一种协议,它定义了对象如何被逐个访问。迭代器与 for…of、扩展运算符、解构赋值等语法密切相关。 📘 迭代器工作原理 ES6 迭代器的工作原理基于两个核心机制…...
如何选择 边缘计算服务器
边缘计算服务器选型指南(2025年更新版) 一、明确应用场景需求 场景细分 工业控制、自动驾驶等需毫秒级响应的场景,优先选择集成多核处理器(如Xeon D系列)和实时算法加速模块的机型,确保延迟≤50ms&…...
VMware如何安装?Ubuntu详细步骤
VMware如何安装?Ubuntu详细步骤如下: 在VMware中安装Ubuntu是一个常见的操作,适用于开发、测试或学习Linux的场景。以下是详细的实战步骤和注意事项,帮助你顺利完成安装。 准备工作 软件下载: VMware Workstation/Play…...
【Bootstrap V4系列】学习入门教程之 组件-卡片(Card)高级用法
【Bootstrap V4系列】学习入门教程之 组件-卡片(Card)高级用法 一、Sizing 尺寸1.1 Using grid markup 使用网格标记1.2 Using utilities 使用实用程序1.3 Using custom CSS 使用自定义CSS 二、Text alignment 文本对齐方式三、Navigation 导航 一、Sizi…...
WiFi那些事儿(八)——802.11n
目录 802.11n 技术简介与测试项 一、802.11n 技术简介 (一)标准概述 (二)关键技术特性 1. MIMO(多输入多输出)技术 2. 信道绑定(Channel Bonding) 3. 帧聚合(Fram…...
Transformer数学推导——Q56 推导动态残差门控(Dynamic Residual Gating)的权重更新公式
该问题归类到Transformer架构问题集——残差与归一化——残差连接。请参考LLM数学推导——Transformer架构问题集。 1. 引言 在深度学习的演进历程中,网络结构的创新始终围绕着如何更高效地处理信息、提升模型性能展开。动态残差门控(Dynamic Residual…...
Kaggle——House Prices(房屋价格预测)简单实现
题目: 从Kaggle的“House Prices - Advanced Regression Techniques”数据集使用Pandas读取数据,并查看数据的基本信息。选择一些你认为对房屋价格有重要影响的特征,并进行数据预处理(如缺失值处理、异常值处理等)。…...
Vue项目Git提交流程集成
Vue项目Git提交流程集成 本教程将指导你如何在Vue项目中集成一个规范化的Git提交流程,包括代码规范检查、提交信息规范和自动化工具配置。 前置条件 Node.js 14.0 和 npm/yarn/pnpmVue项目(Vue 2或Vue 3均可)Git已初始化的仓库 一、规范化…...
使用 OpenSSL 吊销 Kubernetes(k8s)的 kubeconfig 里的用户证书
一.用 OpenSSL 依据已有的自签名 CA 注销签发的证书的步骤 1. 准备工作 你得有自签名 CA 的私钥(通常是 .key 文件)、CA 证书(通常是 .crt 文件)以及证书吊销列表(CRL)文件。若还没有 CRL 文件,…...
kubeadm部署k8s
我在阿里云上部署的k8s master 4c/8g/40g rocky linux8.9 node1/node2 2c/4g/40g rocky linux8.9 安装docker (我安装的是v1.19.1版本,是旧版本,可以装新版本,docker的版本和kubeadm,kubectl,kubelet版本相同) 1.所有…...
FPGA实战项目1——坦克大战
FPGA实战项目1——坦克大战 根据模块化思想,可将此任务简单的进行模块拆分: 系统原理,模块划分,硬件架构,算法支持,Verilog实现框架 一,系统总体原理 1. 核心设计思想 硬件并行处理&#x…...
LeetCode 1781. 所有子字符串美丽值之和 题解
示例 输入:s "aabcb" 输出:5 解释:美丽值不为零的字符串包括 ["aab","aabc","aabcb","abcb","bcb"] ,每一个字符串的美丽值都为 1这题光用文字解说还是无法达到讲…...
Spring Web MVC————入门(1)
今天开始正式带大家学习Spring部分的内容了,大家尝试去弄个专业版嗷,学习起来爽一点 在idea中下载这个插件就行了 我们之后开始创建Spring项目, 蓝色 部分自己起名,type选Maven,其他的默认就好了,之后nex…...
关于 js:1. 基础语法与核心概念
js 全称 JavaScript(简称 JS),是 一种运行在浏览器和服务器端的脚本语言。 用途: 浏览器端交互(如:点击按钮出现弹窗) 网页动态内容渲染(如:淘宝、京东页面更新…...
云境天合水陆安全漏电监测仪—迅速确定是否存在漏电现象
云境天合水陆安全漏电监测仪是一种专为水下及潮湿环境设计的电气安全检测设备,通过高灵敏度电磁传感器探测漏电电流产生的交变磁场,基于法拉第电磁感应定律,自动区分高灵敏度信号和低灵敏度信号,精准定位泄漏电源的具体位置。一旦…...
二、Hadoop狭义和广义的理解
作者:IvanCodes 日期:2025年5月6日🫠 专栏:Hadoop教程 Hadoop 的双重身份:核心框架与生态系统 在大数据领域,Hadoop 是一个广为人知的概念,但它并非单指某一个软件,而是涵盖了两个层…...
DTU_DTU厂家_5G/4G DTU终端_DTU模块_厦门计讯物联科技有限公司
在物联网蓬勃发展的当下,数据的高效、稳定、可靠的传输成为关键。厦门计讯物联科技有限公司(以下简称“计讯物联”)作为国内工业物联网领域的核心厂商,专注于5G/4G DTU终端、DTU模块及无线数传设备的研发与生产,致力于为智慧城市、能源电力、…...