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. 在PHP中使用SPL库中的对象方法进行XML与数组的转换

    虽说现在很多的服务提供商都会提供 JSON 接口供我们使用,但是,还是有不少的服务依然必须使用 XML 作为接口格式,这就需要我们来对 XML 格式的数据进行解析转换.而 PHP 中并没有像 json ...

  2. PHP设计模式之备忘录模式

    备忘录,这个名字其实就已经很形象的解释了它的作用.典型的例子就是我们原来玩硬盘游戏时的存档功能.当你对即将面对的大BOSS有所顾虑时,一般都会先保存一次进度存档.如果挑战失败了,直接读取存档就可以恢复 ...

  3. C# 将PPT转为OFD/DPT/DPS/ODP/POTX/UOP

    本文分享在C#代码程序中,如何将PPT幻灯片文档转换为多种文件格式,如:OFD.DPT.DPS.ODP.POTX.UOP等.只需在加载PPT幻灯片源文档后,调用ppt.SaveToFile(strin ...

  4. 搭建http文件服务器 - python3使用http.server搭建http文件服务器

    适用场景 只要有python3就可以,windows系统cmd窗口直接敲命令,Linux系统,直接敲命令 step-1 cd cd 到需要搭建服务器的目录 step-2 http.server pyt ...

  5. 写SQL的套路

    定义问题 转化问题 如要解决的问题是:查出每门课程成绩都大于80分学生的姓名,可以转化为:只要学生最小分数的课程大于80分,就是所有课程成绩都大于80分. 查询同名同姓学生名单并统计同名人数--> ...

  6. python风格对象

    对象表示形式 python提供了两种获取对象字符串表示形式的标准方式 repr()         //便于开发者理解的方式返回对象的字符串表示形式(一般来说满足obj==eval(repr(obj) ...

  7. mybatis: No enum constant org.apache.ibatis.type.JdbcType."VARCHAR"

    mybatis 插入的时候一句sql报错如下. org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ib ...

  8. Unity3D组成

    从宏观的角度来看,分为七个模块: 1.图形模块(Graphics). 2.物理模块(Physics) 3. 音效模块(Audio) 4.动作模块(Animation) 5.导航模块(Navigatio ...

  9. aizhan查询旁IP网站脚本

    <?php print_r("-------------------------\r\n"); print_r("-------爱站旁站查询------\r\n&q ...

  10. Unity——FSM有限状态机

    FSM有限状态机 一.设计思路 1.共同的状态父类,提供可重写的进入,保持,退出该状态的生命周期方法: 2.状态机,管理所有状态(增删查改),状态机运行方法(Run): 3.在角色控制器中,实例化状态 ...