linux 内核采用页式存储管理。虚拟地址空间划分成固定大小的“页面”,由MMU在运行时将虚拟地址映射成某个物理内存页面中的地址。页式内存管理比段式内存管理有很多好处,但是由于Intel是先使用段式管理的,然后才发明了页式管理,为了兼容,i386 CPU 一律对程序中使用的地址先进行段式映射,然后才能进行页式映射,既然CPU的硬件结构是这样,linux内核也只好服从intel的选择。通过一个例子看看linux内核是怎样在i386 CPU 上进行地址映射的。

假设我们写了这么一个程序:

 1 #include <stdio.h>
2
3 greeting()
4 {
5 printf("hello, world!\n");
6 }
7
8 main()
9 {
10 greeting();
11 }

经过编译后,我们得到可执行代码hello。用命令:objdump -d hello来反汇编这一段二进制代码

从上面可以看出,ld给greeting()分配的地址为0x80483e4,在elf格式代码中,ld 总是从0x80000000开始安排程序的“代码段”,对每个程序都是这样,至于程序在执行时在物理内存中的实际位置则就要由内核在为其建立内存映射时临时作出安排。

假设该程序已经在运行,整个映射机制都已经建立好,并且CPU 正在执行main()中的“call 80483e4”这条指令,要转移到虚拟地址0x080483e4上去,接下来就一步一步的走过这个地址映射过程。

首先是段式映射,由于地址0x080483e4是一个程序的入口,更重要的是在执行的过程中是由CPU 中的“指令计数器”EIP所指向的,所以在代码段中,因此,i386 CPU使用代码段寄存器CS的当前值来作为段式映射的“选择码”,也就是用它作为在段描述表中的下标。

接下来看看CS的内容,内核建立一个进程时都要将其段寄存器设置好(?),代码在include/asm-i386/processor.h 中:

可以看到,除了CS被设置成USER_CS外,其他的所有段寄存器都被设置成USER_DS。就是说,虽然Intel 的意图是将一个进程的映像分成代码段、数据段和堆栈段,linux 内核中堆栈段和数据段式不分的。

再来看看USER_CS和USER_DS到底是什么

也就是说,linux 内核中只是用四种不同的段寄存器值,两种用于内核本身,两种用于所有的进程。现在我们将这四种数值用二进制展开并与段寄存器的格式对照:

Index               TI   RPL

————————————————————————————————————————————

__KERNEL_CS    0x10        0000  0000  0001  0  |  0  | 0  0

__KERNEL_DS    0x18        0000  0000  0001  1  |  0  | 0  0

__USER_CS      0x23        0000  0000  0010  0  |  0  | 1  1

__USER_DS      0x2B        0000  0000  0010  1  |  0  | 1  1

经过和段寄存器的格式对照:

__KERNEL_CS       index = 2              TI = 0              RPL = 0

__KERNEL_DS       index = 3              TI = 0              RPL = 0

__USER_CS         index = 4              TI = 0              RPL = 3

__USER_DS         index = 5              TI = 0              RPL = 3

TI 都是0说明用的都是GDT,RPL只用了0和3,内核为0级,用户为3级

我们上面写的程序显然不属于内核,所以在进程的用户空间中运行,内核在调度该进程进入运行时,把CS设置成__USER_CS,即0x23,所以,CPU 以4为下标在全局描述表GDT中招对应的段描述项。

GDT内容如下:

把4个段描述项的内容按二进制展开,和段描述项的定义对照,可以得出以下结论:

每个段都是从0地址开始的整个4GB空间,虚地址到线性地址的映射保持不变。所以,linux 内核设计的段式映射机制把地址0x080483e4 映射到了自身,现在作为线性地址出现了,下面进入页式映射过程:

每个进程都有其自身的页面目录PGD,指向这个目录的指针保持在每个进程的mm_struct数据结构中,每当调度一个进程进入运行的时候,内核都要为即将运行的进程设置好控制寄存器CR3,而MMU的硬件则总是从CR3中取得指向当前页面目录的指针。不过,CPU 在执行程序时使用的虚存地址,而MMU硬件在进行映射时所用的则是物理地址,这是在switch_mm()函数中完成的

1 static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, struct task_struct *tsk, unsigned cpu)
2 {
3 ……
4
5 asm volatile("movl %0, %%cr3" : : "r" (__pa(next->pgd)));
6 ……
7 }

当我们程序中要转移到地址0x80483e4去的时候,进程正在运行中,CR3早已设置好,指向我们这个进程的页面目录了,先将线性地址展开:

0000 1000 0000 0100 1000 0011 1110 0100

0000100000  0001001000   0011 1110 0100

32                    72                 996

已CR3中的内容指向的地址为基地址,以32为下标就找到了目录项,取得到的目录项中的高20位然后加上12个0就得到了该页面表的指针。同理再以刚刚找到的页面表指针为基地址,以72为下标,找到页表项,然后取高20位,再加上996即是greeting()函数的物理地址。

