在用户进程和物理内存(磁盘存储器)之间引入虚拟内存主要有以下的优点:
- 地址空间:提供更大的地址空间,并且地址空间是连续的,使得程序编写、链接更加简单 。
- 进程隔离:不同进程的虚拟地址之间没有关系,所以一个进程的操作不会对其他进程造成影响 。
- 数据保护:每块虚拟内存都有相应的读写属性,这样就能保护程序的代码段不被修改,数据块不能被执行等,增加了系统的安全性 。
- 内存映射:有了虚拟内存之后,可以直接映射磁盘上的文件(可执行文件或动态库)到虚拟地址空间 。
- 共享内存:比如动态库只需要在内存中存储一份,然后将它映射到不同进程的虚拟地址空间中,让进程觉得自己独占了这个文件 。
- 物理内存管理:物理地址空间全部由操作系统管理,进程无法直接分配和回收,从而系统可以更好的利用内存,平衡进程间对内存的需求 。
操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的权限 。
为了避免用户进程直接操作内核,保证内核安全,操作系统将虚拟内存划分为两部分,一部分是内核空间(Kernel-space),一部分是用户空间(User-space) 。
在 Linux 系统中,内核模块运行在内核空间,对应的进程处于内核态;而用户程序运行在用户空间,对应的进程处于用户态 。
内核进程和用户进程所占的虚拟内存比例是 1:3,而 Linux x86_32 系统的寻址空间(虚拟存储空间)为 4G(2 的 32 次方),将最高的 1G 的字节(从虚拟地址 0xC0000000 到 0xFFFFFFFF)供内核进程使用,称为内核空间 。
而较低的 3G 的字节(从虚拟地址 0x00000000 到 0xBFFFFFFF),供各个用户进程使用,称为用户空间 。
下图是一个进程的用户空间和内核空间的内存布局:
文章插图
内核空间
内核空间总是驻留在内存中,它是为操作系统的内核保留的 。应用程序是不允许直接在该区域进行读写或直接调用内核代码定义的函数的 。
上图左侧区域为内核进程对应的虚拟内存,按访问权限可以分为进程私有和进程共享两块区域:
- 进程私有的虚拟内存:每个进程都有单独的内核栈、页表、task 结构以及 mem_map 结构等 。
- 进程共享的虚拟内存:属于所有进程共享的内存区域,包括物理存储器、内核数据和内核代码区域 。
每个普通的用户进程都有一个单独的用户空间,处于用户态的进程不能访问内核空间中的数据,也不能直接调用内核函数的 ,因此要进行系统调用的时候,就要将进程切换到内核态才行 。
用户空间包括以下几个内存区域:
- 运行时栈:由编译器自动释放,存放函数的参数值,局部变量和方法返回值等 。每当一个函数被调用时,该函数的返回类型和一些调用的信息被存储到栈顶,调用结束后调用信息会被弹出并释放掉内存 。
- 运行时堆:用于存放进程运行中被动态分配的内存段,位于 BSS 和栈中间的地址位 。由卡发人员申请分配(malloc)和释放(free) 。堆是从低地址位向高地址位增长,采用链式存储结构 。
- 代码段:存放 CPU 可以执行的机器指令,该部分内存只能读不能写 。通常代码区是共享的,即其他执行程序可调用它 。假如机器中有数个进程运行相同的一个程序,那么它们就可以使用同一个代码段 。
- 未初始化的数据段:存放未初始化的全局变量,BSS 的数据在程序开始执行之前被初始化为 0 或 NULL 。
- 已初始化的数据段:存放已初始化的全局变量,包括静态全局变量、静态局部变量以及常量 。
推荐阅读
- 淘宝从百万到千万级并发的14次服务端架构演进之路
- 几百万扔进水?买二手房千万避开这几类房源
- 分布式、高并发、多线程,这些概念还傻傻分不清吗?
- Java 并发编程:如何保证共享变量的原子性?
- 硬核!如何模拟 5w+ 的并发用户?
- 格鲁吉亚中国茶王刘峻周诞辰150周年之际获百万赔偿
- 安徽省科技厅强化科技支撑推进精准扶贫
- PHP导出百万条数据方法
- 重金寻人 , 你是我们要找的百万英雄吗 内含福利
- 带你深入了解高并发架构