由于操作系统的进程与进程之间是共享 CPU 和内存资源的,因此需要一套完善的内存管理机制防止进程之间内存泄漏的问题。
物理内存与虚拟内存
虚拟内存为每个进程提供了一个一致的、私有的地址空间,它让每个进程产生了一种自己在独享主存的错觉(每个进程拥有一片连续完整的内存空间)。
物理内存: 指通过物理内存条而获得的内存空间,而虚拟内存则是指将硬盘的一块区域划分来作为内存。内存主要作用是在计算机运行时为操作系统和各种程序提供临时储存。
虚拟内存: 是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间)。而实际上,虚拟内存通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换,加载到物理内存中来。
每个进程所能使用的虚拟地址大小和 CPU 位数有关。
在 32 位的系统上,虚拟地址空间大小是 2^32=4G,在 64 位系统上,虚拟地址空间大小是 2^64=16G,而实际的物理内存可能远远小于虚拟内存的大小。
每个用户进程维护了一个单独的页表(Page Table),虚拟内存和物理内存就是通过这个页表实现地址空间的映射的。
为了获取到实际的数据,CPU 需要将虚拟地址转换成物理地址,CPU 转换地址时需要用到进程的页表,而页表里面的数据由操作系统维护。
其中页表可以简单的理解为单个内存映射的链表(当然实际结构很复杂)。
里面的每个内存映射都将一块虚拟地址映射到一个特定的地址空间(物理内存或者磁盘存储空间)。
每个进程拥有自己的页表,和其他进程的页表没有关系。
用户进程访问虚拟内存的过程(COPY)
引用虚拟内存的优点
1. 提供更大的地址空间,并且地址空间是连续的,使得程序编写、链接更加简单。
2. 不同进程的虚拟地址之间没有关系,所以一个进程的操作不会对其他进程造成影响。
3. 每块虚拟内存都有相应的读写属性,这样就能保护程序的代码段不被修改,数据块不能被执行等,增加了系统的安全性。
4. 有了虚拟内存之后,可以直接映射磁盘上的文件(可执行文件或动态库)到虚拟地址空间。
这样可以做到物理内存延时分配,只有在需要读相应的文件的时候,才将它真正的从磁盘上加载到内存中来,而在内存吃紧的时候又可以将这部分内存清空掉,提高物理内存利用效率,并且所有这些对应用程序都是透明的。
5. 多个进程共享内存,比如动态库只需要在内存中存储一份,然后将它映射到不同进程的虚拟地址空间中,让进程觉得自己独占了这个文件。
进程间的内存共享也可以通过映射同一块物理内存到进程的不同虚拟地址空间来实现共享。
6. 物理地址空间全部由操作系统管理,进程无法直接分配和回收,从而系统可以更好的利用内存,平衡进程间对内存的需求。
内核与用户空间
操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的权限。
为了避免用户进程直接操作内核,保证内核安全,操作系统将虚拟内存划分为两部分,一部分是内核空间,一部分是用户空间。
在 Linux 系统中,内核模块运行在内核空间,对应的进程处于内核态;而用户程序运行在用户空间,对应的进程处于用户态。
内核进程和用户进程所占的虚拟内存比例是 1:3,而 Linux x86_32 系统的寻址空间(虚拟存储空间)为 4G(2 的 32 次方),将最高的 1G 的字节(从虚拟地址 0xC0000000 到 0xFFFFFFFF)供内核进程使用,称为内核空间。
内核空间总是驻留在内存中,它是为操作系统的内核保留的。应用程序是不允许直接在该区域进行读写或直接调用内核代码定义的函数的。
每个普通的用户进程都有一个单独的用户空间,处于用户态的进程不能访问内核空间中的数据,也不能直接调用内核函数的 ,因此要进行系统调用的时候,就要将进程切换到内核态才行。
内核态可以执行任意命令,调用系统的一切资源,而用户态只能执行简单的运算,不能直接调用系统资源。用户态必须通过系统接口(System Call),才能向内核发出指令。
linux层级结构