linux 内核源代码情景分析——地址映射的全过程的更多相关文章

  1. Linux内核源代码情景分析系列

    http://blog.sina.com.cn/s/blog_6b94d5680101vfqv.html Linux内核源代码情景分析---第五章 文件系统  5.1 概述 构成一个操作系统最重要的就 ...

  2. Linux内核源代码情景分析-fork()

    父进程fork子进程: child = fork() fork经过系统调用.来到了sys_fork.具体过程请參考Linux内核源码情景分析-系统调用. asmlinkage int sys_fork ...

  3. linux 内核源代码情景分析——linux 内存管理的基本框架

    386 CPU中的页式存管的基本思路是:通过页面目录和页面表分两个层次实现从线性地址到物理地址的映射.这种映射模式在大多数情况下可以节省页面表所占用的空间.因为大多数进程不会用到整个虚存空间,在虚存空 ...

  4. linux 内核源代码情景分析——linux 内核源代码中的C语言代码

    linux 内核的主体是以GNU的C语言编写的,GNU为此提供了编译工具gcc.GNU对C语言本身作了不少扩充. 1) gcc 从 C++ 语言中吸收了"inline"和" ...

  5. linux 内核源代码情景分析——用户堆栈的扩展

    上一节中,我们浏览了一次因越界访问而造成映射失败从而引起进程流产的过程,不过有时候,越界访问时正常的.现在我们就来看看当用户堆栈过小,但是因越界访问而"因祸得福"得以伸展的情景. ...

  6. Linux内核源代码情景分析-中断半

    一.中断初始化 1.中断向量表IDT初始化 void __init init_IRQ(void) { int i; #ifndef CONFIG_X86_VISWS_APIC init_ISA_irq ...

  7. linux 内核源代码情景分析——越界访问

    页式存储管理机制通过页面目录和页面表将每个线性地址转换成物理地址,当遇到下面几种情况就会使CPU产生一次缺页中断,从而执行预定的页面异常处理程序: ① 相应的页面目录或页表项为空,也就是该线性地址与物 ...

  8. linux 内核源代码情景分析——几个重要的数据结构和函数

    页面目录PGD.中间目录PMD和页面表PT分别是由表项pgd_t.pmd_t和pte_t构成的数组,而这些表项都是数据结构 1 /* 2 * These are used to make use of ...

  9. linux 内核源代码情景分析——linux 内核源码中的汇编语言代码

    1. 用汇编语言编写部分核心代码的原因: ① 操作系统内核中的底层程序直接与硬件打交道,需要用到一些专用的指令,而这些指令在C语言中并无对应的语言成分: ② CPU中的一些特殊指令也没有对应的C语言成 ...

随机推荐

  1. 10.6Java学习

    1.类,对象,方法的定义.2.标识符分为两类:关键字/常见的基本类型:boolean(布尔型),byte(字节型),char(字符型),double(双精度),float(浮点),int(整型),lo ...

  2. springboot:@ConditionalOnProperty根据不同时机注入不同实现的bean

    一.引言 在开发中经常会碰到这样的情形,一个接口会有不同的实现,但在开发中都是基于接口的注入,那么怎么根据不同的需求注入不同的类型就是一个值得考虑的问题.在注入属性时常用的两个注解是@Autowire ...

  3. webpack线上和线下模式

    区别: 1 线下模式代码没有压缩,source-map是全的,比较容易定位错误,调试方便 2 线上模式的代码是压缩的,文件小, 分开打包: 方式一:有点麻烦 在package.json文件 " ...

  4. requests接口自动化-assert断言

    断言,自动判断接口返回的结果与预期结果是否一致 from common.get_mysql import * def test_assert(): a=0 b=1 # assert a,'断言失败打印 ...

  5. P5048-[Ynoi2019 模拟赛]Yuno loves sqrt technology III【分块】

    正题 题目链接:https://www.luogu.com.cn/problem/P5048 题目大意 就是这个 [QA]区间众数,但空间很小 长度为\(n\)的序列,要求支持查找区间众数出现次数. ...

  6. JPA基本用法

    jpa基本查询 1.继承JpaRepository,生成了CRUD方法 public void testBaseQuery() throws Exception {   User user=new U ...

  7. Maccms8.x(苹果cms)命令执行漏洞

    getshell payload(a): http://0-sec.org/index.php?m=vod-search&wd={if-A:assert($_POST[a])}{endif-A ...

  8. Java网络爬虫技术《一》 HttpClient

    HttpClient HttpClient 是Apache Jakarta Common 下的子项目,可以用来提供高效的.最新的.功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP ...

  9. NOIP 模拟 六十八

    咕了十几场了,还是写一写吧.. T1 玩水 发现满足三个人路径不同必须要有2个及以上的斜线相同结构,需要注意如果同一行或者同一列的话必须要相邻才行. #include<bits/stdc++.h ...

  10. The Data Way Vol.1|风口下的开源市场:如何看待开源与商业的关系?

    关于「The Data Way」 「The Data Way」是由 SphereEx 公司出品的一档播客节目.这里有开源.数据.技术的故事,同时我们关注开发者的工作日常,也讨论开发者的生活日常:我们聚 ...