免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1341 | 回复: 0
打印 上一主题 下一主题

[内核入门] Intel i386 CPU(保护模式下的段式内存管理) [复制链接]

论坛徽章:
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
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2016-09-14 22:13 |只看该作者 |倒序浏览
本帖最后由 _nosay 于 2016-09-15 00:53 编辑

  • 段寄存器出现背景
   
    上图为Intel CPU的发展过程,学过《微机原理》这本书的朋友,应该对8086这块CPU比较熟悉,它是16位的,即逻辑运算单元(ALU)每次可以计算16位数据,而数据总线却为20位。C语言中的基本数据类型大小(char 8bit, short 16bit, int 32bit, long 32/64bit)是依据ALU位数设计的,而不是数据总线位数,所以说在任何一门计算机语言里,看不到20bit的基本数据类型,所以运行8086 CPU上的程序,如果用两个16bit变量表示地址,则每个地址都会浪费12bit,Intel那帮无孔不入的天才,怎会容忍的了,所以诞生了段寄存器。
    题外话,和内核学习无关:提到寄存器,不禁想起汇编语言对于段寄存器赋值的一些限制,比如不能给CS段寄存器赋值,但可以通过call,ret或jmp指令间接的达到修改CS的目的,DS、SS、ES也不能直接赋立即数,而是要通过通用寄存器赋值(就是因为一些“没有理由的限制”,让我一直学不进汇编过,后来学习opcode,即机器码格式规范,摸清了一部分理由,比如不同的汇编指令会得到同一条opcode,那只好在汇编器里限制其中一条属于不合法指令)。

  • 保护模式

    等发展到80386的时候,已经完全支持保护模式(即实模式<->保护模式可以双向切换,而80286只支持从实模式->保护模式切换),而且它的ALU和数据总线都是32位,一个地址正好可以由一个32bit变量表示了,按道理80386 CPU里面不再需要有段寄存器这种东西了。然而,比如一个i386之前的电脑用户,发现换个80386的CPU后,之前用习惯的一些软件都用不了了,然后坏事传千里,80386 CPU还能卖的出去吗?所以Intel攻城狮们决定留下段寄存器,但也不是白留,还是要尽量“榨取”,所以在段式内存管理的基础上实现保护模式(下面即将学习)。

    实模式<->保护模式“切换”,包括今后将会学习的用户态<->内核态“切换”,切换的本质是什么?
    不妨先想想大自然现象的变化,白天黑夜在切换,一年四季在切换,其实就是太阳与地球位置关系的一个变化,换到计算机的世界里,其实就是一些硬件状态的变化。我曾经问过老师,为什么在C代码里写个if,它就表示“如果”?不都是C语言写的程序么,为什么别人写的就是内核代码,权限很高,想干嘛干嘛,而我写的就不可以?其实都反应在硬件的状态上:编译器将if编译成01串,电路按照这个01串加上高低电平,它就成为一个“判断电路”(要了解数字电路等电子工程的知识);内核代码由BIOS启动,而用户程序由内核启动,内核优先拿到权限,并且限制了用户程序的权限,所以内核与用户程序的区别,本质不是在指令上,而是它们执行时硬件的状态不一样,比如如果用内核代码编译出来的二进制代码,能在shell里启动,那它就是一个用户进程。

    硬件状态具体是指哪些硬件呢?
    主要是一些寄存器,比如标志寄存器、段寄存器。比如保护模式时,段寄存器的含义就不再像实模式时单纯表示段基址,就有3bit用于表示状态:
   
    这里顺便也看到段寄存器确实不只是为实模式保留,在保护模式下并没有闲着没用,还是被利用的满满的:3~15bit表示一个全局/局部描述符表下标,TI位表示使用全局还是局部描述符表,RPL表示当前进程处于用户态还是内核态(不知道有没有朋友对于Linux内核存在这样的错觉:内核代码是由一个权限最高的进程独立执行。其实层次式内核,确实是这样,但Linux是宏内核,内核指令相当于一块公共区域,可以在用户进程的上下文环境中执行,只不过用户必须通过系统调用/陷阱/中断进入内核态,就像银行,普通人不能随意进出,但是你的钱可以合理的进出,另外关于用户态与内核态的切换,上述也提到了,本质就是硬件状态的改变,详见《Linux内核代码情景分析》第三章内容)。

    虽然80386 CPU的地址可以用单独一个变量表示了,但那样为仅仅能表示段的起始地址,而保护模式,需要关于段的更多信息,比如访问这个段最低要求什么权限、是否允许读/写等,所以出现了段描述符结构:
   
    其中B31-B24、B23-B16、B15-B0合起来32位表示段基址,其它的即为保护模式下要求的额外信息,至少为什么结构设计这么奇怪,之前提到过:Intel发展过程中对兼容性的考虑。

    上述说明过,段寄存器3~15bit表示一个全局/局部描述符表下标,那么硬件在完成地址映射的过程中,去哪找全局/局部描述符表呢?
    为此,80386 CPU增加了两个寄存器:GDTR、LDTR,分别指向全局描述符表、局部描述符表。并且由于是新增的,旧版本的CPU没有这两个寄存器,所以80386又将访问/修改这两个寄存器的指令设计为“特权指令”,从而为内核态与用户态的分离,做出了基础保障。

    实际上,Linux内核采用的是页式内存管理,只不过80386 CPU的设计,决定了MMU进行地址映射时必须得经过段式映射,所以Linux内核也不得不设置一个描述符表,保证硬件这步操作通过,但内核根本就“不稀罕”能从段式管理过程中得到什么好处,比如它将描述符中的段基址设置为0,长度设置为4G,也不指望依靠p标志位对段进行虚存管理(交换分区技术:将暂时不用的内存换出到磁盘,需要时再换入,不要与虚拟地址管理混淆),也几乎不用局部描述符表,所以就不详细分析段式管理了(后面会一起学习更先进的页式内存管理机制)。

    最后提醒一下,实模式-保护模式、段式内存管理-页式内存管理,是两组独立的概念,实模式下段式管理,段寄存器存的仅仅是段基址,保护模式下,段寄存器的含义被划分为3~15bit+TI+RPL。另外CPU处于实模式还是保护模式,是否开启了段式映射/页式映射,这些都属于硬件状态,可以通过标志寄存器中相应的位进行设置或判断。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP