进程如何共享虚拟内存(Linux)

时间:2017-05-09 06:58:22

标签: c linux memory

我不确定我的问题是Linux问题,还是操作系统无关。

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

他们每个人都维护自己的堆栈,堆等用户空间内吗?

Scenario A

或者他们只是拥有整个堆栈,堆等,直到下一个进程出现并抢占它?

Scenario B

谢谢你们!

1 个答案:

答案 0 :(得分: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兆字节;单独的堆栈就需要几百GB的内存,因此并发线程的数量主要是受限于可用于其堆栈的内存。非递归线程工作器函数最多只需要几十千字节的堆栈,并且只需几行代码就可以显式设置新创建的pthread的堆栈大小。然后,最大值单个进程中的并发线程数通常大约为一千或更多,通常取决于系统管理员或默认分发的进程限制。)

如上图所示,没有" OS"。

事实上,我们确实需要拆分"操作系统"分为两个完全独立的部分:内核(提供system calls中实现的功能)和库(实现用户空间处理器可用的非系统调用接口,通常从标准C库开始)。 / p>

我只画了一个" Libs" (对于库)上面的框,但实际上,每个库的代码往往会获得自己独立的内存段。

让我们看看Linux中的一个特定示例(因为那是我现在正在使用的); cat命令。在Linux中,/sys/proc文件系统是特殊的伪文件系统树,它们根本不对应任何存储介质上的任何文件,但是只要它们被访问就由内核构建 - 基本上,它们是内核提供的内核已知数据的实时视图。 /proc/self子树包含有关"当前进程的信息" - 也就是说,在检查该目录的任何进程上。 (如果多个同时检查它们,它们每个都只看到自己的数据,因为这不是一个普通的文件系统,而是根据需要创建和提供内核。)

进程ID为/proc/self/maps的进程的/proc/PID/maps(或PID)伪文件描述了进程具有的所有内存映射。如果我们运行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()扩展的数据段(或#34;堆")是第三个(即sbrk(0)将返回0x60d000。)

该进程有一些堆,正确,从地址0x215f000到(但不包括)0x2180000。

下一个段是当前区域设置数据的只读映射。 C库将此用于区域设置感知接口。

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

保护模式(rw-p)的下一个分段以及最后一列没有名称的其他分段是单独的数据段或堆。

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

rw-p段是初始线程的堆栈。 ([stack]是单线程的,因此它只有一个线程。)cat段由内核提供(允许进程直接访问某些内核提供的数据,而不必承担开销)一个系统调用)。内核提供[vdso][vvar]段,用于加速不需要完整上下文切换的系统调用。

所以,正如你所看到的,完整的图片更加分散,但也比自由形式更自由(就像更多的自由形式),而不是旧的C和操作系统书籍你会相信。