单片机怎么改成汇编_单片机汇编 - CSDN
  • 实践!C语言是怎么变成汇编

    千次阅读 2015-03-08 20:27:24
    在大学计算机组成原理一课中学习各种汇编语言跟C语言的关系,同时在单片机接口...但是从来没亲自实践一下C语言是怎么变成汇编的过程 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”

    “郭孟琦 + 原创作品转载请注明CSDN博客 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”


    在大学计算机组成原理一课中学习各种汇编语言跟C语言的关系,同时在单片机接口技术中也学习了C51的汇编语言,在一些MCU调试中也看到了反汇编的内容。但是从来没亲自实践一下C语言是怎么变成汇编的过程以及他们之间的对应关系。作为作业今天就在这里写下我的发现吧。


    按照要求这是一段很简单的c语言程序


    进行编译生成汇编文件

    生成的main.s文件 已经把多余的命令删除了(gvim好像确实没vim好用。。。)


    一开始是这样的 空栈

    ebp!esp!
     
     
     
     

    程序从main标签处运行,开始push,mov后(我用ebp!和esp!来表示实际的ebp esp所指向的位置吧,表示起来好麻烦 ebp(main)是指向0那个位置的ebp 就是表格最上边那个位置)

     
    ebp!esp!ebp(main)
     
     
     
    到了subl esp减4 向下移动一个字节 并且把立即数233存到了esp指向的位置(间接寻址?)

     
    ebp!ebp(main)
    esp! 233
     
     
    当执行call f时 eip会被push

     
    ebp!ebp(main)
     233
    esp!eip(main)
     
    接下来看f标签

    在push 和movl后 ebp(f)是指向第二行那个ebp

     
    ebp(main)
     233
    eip(main)
    esp!ebp!ebp(f)
     
    同样esp在减4后向下移动1个地址

     
    ebp(main)
     233
    eip(main)
    ebp!ebp(f)
    esp!
     
    接下来因为栈空间向下生长,变址寻址向上加8也就是向上2个位置(就是233)存入eax

    接下来要把eax的内容存入esp指向的位置就成了

     
    ebp(main)
     233
    eip(main)
    ebp!ebp(f)
    esp!233
     
    又一次的Call

     
    ebp(main)
     233
    eip(main)
    ebp!ebp(f)
    233
    esp!eip(f)
    进入g标签,前两句类似(其实就是存储原来ebp的位置将ebp指向新的位置),变址寻址向上加8也就是向上2个位置(就是233)存入eax

     
    ebp(main)
     233
    eip(main)
    ebp(f)
    233
    eip(f)
    ebp!esp!ebp(g)
     
    然后666加上eax里的内容也就是c语言中的 “x +666”啦 到这里函数的嵌套总算是完事了开始一层层恢复,首先是popl ebp,ebp指向f 中的ebp位置了 同时 esp也会加4向上移动

     
    ebp(main)
     233
    eip(main)
    ebp!ebp(f)
    233
    esp!eip(f)
    ebp(g)
     
    然后ret相当于 pop eip程序回到 刚才 eip(f)的那个位置啦

     
    ebp(main)
     233
    eip(main)
    ebp!ebp(f)
    esp!233
    eip(f)
    ebp(g)
     
    leave实际上是

    mov %ebp %esp   

    pop %ebp

    也就是将上一级的栈恢复回来(有个问题为什么在g里没有leave? 我认为popl %ebp就ok了 因为当时后序操作并未改变esp,esp和ebp指向同一位置 就没必要mov %ebp %esp 了可能就被优化掉了。)

    要注意mov后pop操作又再一次改变了esp指向的位置

     
    ebp!ebp(main)
     233
    esp!eip(main)
    ebp(f)
    233
    eip(f)
    ebp(g)
     

    我们继续走ret

     
    ebp!ebp(main)
    esp!233
    eip(main)
    ebp(f)
    233
    eip(f)
    ebp(g)
     
    别忘了结果一直在存着eax,现在再加1 就是c语言里 的f(233)+1

    最后leave恢复之前的 ebp和esp

    ebp!esp!
    ebp(main)
    233
    eip(main)
    ebp(f)
    233
    eip(f)
    ebp(g)
     
     跟刚开始是不是一样了!


    那么到现在为止,计算机是如何工作的?

    我认为计算机的工作方式就是执行一系列指令,在其中当我们只用高级语言中的“函数调用”等需要跳转时计算机总会先保存现在的状态然后再处理完成后再依次恢复之前的状态,同时需要“带回去”的数据另存在其他地方比如:eax。此外我终于明白为什么C语言只能有一个返回值了。。。。

    展开全文
  • 51单片机汇编改C语言(一)

    千次阅读 2019-05-11 16:48:29
    结果老师给了他一份汇编程序,由于自己之前做过汇编编程的工作,就胆(no)大(zuo)妄(no)为(dai)的说是给他翻译成C语言的。 当我仔细瞅了那份程序之后。额,好吧,我只能说我尽力而为吧! 东西只有一篇近2000行的汇编...

    之前答应给同学帮忙做毕设,想着应该很简单。结果老师给了他一份汇编程序,由于自己之前做过汇编编程的工作,就胆(no)大(zuo)妄(no)为(dai)的说是给他翻译成C语言的。
    当我仔细瞅了那份程序之后。额,好吧,我只能说我尽力而为吧!

    东西只有一篇近2000行的汇编程序和一份原理图,程序中无任何变量,需要变量或者是标志位时,直接操作一个RAM地址,子程序起名也是相当佛系。目前我只注释了一小部分的程序,大概三四百行吧,贴出来,作为一个笔记,以供后面学习。
    由于不是本人程序,所以会在程序中做删减,但不会影响汇编语言的学习。注释为?的,意思是我也没看懂到底什么意思……同样的内容,在前面注释过的,后面不再注释。最后,向那个年代用汇编写单片机程序的老师们致敬。嘻嘻!_!

    	ORG 0000H	;程序起始地址
    	LJMP MAIN
    	ORG 0003H	;外部中断0	
    	LJMP I0000
    	ORG 000BH	;定时器0
    	LJMP T0000
    	ORG 0023H	;串行中断
    	LJMP ES000
    	ORG 0030H	;程序存储区(用户RAM)
    MAIN:
    	MOV R0,#7FH		;将数据7FH存入R0寄存器
    MAIN1:
    	MOV @R0,#00H	;将0存入7FH地址
    	DJNZ R0,MAIN1	;给R0-1,若不为0,则跳转到MAIN1,上面这两条语句是一个小循环,意思是将00-7FH地址清0,以便后续使用。
    	MOV SP,#60H		;将60H存入堆栈指针,即栈顶指针地址为60H	
    	LCALL WR100		;转到WR100,写数据到外部器件,应该是初始化外部器件
    	MOV TMOD,#21H	;配置定时器控制寄存器,T1为8位自动加载计数器模式,T2为,16位定时器模式
    	MOV TH0,#0FAH
    	MOV TL0,#00H	;T0
    	MOV TH1,#0FBH
    	MOV TL1,#0FBH	;T1
    	ANL PCON,#7FH	;配置电源控制寄存器,默认值
    	SETB SM0		;以下6行配置SCON串行通信控制寄存器
    	SETB SM1		;配置串行通信波特率为11:由定时器T1或T2的溢出率和SMOD所定:2SMOD ×(T1溢出率)/32
    	CLR SM2			;多机通信控制位
    	CLR RI			;接收中断标志位
    	CLR TI			;发送终端标志位
    	CLR REN			;禁止接收
    	SETB P1.4		
    	SETB TR0		;允许T0计数
    	SETB TR1		;允许T1计数
    	SETB ET0		;定时器0中断充许
    	SETB ES			;打开串行中断
    	SETB PT0		;定时器0中断优先
    	SETB EX0		;打开外部中断INT0
    	SETB EA			;打开总中断
    	CLR P1.0		;根据原理图,P1.0接在外部看门狗上,应该是喂狗信号,后面同理		
    	SETB P1.0
    	MOV DPTR,#0E000H	;将0E00H写入DPTR(数据指针寄存器),片外RAM首地址?
    MAIN2:
    	MOV A,#00H		
    	MOVX @DPTR,A
    	INC DPTR
    	MOV A,DPH
    	CJNE A,#0E4H,MAIN2	;清零0E000H-0E4000H地址区间
    	CLR P1.0
    	SETB P1.0
    	MOV DPTR,#0F000H	
    MAIN3:
    	MOV A,#00H
    	MOVX @DPTR,A
    	INC DPTR
    	MOV A,DPH
    	CJNE A,#0F5H,MAIN3	;清零0F000H-0F5000H地址区间
    	CLR P1.0
    	SETB P1.0	
    	MOV DPTR,#0E000H
    	MOV A,#00H
    	MOVX @DPTR,A	
    	INC DPTR
    	MOVX @DPTR,A	
    	INC DPTR
    	MOVX @DPTR,A	
    	INC DPTR
    	MOVX @DPTR,A		;清零0E000H-0E0003H地址区间	
    	INC DPTR
    	MOV A,#0F1H
    	MOVX @DPTR,A	
    	INC DPTR
    	DEC A
    	MOVX @DPTR,A		;将0F1H写入0E0004H,将0F0H写入0E0005H
    	INC DPTR		
    	MOV A,#0C0H
    	MOVX @DPTR,A		;将0C0H写入0E0006H
    	INC DPTR
    	MOV A,#05H			
    	MOVX @DPTR,A		;将05H写入0E0007H
    	MOV DPTR,#7FFFH		
    	CPL A
    	CJNE A,#03H,MAIN4	;?
    MAIN4:
    	JNC MAIN5
    	MOV A,#03H			;以下赋值原理同上,不再解释			
    MAIN5:
    	;此处有删减
    	SETB 00H		;标志位0		
    	MOV 12H,#01H	;R2第2组通用寄存器
    	MOV 2FH,#00H	;位寻址区
    	MOV 32H,#03H	;用户RAM区,以上三句,应该是设置了三个变量
    	MOV C,P1.5			
    	CPL C
    	MOV 78H,C
    	MOV C,P1.6
    	CPL C
    	MOV 79H,C
    	MOV C,P1.7
    	CPL C
    	MOV 7AH,C		;读取P1.5、P1.6、P1.7的值,并取反后存到78H、79H、7AH
    XDY00:
    	CLR P1.0
    	SETB P1.0
    	JNB 01H,M0000	;若标志位1,为0,跳转到M0000
    	LJMP M0100		;否则跳转到M0100
    	;程序由此转向两个分支,后续。。。
    

    子程序(有删减)

    WR100:
    	MOV A,#06H		;将06H存入累加器A	
        CLR P1.0		;清零P1.0
    	LCALL WR200		;转到WR200,写8位数据到外部器件
    	SETB P1.0		;置位P1.0
        MOV A,#01H		;同上,将01H写入外部器件
    	CLR P1.0
    	LCALL WR200		
    	MOV A,#20H		;同上,将20H写入外部器件
    	LCALL WR200
    	SETB P1.0
    	LCALL DEL20		;转到DEL20,延时
        RET
    WR200:
    	MOV R3,#08H		;将08H存入R3寄存器
    WR201:
    	RLC A			;将A带进位左移一位
    	MOV P1.2,C		;上一次左移无进位,因此P1.2为0
    	CLR P1.3		;清零P1.3
    	SETB P1.3		;置位P1.3
    	DJNZ R3,WR201	;将R3-1,若不为0,转到WR201
    	RET				;返回。综上,此函数是将A通过左移写入外部器件,类似于I2C,SPI通信的写寄存器操作,A是所要写入的内容
    DEL20:
    	MOV R2,#0FH
    DEL21:
    	MOV R3,#0BAH
    DEL22:
    	NOP
        NOP
        NOP
        DJNZ R3,DEL22
    	CPL P1.0		;对P1.0每隔(3个指令周期*0BAH)这么长时间取反
    	DJNZ R2,DEL21	;取反0FH次
        RET				;返回,综上,此函数可以实现将P1.0取反0FH次,每次保持0BAH*3T,T为1指令周期,类似于让LED闪烁的函数
    
    展开全文
  • 理解单片机系统 一、理解CPU的三种工作模式 从80386开始,CPU有三种工作方式:实模式(real-mode)、保护模式(protected-mode)和虚拟8086模式。只有在刚刚启动的时候是实模式,等到操作系统运行起来以后就切换到...

    理解单片机系统

    一、理解CPU的三种工作模式

    从80386开始,CPU有三种工作方式:实模式(real-mode)、保护模式(protected-mode)和虚拟8086模式。只有在刚刚启动的时候是实模式,等到操作系统运行起来以后就切换到保护模式。实模式只能访问地址在1M以下的内存称为常规内存,我们把地址在1M 以上的内存称为扩展内存。在保护模式下,全部32条地址线有效,可寻址高达4G字节的物理地址空间; 扩充存储器分段管理机制存储器分页管理机制(可选的)不仅为存储器共享和保护提供了硬件支持,而且为实现虚拟存储器提供了硬件支持;支持多任务,能够快速地进行任务切换(switch)和保护任务环境(context); 4个特权级和完善的特权检查机制,既能实现资源共享又能保证代码和数据的安全和保密及任务的隔离; 支持虚拟8086方式,便于执行8086程序。

    实模式(Real Mode)

    它是 Intel公司80286及以后的x86(如80386,80486和80586等)处理器为了兼容以前的处理器(CPU)的一种操作模式。实模式被特殊定义为20位地址内存可访问空间上,这就意味着它的容量是2的20次幂(1M)的可访问内存空间(物理内存和BIOS-ROM),软件可通过这些地址直接访问BIOS程序和外围硬件。实模式下处理器没有硬件级的内存保护概念和多道任务的工作模式。但是为了向下兼容以前的处理器,所以80286及以后的x86系列处理器在开机启动时仍然先工作在实模式下。80186和早期的处理器仅有一种操作模式,就是后来我们所定义的实模式。实模式虽然能访问到1M的地址空间,但是由于BIOS的映射作用(即BIOS占用了部分空间地址资源),所以真正能使用的物理内存空间(内存条),也就是在640k到924k之间。1M 地址空间组成是由 16位的段地址和16位的段内偏移地址组成的。用公式表示为:物理地址=左移4位的段地址+偏移地址。

    80286处理器体系结构引入了地址保护模式的概念,处理器能够对内存及一些其他外围设备做硬件级的保护设置(保护设置实质上就是屏蔽一些地址的访问)。使用这些新的特性,然而必不可少一些额外的在80186及以前处理器没有的操作规程。自从最初的x86微处理器规格以后,它对程序开发完全向下兼容,80286芯片被制作成启动时继承了以前版本芯片的特性。它工作在实模式下时暂时先关闭了新增的保护功能特性等,因此能使以往的软件继续工作在新的芯片下。直到今天,甚至最新的x86处理器都是在计算机加电启动时都是工作在实模式下,它能运行为以前处理器芯片写的程序。

    DOS操作系统(例如 MS-DOS,DR-DOS)工作在实模式下,微软Windows早期的版本(它本质上是运行在DOS上的图形用户界面应用程序,实际上本身并不是一个操作系统)也是运行在实模式下,直到Windows3.0,它运行期间既有实模式又有保护模式,所以说它是一种混合模式工作。它的保护模式运行有两种不同意义(因为80286并没有完全地实现80386及以后的保护模式功能):
    “标准保护模式”:这就是程序运行在保护模式下;
    “虚拟保护模式”:它实质上还是实模式,是实模式上模拟的保护模式也使用32位地址寻址方式。Windows3.1彻底删除了对实模式的支持。在80286处理器芯片以后,Windows3.1成为主流操作系统(Windows/80286不是主流产品)。目前差不多所有的X86系列处理器操作系统(Linux,Windows95 and later,OS/2等)都是在启动时进行处理器设置而进入保护模式的。

    实模式工作机理:

    • 对于8086/8088来说计算实际地址是用绝对地址对1M求模。8086的地址线的物理结构:20根,也就是它可以物理寻址的内存范围为2^20个字节,即1 M空间,但由于8086/8088所使用的寄存器都是16位,能够表示的地址范围只有0-64K,这和1M地址空间来比较也太小了,所以为了在8086/8088下能够访问1M内存,Intel采取了分段寻址的模式:16位段基地址:16位偏移EA,其绝对地址计算方法为:16位基地址左移4位+16位偏移=20位地址。比如:DS=1000H EA=FFFFH 那么绝对地址就为:10000H +0FFFFH = 1FFFFH 地址单元 。通过这种方法来实现使用16位寄存器访问1M的地址空间,这种技术是处理器内部实现的,通过上述分段技术模式,能够表示的最大内存为: FFFFh: FFFFh=FFFF0h+FFFFh=10FFEFh=1M+64K-16Bytes(1M多余出来的部分被称做高端内存区HMA)。但8086/8088只有20位地址线,只能够访问1M地址范围的数据,所以如果访问100000h~10FFEFh之间的内存(大于1M空间),则必须有第21根地址线来参与寻址(8086/8088没有)。因此,当程序员给出超过1M(100000H-10FFEFH)的地址时,因为逻辑上正常,系统并不认为其访问越界而产生异常,而是自动从0开始计算,也就是说系统计算实际地址的时候是按照对1M求模的方式进行的,这种技术被称为wrap-around。
    •  对于80286或以上的CPU通过A20 GATE来控制A20地址线。技术发展到了 80286,虽然系统的地址总线由原来的20根发展为24根,这样能够访问的内存可以达到2^24=16M,但是Intel在设计80286时提出的目标是向下兼容,所以在实模式下系统所表现的行为应该和8086/8088所表现的完全一样,也就是说,当启动运行在实模式下时,80386以及后续系列应该和8086/8088完全兼容仍然使用A20地址线。所以说80286芯片存在一个BUG:它开设A20地址线时,在保护模式下,如果程序员访问100000H-10FFEFH之间的内存,系统将实际访问这块内存(没有wrap-around技术);如果在实模式下,也可以访问100000H-10FFEFH间的内存,这时采用的是wrap-around技术,这时两种模式下访问同一个内存区却会得到不同的数据,因此说是一个bug。我们来看一副图:

      https://img-my.csdn.net/uploads/201205/14/1336959769_1239.jpg


      为了解决上述兼容性问题,IBM使用键盘控制器上剩余的一些输出线来管理第21根地址线(从0开始数是第20根) 的有效性,被称为A20 Gate:
    • 如果A20 Gate被打开,则当程序员给出100000H-10FFEFH之间的地址的时候,系统将真正访问这块内存区域;
    • 如果A20 Gate被禁止,则当程序员给出100000H-10FFEFH之间的地址的时候,系统仍然使用8086/8088的方式即取模方式(8086仿真)。绝大多数IBM PC兼容机默认的A20 Gate是被禁止的。现在许多新型PC上存在直接通过BIOS功能调用来控制A20 Gate的功能。

    保护模式(Protected Mode)

    在实模式下,在80286以及更高系列的PC中,即使A20 Gate被打开,在实模式下所能够访问的内存最大也只能为10FFEFH,尽管它们的地址总线所能够访问的能力都大大超过这个限制。为了能够访问10FFEFH以上的内存,则必须进入保护模式。

    (286是Intel 80286的另一种叫法) 它又被称作为虚拟地址保护模式。尽管在Intel 80286手册中已经提出了虚地址保护模式,但实际上它只是一个指引,真正的32位地址出现在Intel 80386上。保护模式本身是80286及以后兼容处理器序列之后产成的一种操作模式,它具有许多特性设计为提高系统的多道任务和系统的稳定性。例如内存的保护,分页机制和硬件虚拟存储的支持。现代多数的x86处理器操作系统都运行在保护模式下,包括Linux,Free BSD,和Windows3.0(它也运行在实模式下,为了和Windows 2.x应用程序兼容)及以后的版本。

    80286及以后的处理器另一种工作模式是实模式(仅当系统启动的一瞬间),本着向下兼容的原则屏蔽保护模式特性,从而容许老的软件能够运行在新的芯片上。作为一个设计规范,所有的x86系列处理器,除嵌入式Intel80387之外,都是系统启动工作在实模式下,确保遗留下的操作系统向下兼容。它们都必须被启动程序(操作系统程序最初运行代码)重新设置而相应进入保护模式的,在这之前任何的保护模式特性都是无效的。在现代计算机中,这种匹配进入保护模式是操作系统启动时最前沿的动作之一。

    在被调停的多道任务程序中,它可以从新工作在实模式下是相当可能的。保护模式的特性是阻止被其他任务或系统内核破坏已经不健全的程序的运行,保护模式也有对硬件的支持,例如中断运行程序,移动运行进程文档到另一个进程和置空多任务的保护功能。

    386及以后系列处理器不仅具有保护模式又具有32位寄存器,结果导致了处理功能的混乱,因为80286虽然支持保护模式,但是它的寄存器都是16位的,它是通过自身程序设定而模拟出的32位,并非32位寄存器处理。归咎于这种混乱现象,它促使Windows/386 及以后的版本彻底抛弃80286的虚拟保护模式,以后保护模式的操作系统都是运行在80386以上,不再运行在80286(尽管80286模式支持保护模式),所以说80286是一个过渡芯片,它是一个过渡产品。

    尽管 286和386处理器能够实现保护模式和兼容以前的版本,但是内存的1M以上空间还是不易存取,由于内存地址的回绕,IBM PC XT (现以废弃)设计一种模拟系统,它能过欺骗手段访问到1M以上的地址空间,就是开通了A20地址线。在保护模式里,前32个中断为处理器异常预留,例如,中断0D(十进制13)常规保护故障和中断00是除数为零异常。

    如果要访问更多的内存,则必须进入保护模式,那么,在保护模式下,A20 Gate对于内存访问有什么影响呢?

    为了搞清楚这一点,我们先来看一看A20的工作原理。A20,从它的名字就可以看出来,其实它就是对于A20(从0开始数)的特殊处理(也就是对第21根地址线的处理)。如果A20 Gate被禁止,对于80286来说,其地址为24根地址线,其地址表示为EFFFFF;对于80386极其随后的32根地址线芯片来说,其地址表示为FFEFFFFF。这种表示的意思是:

    https://img-my.csdn.net/uploads/201205/14/1336960202_9190.jpg

    •  如果A20 Gate被禁止。则其第A20在CPU做地址访问的时候是无效的,永远只能被作为0。所以,在保护模式下,如果A20 Gate被禁止,则可以访问的内存只能是奇数1M段,即1M,3M,5M…,也就是00000-FFFFF,200000-2FFFFF,300000-3FFFFF…
    • 如果A20 Gate被打开。则其第20-bit是有效的,其值既可以是0,又可以是1。那么就可以使A20线传递实际的地址信号。如果A20 Gate被打开,则可以访问的内存则是连续的。

    实模式和保护模式的区别

    从表面上看,保护模式和实模式并没有太大的区别,二者都使用了内存段、中断和设备驱动来处理硬件,但二者有很多不同之处。我们知道,在实模式中,内存被划分成段,每个段的大小为 64KB ,而这样的段地址可以用 16位来表示。内存段的处理是通过和段寄存器相关联的内部机制来处理的,这些段寄存器( CS 、 DS 、 SS 和ES )的内容形成了物理地址的一部分。具体来说,最终的物理地址是由 16 位的段地址和 16 位的段内偏移地址组成的。用公式表示为:物理地址 = 左移 4 位的段地址 + 偏移地址。在保护模式下,段是通过一系列被称之为 “ 描述符表 ” 的表所定义的。段寄存器存储的是指向这些表的指针。用于定义内存段的表有两种:全局描述符表 (GDT) 和局部描述符表 (LDT) 。GDT 是一个段描述符数组,其中包含所有应用程序都可以使用的基本描述符。在实模式中,段长是固定的 ( 为 64KB) ,而在保护模式中,段长是可变的,其最大可达 4GB 。LDT 也是段描述符的一个数组。与 GDT 不同,LDT 是一个段,其中存放的是局部的、不需要全局共享的段描述符。每一个操作系统都必须定义一个 GDT ,而每一个正在运行的任务都会有一个相应的 LDT 。每一个描述符的长度是 8 个字节,格式如图 3 所示。当段寄存器被加载的时候,段基地址就会从相应的表入口获得。描述符的内容会被存储在一个程序员不可见的影像寄存器 (shadow register) 之中,以便下一次同一个段可以使用该信息而不用每次都到表中提取。物理地址由 16 位或者 32 位的偏移加上影像寄存器中的基址组成。实模式和保护模式的不同可以从下图很清楚地看出来。

    实模式地址
     

    https://img-my.csdn.net/uploads/201205/14/1336960423_5675.jpg

    保护模式地址

    https://img-my.csdn.net/uploads/201205/14/1336960439_4547.jpg

    总结:保护模式同实模式的根本区别是进程内存受保护与否。可寻址空间的区别只是这一原因的果。实模式将整个物理内存看成分段的区域,程序代码和数据位于不同区域,系统程序和用户程序没有区别对待,而且每一个指针都是指向"实在"的物理地址。这样一来,用户程序的一个指针如果指向了系统程序区域或其他用户程序区域,如果这个指针修改了这个区域的某一个值,那么对于这个被修改的系统程序或用户程序,其后果就很可能是灾难性的。为了克服这种低劣的内存管理方式,处理器厂商开发出保护模式。这样,物理内存地址不能直接被程序访问,程序内部的地址(虚拟地址)要由操作系统转化为物理地址去访问,程序对此一无所知

    至此,进程有了严格的边界,任何其他进程根本没有办法访问不属于自己的物理内存区域,甚至在自己的虚拟地址范围内也不是可以任意访问的,因为有一些虚拟区域已经被放进一些公共系统运行库。这些区域也不能随便修改,若修改就会有: SIGSEGV(linux 段错误);非法内存访问对话框(windows 对话框)。

    CPU启动环境为16位实模式,之后可以切换到保护模式。但从保护模式无法切换回实模式 。对于80X86处理器来说,从80386处理器开始,除了以前的实模式外,还增添了保护模式和V86模式。实模式和V86模式都是为了和8086兼容而设置的。

    实模式: 
          内存寻址方式为:段式寻址,即物理地址=段地址*16   +   段内偏移地址 
          可寻址任意地址,所有指令都相当于工作在特权级。
          dos工作在实模式下 
    保护模式: 
          内存寻址方式为:支持内存分页和虚拟内存 
          支持多任务,可依靠硬件用一条指令即可实现任务切换,不同任务可工作在不同的优先级下,操作系统工作在最高优先级0上,应用程序则运行在较低优先级上。从实模式到保护模式,需要建立GDT、IDT等数据表,然后通过修改控制寄存 器CR0的控制位(位0)来实现。
          Windows工作在保护模式下。
    虚拟8086模式: 
    内存寻址方式:段式寻址,与实模式一样 
            支持多任务和内存分页 
            v86模式主要是为了在保护模式下兼容以前的实模式应用,即可支持多任务,但每个任务都是实模式的工作方式。另外,中断和异常等的处理对于不同的工作模式都是不同的,具体的可以去参看一些相关书籍。

    二、理解8086微机系统的组成

    https://i.imgur.com/LiO0fGt.png

    1、对于汇编程序而言,我们需要关心CPU中的寄存器、存储器地址、端口(I/O地址)
    【内存单元的两个元素】: 地址(编号)和值(内容)。
    【字节、字、双字】:8086的内存以字节编址,每个内存单元有唯一的地址(物理地址),可以存放一个字节。字:一个字占据两个连续的字节。双字:双字占据两个连续的字。
    【数据的地址对齐】:字单元安排在偶地址(xxx0B),双字单元安排在模4地址(xx00B)等。对于非对齐地址的数据,处理器访问时需要多次访问存储器,这样做花费时间较多。
    【总线】:8086的系统总线有3种:数据总线;地址总线:8086CPU外部一共有20条地址总线,但在CPU内部一次只能传送16位地址;控制总线
    【I/O】:I/O地址叫做端口,通常采用十六进制数来表达端口:8086的I/O端口为16位,可支持64k个8位端口;I/O地址范围为:0000H ~ FFFFH
    【8086的功能结构】:总线接口单元BIU:主要负责读取指令和操作数。执行单元EU:主要负责指令译码和执行。

    2、汇编语言程序、汇编程序、连接程序、调试程序
    汇编程序:汇编程序将汇编语言源程序翻译(或称作“汇编”)成机器代码目标模块。
    .ASM -> .OBJ;注意区分汇编程序与汇编语言源程序。
    连接程序:连接程序将汇编后的目标模块转换为可执行程序。
    调试程序:调试程序以便排错、分析等。

    3、寄存器组

    16位通用寄存器】
    AX、BX、CX、DX、SI、DI、BP、SP
    其中AX、BX、CX、DX可以分作高8位和低8位的两个独立寄存器。如:AH和AL。我们对其中8位的操作,并不影响另外对应的8位数据。

    1、数据寄存器ax、bx、cx、dx
    数据寄存器用来存放计算的结果和操作数,也可以存放地址。
    每个寄存器又有它们各自的专用目的。

    AX——累加器,使用频度最高,用于算术、逻辑运算以及与外设传送信息等;
    BX——基址寄存器,常用做存放存储器地址;
    CX——计数器,作为循环和串操作等指令中的隐含计数器;
    DX——数据寄存器,常用来存放双字长数据的高16位,或存放外设端口地址。

    2、变址寄存器si、di
    变址寄存器常用于存储器寻址时提供地址

    SI是源变址寄存器

    DI是目的变址寄存器
    串操作类指令中,SI和DI具有特别的功能

    3、指针寄存器sp、bp
    指针寄存器用于寻址内存堆栈内的数据:
    SP为堆栈指针寄存器,指示栈顶的偏移地址。
    SP不能再用于其他目的,具有专用目的。SP始终指向栈顶。
    BP为基址指针寄存器,表示数据在堆栈段中的基地址。
    SP和BP寄存器与SS段寄存器联合使用以确定堆栈段中的存储单元地址

    4、堆栈
    8086中堆栈通常有处理器自动维持,由堆栈段寄存器SS和堆栈指针寄存器SP共同指示。

    5、指令指针寄存器IP
    指示代码段中指令的偏移地址。与代码段寄存器CS连用(CS:IP)。

    6、标志寄存器
    标志(flag)用于反映指令执行结果或控制指令执行形式。

    16位的标志寄存器——程序状态字PSW寄存器。
    https://i.imgur.com/s3JIoyw.png
    其中,状态标志(6个,CF ZF SF PF OF AF)——用来记录程序运行结果的状态信息,许多指令的执行都将相应地设置它。控制标志(3个,DF IF TF)——可由程序根据需要用指令设置,用于控制处理器执行指令的方式。

    • 进位标志CF(Carry Flag):当运算结果的最高有效位有进位(加法)或借位(减法)时,CF=1,or CF=0;
    • 零标志位ZF(Zero Flag):若运算结果为0时,ZF=1,or ZF=0;
    • 符号标志位SF(Sign Flag):运算结果最高位为1,则SF=1,or SF=0。
      有符号数据用最高有效位表示数据的符号。所以,最高有效位就是符号标志的状态
    • 奇偶标志位PF(Parity Flag):当运算结果最低字节中“1”的个数为零或偶数时,PF=1;or PF=0。
      PF标志仅反映最低8位中“1”的个数是偶或奇,即使是进行16位字操作。
    • 溢出标志OF(Overflow Flag):若算术结果有溢出,OF=1,or OF=0;【什么是溢出?溢出判断】
    • 辅助进位标志AF(Auxiliary Carry Flag):运算时D3位(低半字节)有进位或借位时,AF=1,or AF=0。这个标志主要由处理器内部使用,用于十进制算术运算调整指令中,用户一般不必关心。
    • 方向标志DF(Direction Flag):用于串操作指令中,控制地址的变化方向:设置DF=0,存储器地址自动增加;设置DF=1,存储器地址自动减少。CLD指令复位方向标志:DF=0;STD指令置位方向标志:DF=1。
    • 中断允许标志IF(Interrupt-enable Flag):用于控制外部可屏蔽中断是否可以被处理器响应:设置IF=1,允许中断;设置IF=0,禁止中断。CLI指令复位中断标志:IF=0;STI指令置位中断标志:IF=1。
    • 陷阱标志TF(Trap Flag):用于控制处理器进入单步操作模式:设置TF=0,处理器正常工作;设置TF=1,处理器单步执行指令。单步执行指令——处理器在每条指令执行结束时,便产生一个编号为1的内部中断。这种内部中断称为单步中断。所以TF也称为单步标志。

    7、段寄存器
    段地址——段的起始地址的高16位地址。段内再由16位二进制数来寻址。
    偏移地址——段内存储单元到段首地址的字节的距离。
    物理地址——用20位二进制数表示。地址范围为00000H ~ FFFFFH。物理地址唯一标识一个存储单元。
    逻辑地址——段地址:偏移地址,逻辑地址不唯一。
    物理地址=段地址x16+偏移地址

    8086有4个16位的段寄存器。

    代码段CS(Code Segment):指明代码段的 起始地址。
    代码段用来存放程序的指令序列。指令指针寄存器IP指示下一条指令的偏移地址。处理器利用CS:IP取得下一条要执行的指令。

    堆栈段SS(Stack Segment):指明堆栈段的起始地址。
    堆栈段确定堆栈所在的主存区域。堆栈指针寄存器SP指示堆栈栈顶的偏移地址。处理器利用SS:SP操作堆栈栈顶的数据。

    数据段DS(Data Segment):指明数据段的起始地址。
    数据段存放运行程序所用的数据。各种主存寻址方式(有效地址EA)得到存储器中操作数的偏移地址。处理器利用DS:EA存取数据段中的数据。

    附加段ES(Extra Segment):指明附加段的起始地址。
    附加段是附加的数据段,也用于数据的保存。各种主存寻址方式(有效地址EA)得到存储器中操作数的偏移地址。处理器利用ES:EA存取附加段中的数据。
    串操作指令将附加段作为其目的操作数的存放区域。

    【关于分段】

    1、8086对逻辑段的要求:
    ① 段地址低4位均为0
    ② 每段最大不超过64KB(216 B),但并不要求必须为64KB
    ③ 各段之间可以独立,也可以有重叠

    2、如何分配各个逻辑段:
    ① 程序的指令序列必须安排在代码段。
    ② 程序使用的堆栈一定在堆栈段。
    ③ 程序中的数据默认是安排在数据段,也经常安排在附加段,尤其是串操作的目的区必须是附加段。数据的存放比较灵活,实际上可以存放在任何一种逻辑段中。

    8、段超越前缀指令
    没有指明时,一般的数据访问在DS段;使用BP访问主存,则在SS段
    默认的情况允许改变,需要使用段超越前缀指令;8086指令系统中有4个,用于明确指定数据所在的逻辑段:
    CS: ;代码段超越,使用代码段的数据
    SS: ;堆栈段超越,使用堆栈段的数据
    DS: ;数据段超越,使用数据段的数据
    ES: ;附加段超越,使用附加段的数据

    【段超越示例】

    https://i.imgur.com/MmY9rui.png

    【不允许使用段超越的情况】
    串处理指令的目的串必须用ES段;PUSH指令的目的和POP指令的源必须用SS段;指令必须存放在CS段。

    【段寄存器的使用规定】

    https://i.imgur.com/x6FdLiI.png

    【补充】

    【什么是溢出】
    处理器内部以补码表示有符号数。8位表达的整数范围是:+127~-12816位表达的范围是:+32767~-32768。如果运算结果超出这个范围,就产生了溢出。有溢出,说明有符号数的运算结果不正确。

    【溢出和进位】
    溢出标志OF和进位标志CF是两个意义不同的标志。
    进位标志表示无符号数运算结果是否超出范围,运算结果仍然正确;
    溢出标志表示有符号数运算结果是否超出范围,运算结果已经不正确。

    https://i.imgur.com/InwlxCm.png

    【如何运用溢出和进位】
    处理器对两个操作数进行运算时,按照无符号数求得结果,并相应设置进位标志CF;同时,根据是否超出有符号数的范围设置溢出标志OF。应该利用哪个标志,则由程序员来决定。也就是说,如果将参加运算的操作数认为是无符号数,就应该关心进位;认为是有符号数,则要注意是否溢出。

    【溢出判断】
    判断运算结果是否溢出有一个简单的规则:
    只有当两个相同符号数相加(包括不同符号数相减),而运算结果的符号与原数据符号相反时,产生溢出;因为,此时的运算结果显然不正确。其他情况下,则不会产生溢出。

    补充

    1.什么是逻辑地址?
    逻辑地址是用户编程时使用的地址,分为段地址和偏移地址两部分。
    逻辑地址表示形式:3020:055AH---------(汇编语言中,数字后面加H表示16进制)

    2.为什么要用逻辑地址?(逻辑地址的产生背景)
    8086cpu访问存储器时,地址寄存器(16位)要先向地址总线发出地址信号(地址总线是专门用来存取内存地址的,故与内存单元有关,20位),而地址寄存器只有16位,从地址寄存器发出的地址信号,所能访问的存储空间只有2^16 = 65536 = 64KB,达不到20位地址总线所提供的地址范围。针对这种情况,就把内存地址分为若干段,每段有一些存储单元构成。用段地址指出是哪一段,偏移地址标明是段中的哪一个单元。

    3.什么叫段地址,偏移地址?之间有什么关系?
    Ⅰ.把内存地址分为若干段,每段有一些存储单元构成。用段地址指出是哪一段(若是指向同一个存储单元,段地址可以不一样,无非就是偏移地址不一样而已,但是都可以指向同一个物理地址,因此段地址只是一个被访问的存储单元(变量)的起始地址,并不是固定的),偏移地址标明是段中的哪一个单元
    Ⅱ.段地址和偏移地址都是16位2进制数。
    Ⅲ.段地址和偏移地址有多种组合,故存在多个地址组合指向同一个存储单元上。

    4.逻辑地址唯一么?
    不唯一,因为段地址和偏移有多种组合,故存在多个地址组合指向同一个存储单元上。例如:3020:055AH和3000:07AAH就是两种组合,但都是指向同一个存储单元,

    5.cpu执行程序时,采用的是逻辑地址还是物理地址?
    物理地址---用户编程时采用的逻辑地址在cpu执行程序时都要转换成物理地址。这是由cpu的地址加法器完成的。

    6.逻辑地址怎样转换为物理地址?
    转换时,先将16位的段地址左移4位,相当于乘以16或者16进制的10H,再和偏移地址相加。转换公式为:物理地址 = 段地址*10H + 偏移地址。如:将3020:055AH转换为物理地址:----= 3020*10H(左移四位)+055AH = 3075AH

    7.段与偏移地址是什么关系?
    段是由存储单元构成的,段包含偏移地址对应的存储单元。即偏移地址对应的字节存储单元在段中。.

    8.段的大小指的是什么?
    指的是这个段包含存储单元的多少。

    9.将内存分段的依据?以及段的相关知识
    段地址和偏移地址都是16位二进制数,每段最大64K字节单元(2^16=65536 = 64KB),每段最小16个字节单元(硬性规定),也可以100个,1000个到最多达到65536个。偏移地址范围:0000H --- FFFFH

    10.什么叫小段?
    规定每16个字节单元为一小段。

    三、理解“逻辑地址、线性地址、物理地址和虚拟地址

    1、各种地址概念

    物理地址(physical address)
    用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。
    ——这个概念应该是这几个概念中最好理解的一个,但是值得一提的是,虽然可以直接把物理地址理解成插在机器上那根内存本身,把内存看成一个从0字节一直到最大空量逐字节的编号的大数组,然后把这个数组叫做物理地址,但是事实上,这只是一个硬件提供给软件的抽像,内存的寻址方式并不是这样。所以,说它是与地址总线相对应,是更贴切一些,不过抛开对物理内存寻址方式的考虑,直接把物理地址与物理的内存一一对应,也是可以接受的。也许错误的理解更利于形而上的抽像。
    虚拟内存(virtual memory)
    这是对整个内存的抽像描述。它是相对于物理内存来讲的,可以直接理解成不直实的假的内存,例如,一个0x08000000内存地址,它并不对就物理地址上那个大数组中0x08000000 - 1那个地址元素;之所以是这样,是因为现代操作系统都提供了一种内存管理的抽像,即虚拟内存(virtual memory)。进程使用虚拟内存中的地址,由操作系统协助相关硬件,把它转换成真正的物理地址。这个转换,是所有问题讨论的关键。有了这样的抽像,一个程序,就可以使用比真实物理地址大得多的地址空间,甚至多个进程可以使用相同的地址。不奇怪,因为转换后的物理地址并非相同的。
    ——可以把连接后的程序反编译看一下,发现连接器已经为程序分配了一个地址,例如,要调用某个函数A,代码不是call A,而是call 0x0811111111 ,也就是说,函数A的地址已经被定下来了。没有这样的转换,没有虚拟地址的概念,这样做是根本行不通的。打住了,这个问题再说下去,就收不住了。
    逻辑地址(logical address)
    Intel为了兼容,将远古时代的段式内存管理方式保留了下来。逻辑地址指的是机器语言指令中,用来指定一个操作数或者是一条指令的地址。以上例,我们说的连接器为A分配的0x08111111这个地址就是逻辑地址。不过不好意思,这样说,好像又违背了Intel中段式管理中,对逻辑地址要求,一个逻辑地址,是由一个段标识符加上一个指定段内相对地址的偏移量,表示为 [段标识符:段内偏移量],也就是说,上例中那个0x08111111,应该表示为[A的代码段标识符: 0x08111111],这样,才完整一些
    线性地址(linear address)
    线性地址或也叫虚拟地址(virtual address)跟逻辑地址类似,它也是一个不真实的地址,如果逻辑地址是对应的硬件平台段式管理转换前地址的话,那么线性地址则对应了硬件页式内存的转换前地址。
    CPU将一个虚拟内存空间中的地址转换为物理地址,需要进行两步:首先将给定一个逻辑地址(其实是段内偏移量,这个一定要理解!!!),CPU要利用其段式内存管理单元,先将为个逻辑地址转换成一个线程地址,再利用其页式内存管理单元,转换为最终物理地址。
    这样做两次转换,的确是非常麻烦而且没有必要的,因为直接可以把线性地址抽像给进程。之所以这样冗余,Intel完全是为了兼容而已。

    //导读注意:下面2-5部分,分别依次按照CPU段式内存管理、Linux段式内存管理、CPU页式内存管理、Linux页式内存管理

    2CPU段式内存管理,逻辑地址如何转换为线性地址

    一个逻辑地址由两部份组成,段标识符: 段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节,如图:

    索引号,或者直接理解成数组下标——那它总要对应一个数组吧,它又是什么东东的索引呢?这个东东就是段描述符(segment descriptor)”,呵呵,段描述符具体地址描述了一个段(对于这个字眼的理解,我是把它想像成,拿了一把刀,把虚拟内存,砍成若干的截——段)。这样,很多个段描述符,就组了一个数组,叫段描述符表,这样,可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段,我刚才对段的抽像不太准确,因为看看描述符里面究竟有什么东东——也就是它究竟是如何描述的,就理解段究竟有什么东东了,每一个段描述符由8个字节组成,如下图:

    这些东东很复杂,虽然可以利用一个数据结构来定义它,不过,我这里只关心一样,就是Base字段,它描述了一个段的开始位置的线性地址。
    Intel设计的本意是,一些全局的段描述符,就放在全局段描述符表(GDT)”中,一些局部的,例如每个进程自己的,就放在所谓的局部段描述符表(LDT)”中。那究竟什么时候该用GDT,什么时候该用LDT呢?这是由段选择符中的T1字段表示的,=0,表示用GDT=1表示用LDT
    GDT在内存中的地址和大小存放在CPUgdtr控制寄存器中,而LDT则在ldtr寄存器中。
    好多概念,像绕口令一样。这张图看起来要直观些:

    首先,给定一个完整的逻辑地址[段选择符:段内偏移地址]
    1、看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。我们就有了一个数组了。
    2、拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样,它了Base,即基地址就知道了。
    3、把Base + offset,就是要转换的线性地址了。
    还是挺简单的,对于软件来讲,原则上就需要把硬件转换所需的信息准备好,就可以让硬件来完成这个转换了。OK,来看看Linux怎么做的。

    3Linux的段式内存管理

    Intel要求两次转换,这样虽说是兼容了,但是却是很冗余,呵呵,没办法,硬件要求这样做了,软件就只能照办,怎么着也得形式主义一样。另一方面,其它某些硬件平台,没有二次转换的概念,Linux也需要提供一个高层抽像,来提供一个统一的界面。所以,Linux的段式管理,事实上只是哄骗了一下硬件而已。按照Intel的本意,全局的用GDT,每个进程自己的用LDT——不过Linux则对所有的进程都使用了相同的段来对指令和数据寻址。即用户数据段,用户代码段,对应的,内核中的是内核数据段和内核代码段。这样做没有什么奇怪的,本来就是走形式嘛,像我们写年终总结一样。
    include/asm-i386/segment.h

    1. #define GDT_ENTRY_DEFAULT_USER_CS        14
    2. #define __USER_CS (GDT_ENTRY_DEFAULT_USER_CS * 8 + 3)
    3.  
    4. #define GDT_ENTRY_DEFAULT_USER_DS        15
    5. #define __USER_DS (GDT_ENTRY_DEFAULT_USER_DS * 8 + 3)
    6.  
    7. #define GDT_ENTRY_KERNEL_BASE        12
    8.  
    9. #define GDT_ENTRY_KERNEL_CS       (GDT_ENTRY_KERNEL_BASE + 0)
    10. #define __KERNEL_CS (GDT_ENTRY_KERNEL_CS * 8)
    11.  
    12. #define GDT_ENTRY_KERNEL_DS     (GDT_ENTRY_KERNEL_BASE + 1)
    13. #define __KERNEL_DS     (GDT_ENTRY_KERNEL_DS * 8)

    复制代码
    把其中的宏替换成数值,则为:

    1. #define __USER_CS 115        [00000000 1110  0  11]
    2. #define __USER_DS 123        [00000000 1111  0  11]
    3. #define __KERNEL_CS 96      [00000000 1100  0  00]
    4. #define __KERNEL_DS 104    [00000000 1101  0  00]

    复制代码
    方括号后是这四个段选择符的16位二制表示,它们的索引号和T1字段值也可以算出来了

    1. __USER_CS              index= 14   T1=0
    2. __USER_DS              index= 15   T1=0
    3. __KERNEL_CS           index=  12  T1=0
    4. __KERNEL_DS           index= 13   T1=0

    复制代码
    T1均为0,则表示都使用了GDT,再来看初始化GDT的内容中相应的12-15(arch/i386/head.S)

    1. .quad 0x00cf9a000000ffff        /* 0x60 kernel 4GB code at 0x00000000 */
    2. .quad 0x00cf92000000ffff        /* 0x68 kernel 4GB data at 0x00000000 */
    3. .quad 0x00cffa000000ffff        /* 0x73 user 4GB code at 0x00000000 */
    4. .quad 0x00cff2000000ffff        /* 0x7b user 4GB data at 0x00000000 */

    复制代码
    按照前面段描述符表中的描述,可以把它们展开,发现其16-31位全为0,即四个段的基地址全为0
    这样,给定一个段内偏移地址,按照前面转换公式,0 + 段内偏移,转换为线性地址,可以得出重要的结论,Linux下,逻辑地址与线性地址总是一致(是一致,不是有些人说的相同)的,即逻辑地址的偏移量字段的值与线性地址的值总是相同的。!!!
    忽略了太多的细节,例如段的权限检查。呵呵。
    Linux中,绝大部份进程并不例用LDT,除非使用Wine ,仿真Windows程序的时候。

    4.CPU的页式内存管理

    CPU的页式内存管理单元,负责把一个线性地址,最终翻译为一个物理地址(注意,相互独立的进程都有自己的页目录和页表,就算多个进程具有相同的线性地址,最后转换到物理地址也是不一样的,所以不会互相干扰)。从管理和效率的角度出发,线性地址被分为以固定长度为单位的组,称为页(page),例如一个32位的机器,线性地址最大可为4G,可以用4KB为一个页来划分,这页,整个线性地址就被划分为一个tatol_page[2^20]的大数组,共有220个次方个页。这个大数组我们称之为页目录。目录中的每一个目录项,就是一个地址——对应的页的地址。另一类“,我们称之为物理页,或者是页框、页桢的。是分页单元把所有的物理内存也划分为固定长度的管理单位,它的长度一般与内存页是一一对应的。
    这里注意到,这个total_page数组有2^20个成员,每个成员是一个地址(32位机,一个地址也就是4字节),那么要单单要表示这么一个数组,就要占去4MB的内存空间。为了节省空间,引入了一个二级管理模式的机器来组织分页单元。文字描述太累,看图直观一些:

    如上图,
    1、分页单元中,页目录是唯一的,它的地址放在CPUcr3寄存器中,是进行地址转换的开始点。
    2、每一个活动的进程,因为都有其独立的对应的虚似内存(页目录也是唯一的),那么它也对应了一个独立的页目录地址。——运行一个进程,需要将它的页目录地址放到cr3寄存器中,将别个的保存下来。
    3、每一个32位的线性地址被划分为三部份,面目录索引(10):页表索引(10):偏移(12)

    依据以下步骤进行转换:
    1、从cr3中取出进程的页目录地址(操作系统负责在调度进程的时候,把这个地址装入对应寄存器);
    2、根据线性地址前十位,在数组中,找到对应的索引项,因为引入了二级管理模式,页目录中的项,不再是页的地址,而是一个页表的地址。(又引入了一个数组),页的地址被放到页表中去了。
    3、根据线性地址的中间十位,在页表(也是数组)中找到页的起始地址;
    4、将页的起始地址与线性地址中最后12位相加,得到最终我们想要的葫芦;

    这个转换过程,应该说还是非常简单地。全部由硬件完成,虽然多了一道手续,但是节约了大量的内存,还是值得的。那么再简单地验证一下:
    1、这样的二级模式是否仍能够表示4G的地址;
    页目录共有:2^10项,也就是说有这么多个页表
    每个目表对应了:2^10页;
    每个页中可寻址:2^12个字节。
    还是2^32 = 4GB

    2、这样的二级模式是否真的节约了空间;
    也就是算一下页目录项和页表项共占空间 (2^10 * 4 + 2 ^10 *4) = 8KB。哎,……怎么说呢!!!
    红色错误,标注一下,后文贴中有此讨论。。。。。。
    <深入理解计算机系统>中的解释,二级模式空间的节约是从两个方面实现的:
    A
    、如果一级页表中的一个页表条目为空,那么那所指的二级页表就根本不会存在。这表现出一种巨大的潜在节约,因为对于一个典型的程序,4GB虚拟地址空间的大部份都会是未分配的;
    B、只有一级页表才需要总是在主存中。虚拟存储器系统可以在需要时创建,并页面调入或调出二级页表,这就减少了主存的压力。只有最经常使用的二级页表才需要缓存在主存中。——不过Linux并没有完全享受这种福利,它的页表目录和与已分配页面相关的页表都是常驻内存的。
    值得一提的是,虽然页目录和页表中的项,都是4个字节,32位,但是它们都只用高20位,低12位屏蔽为0——把页表的低12屏蔽为0,是很好理解的,因为这样,它刚好和一个页面大小对应起来,大家都成整数增加。计算起来就方便多了。但是,为什么同时也要把页目录低12位屏蔽掉呢?因为按同样的道理,只要屏蔽其低10位就可以了,不过我想,因为12>10,这样,可以让页目录和页表使用相同的数据结构,方便。
    本贴只介绍一般性转换的原理,扩展分页、页的保护机制、PAE模式的分页这些麻烦点的东东就不啰嗦了……可以参考其它专业书籍。

    5.Linux的页式内存管理

    原理上来讲,Linux只需要为每个进程分配好所需数据结构,放到内存中,然后在调度进程的时候,切换寄存器cr3,剩下的就交给硬件来完成了(呵呵,事实上要复杂得多,不过偶只分析最基本的流程)。
    前面说了i386的二级页管理架构,不过有些CPU,还有三级,甚至四级架构,Linux为了在更高层次提供抽像,为每个CPU提供统一的界面。提供了一个四层页管理架构,来兼容这些二级、三级、四级管理架构的CPU。这四级分别为:
    页全局目录PGD(对应刚才的页目录)
    页上级目录PUD
    (新引进的)
    页中间目录PMD
    (也就新引进的)
    页表PT
    (对应刚才的页表)。

    整个转换依据硬件转换原理,只是多了二次数组的索引罢了,如下图:

    那么,对于使用二级管理架构32位的硬件,现在又是四级转换了,它们怎么能够协调地工作起来呢?嗯,来看这种情况下,怎么来划分线性地址吧!
    从硬件的角度,32位地址被分成了三部份——也就是说,不管理软件怎么做,最终落实到硬件,也只认识这三位老大。
    从软件的角度,由于多引入了两部份,,也就是说,共有五部份。——要让二层架构的硬件认识五部份也很容易,在地址划分的时候,将页上级目录和页中间目录的长度设置为0就可以了。
    这样,操作系统见到的是五部份,硬件还是按它死板的三部份划分,也不会出错,也就是说大家共建了和谐计算机系统。
    这样,虽说是多此一举,但是考虑到64位地址,使用四层转换架构的CPU,我们就不再把中间两个设为0了,这样,软件与硬件再次和谐——抽像就是强大呀!!!
    例如,一个逻辑地址已经被转换成了线性地址,0x08147258,换成二制进,也就是:
    0000100000 0101000111 001001011000
    内核对这个地址进行划分
    PGD = 0000100000
    PUD = 0
    PMD = 0
    PT = 0101000111
    offset = 001001011000

    现在来理解Linux针对硬件的花招,因为硬件根本看不到所谓PUD,PMD,所以,本质上要求PGD索引,直接就对应了PT的地址。而不是再到PUDPMD中去查数组(虽然它们两个在线性地址中,长度为02^0 =1,也就是说,它们都是有一个数组元素的数组),那么,内核如何合理安排地址呢?
    从软件的角度上来讲,因为它的项只有一个,32位,刚好可以存放与PGD中长度一样的地址指针。那么所谓先到PUD,到到PMD中做映射转换,就变成了保持原值不变,一一转手就可以了。这样,就实现了逻辑上指向一个PUD,再指向一个PDM,但在物理上是直接指向相应的PT的这个抽像,因为硬件根本不知道有PUDPMD这个东西

    然后交给硬件,硬件对这个地址进行划分,看到的是:
    页目录 = 0000100000
    PT = 0101000111
    offset = 001001011000

    嗯,先根据0000100000(32),在页目录数组中索引,找到其元素中的地址,取其高20位,找到页表的地址,页表的地址是由内核动态分配的,接着,再加一个offset,就是最终的物理地址了。

    参见:
    http://www.cnblogs.com/diyingyun/archive/2012/01/03/2311327.html
    "
    http://blog.sina.com.cn/s/blog_79ba23780102vz77.html"
    "https://www.cnblogs.com/exRunner/p/7531850.html"

     

    展开全文
  • PIC单片机之反汇编

    万次阅读 2015-11-02 02:10:24
     反汇编 ,一般情况大家用的不多。但在一些行业确实比较常见。比如 破解加密算法,获得加密密钥,或者自己无法写出完全一致的程序又要修改一些地方的时候反汇编就是必不可少的。反汇编是比较枯燥,又有一定挑战性的...

              前言 

              反汇编 ,一般情况大家用的不多。但在一些行业确实比较常见。比如 破解加密算法,获得加密密钥,或者自己无法写出完全一致的程序又要修改一些地方的时候反汇编就是必不可少的。反汇编是比较枯燥,又有一定挑战性的东西下面我给大家讲解如何 从BIN文件->HEX文件->汇编->C语言的全部过程。

                  第一步破解芯片

                这个作为一般是要找专业破解芯片的公司,帮我们把芯片内的  flash 和EEPROM 中的数据读出来,从而得到BIN文件或者HEX。这一步唯一的困难点就是你要花钱。。。

                 第二步BIN转HEX

                因为PIC的老的MPLAB 或者MPLAB X IDE。都不支持读取BIN文件。所以你必须事先将BIN文件转为HEX文件。当然你如果已经有了HEX文件这一步可以忽略。 我个人是比较喜欢用QL-2006这款单片机烧器的烧写软件将BIN转成HEX。

                 第三步将HEX转成汇编

            1,打开 MPLAB IDE  Configure->Select Chip 选择芯片型号。

                 2, 点击菜单栏File->Import 导入HEX文件。

                 3,查看程序存储空间点击菜单栏 View ->Progarm Memory 跳出Progarm Memory 窗口选择点击选择Machine,Machine显示的 就是每个机器码对应的汇编语言。


                 4,去掉LINE(行号) 去掉OpCode(机器码) 只留下Address(地址)和Disassemly(汇编)这两列。右击最上边的一栏就可以去掉 相应列的勾选。


                 5,右击文本框 点击Output To File (输出文件)。这样我们就得到了。反汇编之后的文本文件了。


                 6,然后用以上类似的方法输出 EEPROM中的数据

               第四步 建立工程

               1,将上一步得到的文件,将扩展名由 .TEXT 改为 .asm.我们就建立新的工程将改汇编文件添加进去。

              第五步 最苦逼的阶段看懂汇编加注释。

           1,这个阶段最重要的你本身必须懂汇编。

                这一步最累了 就是吧 对应的 特殊功能寄存器,从机器码改为由意义的名称。

                 如  BSF 0x3.0 修改为 BSF STATUS,RP0

                值得必须注意的是你一定要知道你所修改的的寄存器 是在那个页面的。

                 如同样是CLRF 0x5,

                 在BANK0 代表CLRF GPIO,在BANK1 代表 CLRF TRISIO(具体代表什么你要查看对应型号的数据手册)


             2,区分那些数代表  数值  那些数 代表 寄存器 那些数 代表 程序地址。这个一定要注意。

                 如 :MOVLW 0x3 代表数值0x3

                        MOVWF 0x3 代表寄存器STATUS

                        GOTO  0x3   代表程序地址0x3

               3,反汇编的难点和重点,就是看懂原作者 写的程序是什么意思。在这里我只是说分享一下我的做法。

                   1,一般大概猜测 对应 寄存器是什么意思 我多会将其取个有意义的名字。如计数器我就取CONTER。然后查找替换

                    2,大概猜测到 子程序是什么意思 也同样给他去个有意义的名字。

                   3,另外建立个文本。作为一种笔记。对于你猜测的 子程序 或者 寄存器的作用做一个记录。

                   4,然后就是耐心的死磕了。

                   最后变成下面的 这样子。当然要想能编译还要把前面多余的 地址列按ctrl+alt 选择去掉。

          

                第六步    汇编转C

           到这一步必须事先对汇编的意思几乎都看懂了才行你就将你所理解的汇编直接改写成C就行了。

               汇编与C之间有许多明显的对应关系下来举个例子。

                6.1 const 数组

                 如 :

                 汇编

                            ADDWF PCL,F

                             RETLW  0x00

                             RETLW  0x01

                             RETLW  0x02

                             RETLW  0x03

                   对应C语言 const   unsigned char Tab[4]={0x00,0x01,0x02,0x03} 

                  

                 汇编  赋值语句

                    MOVLW 0x1

                    MOVWF  BUF

                 C语言 赋值语句

                    BUF = 0x1;


                 汇编 与

                  MOVLW  0X0F

                  ANDWF  BUF

                  C语言  与

                  BUF  &= 0x0F

                  

                  汇编  左移

                MOVLW 0x4

                MOVWF  COUTER;  

                 BCF  STATUS,C

    RLF_LOOP

                  RLF   BUF

                 DECFSZ  COUTER,F

                 GOTO     RLF_LOOP

                 C语言 左移

                 BUF <<=4;


               汇编   数据指针

                MOVLW   TEMP

                 MOVWF  pBUF

                 MOVFW  FSR

                 MOVLW   0x2

                 ADDWF  INDF.F

                 C语言  数据指针

                  unsigned char * pBUF;

                  pBUF = &TEMP;

                  *pBUF +=2; 

                

                  汇编 判断相等

                  MOVFW  A

                  XORWF B,W

                  BTFSS STATUS,Z

                  GOTO    ELSE

                  ..............................

    ELSE

               ...............................

                 C语言  判断相等

                 if(A==B)

                 {

                      ...........................

                 }

                 else

                  {

                        ..........................

                  }


         等等等 这里就不在赘述了。

                                     

         

              

             

                   

              

                  

                   

                   

                   

    展开全文
  • 51单片机回顾【基本汇编语言】

    千次阅读 2018-08-26 17:20:04
    数据传递类指令  以累加器为目的操作数的指令  MOV A,Rn  MOV A,direct  MOV A,@Ri  MOV A,#data ... 第一条指令中,Rn代表的是R0-R7。第二条指令中,direct就是指的直接地址,而第三条指令中,就是我们...
  • MCS-51单片机汇编指令详解

    千次阅读 多人点赞 2015-12-09 19:37:26
    以累加器为目的操作数的指令 MOV A,Rn MOV A,direct MOV A,@Ri MOV A,#data 这组指令功能是把源地址单元中的内容送入直接地址,源操作数不变。 第一条指令中,Rn代表的是R0-R7。 第二条指令中,direct...M
  • 数据传递类指令  以累加器为目的操作数的指令  MOV A,Rn  MOV A,direct  MOV A,@Ri  MOV A,#data ...第二条指令中,direct就是指的直接地址,而第三条指令中,就是我们刚才讲过的。...
  • 单片机编程中在C语言里嵌入汇编比较常见,只需要在嵌入前后写入:#pragma asm MOV A,#0x00#pragma endasm两个声明即可,在它们中间就可以使用汇编代码,因为汇编是机器码,执行速度快,在对程序运行速度要求高的...
  • 1.ORG,在汇编语言中也是一条指令,其作用是告诉汇编程序,在开始执行的时候,将某段机器语言装载到内存中的哪个地址。2.长转移指令的功能是:把指令码中的目标地址addr16装入程序计数器PC,使机器执行下一条指令时...
  • 这是一个简单的动态显示共阳极数码管的汇编程序,74HC573完成段选,位选由单片机I/O端口完成。 Proteus电路图 只要看该部分电路连线即可,完整JD51单片机电路图见后方资料。 汇编程序 ORG 00H START: SETB P2.5;...
  • 用这个矩阵键盘做单片机输入,插 P1 口的 P1.0~P1.6。想问的是,当:按下 1 键 P0 口的 P0.0 输出高电平;按下 2 键 P0.1 输出高电平;……一直到 8 键就可以了。还有一个要求,当按下一个键时延时5秒并锁住其它按键...
  • 51单片机 汇编与C语言

    2020-06-24 17:26:56
    Ax51宏汇编器控制命令,禁止预定义的8051。使编译器不使能预定义的;8051符号,避免产生重复定义的错误。 反正就是这么个意思,实际意思还是不怎么懂。 $INCLUDE (8051.MCU) ;就是字面上意思了,没什么不好懂的。 ...
  • 三星汇编器中文资料,汇编器,三星,中文。对于做三星单片机变成的朋友很有帮助
  • 51单片机c嵌汇编教程

    千次阅读 2010-07-16 16:26:00
    原帖:http://bbs.cepark.com/thread-13010-1-1.html<br />http://bbs.cepark.com/thread-13028-1-1.html(原帖有附件)   ...c51shi.c为主程序,asm.c为汇编函数。 <br />第二步:在 Project 窗
  • 但是单片机里,数字是用二进制来表示的:这个就有一点拗口啦/ 虽然我们的教材到这里你可能还没有学会一个指令。 但是我的意思是首先作几个试验,提高大家对单片机的兴趣。 具体的指令太多了, 不过还好,一般我们...
  • 扩展存储器读写实验 一.实验要求 编制简单程序,对实验板上提供的外部存贮器(62256)进行读写操作。 二....1.学习片外存储器扩展...用户藉此来熟悉MCS51单片机编程的基本规则、基本指令的使用和使用本仿真实验系统...
  • 单片机在选用语言上共进化了三次:二进制机器码→汇编语言→C语言。下面来说说 二、早期的二进制机器码 最早期的时候,CPU也很简单,指令集很少,二进制位数也不多。那时候编译器也没被发明,编程语言也没被发明,...
1 2 3 4 5 ... 20
收藏数 3,924
精华内容 1,569
关键字:

单片机怎么改成汇编