-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3cdf006
commit dcba216
Showing
1 changed file
with
137 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
--- | ||
title: 内存管理 | ||
date: 2022-11-19 22:32:12 | ||
categories: | ||
- Backend | ||
tags: | ||
- Operating System | ||
- 操作系统 | ||
--- | ||
|
||
# 内存管理 | ||
|
||
## 虚拟内存 | ||
|
||
### 引入的目的 | ||
|
||
把进程所使用的地址「隔离」开来,即让操作系统为每个进程分配独立的一套「**虚拟地址**」,互不干涉。但是有个前提每个进程都不能访问物理地址,至于虚拟地址最终怎么落到物理内存里,对进程来说是透明的,操作系统已经把这些都安排的明明白白了。 | ||
|
||
### 实现 | ||
|
||
> **操作系统会提供一种机制,将不同进程的虚拟地址和不同内存的物理地址映射起来。** | ||
> | ||
> 如果程序要访问虚拟地址的时候,由操作系统转换成不同的物理地址,这样不同的进程运行的时候,写入的是不同的物理地址,这样就不会冲突了。 | ||
 | ||
|
||
### 操作系统管理虚拟地址与物理地址之间的关系:**分页和分段** | ||
|
||
#### 内存分段 Segmentation | ||
|
||
分段的好处是能产生连续的内存空间,但是会出现外部内存碎片和内存交换过大的问题。 | ||
|
||
段选择因子和段内偏移量: | ||
|
||
- **段选择子**就保存在段寄存器里面。段选择子里面最重要的是**段号**,用作段表的索引。**段表**里面保存的是这个**段的基地址、段的界限和特权等级**等。 | ||
- 虚拟地址中的**段内偏移量**应该位于 0 和段界限之间,如果段内偏移量是合法的,就将段基地址加上段内偏移量得到物理内存地址。 | ||
|
||
 | ||
|
||
但它也有一些不足之处: | ||
|
||
- 第一个就是**内存碎片**的问题。(外部内存碎片)原因:些未分配的连续内存区域太小,以至于不能满足任意进程的内存分配请求。解决方法是内存交换。 | ||
- 第二个就是**内存交换的效率低**的问题。原因:经常会产生外部碎片,需要进行和硬盘之间的内存交换,重新Swap区域,会产生性能瓶颈。 | ||
|
||
#### 内存分页 Paging | ||
|
||
**分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小**。这样一个连续并且尺寸固定的内存空间,我们叫**页**(*Page*)。在 Linux 下,每一页的大小为 `4KB`。 | ||
|
||
 | ||
|
||
##### 缺页异常 | ||
|
||
而当进程访问的虚拟地址在页表中查不到时,系统会产生一个**缺页异常**,进入系统内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行。 | ||
|
||
##### 内部碎片 | ||
|
||
**采用了分页,页与页之间是紧密排列的,所以不会有外部碎片。**但是,因为内存分页机制分配内存的最小单位是一页,即使程序不足一页大小,我们最少只能分配一个页,所以页内会出现内存浪费,所以针对**内存分页机制会有内部内存碎片**的现象。 | ||
|
||
##### 换入换出 | ||
|
||
如果内存空间不够,操作系统会把其他正在运行的进程中的「最近没被使用」的内存页面给释放掉,也就是暂时写在硬盘上,称为**换出**(*Swap Out*)。一旦需要的时候,再加载进来,称为**换入**(*Swap In*)。所以,一次性写入磁盘的也只有少数的一个页或者几个页,不会花太多时间,**内存交换的效率就相对比较高。** | ||
|
||
 | ||
|
||
##### 缺陷 | ||
|
||
有**空间**上的缺陷。 | ||
|
||
在 32 位的环境下,虚拟地址空间共有 4GB,假设一个页的大小是 4KB(2^12),那么就需要大约 100 万 (2^20) 个页,每个「页表项」需要 4 个字节大小来存储,那么整个 4GB 空间的映射就需要有 `4MB` 的内存来存储页表。 | ||
|
||
这 4MB 大小的页表,看起来也不是很大。但是要知道每个进程都是有自己的虚拟地址空间的,也就说都有自己的页表。 | ||
|
||
那么,`100` 个进程的话,就需要 `400MB` 的内存来存储页表,这是非常大的内存了,更别说 64 位的环境了。 | ||
|
||
##### 多级页表 | ||
|
||
将页表(一级页表)分为 `1024` 个页表(二级页表),每个表(二级页表)中包含 `1024` 个「页表项」,形成**二级分页**。 | ||
|
||
 | ||
|
||
> 如果使用了二级分页,一级页表就可以覆盖整个 4GB 虚拟地址空间,但**如果某个一级页表的页表项没有被用到,也就不需要创建这个页表项对应的二级页表了,即可以在需要时才创建二级页表**。 | ||
> 那么为什么不分级的页表就做不到这样节约内存呢? | ||
> | ||
> 我们从页表的性质来看,保存在内存中的页表承担的职责是将虚拟地址翻译成物理地址。假如虚拟地址在页表中找不到对应的页表项,计算机系统就不能工作了。所以**页表一定要覆盖全部虚拟地址空间,不分级的页表就需要有 100 多万个页表项来映射,而二级分页则只需要 1024 个页表项** | ||
> 对于 64 位的系统,两级分页肯定不够了,就变成了四级目录 | ||
##### TLB | ||
|
||
多级页表虽然解决了空间上的问题,但是虚拟地址到物理地址的转换就多了几道转换的工序,这显然就降低了这俩地址转换的速度,也就是带来了时间上的开销。 | ||
|
||
我们就可以利用这一特性,把最常访问的几个页表项存储到访问速度更快的硬件,于是计算机科学家们,就在 CPU 芯片中,加入了一个专门存放程序最常访问的页表项的 Cache,这个 Cache 就是 TLB(*Translation Lookaside Buffer*) ,通常称为页表缓存、转址旁路缓存、快表等。 | ||
|
||
 | ||
|
||
#### 段页式内存管理 | ||
|
||
##### 管理方式 | ||
|
||
- 先将程序划分为多个有逻辑意义的段,也就是前面提到的分段机制; | ||
- 接着再把每个段划分为多个页,也就是对分段划分出来的连续空间,再划分固定大小的页; | ||
|
||
这样,地址结构就由**段号、段内页号和页内位移**三部分组成。 | ||
|
||
 | ||
|
||
### Linux的虚拟地址空间分布 | ||
|
||
在 Linux 操作系统中,虚拟地址空间的内部又被分为**内核空间和用户空间**两部分,不同位数的系统,地址空间的范围也不同。比如最常见的 32 位和 64 位系统,如下所示: | ||
|
||
 | ||
|
||
- `32` 位系统的内核空间占用 `1G`,位于最高处,剩下的 `3G` 是用户空间; | ||
- `64` 位系统的内核空间和用户空间都是 `128T`,分别占据整个内存空间的最高和最低处,剩下的中间部分是未定义的。 | ||
|
||
> 内核空间与用户空间的区别: | ||
> | ||
> - 进程在用户态时,只能访问用户空间内存; | ||
> - 只有进入内核态后,才可以访问内核空间的内存; | ||
虽然每个进程都各自有独立的虚拟内存,但是**每个虚拟内存中的内核地址,其实关联的都是相同的物理内存**。这样,进程切换到内核态后,就可以很方便地访问内核空间内存。 | ||
|
||
 | ||
|
||
### 用户空间分布 | ||
|
||
 | ||
|
||
通过这张图你可以看到,用户空间内存,从**低到高**分别是 6 种不同的内存段: | ||
|
||
- 程序文件段(.text),包括二进制可执行代码; | ||
- 已初始化数据段(.data),包括静态常量; | ||
- 未初始化数据段(.bss),包括未初始化的静态变量; | ||
- 堆段,包括动态分配的内存,从低地址开始向上增长; | ||
- 文件映射段,包括动态库、共享内存等,从低地址开始向上增长; | ||
- 栈段,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 `8 MB`。当然系统也提供了参数,以便我们自定义大小; |