目前正在尝试理解内存映射在Linux中是如何工作的(或者一般来说,真的),我将从操作系统概念中介绍POSIX系统中共享内存的一个示例。这两个文件如下:
生产文件
// This is the producer file for the Shared memory object
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>
int main()
{
// ---------------- PRODUCER ESTABLISHES SHARED MEMORY OBJECT AND WRITES TO IT ----------------
// Specifying the size in bytes of the shared memory object
const int SIZE = 4096;
// Name of the shared memory space
const char *name = "OS";
// The actual strings to write to shared memory
const char *message_0 = "Hello";
const char *message_1 = "World!";
// Shared memory file descriptor will be stored in this
int fd;
// Pointer to the shared memory object will be stored in this
char *ptr;
// Checking error
int errnum;
// Create a shared memory object. This opens (establishes a connection to) a shared memory object.
fd = shm_open(name, O_CREAT | O_RDWR,0666);
if (fd == -1)
{
errnum = errno;
fprintf(stdout, "Value of errno: %dn", errno);
perror("Error printed by perror");
fprintf(stdout, "Error opening file: %sn", strerror(errnum));
return 1;
}
// Configure the size of the shared-memory object to be 4096 bytes
ftruncate(fd, SIZE);
// Memory map the shared memory object
ptr = (char *) mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
printf("Ptr is: %pn", ptr);
if (ptr == MAP_FAILED)
{
errnum = errno;
fprintf(stdout, "Value of errno in ptr: %dn", errno);
perror("Error printed by perror");
fprintf(stdout, "Error opening file: %sn", strerror(errnum));
return 1;
}
// Write to the shared memory object
sprintf(ptr, "%s", message_0);
ptr += strlen(message_0);
sprintf(ptr, "%s", message_1);
ptr += strlen(message_1);
return 0;
}
消费者文件
// This is the consumer file for the Shared memory object, in which it reads what is in the memory object OS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>
int main()
{
// Size in bytes of shared memory object
const int SIZE = 4096;
// Name of the shared memory space
const char *name = "OS";
// Shared memory file descriptor will be stored in this
int fd;
// Pointer to the shared memory object will be stored in this
char *ptr;
// Checking error
int errnum;
// Open the shared memory object
fd = shm_open(name, O_RDWR, 0666);
// If error in shm_open()
if (fd == -1)
{
errnum = errno;
fprintf(stdout, "Value of errno: %dn", errno);
perror("Error printed by perror");
fprintf(stdout, "Error opening file: %sn", strerror(errnum));
return 1;
}
// Memory-map the shared memory object
ptr = (char *) mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
printf("Ptr is: %pn", ptr);
if (ptr == MAP_FAILED)
{
errnum = errno;
fprintf(stdout, "Value of errno in ptr: %dn", errno);
perror("Error printed by perror");
fprintf(stdout, "Error opening file: %sn", strerror(errnum));
return 1;
}
// Read from the shared memory object
printf("%sn", (char *) ptr);
// Remove the shared memory object (delete it)
shm_unlink(name);
return 0;
}
当我输出指向共享内存对象(printf("Ptr value: %pn, ptr)
)的指针时,我得到了消费者和生产者文件的两个不同的值。为什么会发生这种情况?
据我所知,指针ptr
指向共享内存对象,它位于物理内存中。通过将物理内存映射到两个进程的地址空间,该物理内存仅在两个进程之间共享。然而,这将要求指针指向物理内存中的相同地址,不是吗?还是指向虚拟内存(即进程的地址空间)?如果是这样,那这本身就指向物理记忆吗?
谢谢!
每个进程都有自己的虚拟内存地址空间。通常,每个进程从虚拟内存到物理内存的映射是不同的。虽然共享内存可能位于地址0x1000的物理内存中,但生产者进程可能将其映射到地址0x7000的虚拟内存中,而消费者进程可能将其映射到地址0x4000的虚拟内存中。
此外,如果共享内存由于某种原因被交换出内存,系统稍后可以将其重新加载到不同的物理地址,例如0x13000,并更新进程中的映射,以便它在每个生产者和消费者进程中出现在与以前相同的地址上。
Producer
和consumer
是两个不同的进程,所以它们各自有自己的虚拟内存空间。当您附加共享段时,您可以指定您没有优先选择的内核(在未分配的虚拟地址空间中)将其放置在何处,因此您通常会得到不同的指针,因为很可能两个进程都有不同的内存映射。想想这样一个场景,一个进程在地址A上分配了内存映射,而另一个进程在地址A上被它分配的其他东西占用(例如,堆的动态内存,或不同的共享库)很明显,内核不能在相同的(虚拟)地址范围内分配两个不同的东西(甚至重叠一些其他映射),所以它使用不同的位置,并返回不同的指针。
不用担心,因为两个虚拟地址都映射到相同的物理地址(这是肯定的,因为它是一个共享内存段),并且指针最终指向相同的对象。内核通常不知道它在不同的进程中放置了相同的段,它不需要这样做。这意味着,与您所想的相反,您极不可能使两个指针相等。
由于这些进程都不知道另一个进程的虚拟地址空间,所以没有冲突,但永远不会将地址传递给另一个进程使用,因为虚拟地址空间中的地址在另一个不同进程的虚拟地址空间中绝对没有意义。
进程通常没有办法(也不可能)知道内核如何分配内存和构建虚拟地址到物理地址的映射。甚至对于内核本身(内核也在它的虚拟内存空间中运行,并且只处理映射的表——这些表在内核的虚拟地址空间中,但不在任何用户进程的虚拟地址空间中,所以这就是内核能够改变映射的原因,但对其他进程来说是不可能的)
进程不可能知道虚拟地址在实际内存中所指向的位置。对于某些管理员进程来说,这可以通过设备/dev/mem
实现(为此需要一个特殊的设备)。但是,如果你试图在没有实际映射的情况下查看那里,你会得到一堆页面,有些属于一个进程,有些属于另一个进程,没有明显的结构,或者你也不知道这些页面的内容是什么意思。
有另一个设备/dev/kmem
映射到内核虚拟地址空间,并允许进程(例如调试器)访问那里的映射表....但这是一个非常危险的弯道,因为你可以很容易地通过调整你的系统崩溃。认为你不能正常地停止内核(这会停止系统),或者如果你可以这样做,你将停止应该运行的系统的重要部分。