携手创作,共同成长!这是我参与「日新计划 8 月更文挑战」的第2天,点击查看活动详情
举个栗子:我的笔记本电脑内存就是 16GB
,也是 内存容量,指的是 物理内存。
-
物理内存也称为主存,大多数计算机用的主存都是 动态随机访问内存(
DRAM
) 。 -
只有 内核态 才可以直接访问物理内存。
问题:为什么只有 内核态 才能直接访问 物理内存?
回答:区分为用户态和内核态,主要目的为了 保护系统程序。
-
内核态(
Kernal Mode
): 用户空间中的代码使用和一个局部的内存空间。 -
用户态(
User Mode
): 内核空间中的代码可以访问所有内存。
问题:用户进程要访问内存时,该怎么办呢?
回答:
Linux
内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的。这样,进程就可以很方便地访问内存,更确切地说是访问 虚拟内存。
- 在用户程序中,凡是与资源有关的操作( 存储分配、进行
I/O
传输以及管理文件等),都必须通过系统调用方式操作系统提出服务请求,并由操作系统代为完成。 - 系统通过 硬件中断机制 进入内核态。
虚拟内存(virtual memory
) :将用户逻辑内存与物理内存分开。
为什么需要虚拟内存?
可以在现有物理内存有限的情况下,为程序员提供了巨大的虚拟内存。
虚拟内存使编程更加容易,因为程序员不再需要担心可用的有限物理内存空间,只需要关注所要解决的问题。
Tips
:由于进程的虚拟地址空间比物理内存大很多,Linux
还提供了一系列的机制,应对内存不足的问题,比如缓存的回收、交换分区Swap
以及OOM
等。
# 来看下进程的 虚拟内存 与 实际使用内存对比:
$ top
...
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
9481 donald 20 0 1.181t 2.384g 119996 R 5.6 15.3 16:04.50 chrome
# VIRT :是进程虚拟内存的大小,只要是进程申请过的内存,即便还没有真正分配物理内存,也会计算在内。
# RES : 进程实际使用的物理内存大小,但不包括 Swap 和共享内存。
# 发现 VIRT 是 1.181t,而 RES 才 2.384g,申请的虚拟内存空间远远大于实际所申请的物理内存
由上图可知:普通进程看到的是内核提供的虚拟内存,这些虚拟内存还需要通过页表,由系统映射为物理内存。
当进程通过
malloc()
或者mmap()
申请内存后,内存并不会立即分配,而是在首次访问时,才通过 缺页异常 陷入内核中分配内存。
进程虚拟地址空间是通过 Paging
(分页)这种方式来映射为物理内存,分页过程:
-
CPU
将要请求的虚拟地址传给MMU
(Memory Management Unit
, 内存管理单元) -
MMU
先在高速缓存TLB
(Translation Lookaside Buffer
, 页表缓存)中查找转换关系- 如果找到相应的物理地址,则直接访问
- 如果找不到,则在地址转换表(
Page Table
)里查找计算
-
最后,进程访问的虚拟地址就对应到实际的物理地址。
虚拟内存空间
虚拟地址空间的内部又被分为 内核空间 和 用户空间 两部分,不同字长(也就是单个 CPU
指令可以处理数据的最大长度)的处理器,地址空间的范围也不同。
-
内核空间(
Kernal Space
) :这个空间只有内核程序可以访问。 -
用户空间(
User Space
) :这部分内存专门给应用程序使用。
比如最常见的 32 位和 64 位系统,如下所示:
- 32位系统:内核空间占 1G,用户空间占 3G。
- 64位系统:内核空间和用户空间均占 128T,剩下中间部分未定义。
虚拟内存空间的分布情况:以 32位系统为例
- 只读段:包括代码和常量等。数据段,包括全局变量等。
- 堆:包括动态分配的内存,从低地址开始向上增长。
- 文件映射段:包括动态库、共享内存等,从高地址开始向下增长。
- 栈:包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB。