Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
OpenDocCN
think-os-zh
提交
a64a3022
T
think-os-zh
项目概览
OpenDocCN
/
think-os-zh
通知
0
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
T
think-os-zh
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
a64a3022
编写于
7月 02, 2016
作者:
W
wizardforcel
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
ch3
上级
1cfb52d0
变更
1
隐藏空白更改
内联
并排
Showing
1 changed file
with
53 addition
and
0 deletion
+53
-0
ch3.md
ch3.md
+53
-0
未找到文件。
ch3.md
浏览文件 @
a64a3022
...
...
@@ -107,3 +107,56 @@ Address of p is 0x 1c3b010
作为一个练习,你需要在你的电脑上运行这个程序,并将你的结果与我的结果比较。添加对
`malloc`
的第二个调用来检查你系统上的堆区是否向上增长(地址更高)。添加一个函数来打印出局部变量的地址,检查栈是否向下增长。
## 3.5 静态局部变量
栈上的局部变量有时称为“自动变量”,因为它们当函数创建时自动被分配,并且当函数返回时自动被释放。
C语言中又另一种局部变量,叫做“静态变量”,它分配在在
`static`
段上。它在程序启动时初始化,并且在函数调用之间保存它的值。
例如,下面的函数跟踪了它所调用的次数:
```
c
int
times_called
()
{
static
int
counter
=
0
;
counter
++
;
return
counter
;
}
```
`static`
关键字表示
`counter`
是静态局部变量。它的初始化只发生一次,就是程序启动的时候。
如果你将这个函数添加到
`aspace.c`
,你可以确定
`counter`
和全局变量一起分配在
`static`
段上,而不是在栈上。
## 3.6 地址翻译
虚拟地址(VA)如何翻译成物理地址(PA)?基本的机制十分简单,但是简单的实现方式十分耗时,并且占据大量空间。所以实际的实现会复杂一点。
大多数处理器提供了内存管理单元(MMU),位于CPU和主存之间。MMU在VA和PA之间执行快速的翻译。
1.
当程序读写变量时,CPU会得到VA。
2.
MMU将VA分成两部分,称为页码和偏移。“页”是一个内存块,页的大小取决于操作系统和硬件,通常为1~4KiB。
3.
MMU在“页表”里查找页码,然后获取相应的物理页码。之后它将物理页码和偏移组合得到PA。
4.
PA传递给主存,用于读写指定地址。
作为一个例子,假设VA为32位,物理内存为1GiB,划分为1KiB的页面。
+
由于1GiB为
`2 ** 30`
个字节,物理页的数量为
`2 ** 20`
个,它们也成为“帧”。
+
虚拟地址空间的大小为
`2 ** 32`
字节,这个例子中,页的大小为
`2 ** 10`
字节,所以共有
`2 ** 22`
个虚拟页。
+
偏移的大小取决于页的大小。这个例子中页的大小为
`2 ** 10`
字节,所以需要10位来指定页中的一个字节。
+
如果VA是32位,而偏移是10位,剩余的22位构成了虚拟页码。
+
由于共有
`2 ** 20`
个物理页,每个物理页码是20位。加上10位的偏移,PA的结果为30位。
到目前为止,看上去是是可行的。但是让我们考虑一下页表应该占多大。页表最简单的实现是一个数组,每个虚拟页面是一个条目。每个条目都包含一个物理页码,在例子中它是20位,加上每帧的一些额外的数据,所以我们认为每个条目占用3~4个字节。由于共有
`2 ** 22`
个虚拟页,页面共需要
`2 ** 24`
个字节,或16MiB。
由于我们需要为每个进程创建一个页表,一个运行256个进程的系统就需要
`2 ** 32`
个字节,或者4GiB,这还只是页面的空间!这些就占用了全部32位虚拟地址。而在48或64位的虚拟地址上,这个数量更加荒谬。
幸运的是,并不需要这么大的空间,因为大多数进程不使用虚拟地址空间的每个小片段。而且,如果一个进程不使用某个虚拟页面,我们也不需要在页表中为其分配条目。
也就是说,页表是“稀疏”的,这暗示了最简单的实现,即页表条目的数组是个糟糕的想法。幸运的是,稀疏数组有一些不错的实现方式。
一种选择是多级页表,它被多数操作系统例如Linux所采用。另一种选择是关联表,其中每个条目包含虚拟页码和物理页码。在软件上搜索关联表会非常慢,但是硬件上我们可以并行搜索整个表,所以关联数组经常用于在MMU中表示页表。
你可以在
[
页表的维基百科页面
](
http://en.wikipedia.org/wiki/Page_table
)
阅读更多关于这些实现的信息。你也可能会找到有趣的细节。但是基本的想法就是页表应做成稀疏的,所以我们需要为稀疏数组选择一个好的实现方式。
我之前提到了操作系统可以中断一个运行中的进程,保存它的状态,之后运行其它进程。这个机制叫做“上下文切换”。由于每个进程都有自己的页表,操作系统需要和MMU配合来保证每个进程拿到了正确的页表。在旧机器上,MMU中的页表信息在每次上下文切换时会被替换掉,开销非常大。在新的系统中,MMU的每个页表条目包含进程ID,所以多个进程的页表可以同时储存在MMU中。
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录