免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 797 | 回复: 0

[内核入门] 地址映射准备工作(Linux内核的设置) [复制链接]

论坛徽章:
13
15-16赛季CBA联赛之八一
日期:2016-07-08 21:00:1415-16赛季CBA联赛之同曦
日期:2017-02-15 14:26:1515-16赛季CBA联赛之佛山
日期:2017-02-20 14:19:2615-16赛季CBA联赛之青岛
日期:2017-05-07 16:49:1115-16赛季CBA联赛之广夏
日期:2017-07-30 09:13:1215-16赛季CBA联赛之广东
日期:2018-07-05 22:34:3615-16赛季CBA联赛之江苏
日期:2018-09-03 12:10:2115-16赛季CBA联赛之上海
日期:2018-09-25 03:49:2215-16赛季CBA联赛之广东
日期:2018-09-25 04:09:12
发表于 2016-09-18 14:37 |显示全部楼层
本帖最后由 _nosay 于 2016-09-18 14:47 编辑

  • 软件对于地址映射过程的抽象
    pgd-pmd-pt.png

    这个图似曾相识,跟介绍80386地址映射过程用的图很像,但不完全一样:
    pgd-pt.png
    按照这个图,剧本应该是这样:
    Linux内核:我安排了一个目录表,你到时候根据CR3寄存器以及线性地址的高10位去找。
    80386:yes sir !
    Linux内核:我还安排了一个页表,你找到目录后,再根据线性地址的中间10位去找。
    80386:yes sir !

    结果,Linux内核却不遵守约定,将线性地址的含义多划分了一层!这样还能设置出80386映射过程中需要的表吗?
    答案:能。Linux内核只需要认为PGD下标是10bit、PMD下标是0位、PT下标是10bit,就与80386 CPU的要求一致了。

    Linux内核为什么要这样呢?不多此一举吗?
    答案:不。辛辛苦苦写个内核,总不能只能运行在一种CPU上吧,除了80386,还有很多型号的CPU呢,况且80386只是32位的,还有64位的呢。之前提到过,将映射分为两层,是为了将记录表“撕成”一张张小表,而对于更厚的书,不光分页就够了,还要分册。所以说Linux内核作为软件,是对所有CPU的映射过程做了一次抽象,都抽象为三个步骤,相当于三个活动的格子,当确定要在哪一种具体的CPU执行时,将这三个格子调整为合适的大小,再编译即可。

    那么,Linux对于80386的设置,就要将“PMD格子”的大小移动成0,设置PMD表时,相当于“什么都没做”:
   pgtable-2level.png


  • 用户空间、内核空间
   space(2).png
    之前提到过,Linux作为宏内核,并不单独作为一个进程,通过进程间通信为用户进程提供服务,而是在设计上,将4G虚拟空间划分为“3~4G”(内核空间)+“0~3G”(用户空间),“3~4G”只对应一份映射表(映射规则:只需要减3G偏移即可),任何进程访问这个范围的虚拟地址时,都会访问到相同的物理地址,而每次创建一个新的进程时,会为该进程创建一个属于自己的“0~3G”映射表,内核一般会保证将不同进程的相同虚拟地址映射到不同物理地址(相对于内核空间的映射,要复杂得多)。
    用户态到内核态的转换,并没有进行进程切换,而只是空间的转换,比如EIP原来指向的是0~3G空间中的指令,转换后指向3~4G空间的指令。内核为“0~3G”、“3~4G”虚拟地址设置的段描述符不一样,导致硬件在访问3~4G虚拟地址时,权限检查更严格,用户态直接访问内核空间时会被硬件阻拦,所以Linux提供了系统调用等机制,保证用户进程可以向内核态切换,又不能“擅自”切换(所谓“切换”,前面也提到过,就是硬件状态的改变(《Linux内核源代码情景分析》第三章))。
    这样,所有进程都可以进入3~4G内核空间,并利用内核接口,修改该公共空间,而修改的安全性就需要内核程序员在接口中保证,比如非银行工作人员,在特殊情况下需要进入银行内部时,必须由可信任人员带领着进去,一旦有什么轻举妄动,……

    了解:段寄存器中有13bit用于段描述表下标,所以段描述表最大有8192个描述项,再大就没有意义了,因为通过下标值找不到了,而第一项不用、第二/三项分别用于内核态代码段/数据段、第四/五项分别用于用户态代码段/数据段等,总之最终还有8180个可以用,而每个进程都需要占用两个,分别用于LDT段、TSS段(后面再介绍),所以理论上系统中不会超过个4090进程。


  • 具体设置及映射过程
    以上说明了软件在逻辑上进行了三层映射,并且用户进程在创建时,需要建立0~3G空间某些虚拟地址与物理地址之间的映射,以下通过实例“call 08048368”,具体看一下内核是如何设置以及硬件是如何根据设置找到物理地址的。
   start_thread.png
    按保护模式下段寄存器含义可得:
__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

    由于所举的例子是用户进程中一条call指令,CPU会根据cs寄存器对0x08048368进行段式映射,而内核在创建该用户进程时,将它的cs镜像设置为__USER_CS(即保证切换到该进程时,cs寄存器的值肯定为0x23),根据段寄存器值CPU就会知道,当前进程权限为3(RPL=3),并去全局描述符表(TI=0)找第4项(index=4)作为当前段描述符,即0x00cffa000000ffff。
    展开0x00cffa000000ffff:00000000 11001111 11111010 00000000 00000000 00000000 11111111 11111111
    按段描述符结构体定义得:段基址0,段长度4G,DPL=3,属性为代码段、可读、可执行。
    这样,CPU检查权限(RPL≤DPL)没问题,检查属性也没问题,就用指令中的地址加段基址得到线性地址,仍然是0x08048368,检查越界还是没问题,所以就完成了段式映射过程。

    关于页式映射,只是和段式映射的方法不一样,在整体过程上仍然相同:由Linux内核在创建进程时做好准备,硬件根据映射,最终得到要call的指令的物理地址,即真实的内存位置。


  • 用户进程在启动时,内核为它做好了一切,系统本身在电脑刚开时启动,由谁为它准备呢?
    这种映射关系是为保护模式准备的,而系统刚启动时是实模式运行,根本不依赖这种映射关系,之后才渐渐切换到保护模式(详见《Linux内核源代码情景分析》第十章,系统的引导和初始化)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP