2017-05-09 286 views
2

我不确定我的问题是Linux问题还是操作系统不可知论者。进程如何共享虚拟内存(Linux)

如果我有三个进程正在运行(我们称它们为P0,P1和P2),并且它们对于用户来说似乎是同时运行的,它们是如何共享的?

他们各自维护自己的堆栈,堆等里面用户空间?

Scenario A

还是他们只是拥有整个堆,堆,等等,直到下一道工序走来,并抢占了呢?

Scenario B

谢谢你们!

+1

看看这里:https://www.softprayog.in/programming/interprocess-communication-using-posix-shared-memory-in-linux –

+2

物理计算机内存使用方案A中的情况。但过程不看到物理地址。每个进程都有自己的虚拟地址空间,CPU在每个内存访问时将虚拟地址转换为物理地址。从进程的角度来看,它看起来像是它可以使用整个地址空间,就像场景B.请参阅:http://stackoverflow.com/questions/22290347/understanding-virtual-address-virtual-memory-and -paging – Marian

回答

4

在Linux和其他大多数当前使用的通用操作系统中,内存根本不是单个线性阵列:底层物理内存在的页面级别使用virtual memory进行管理。

本质上,每个进程都有自己的虚拟地址空间。它大部分是空的,未映射 - 试图访问它导致一个segmentation fault或一般的保护违规,通常会杀死进程 - ;该进程只能访问内核明确设置为进程可访问的内存。

在大多数情况下,进程无法直接访问内核内存。要执行系统调用 - 例如,打开或读取或写入文件或设备 - 处理器核心本质上会执行内核模式context switch,其中内核数据结构以及用户空间中当前进程使用的内存,可同时访问(但不一定在内核空间中的用户空间中的相同虚拟地址)。

这意味着,每一个过程可访问的存储器实际上是相当分散和不连续的时下:

╔════════╗ ╔════════╗ ╔═══════╗ 
    ║ Code ║ ║ Data ║ ║ Stack ║ 
    ╚════════╝ ╟────────╢ ╚═══════╝ 
    ╔════════╗ ║ BSS ║ 
    ║ ROdata ║ ╟────────╢ 
    ╚════════╝ ║ Heap ║ 
    ╔════════╗ ╚════════╝ 
    ║ Libs ║ 
    ╚════════╝ 

如果地址空间随机化是在使用中,在上述各块体的地址可以甚至从一个运行变化到下一个。通常,代码(只读和可执行文件)和只读数据加载到固定地址,但动态链接的库,堆栈和数据的地址不同。

没有理由为什么以上其中一个应该有比另一个更高或更低的地址,所以我故意将它们彼此相邻,而不是在一个列中!

初始化的数据和未初始化的数据通常是连续的段,只有来自可执行文件(数据段)的初始化数据部分加载。在Unix和类POSIX系统中,堆遵循未初始化的数据(并且可以使用系统调用brk()sbrk()进行扩展)。在像Linux这样的POSIXy系统中,以及大多数其他系统中,一个进程也可以通过(匿名)内存映射增加“堆”。

进程中的初始线程也获得一个单独的堆栈段。任何额外的线程也将获得他们自己的堆栈。

(学习使用POSIX线程的一个典型练习是找出进程可以创建多少个并发线程。Linux中的典型结果只有一百或几百,很多学习者觉得这很奇怪。这种低数字的原因实际上是默认的堆栈大小,在当前的GNU/Linux桌面分布上大概是8兆字节;单独一百个线程的堆栈需要几千兆字节的内存,因此并发线程的数量主要受限于可用于其堆栈的内存。非递归线程工作函数最多只需要几十千字节的堆栈,并且只需要几行代码即可为新创建的pthread显式设置堆栈大小。然后,单个进程中并发线程的最大数量通常在千位或更多的数量级上,通常取决于系统管理员设置的进程限制或缺省分配。)

正如您在上图中没有“操作系统”。事实上,我们确实需要将“操作系统”分成两个完全独立的部分:内核(它提供了在system calls中实现的功能)和库(实现非系统调用接口可用于用户空间处理器,通常从标准C库开始)。

