我不确定我的问题是Linux还是操作系统无关的问题。
如果我有三个正在运行的进程(我们称它们为P0,P1和P2),并且在用户看来它们正在同时运行,那么它们如何共享?
它们每个都在用户空间中维护自己的堆栈,堆等吗?
还是他们只是拥有整个堆栈,堆等,直到出现下一个进程并抢占它?
谢谢你们!
最佳答案
在Linux和大多数其他当前使用的通用操作系统中,内存根本不是单个线性阵列:基础物理内存是使用virtual memory在页面级别进行管理的。
本质上,每个进程都有自己的虚拟地址空间。其中大多数是空的,未映射的,并且试图访问它会导致segmentation fault或一般性保护违规,通常会杀死进程-该进程只能访问内核已明确设置为可访问的内存。
在大多数情况下,进程也无法直接访问内核内存。为了执行系统调用(例如,打开或读取或写入文件或设备),处理器内核实质上对内核模式执行context switch,其中内核数据结构以及用户空间中当前进程使用的内存可同时访问(),但不必在内核空间中与用户空间中的虚拟地址相同)。
这意味着当今每个进程可访问的内存实际上是分散且不连续的:
╔════════╗ ╔════════╗ ╔═══════╗
║ Code ║ ║ Data ║ ║ Stack ║
╚════════╝ ╟────────╢ ╚═══════╝
╔════════╗ ║ BSS ║
║ ROdata ║ ╟────────╢
╚════════╝ ║ Heap ║
╔════════╗ ╚════════╝
║ Libs ║
╚════════╝
如果使用地址空间随机化,则即使从一次运行到下一次运行,上述每个段的地址也可能有所不同。通常,代码(只读和可执行)和只读数据被加载到固定地址,但是动态链接的库,堆栈和数据的地址会有所不同。
也没有理由为什么上面的一个地址比另一个地址具有更高或更低的地址,所以我特意将它们彼此相邻而不是放在同一列中!
初始化的数据和未初始化的数据通常在连续的段中,只有从可执行文件(数据部分)中加载已初始化的数据部分。在类似Unix和POSIX的系统中,堆遵循未初始化的数据(可以使用
brk()
或sbrk()
syscall进行扩展)。在Linux之类的POSIXy系统中,甚至在大多数其他系统中,一个进程也可以通过(匿名)内存映射来具有其他“堆”。进程中的初始线程还会获得一个单独的堆栈段。任何其他线程也将获得自己的堆栈。
(学习使用POSIX线程的一个典型练习是找出一个进程可以创建多少个并发线程。Linux中的典型结果只有一百或几百个,许多学习者发现这很奇怪。这样做的原因一个低的数字实际上是默认的堆栈大小,在当前的GNU/Linux桌面发行版中大约为8兆字节;仅一百个线程的堆栈就需要将近一GB的内存,因此并发线程数主要受内存限制非递归线程工作器函数最多仅需要几十个千字节的堆栈,并且只需几行代码即可显式设置新创建的pthread的堆栈大小。单个进程中的线程通常大约在一千个或更多,通常取决于系统管理员设置的进程限制或默认情况下的分发。)
如上图所示,没有“OS”。
实际上,我们确实确实需要将“操作系统”分为两个完全独立的部分:内核(提供在system calls中实现的功能)和库(实现对用户空间处理器可用的非系统调用接口(interface))从标准C库开始)。
我在上面仅画了一个“Libs”(对于库)框,但实际上,每个库的代码都倾向于获得各自独立的内存段。
让我们看一下Linux中的一个特定示例(因为这就是我现在正在使用的);
cat
命令。在Linux中,/sys
和/proc
文件系统是特殊的伪文件系统树,它们根本不与任何存储介质上的任何文件相对应,而是由内核在每次访问它们时构造的-本质上,它们是内核提供的实时 View 内核已知的数据。 /proc/self
子树包含有关“当前进程”的信息,即有关正在检查该目录的进程的信息。 (如果同时检查多个数据,则他们每个人只会看到自己的数据,因为这不是普通的文件系统,而是根据需要由内核创建和提供的。)/proc/self/maps
(或进程ID为/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()
扩展的数据段(或“堆”)是第三个数据段(即sbrk(0)
将返回0x60d000
)。该进程有一些适当的堆,从地址0x215f000到(但不包括)0x2180000。
下一段是当前语言环境数据的只读映射。 C库将其用于语言环境感知接口(interface)。
接下来的四段是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和操作系统书籍更加分散,而且更加自由(如自由格式)。
关于c - 如何处理共享虚拟内存(Linux),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43863158/