|
汇编语言程序设计--笔记
1.5.3.2.3 逻辑地址和物理地址 1.5.3.2.4 例题 附图: 段地址和偏移地址的概念: 由于8086 CPU的寄存器只有16位,而地址线有20根,为使用16位的寄存器访问20位的内存地址(访问内存时,大多情况下需将内存地址放到寄存器中),8086 CPU将1M内存分成多个段(Segment)。每个段有自己的段起始地址(简称段地址,16位宽),每个段的最大长度为64K。当我们访问一个内存单元时,必须告诉CPU所访问的内存单元的段地址以及该单元距段首的距离(多少字节),该距离称为段内偏移地址(简称偏移量,Offset,16位宽)。离开段地址,孤立地谈偏移地址是没有任何意义的。在同一个段内,偏移量的变化范围是0000H~FFFFH。 为表示方便,8086规定,内存地址的表示方法为:16位段地址:16位偏移地址。其转换为20位物理地址的公式规定如下: 20位物理地址=16位段地址左移4位 + 16位偏称地址 注:由于左移4位后,段地址的低4位一定是0H,所以一个段的起始物理地址一定是xxxx0H的形式。 例如1A2B:5DF2对应的20位物理地址是200A2H: 1A2BH->左移4个二进制位->1A2B0H + 5DF2H = 200A2H 显然,表示同一个物理地址时可以有近64K种段地址和偏移量的组合。例如2000:00A2和200A:0002都表示同一个物理地址200A2H。 (此文出自Write An OS In A Week(2) 萝卜甲@ZJU 2004 badturnip@163.com) 经过昨天的热身准备,今天可以进入一些比较细节的内容了。在开始设计前首先要熟悉一下x86的硬件体系,怎么说在Intel的平台下混只好这样啦。 在Intel PC处理器80386之前的都是属于玩具型的,没有大地址寻址,没有硬件保护,没有虚拟存储,没有分页机制,不可能支持现代操作系统。所以我们的目标是80386以上的CPU,从80386到现在最新的Pentium IV,体系结构都是差不多的,无非是速度快了,缓存加了,修修补补,增加了了一些新的功能。下面我们就来看一下Intel的保护模式机制,对了,在介绍Intel保护模式之前,你最好能搞到一份Intel IA-32体系结构手册(IA-32 Intel Architechure Software Developer's Manual),三卷装,第三卷System Programming对Intel的IA-32机制讲得很详细。不过想想Intel用整整780页描述的内容我们今天一天要把它搞定,真是。。。。。很寒啊。没有接触过保护模式的xdjm们准备好了吗?一定要坚定信念,顽强拼搏,义无反顾。。。。来吧! 保护模式对于没有接触过的人来说可能第一次接触很是让人困惑,他的核心机制其实是一个二级地址转换机制。在IA-32体系结构下存在的有三个地址空间的概念: 虚拟地址空间(又称逻辑地址空间),线性地址空间,物理地址空间。还有两个转换机制:段机制和分页机制。虚拟地址通过段机制转换成线性地址,线性地址通过分页机制转换成物理地址。如下: 虚拟地址 ---------> 线性地址 ---------> 物理地址 段机制 分页机制 先来看看最简单也最容易理解的物理地址空间吧,物理地址空间所对应的地址就是真正的物理内存空间,IA-32体系结构的物理地址是一个32-bit的数值,所以他的最大物理地址寻址就是0 到 2^32 - 1 共4G空间,比如你有一个物理地址0x00010000,这个地址就是你内存中1M的那个位置。 线性地址空间是一个想象的地址空间,32位,从0 到 2^32 - 1,即 0x00000000 到 0xFFFFFFFF,跟物理地址空间是一样的性质。线性地址空间通过分页转换机制转换成物理地址。 最后来看看虚拟地址空间,虚拟地址就是程序给出的地址,在IA-32,一个虚拟地址是48-bit的数值,其中16位为段地址,32位为段内偏移地址,比如有一个mov eax,ds:[eax],16位的ds就是段地址(其实是段选择子Selector,下面详细介绍),eax里面给出的就是32-bit偏移。这里顺便说一下386以后的Intel CPU寄存器的设置,8086下16-bit的ax,bx,cx,dx,si,di,bp,sp,ip,flag被扩展成了32-bit的eax,ebx,ecx,edx,esi,edi,ebp,esp,eip,eflag。cs,ds,es,ss的还是保持了16-bit,但是他们的意义已经变了,还增加了两个段寄存器fs和gs。另外IA-32下还有很多其他的寄存器,比如控制寄存器CR0,CR1,CR2,CR3,CR4用来控制CPU的工作状态,调试寄存器DR0 - DR7用来调试等。 OK,下面进入了比较实质性的内容,让我们来看看比较容易头晕的段机制,看看一个虚拟地址是怎么被转换成线性地址的。在8086下写过汇编的应该知道, 虚拟地址(20-bit) = 段的基地址(16-bit 左移4位,为20-bit) + 段内偏移(16-bit), 但是8086没有任何地址转换机制,所以这个20-bit的虚拟地址就是物理地址了。保护模式下的线性地址是由段的线性基地址(32-bit) + 段内偏移(32-bit)形成的,但是注意,这个段的线性基地址(Base Address)不是由段寄存器直接给出的,而是段寄存器指出一个特定的8个字节的一个结构,由这个结构给出该段的线性基地址,Intel把该结构称为段描述符(Segment Descriptor)。 这个8个字节的段描述符结构到底里面保存了哪些东西呢?第一,该段的线性基地址,就是说这个段在线性空间中从哪个位置开始,相当于8086下的段地址,只不过这个地址是32位的。第二,该段的长度,这个应该很好理解,如果一个偏移地址超过了段的长度,那么该偏移地址会被CPU判断为无效,CPU会产生一个异常(exception),相当于一个中断,然后操作系统会捕获这个异常,然后OS该怎么处置产生这个异常的程序就是OS的事了,我们这里先跳过了。第三,这个描述符里面还保存了该段的属性,比如该段的权限设置啊,怎么样的权限才能访问这个段,这个涉及到8086没有的权限问题,我们下面会提到,还有该段是什么内容啊,市数据段呢,还是代码段呢,还是其他呢,这个其他里面包含了很多类型,Intel共定义了16种类型,包括一些门啊,还有任务描述段啦等等一堆,这个门又分中断们,陷阱门....任务描述符里面是....好....慢点慢点,有的观众已经开始撤离了,那我们暂且把他们放在一边,否则就真的是无穷无尽的地狱了,阿弥陀佛! 除了这些以外,8个字节的描述符还保存了一些其他相关的信息,我们暂且不理他。这些描述符位于什么地方呢?这些描述符本身是位于线性地址空间中的,是由程序员设置的,也就是说我们作为OS开发者要干的活。我们在整个OS中可能需要设置很多个这样的描述符,比如要为内核的数据(堆栈也被认为是数据)设置一个描述符,为内核的代码设置一个描述符,为用户程序的数据设置一个,还要为用户的代码设置一个。看过OS理论的都知道,OS内核和用户程序永远是对立统一的,就是说OS既要为用户程序服务,又要限制用户的权限,管理和分配用户程序的资源,从这一点上来看得话,OS内核很像政府机构,用户程序很想人民大众,政府要为人民服务,又要限制,管理和分配人民的资源。从这个比喻来讲,单一内核很像计划经济,OS内核分配和管理一切,看似效率比较高,其实整体结构不佳。而微内核像市场经济,OS内核只起到监督和协调的作用,很多政府机构变成了服务者(相当于微内核中的Server)..... 咳咳,貌似扯远了.....继续回到原来的内容,嗯.....刚才讲什么来着? 对!描述符,为什么那个该死的Intel要搞那么一套复杂的古怪的东西来折磨我们呢?其实据我猜测,其中原因是很多的,我把它归结如下: 1 向上兼容。 因为8086有段机制,留下了很多段寄存器,所以总不能空着不用吧,所以到了386就成了段机制。 2 段机制的保护作用。 刚才提到的段描述符里面保存了关于段的长度以及段的属性以及段的访问权限,这个就是为了保护OS内核不受外界非法访问的一个机制。 3 段机制在程序数据共享和重定位方面有优势。 这个就比较麻烦了,撤开去又是很大的一个话题,不讲了 :) 4 Intel纯粹耍酷,存心要玩死OS developer -_-!b,看了那个描述符的结构真是想杀到Intel总部把那帮家伙咔咔了。因为在设计80286这个不完全变态产品的时候,Intel的设计人员没有想到为80386留条后路,所以留下了一系列的古怪结构,就像古代女人的小脚 ---- 蹩足得很啊! 这里既然提到了保护,就说边说说Intel的保护机制吧。在IA-32下,所有的代码和数据共分4个级别的权限,0,1,2,3,也就是常说的Ring 0, Ring 1, Ring 2, Ring 3。数字越小,权限越大。权限大的能访问全县小的,反之不成立,同级能互访。由于4级的权限太多,一般很少都被用到,一般的OS只用到了Ring 0和Ring 3。把内核的代码和数据放在Ring 0下,把用户的代码和数据放在Ring 3下是很常见的一种做法。如果权限低的代码试图访问权限高的数据,CPU就会产生一个exception,CPU会自动把控制权交给处于Ring 0的OS kernel,由OS kernel来处置越权访问的代码(这就是很常见的General protection error)。至于为什么要保护就不用我在这里罗嗦了吧,你总不想你的Kernel数据和代码随便用户程序改动吧?否则还有快感? 看到这里,我做个调查:各位讨不讨厌那个复杂古怪的段机制?十有八九,我敢肯定,十分讨厌,至少我个人是不太喜欢。不过我不得不宣布一个好消息,那就是:我们可以跳过段机制! (西瓜皮,西红柿,烂橘子马上向我飞来: 不早说!害我死亡了那么多脑细胞!)只要我们恰当地设置好描述符,我们就可以将段机制的影响减到最少,可以让你几乎感觉不到他的存在。设置方法如下:设置4个描述符,OS kernel data, OS kernel code, User data, User code。访问权限分别为 0, 0, 3, 3。每个段的线性基地址都为0,这样的话,每个虚拟地址给出的偏移就是线性地址,这就是传说中的flat mode。怎么样,够简单吧?如果你还不是很明白的话我们来看一个简单的例子: 你写了一个程序,里面有 char * p; p = (char *)(0x00010000); *p = 'A'; 经过编译以后变成了这样: mov eax,0x00010000 mov [eax],65 ; 65是'A'的ASCII码 这里你没有看到段地址,是因为Intel规定使用eax寄存器寻址的话如果不指定段寄存器,则默认为ds,就像esp默认为ss一样,Intel有一套段寄存器默认规则。所以这个程序给出的虚拟地址其实是ds:eax即ds:0x00010000。下面我们来看看这个虚拟地址如何被转换成线性地址。首先CPU根据ds的内容,去查找相应的描述符,这些描述符都放在一些表中,成为描述符表(Description Table),就像描述符数组一样,很多描述符都放在那些表中,Intel规定有三种表:全局描述符表GDT, 局部描述符表LDT和中断描述符表IDT,这些表的本身都是放在线性地址空间中的,这些表的具体在线性空间中的位置是这样的:GDT和IDT分别由CPU的寄存器GDTR和IDTR指向,而LDT由GDT中的描述符指向。一般数据段或者代码段的描述符都放在GDT或者LDT中,而具体从这两个表中哪一个中取出来则取决于段寄存器中的bit0。反正就是由ds可以得到一个描述符,然后我们根据先前设置的描述符,取得该段的线性基地址。还记得刚才我们想跳过那该死的段机制吗?OK!如果刚才你的那几句代码是出自于你的OS KERNEL的话,那么我们从ds得到的应该就是OS kernel data描述符,然后还记得我们把所有描述符的线性基地址设为0了么?所以最后的线性地址为 0 + eax 即 0x00010000, omg!千万里我追寻着你~~~终于得到线性地址了,其中还涉及到许多寄存器设置以及映射寄存器等概念,我们暂且跳过了,如果想看得仔细的话就看一下那本Intel 圣经吧! 里程碑啊!绝对是里程碑,我们终于到了线性地址了!下面就要从线性地址转换成为物理地址了。 嗯,是不是已经头晕脑涨了呢?先休息一会儿吧,20分钟后我们再继续。(20分钟休息) OK!休息完了我们继续。不要紧张,分页机制比分段机制简单得多,而且你应该也在很多OS理论书或者计算机组成里面看到过。Intel的分页机制就是把物理内存分割为一个一个的页,每个页的大小为4K(4096 bytes),成为页框(Page frame),从0 - 4K为一页,4K - 8K, 8K - 12K .... 一直到物理内存结束。线性地址空间也被分割为一个一个页,每个大小也是4K,跟物理内存一样。分页的实质其实是为了完成线性地址空间中的页到物理内存中的页的映射,从而完成虚拟存储以及数据保护和共享等。线性空间的页只能被映射到唯一的一个页框,而一个页框可以同时被多个线性空间中页映射,这样就有利于数据共享。 分页映射是怎么实现的呢?我们来看一下一个很简单的映射方案:线性空间中的每个页由一个数据结构描述其对应于物理空间中的哪个页框,由于物理空间最多可能有 2^32 / 4096 = 2^20 个页框,所以这个数据结构至少20-bit,在加上一些页的属性啊,访问权限等描述,一般都取32-bit,也就是4个字节。所以一个线性空间到物理空间的映射需要 2^32 / 4096 * 4 = 4M 的空间来描述映射函数。由于现代操作系统需要做到地址空间隔离,就是每个进程都有一个独立的线性空间,以便于数据隔离,这样的话这些映射函数的空间消耗十分大。 所以Intel采用了2级映射机制,首先每个线性空间有一个4K字节的页目录(Page directory),页目录里面存放有1K个页表指针,每个指针4个字节,这些指针可以指向一些称为页表(Page Table)的结构,每个页表也是4K字节大小,里面放着也是同样的指针,这些指针指向真正的物理内存的页框。不像刚才第一个方案一样,这个方案中每个线性空间必须有一个页目录,但页表的数量可以动态改变。我们来看看一个线性地址是怎么被转换成物理地址的。一个线性地址有32-bit,首先取高最高的10-bit,也就是bit 22 到 bit 31,以这10-bit的数字为索引,在页目录中找到相应的页表地址,然后取中间10-bit,也就是bit 12 到 bit 21,以这10-bit为索引找到刚才页表中的页框地址,然后将这页框地址 + 这个线性地址的低12位就是物理地址了。整个-过程如下: 线性地址: 31 22 21 12 11 0 ------------------------------------------------- | 10 bits | 10 bits | 12 bits | ------------------------------------------------- | | | | | | | | | | | | | | | | | | | | | | | |--------| | |--------| | | | | -->| XXXX ------+------> 物理地址 | |--------| |--------| --->| XXXX ----| | | |--------| | |--------| | | | | | |--------| | | | | | --->|--------| | | page table page dir ->|--------| 这些页目录啊,页表啊都是OS developer设置的,放在物理空间中。为什么要采取这样一种2级映射的方案呢?原因是为了节省内存的开销。本来一个线性地址要消耗4M的空间来映射,现在的方案是每个线性地址空间只需要一个页目录(4K),而页表则可以按需添加和减少,比如线性地址0x80000000 - 0xFFFFFFFF都是没有映射的,则这些地址对应在页目录的位置中的第512项到1023项都可以设为空,就不需要页表,所以如果一个线性空间只映射了4M,则整个映射函数只要占用一个页目录和一个页表也就是总共(8K)的空间,大大节省了。 大家还记不记得刚才讲段模式的提到分页机制也可以有保护?大家有没有发现页目录和页表的每一项都是指向相应的页框的(页目录,页表本身都占用一个页框),但是每个页框的地址都是可以被4K整除的,所以每个指针的低12位都是0。但是页目录和页表的每一项都有4个字节,所以这4个字节的低12位就用来放一些其他的信息,比如访问权限啦,页属性啊等等。其中有一个属性很重要那就是页的Present属性。如果这个位为1的话说明这个页存在于物理内存中,如果是0的话则不在内存中。当页不在内存中时,如果程序访问到这个页,CPU就会产生一个exception叫page fault,然后OS捕获到这个fault后,判断为缺页错误,经过一系列的合法性检查之后,如果是进程缺页错误的话,OS就把程序的相应的页调入到内存中,然后让进程恢复执行,这就是请页机制(demanding page)。 呼~~~终于一口气讲完了IA-32的核心机制,看到这里大家一定快疯了吧,其实我也是....,把Intel的780页的文档压缩到这么短,真是很有挑战啊! OK,温习一下今天的内容!准备明天的挑战吧!!' |
|
汇编语言程序设计--笔记
1.5.3.2 存储器的分段管理 1.5.3.2.1 为何分段 1.5.3.2.2 地址的表示方法 附图: (此文转载而来,作者不详,如侵犯了你的版权,请予以说明) 我将分别介绍如何在保护模式和实模式操作内存,然而在此之前,我们先熟悉一下这两种模式中内存的结构。 3.1 实模式 事实上,在实模式中,内存比保护模式中的结构更令人困惑。内存被分割成段,并且,操作内存时,需要指定段和偏移量。不过,理解这些概念是非常容易的事情。请看下面的图: |-------| |--------| |--------| |内存 | | | | | | | |--------| |--------|段 | | | 段 | | | | | | | | <------|----偏移地址 | | |--------| |--------| | | | | | | --------- |--------| |--------| 段-寄存器这种格局是早期硬件电路限制留下的一个伤疤。地址总线在当时有20-bit。 然而20-bit的地址不能放到16-bit的寄存器里,这意味着有4-bit必须放到别的地方。因此,为了访问所有的内存,必须使用两个16-bit寄存器。 这一设计上的折衷方案导致了今天的段-偏移量格局。最初的设计中,其中一个寄存器只有4-bit有效,然而为了简化程序,两个寄存器都是16-bit有效,并在执行时求出加权和来标识20-bit地址。 偏移量是16-bit的,因此,一个段是64KB。下面的图可以帮助你理解20-bit地址是如何形成的: |-------16bit---------| |----|----|-----|-----|-----| | - | - | - | -| 0000| 段 |----|----|-----|-----|-----| |-------16bit---------| |----|----|-----|-----|-----| |0000| - | - | -| - | 偏移地址 |----|----|-----|-----|-----| |-----------20位地址--------| |----|----|-----|-----|-----| | - | - | - | -| - |物理地址 |----|----|-----|-----|-----| 段-偏移量标识的地址通常记做 段:偏移量 的形式。 由于这样的结构,一个内存有多个对应的地址。例如,0000:0010和0001:0000指的是同一内存地址。又如, 0000:1234 = 0123:0004 = 0120:0034 = 0100:0234 0001:1234 = 0124:0004 = 0120:0044 = 0100:0244 作为负面影响之一,在段上加1相当于在偏移量上加16,而不是一个“全新”的段。反之,在偏移量上加16也和在段上加1等价。某些时候,据此认为段的“粒度”是16字节。 关于保护模式中内存操作的一点说明 正如3.2节提到到的那样,保护模式中,你可以使用32位的线性地址,这意味着直接访问4GB的内存。由于这个原因,选择器不用像实模式中段寄存器那样频繁地修改。顺便提一句,这份教程中所说的保护模式指的是386以上的保护模式,或者,Microsoft通常称为“增强模式”的那种。 在为选择器装入数值的时候一定要非常小心。错误的数值往往会导致无效页面错误(在Windows中经常出现:)。同时,也不要忘记你的地址是32位的,这也是保护模式的主要优势之一。 |
|
|
|
|
|
|
|
|
|
汇编语言程序设计--笔记
1.5.2 8086寄存器组 附图: 1.5.2.1 例图 附整理后的表图: (转自 www.77169.com DJ管理 收集整理) 汇编语言的准备知识--给初次接触汇编者(1) 汇编语言和CPU以及内存,端口等硬件知识是连在一起的. 这也是为什么汇编语言没有通用性的原因. 下面简单讲讲基本知识(针对INTEL x86及其兼容机) ============================ x86汇编语言的指令,其操作对象是CPU上的寄存器,系统内存,或者立即数. 有些指令表面上没有操作数, 或者看上去缺少操作数, 其实该指令有内定的操作对象, 比如push指令, 一定是对SS:ESP指定的内存操作, 而cdq的操作对象一定是eax / edx. 在汇编语言中,寄存器用名字来访问. CPU 寄存器有好几类, 分别有不同的用处: 1. 通用寄存器: EAX,EBX,ECX,EDX,ESI,EDI,EBP,ESP(这个虽然通用,但很少被用做除了堆栈指针外的用途)这些32位可以被用作多种用途,但每一个都有专长. EAX 是累加器(accumulator), 它是很多加法乘法指令的缺省寄存器. EBX 是基地址(base)寄存器, 在内存寻址时存放基地址. ECX 是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器. EDX是数据寄存器,但它总是被用来放整数除法产生的余数. 这4个寄存器的低16位可以被单独访问,分别用AX,BX,CX和DX. AX又可以单独访问低8位(AL)和高8位(AH), BX,CX,DX也类似. 函数的返回值经常被放在EAX中. ESI/EDI分别叫做源/目标索引寄存器(source/destination index),因为在很多字符串操作指令中, DS:ESI指向源串,而ES:EDI指向目标串. EBP是基址指针(BASE POINTER), 它最经常被用作高级语言函数调用的框架指针(frame pointer). 在破解的时候,经常可以看见一个标准的函数起始代码: push ebp ;保存当前ebp mov ebp,esp ;EBP设为当前堆栈指针 sub esp, xxx ;预留xxx字节给函数临时变量. 这样一来,EBP 构成了该函数的一个框架, 在EBP上方分别是原来的EBP, 返回地址和参数. EBP下方则是临时变量. 函数返回时作 mov esp,ebp/pop ebp/ret 即可. ESP 专门用作堆栈指针. 2. 段寄存器: CS(Code Segment,代码段) 指定当前执行的代码段. EIP (Instruction pointer, 指令指针)则指向该段中一个具体的指令. CS:EIP指向哪个指令, CPU 就执行它. 一般只能用jmp, ret, jnz, call 等指令来改变程序流程,而不能直接对它们赋值. DS(DATA SEGMENT, 数据段) 指定一个数据段. 注意:在当前的计算机系统中, 代码和数据没有本质差别, 都是一串二进制数, 区别只在于你如何用它. 例如, CS 制定的段总是被用作代码, 一般不能通过CS指定的地址去修改该段. 然而,你可以为同一个段申请一个数据段描述符别名而通过DS来访问/修改. 自修改代码的程序常如此做. ES,FS,GS 是辅助的段寄存器, 指定附加的数据段. SS(STACK SEGMENT)指定当前堆栈段. ESP 则指出该段中当前的堆栈顶. 所有push/pop 系列指令都只对SS:ESP指出的地址进行操作. 3. 标志寄存器(EFLAGS): 该寄存器有32位,组合了各个系统标志. EFLAGS一般不作为整体访问, 而只对单一的标志位感兴趣. 常用的标志有: 进位标志C(CARRY), 在加法产生进位或减法有借位时置1, 否则为0. 零标志Z(ZERO), 若运算结果为0则置1, 否则为0 符号位S(SIGN), 若运算结果的最高位置1, 则该位也置1. 溢出标志O(OVERFLOW), 若(带符号)运算结果超出可表示范围, 则置1. JXX 系列指令就是根据这些标志来决定是否要跳转, 从而实现条件分枝. 要注意,很多JXX 指令是等价的, 对应相同的机器码. 例如, JE 和JZ 是一样的,都是当Z=1是跳转. 只有JMP 是无条件跳转. JXX 指令分为两组, 分别用于无符号操作和带符号操作. JXX 后面的XX 有如下字母: 无符号操作: 带符号操作: A = ABOVE, 表示高于 G = GREATER, 表示大于 B = BELOW, 表示低于 L = LESS, 表示小于 C = CARRY, 表示进位或借位 O = OVERFLOW, 表示溢出 S = SIGN, 表示负 通用符号: E = EQUAL 表示等于, 等价于Z (ZERO) N = NOT 表示非, 即标志没有置位. 如JNZ 如果Z没有置位则跳转 Z = ZERO, 与E同. 如果仔细想一想,就会发现 JA = JNBE, JAE = JNB, JBE = JNA, JG = JNLE, JGE= JNL, JL= JNGE, .... 4. 端口 端口是直接和外部设备通讯的地方。外设接入系统后,系统就会把外设的数据接口映射到特定的端口地址空间,这样,从该端口读入数据就是从外设读入数据,而向外设写入数据就是向端口写入数据。当然这一切都必须遵循外设的工作方式。端口的地址空间与内存地址空间无关,系统总共提供对64K个8位端口的访问,编号0-65535. 相邻的8位端口可以组成成一个16位端口,相邻的16位端口可以组成一个32位端口。端口输入输出由指令IN,OUT,INS和OUTS实现,具体可参考汇编语言书籍。 |
|
汇编语言程序设计--笔记
1.5.1 8086的功能结构 附图: ***(此文转载而来,作者不详,如侵犯了你的版权,请予以说明)*** 通用寄存器 下面介绍通用寄存器及其习惯用法。顾名思义,通用寄存器是那些你可以根据自己的意愿使用的寄存器,修改他们的值通常不会对计算机的运行造成很大的影响。通用寄存器最多的用途是计算。 EAX 32-bit宽 通用寄存器。相对其他寄存器,在进行运算方面比较常用。在保护模式中,也可以作为内存偏移指针(此时,DS作为段 寄存器或选择器) EBX 32-bit宽 通用寄存器。通常作为内存偏移指针使用(相对于EAX、ECX、EDX),DS是默认的段寄存器或选择器。在保护模式中,同样可以起这个作用。 ECX 32-bit宽 通用寄存器。通常用于特定指令的计数。在保护模式中,也可以作为内存偏移指针(此时,DS作为 寄存器或段选择器)。 EDX 32-bit宽 通用寄存器。在某些运算中作为EAX的溢出寄存器(例如乘、除)。在保护模式中,也可以作为内存偏移指针(此时,DS作为段 寄存器或选择器)。 上述寄存器同EAX一样包括对应的16-bit和8-bit分组。 用作内存指针的特殊寄存器 ESI 32-bit宽 通常在内存操作指令中作为“源地址指针”使用。当然,ESI可以被装入任意的数值,但通常没有人把它当作通用寄存器来用。DS是默认段寄存器或选择器。 EDI 32-bit宽 通常在内存操作指令中作为“目的地址指针”使用。当然,EDI也可以被装入任意的数值,但通常没有人把它当作通用寄存器来用。DS是默认段寄存器或选择器。 EBP 32-bit宽 这也是一个作为指针的寄存器。通常,它被高级语言编译器用以建造‘堆栈帧’来保存函数或过程的局部变量,不过,还是那句话,你可以在其中保存你希望的任何数据。SS是它的默认段寄存器或选择器。 注意,这三个寄存器没有对应的8-bit分组。换言之,你可以通过SI、DI、BP作为别名访问他们的低16位,却没有办法直接访问他们的低8位。 段寄存器和选择器 实模式下的段寄存器到保护模式下摇身一变就成了选择器。不同的是,实模式下的“段寄存器”是16-bit的,而保护模式下的选择器是32-bit的。 CS 代码段,或代码选择器。同IP寄存器(稍后介绍)一同指向当前正在执行的那个地址。处理器执行时从这个寄存器指向的段(实模式)或内存(保护模式)中获取指令。除了跳转或其他分支指令之外,你无法修改这个寄存器的内容。 DS 数据段,或数据选择器。这个寄存器的低16 bit连同ESI一同指向的指令将要处理的内存。同时,所有的内存操作指令 默认情况下都用它指定操作段(实模式)或内存(作为选择器,在保护模式。这个寄存器可以被装入任意数值,然而在这么做的时候需要小心一些。方法是,首先把数据送给AX,然后再把它从AX传送给DS(当然,也可以通过堆栈来做). ES 附加段,或附加选择器。这个寄存器的低16 bit连同EDI一同指向的指令将要处理的内存。同样的,这个寄存器可以被装入任意数值,方法和DS类似。 FS F段或F选择器(推测F可能是Free?)。可以用这个寄存器作为默认段寄存器或选择器的一个替代品。它可以被装入任何数值,方法和DS类似。 GS G段或G选择器(G的意义和F一样,没有在Intel的文档中解释)。它和FS几乎完全一样。 SS 堆栈段或堆栈选择器。这个寄存器的低16 bit连同ESP一同指向下一次堆栈操作(push和pop)所要使用的堆栈地址。这个寄存器也可以被装入任意数值,你可以通过入栈和出栈操作来给他赋值,不过由于堆栈对于很多操作有很重要的意义,因此,不正确的修改有可能造成对堆栈的破坏。 * 注意 一定不要在初学汇编的阶段把这些寄存器弄混。他们非常重要,而一旦你掌握了他们,你就可以对他们做任意的操作了。段寄存器,或选择器,在没有指定的情况下都是使用默认的那个。这句话在现在看来可能有点稀里糊涂,不过你很快就会在后面知道如何去做。 特殊寄存器(指向到特定段或内存的偏移量): EIP 这个寄存器非常的重要。这是一个32位宽的寄存器 ,同CS一同指向即将执行的那条指令的地址。不能够直接修改这个寄存器的值,修改它的唯一方法是跳转或分支指令。(CS是默认的段或选择器) ESP 这个32位寄存器指向堆栈中即将被操作的那个地址。尽管可以修改它的值,然而并不提倡这样做,因为如果你不是非常明白自己在做什么,那么你可能造成堆栈的破坏。对于绝大多数情况而言,这对程序是致命的。(SS是默认的段或选择器) IP: Instruction Pointer, 指令指针 SP: Stack Pointer, 堆栈指针 好了,上面是最基本的寄存器。 |
|
汇编语言程序设计--笔记
**********此教材的重点******* 1.5 8086微处理器目录 1.5.1 8086的功能结构 1.5.2 8086的寄存器组 1.5.3 存储组织与段寄存器 ----1. 存储器组织(位、字节、地址、字单元。。。) ----2.分段管理(分段、逻辑地址、物理地址。。。) ----3.段寄存器(段寄存器、段超越。。。) 附图略省,以后补充。 |
|
|
|
|
|
|
|
汇编语言程序设计--笔记
1.3.4.2 流水线技术 计算机中的流水线技术和工厂中的流水装配线类似。 1。将执行一条指令需完成的操作分成若干子操作; 2。每个子操作由专门部件完成; 3。各子操作并行工作。由于子操作并行工作,提高了计算机的执行速度。(但要注意,完成一条指令所需要的时间并没有变。)(“每周期完成一条指令”和“完成一条指令需要一个周期”不同。)(超标量) 附图略省,以后补充。 1.3.4.3 流水线技术例图: |
|
汇编语言程序设计--笔记
1.3.4.1 RISC技术 RISC技术:精简指令系统计算机技术。 其特点是:CPU的指令系统不含复杂指令,指令长度固定,指令格式种类少,寻址方式少,指令数量少(只选择使用频率很高的指令),因此,使计算机结构变得更加简单、有效、结合流水线等其他技术,使一个时钟周期完成一条指令,从而大大提高了计算机的速度。如1981年研制成RSCI I型CPU,只有31条指令,其性能却比当时最先进的商品化微处理器MC68000和Z8000快3--4倍。 附图略省,以后补充。 |
|
汇编语言程序设计--笔记
1.3.4 80486(32位CPU) 1.3.5 (32位或准64位CPU) 1989年推出;特点: 1。将高速缓存、协处理器与CPU集成在一个芯片上; 2。部分采用了RISC技术; 3。采用了指令流水线技术; 4。大幅度提高了CPU的主频,可达100MHZ。 1.3.5 Pentium/MMX Pentium(32位或准64位CPU) Pentium:1993年推出;内部数据总线32位,外部数据总线64位;主频60MHZ--200MHZ;采用超标量技术。 MMX Pentium:1996年推出;主频最高233MHZ;新增57条多媒体指令,提高了多媒体软件执行速度。 附图略省,以后补充。 |
|
汇编语言程序设计--笔记
1.3.3 8086(32位CPU) 1985年推出;内外部数据总线32位;地址总线32位,物理寻址范围4GB,虚拟存储器寻址范围可达64TB。主频为16/25/33MHZ。 386除支持286的两种工作方式外,新增虚拟8086模式。虚拟8086模式的特点: 1)既具有保护功能,又能执行实模式下的8086代码,且可以实现多任务。(可同时运行多个DOS程序) 2)可以在虚拟8086模式和保护模式下快速、发福转换。指令系统兼容远16位CPU指令外,全面升级为32位指令,并新增多条指令。 附图略省,以后补充。 |
|
汇编语言程序设计--笔记
1.3.2 80286(16位CPU) 1982年推出:内外数据总线16位;地址总线24位;寻址范围16MB;主频5MHZ--20MHZ。 80286支持两种工作方式:实模式和保护模式。 实模式:相当于一个快速8086。 保护模式:提供虚拟存储管理和多任务的硬件控制。物理寻址范围16MB,虚拟存储器寻址范围可达1GB。指令系统除包含8086/80186指令外,新增15条保护方式指令。 附图略省,以后补充。 |
|
汇编语言程序设计--笔记
1.3 Intel80x86系列微处理器 1.3.1 8086/8088/80186 1.8086/8088/80186 8086:1978年推出;内外数据总线16位;地址总线20位;寻址范围1MB;主频5MHZ。 8088:1979年推出;外部数据总线8位;其余基本同8086,被称为准16位CPU。 80186:增强了8086的功能,但作为计算机的CPU没有被使用过,只作过某些板卡的控制器。 8088:1979年推出,外部数据总线8位,内部数据总线16位;其余基本同8086,被称为准16位CPU。(1979年IBM的PC机为何采用8088,而不用8086,因为当时的I/O设备是8位的。) 我也是学习者,没学完呢,我现在能做的就是边学边发。(除非你希望我学完了再整理打包发给你们) 如果这样做满足不了你的要求,那请你另寻他处。 在学习过程所碰到的问题,希望大家做个记录: 一。我不是讲师,我也是来学习的,所以解答不了你的问题; 二。整理 碰到的问题,为日后汇总做出必要的收集。希望大家能注意这点,把碰到的问题,也发给我一份。先谢谢了!@_@ 三。因为我在论坛的权限问题,我每天只能上传1200K的图片,发5篇帖子,自己打字又慢, 耽误了很多课程的发布。我现在已经学到了第2章,发的内容才第1章,希望你们多多理解、谅解。 |
|
[调试]魔兽世界 高手帮帮我呀。付全部代码及详细说明
顶............. |
|
[翻译]可移植的可执行文件格式全接触(附注释)
顶............. |
操作理由
RANk
{{ user_info.golds == '' ? 0 : user_info.golds }}
雪币
{{ experience }}
课程经验
{{ score }}
学习收益
{{study_duration_fmt}}
学习时长
基本信息
荣誉称号:
{{ honorary_title }}
能力排名:
No.{{ rank_num }}
等 级:
LV{{ rank_lv-100 }}
活跃值:
在线值:
浏览人数:{{ visits }}
最近活跃:{{ last_active_time }}
注册时间:{{ user_info.create_date_jsonfmt }}
勋章
兑换勋章
证书
证书查询 >
能力值