我只在上面画了一个“Libs”(用于图书馆)框,但实际上,每个图书馆的代码往往会得到他们自己独立的内存片段。让我们看看Linux中的一个特定示例(因为这就是我现在使用的);命令cat。在Linux中,/sys/proc文件系统是特殊的伪文件系统树,它们根本不对应任何存储介质上的任何文件,而是由内核在访问时构建的 - 实质上,它们是内核提供的实时视图内核已知的数据。 /proc/self子树包含有关“当前进程”的信息 - 即无论检查该目录的任何进程。 (如果不止一个同时检查它们,它们每个只能看到它们自己的数据,因为这不是一个正常的文件系统,而是内核创建并根据需要提供。)

/proc/self/maps(或进程ID为PID的进程的/proc/PID/maps)伪文件描述进程具有的所有内存映射。如果我们运行cat /proc/self/maps,我们可以看到cat进程本身的映射。在我的机器(在x86-64架构运行64位Linux),它显示

00400000-0040c000 r-xp 00000000 08:05 2359392    /bin/cat 
0060b000-0060c000 r--p 0000b000 08:05 2359392    /bin/cat 
0060c000-0060d000 rw-p 0000c000 08:05 2359392    /bin/cat 
0215f000-02180000 rw-p 00000000 00:00 0     [heap] 
7f735b70f000-7f735c237000 r--p 00000000 08:05 658950  /usr/lib/locale/locale-archive 
7f735c237000-7f735c3f6000 r-xp 00000000 08:05 1179825  /lib/x86_64-linux-gnu/libc-2.23.so 
7f735c3f6000-7f735c5f6000 ---p 001bf000 08:05 1179825  /lib/x86_64-linux-gnu/libc-2.23.so 
7f735c5f6000-7f735c5fa000 r--p 001bf000 08:05 1179825  /lib/x86_64-linux-gnu/libc-2.23.so 
7f735c5fa000-7f735c5fc000 rw-p 001c3000 08:05 1179825  /lib/x86_64-linux-gnu/libc-2.23.so 
7f735c5fc000-7f735c600000 rw-p 00000000 00:00 0 
7f735c600000-7f735c626000 r-xp 00000000 08:05 1179826  /lib/x86_64-linux-gnu/ld-2.23.so 
7f735c7fe000-7f735c823000 rw-p 00000000 00:00 0 
7f735c823000-7f735c825000 rw-p 00000000 00:00 0 
7f735c825000-7f735c826000 r--p 00025000 08:05 1179826  /lib/x86_64-linux-gnu/ld-2.23.so 
7f735c826000-7f735c827000 rw-p 00026000 08:05 1179826  /lib/x86_64-linux-gnu/ld-2.23.so 
7f735c827000-7f735c828000 rw-p 00000000 00:00 0 
7ffeea455000-7ffeea476000 rw-p 00000000 00:00 0   [stack] 
7ffeea48b000-7ffeea48d000 r--p 00000000 00:00 0   [vvar] 
7ffeea48d000-7ffeea48f000 r-xp 00000000 00:00 0   [vdso] 
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] 

前三的有代码(r-xp),只读数据(r--p),并初始化数据(rw-p)对于过程本身。数据段(或“堆”),该过程可使用sbrk()延伸是第三个(即,sbrk(0)将返回0x60d000。)

的方法具有一些堆,适当的,从地址0x215f000直到(但不包括)0x2180000。

下一个段是当前语言环境数据的只读映射。 C库将它用于区域感知接口。

接下来的四个区段是C库正确:代码(r-xp),常无法访问的映射以某种方式使用/ C库(---p)需要,只读数据(r--p),并初始化数据(rw-p)。

下一个段和最后一列中没有名称的其他段,保护模式(rw-p)是独立的数据段或堆。

接下来的三个段是Linux中使用的动态链接器,ld.so。再次,存在代码段(r-xp),只读数据段(r--p)和初始化数据段(rw-p)。

[stack]段是初始线程的堆栈。 (cat是单线程的,所以它只有一个线程。)[vvar]段由内核提供(以允许进程直接访问某些内核提供的数据,而不必承担系统调用的开销)。内核提供[vdso][vsyscall]段以加速不需要完整上下文切换完成的系统调用。因此,正如你所看到的,完整的图片比较老的C和操作系统的书籍会让你相信的更完整的图片更加分散,但也是免费的(比如更自由的形式)。