C 进程如何共享虚拟内存(Linux)
我不确定我的问题是Linux还是操作系统无关 如果我有三个进程在运行(我们称它们为P0、P1和P2),并且在用户看来它们是并发运行的,那么它们如何共享呢 它们是否各自在用户空间中维护自己的堆栈、堆等 或者他们只是拥有整个堆栈、堆等,直到下一个进程出现并抢占它C 进程如何共享虚拟内存(Linux),c,linux,memory,C,Linux,Memory,我不确定我的问题是Linux还是操作系统无关 如果我有三个进程在运行(我们称它们为P0、P1和P2),并且在用户看来它们是并发运行的,那么它们如何共享呢 它们是否各自在用户空间中维护自己的堆栈、堆等 或者他们只是拥有整个堆栈、堆等,直到下一个进程出现并抢占它 在Linux和大多数其他当前使用的通用操作系统中,内存根本不是一个单一的线性阵列:底层物理内存在页面级别使用 本质上,每个进程都有自己的虚拟地址空间。其中大部分是空的、未映射的——试图访问它会导致违反或一般保护,通常会终止进程——;进程只
在Linux和大多数其他当前使用的通用操作系统中,内存根本不是一个单一的线性阵列:底层物理内存在页面级别使用 本质上,每个进程都有自己的虚拟地址空间。其中大部分是空的、未映射的——试图访问它会导致违反或一般保护,通常会终止进程——;进程只能访问内核显式设置为进程可访问的内存 在大多数情况下,进程也不能直接访问内核内存。要执行系统调用(例如,打开或读取或写入文件或设备),处理器核心基本上执行到内核模式,其中内核数据结构和当前进程在用户空间中使用的内存可以同时访问(但不一定在内核空间中与在用户空间中相同的虚拟地址) 这意味着现在每个进程可访问的内存实际上是非常分散和不连续的:
╔════════╗ ╔════════╗ ╔═══════╗
║ Code ║ ║ Data ║ ║ Stack ║
╚════════╝ ╟────────╢ ╚═══════╝
╔════════╗ ║ BSS ║
║ ROdata ║ ╟────────╢
╚════════╝ ║ Heap ║
╔════════╗ ╚════════╝
║ Libs ║
╚════════╝
如果正在使用地址空间随机化,则上述各段的地址可能会因每次运行而有所不同。通常,代码(只读且可执行)和只读数据加载到固定地址,但动态链接库、堆栈和数据的地址不同
也没有理由说上面的一个地址比另一个地址高或低,所以我故意把它们画在一起,而不是一列
初始化数据和未初始化数据通常在一个连续段中,只有初始化数据部分从可执行文件(数据段)加载。在Unix和POSIX类系统中,堆遵循未初始化的数据(并且可以使用brk()
或sbrk()
系统调用进行扩展)。在像Linux这样的POSIXy系统中,甚至在大多数其他系统中,进程也可以通过(匿名)内存映射拥有额外的“堆”
进程中的初始线程还获得一个单独的堆栈段。任何附加线程也将获得自己的堆栈
(学习使用POSIX线程的一个典型练习是找出一个进程可以创建多少并发线程。Linux中的典型结果只有一百或几百个,许多学习者对此感到非常奇怪。这个数字如此低的原因实际上是默认堆栈大小,在当前GNU上大约为8兆字节/Linux桌面发行版;仅用于一百个线程的堆栈就需要将近1GB的内存,因此并发线程的数量主要受其堆栈可用内存的限制。非递归线程工作函数最多只需要几十KB的堆栈,并且只需要几行代码就可以显式执行设置新创建的pthread的堆栈大小。然后,单个进程中并发线程的最大数量通常为1000个或更多,通常取决于系统管理员设置的进程限制或默认情况下的分发。)
如上图所示,没有“操作系统”
事实上,我们确实需要将“操作系统”分为两个完全独立的部分:内核(提供中实现的功能)和库(实现用户空间处理器可用的非系统调用接口,通常从标准C库开始)
我在上面只画了一个“Libs”(用于库)框,但实际上,每个库的代码都倾向于获得各自独立的内存段
让我们看一下Linux中的一个特定示例(因为我现在正在使用这个示例)在Linux中,/sys
和/proc
文件系统是特殊的伪文件系统树,它们根本不对应于任何存储介质上的任何文件,而是在每次访问时由内核构造——本质上,它们是内核提供的内核已知数据的实时视图e/proc/self
子树包含有关“当前进程”的信息,即检查该目录的进程(如果有多个进程同时检查该目录,则每个进程只能看到自己的数据,因为这不是一个正常的文件系统,而是根据需要创建和提供的内核)
/proc/self/maps
(或/proc/PID/maps
,用于进程ID为PID
)的进程)伪文件描述了进程拥有的所有内存映射。如果运行cat/proc/self/maps
,我们可以在我的机器上看到cat
进程本身的映射(在x86-64体系结构上运行的64位Linux)它显示
前三个是进程本身的代码(r-xp
)、只读数据(r--p
)和初始化数据(rw-p
),进程可以扩展使用的数据段(或“堆”)是第三个(即,sbrk(0)
将返回0x60d000
)
进程有一些堆,从地址0x215f000到(但不包括)0x2180000
下一段是当前语言环境数据的只读映射。C库将其用于语言环境感知接口
接下来的四个部分是C库本身:代码(r-xp
)、C库以某种方式使用/需要的通常不可访问的映射(--p
)、只读数据(r--p
)和初始值
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]