心宿二和太阳:linux0.01内核

来源:百度文库 编辑:偶看新闻 时间:2024/05/09 10:14:19

Linux0.01内核分析的一点心得

linux(linux教程 linux培训 )0.01内核基本上分析完了,高版本的内核也看了一点。有一点心得与大家分享一下吧!这里我并不打算说具体的技术方面的东西,而是针对读内核的方法,谈谈自己一点感受。我前段时间主要看的是0.01版本的内核。Linux0.01是Linux的"祖师爷"Linus完成的最早的一个Linux版本,其内核编译后仅仅只有512K,麻雀虽小,五脏俱全,0.01包括了从软盘启动、文件系统、控制台管理的操作系统完整功能,并提供了不少标准的用户接口,具体有kernel, boot, fs, init,mm等几个部分,没有网络部分。为什么选择Linux0.01?各位大虾一看到0.01肯定直摇头:哎呀,都什么时代的东东了,有看的必要么?笔者当初选择0.01并没有太多的想法,只是Tm-linux小组刚开始选择的是0.01,于是就开始读吧,现在仔细想想,读Linux0.01对于初学者来说可能更容易上手些。可能有下面的几个好处吧1)0.01的代码量较小。很多同学都曾有成为Linux高手的欲望,也曾抱回若干砖头书,但Linux的发展何其之快,而coder又是黑客型高手,往往坚持不了多长时间而中途放弃!

2)0.01的代码简单而精简(这个简单当然是相对于后续版本而言的)。实际上0.01完成的就是一个操作系统的最初的要求,包括启动,进程调度,内存管理等,而这些往往与硬件结合,在看高版本的内核时往往还没有接触到这些硬件知识,层层下调已经把你搞糊涂了。

3)从低版本看更能看到技术进步的源动力,比如0.01的内核很小,其启动代码可以只放在一个扇区内,而后续版本的内核较大,无法放入一个扇区内,于是压缩核心的装入方法诞生了。再如内存管理,0.01的内存管理比较简单,内存的申请释放直接通过使用前申请,使用后释放,但考虑以后的版本功能复杂,如何解决可能的"外碎片"问题,如何解决内存不足的问题,于是对后续版本采用的"伙伴算法",slab技术,页面守护进程有更好的理解。我说上面的这一些并非是为0.01做广告啦,而是提醒一下读内核可能遇到的问题:比如说层次复杂难以理清,代码众多不知主次,哪些是操作系统必需的,那些是为提高系统性能的等。看内核的目的是什么,这是每个看内核的人都需要问问自己的问题。我想对于大多数初看内核的人来说,最重要的是解决从操作系统原理到一个实际操作系统的过渡,简单一点说是操作系统到底是怎么跑起来的。操作系统原理大家都学了,但一个操作系统到底是怎么启动的,所谓的保护模式到底是怎么一回事,进程调度和切换到底是怎么进行的,一个文件系统具体应该怎样,可能很多还是模糊的。通过看Linux内核,这些问题可以得到清晰的解答。当然,一个有强大生命力的操作系统决不会仅仅满足于能跑起来,她采用的众多先进的技术使得她有出色的性能,对于以后的工程设计、编程还有类Unix下的系统开发会有很多帮助。鉴于Linux的强大技术实力,在很多领域都会遇到跟其采用技术相关的,如嵌入式领域,网络接口等等。理解了内核,当然在以后的开发中可以事半功倍。我当初读内核的目的比较简单,我就是想看看Linux是怎么跑起来的,为什么多个进程可以互不干扰的运行。我读代码的工具是用的source insight,可惜不能自动识别汇编.s文件,而0.01汇编文件不少。哦,讲了这么多,还没进入主题:我的体会心得啦从哪部分开始读,一般读内核都建议从boot部分开始读,这样循序渐进,而且一般启动部分似乎最神秘,最能满足好奇心啦!不过我是从memory部分开始读的,因为对逻辑地址到物理地址的转换一直很模糊。从你感兴趣的地方开始读,在以后比较容易坚持。在读内核的方法上,我建议"先横后纵"。在读内核的方法上,我建议"先横后纵"。

Souce insight的好处就是你点上某个函数,能自动显示该函数的定义,这样比较方便的跳入察看。对于刚开始看内核的人,我并不太建议一开始用这种方法。因为函数层层嵌套,等你下到最底层,早已忘了当初的调用的功能,建议只跳一层看看调用函数的注解或者简单看看函数代码猜测功能,做一些记号。我个人觉得,操作系同比较重要的是数据结构和算法,算法固然在程序中体现,而数据结构需要查看相关的头文件及相关的文件才能了解。所以刚开始时我还是采用一个文件一个文件的读,即所谓的"横",因为一个文件中的安排总是对特定的数据结构的一些操作,你有更多的机会去理解各个主要数据结构的功能,包括结构中各个子项的含义。当然不要忘了写注释啦(虽然现在的理解可能并不准确)。在一个模块,如FS模块的各个文件都读完之后,就采用了所谓的"纵"了,你可以选择一个比较重要的函数入手,进行层层跳入(或若干层)以领会各个数据结构间的有机关系。如fs模块涉及的主要数据结构有:超级块,节点索引块,数据索引块,高速缓冲块,文件等,在单独理解各个相关文件后,可以从do_execve入手,看一个文件从输入文件参数到最后执行的主要步骤,进而理解VFS和具体fs之间的接口。在每一个主要模块读完之后,最后自己写一份总结(书面总结啦),看看自己对这个模块的把握。因为读代码往往是比较细致的,可能专注于读懂代码本身而忽略了模块的功能和组织形式。这点我认为比较重要,所谓"牛吃草要反刍回味",读代码也许要在看完之后进行总结,这样才能上升一个层次,真正理解其方法和结构的精妙之处。

最后,谈谈看Linux内核需要的一些基础吧

1) Intel386硬件知识,比如各寄存器,TSS,段描述表(IDT,GDT,LDT)等,段页转换机制等(高版本的Linux支持其他硬件体系,但对intelx86的比较熟一些吧)

2) 汇编指令,尤其是AT&T语法及其迁入式汇编,当初我初次看到诸如 代码:

  

  __asm__("std ; repne ; scasw\n\t"

   "jne 1f\n\t" "movw $1,2(%%edi)\n\t"

  

   "movw $1,2(%%edi)\n\t"

   "sall $12,%%ecx\n\t"

   "movl %%ecx,%%edx\n\t"

   "addl %2,%%edx\n\t"

   "movl $1024,%%ecx\n\t"

   "leal 4092(%%edx),%%edi\n\t"

   "rep ; stosl\n\t"

   "movl %%edx,%%eax\n"

   "1:"

   :"=a" (__res)

   :"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),

   "D" (mem_map+PAGING_PAGES-1)

   :"di","cx","dx");

  

  这样的一条汇编语句感到头脑发胀,不过了解其格式后,多看就习惯了。

  3) 操作系统的基本知识,对于操作系统的一般理解,这个大家都有吧!

  4) 一本或几本Linux内核的参考书,有参考书总要好得多,毕竟很多数据结构不用自己猜,我在读0。01感觉有很大不同,很多地方只能猜,不过还是有用的。至于Linux内核的书已经很多了,到都乐去看看就知道了

  5) 恒心+毅力:这是最重要的一点,初始的兴趣往往会被遇到的困难一扫而光,再加上  其他杂七杂八的事情,坚持确实很困难。当初Tm-linux组刚开始参加内核分析的人不下几十,但坚持到底的也就寥寥数人(这只是0.01哦)

  6) 跟别人多多交流,毕竟个人能力有限,通过交流,可以增进知识,我很鼓励书面的交流,虽然可能需要花费一些时间来写,因为书面的东西以后可查,而书面的跟别人交流总希望正确一点,少犯错误啦。这一点西安交大做得比较好,组织了Linux内核论坛,  隔一段时间聚会交流一次,每次有一主题,先有人主讲,然后大家互相讨论。这些你有些没有吗,那么恭喜你啦,因为通过读内核你就可以增加这方面的知识和经验了。读内核也不是最终目的,对自己在嵌入式系统的开发,或Linux的"运用自如",或者实现  自己的操作系统,那才是"剑出鞘时"!

  

 

本人在2010春节期间看了linux0.01的视频教程,是由著名的网络教师由尚德做的,也渐渐习惯了他的风格。

看了将近好几g的视频之后,终于明白了,内核的确不是一般人可以看的,需要很多的基础知识,包括很多我还没有学的知识,所以有一种被强迫而举步维艰的感觉。

   汇编语言在第一次上课的时候就被提到了,而且是AT&T格式的,需要记忆太多的指令,当然还有存储的理解,毕竟我那时还没有学习过汇编。

    讲到汇编,当然在底层有c语言和汇编语言的结合了,物理的存储。这样对于操作系统的基础也有了理解。

   数据结构成了一个工具,无形的工具,无处不在。关于链表,堆栈,队列等等。而且每一个函数都是很长的,关于数据之间的输入和输出处理,真的是很困难。对于程序员的读代码的能力是一个很大的考验。一个c文件,往往是十几个函数在一起。反正是渐渐的有了这样的一个意识。各种特殊的处理,比如各种表的查找,虽然没有仔细的讲,但是都是必不可少。

    Makefile也有很多,幸亏以前看过一些Makefile的基础知识,也可以自己看着摸清楚其中文件之间的关系。对于linuxer的要求就体现出来了。

     对于内核文件目录的熟悉,会对你有一臂之力。

     驱动是内核的一个重头戏,块设备,字符设备都有仔细的讲到,很考验c和驱动知识,电子电路和模拟数字电路知识啊!!

     文件系统的设计和源代码的阅读能力,四大支柱之一。内核里必不可少。

     最后是内存的管理。对了,在c语言中,结构体和指针的要熟练掌握,我现在都有些模糊。

     下面总结一下阅读前的所需知识:c语言知识,最好是标准c和linux下的;汇编语言,at&t的最好;数据结构知识;计算机操作系统组成原理;算法设计;了解linux驱动设计;内存优化设计方面的知识。

     最后一句话,难,不是随便说说!多看几遍!

    

 

文章出处:飞诺网(www.diybl.com):http://www.diybl.com/course/6_system/linux/linuxjq/20100302/196345.htmlX86 CPU工作原理

2008-10-22 21:28

随着AMD Athlon的推出,两大CPU厂商Intel和AMD之间的竞争愈演愈烈,几乎每个月都有新的CPU推出,各个媒体和网上也充斥着各种各样的评测性文章。
  但是,现在我要问一句:“什么是CPU?”我相信大多数人并不知道什么是CPU。当然,你可以回答CPU是中央处理器,或者来一句英文:Central Processing Unit。是的,没错。但,RISC和CISC是什么?什么是“9路超标量设计”、“20级流水线”?什么是“解码”,为什么Athlon和酷睿的解码过程需要的时钟周期大大大于其他的RISC处理器?这些都不是一句“中央处理器”所能够回答的。
  本文希望以比较通俗的语言深入介绍一下CPU的原理。

一、 指令系统
  要讲CPU,就必须先讲一下指令系统。指令系统指的是一个CPU所能够处理的全部指
令的集合,是一个CPU的根本属性。比如我们现在所用的CPU都是采用x86指令集的,他们都是同一类型的CPU,不管是酷睿、Athlon或Joshua。我们也知道,世界上还有比酷睿和Athlon快得多的CPU,比如Alpha,但它们不是用x86指令集,不能使用数量庞大的基于x86指令集的程序,如WindowsXP。之所以说指令系统是一个CPU的根本属性,是因为指令系统决定了一个CPU能够运行什么样的程序。
  所有采用高级语言编出的程序,都需要翻译(编译或解释)成为机器语言后才能运行,这些机器语言中所包含的就是一条条的指令。
1、 指令的格式
  一条指令一般包括两个部分:操作码和地址码。操作码其实就是指令序列号,用来告诉CPU需要执行的是那一条指令。地址码则复杂一些,主要包括源操作数地址、目的地址和下一条指令的地址。在某些指令中,地址码可以部分或全部省略,比如一条空指令就只有操作码而没有地址码。
  举个例子吧,某个指令系统的指令长度为32位,操作码长度为8位,地址长度也为8位,且第一条指令是加,第二条指令是减。当它收到一个“00000010000001000000000100000110”的指令时,先取出它的前8位操作码,即00000010,分析得出这是一个减法操作,有3个地址,分别是两个源操作数地址和一个目的地址。于是,CPU就到内存地址00000100处取出被减数,到00000001处取出减数,送到ALU中进行减法运算,然后把结果送到00000110处。
  这只是一个相当简单化的例子,实际情况要复杂的多。
2、 指令的分类与寻址方式
  一般说来,现在的指令系统有以下几种类型的指令:
(1)算术逻辑运算指令
  算术逻辑运算指令包括加减乘除等算术运算指令,以及与或非异或等逻辑运算指令。现在的指令系统还加入了一些十进制运算指令以及字符串运算指令等。
(2)浮点运算指令
  用于对浮点数进行运算。浮点运算要大大复杂于整数运算,所以CPU中一般还会有专门负责浮点运算的浮点运算单元。现在的浮点指令中一般还加入了向量指令,用于直接对矩阵进行运算,对于现在的多媒体和3D处理很有用。
(3)位操作指令
  学过C的人应该都知道C语言中有一组位操作语句,相对应的,指令系统中也有一组位操作指令,如左移一位右移一位等。对于计算机内部以二进制编码表示的数据来说,这种操作是非常简单快捷的。
(4)其他指令
  上面三种都是运算型指令,除此之外还有许多非运算的其他指令。这些指令包括:数据传送指令、堆栈操作指令、转移类指令、输入输出指令和一些比较特殊的指令,如特权指令、多处理器控制指令和等待、停机、空操作等指令。
  对于指令中的地址码,也会有许多不同的寻址(编址)方式,主要有直接寻址,间接寻址,寄存器寻址,基址寻址,变址寻址等,某些复杂的指令系统会有几十种甚至更多的寻址方式。
3、 CISC与RISC
  CISC,Complex Instruction Set Computer,复杂指令系统计算机。RISC,Reduced Instruction Set Computer,精简指令系统计算机。虽然这两个名词是针对计算机的,但下文我们仍然只对指令集进行研究。
(1)CISC的产生、发展和现状
  一开始,计算机的指令系统只有很少一些基本指令,而其他的复杂指令全靠软件编译时通过简单指令的组合来实现。举个最简单的例子,一个a乘以b的操作就可以转换为a个b相加来做,这样就用不着乘法指令了。当然,最早的指令系统就已经有乘法指令了,这是为什么呢?因为用硬件实现乘法比加法组合来得快得多。
  由于那时的计算机部件相当昂贵,而且速度很慢,为了提高速度,越来越多的复杂指令被加入了指令系统中。但是,很快又有一个问题:一个指令系统的指令数是受指令操作码的位数所限制的,如果操作码为8位,那么指令数最多为256条(2的8次方)。
那么怎么办呢?指令的宽度是很难增加的,聪明的设计师们又想出了一种方案:操作码扩展。前面说过,操作码的后面跟的是地址码,而有些指令是用不着地址码或只用少量的地址码的。那么,就可以把操作码扩展到这些位置。
  举个简单的例子,如果一个指令系统的操作码为2位,那么可以有00、01、10、11四条不同的指令。现在把11作为保留,把操作码扩展到4位,那么就可以有00、01、10、1100、1101、1110、1111七条指令。其中1100、1101、1110、1111这四条指令的地址码必须少两位。
然后,为了达到操作码扩展的先决条件:减少地址码,设计师们又动足了脑筋,发明了各种各样的寻址方式,如基址寻址、相对寻址等,用以最大限度的压缩地址码长度,为操作码留出空间。
  就这样,慢慢地,CISC指令系统就形成了,大量的复杂指令、可变的指令长度、多种的寻址方式是CISC的特点,也是CISC的缺点:因为这些都大大增加了解码的难度,而在现在的高速硬件发展下,复杂指令所带来的速度提升早已不及在解码上浪费点的时间。除了个人PC市场还在用x86指令集外,服务器以及更大的系统都早已不用CISC了。x86仍然存在的唯一理由就是为了兼容大量的x86平台上的软件。
(2)RISC的产生、发展和现状
  1975年,IBM的设计师John Cocke研究了当时的IBM370CISC系统,发现其中占总指令数仅20%的简单指令却在程序调用中占了80%,而占指令数80%的复杂指令却只有20%的机会用到。由此,他提出了RISC的概念。
事实证明,RISC是成功的。80年代末,各公司的RISC CPU如雨后春笋般大量出现,占据了大量的市场。到了90年代,x86的CPU如pentium和k5也开始使用先进的RISC核心。
  RISC的最大特点是指令长度固定,指令格式种类少,寻址方式种类少,大多数是简单指令且都能在一个时钟周期内完成,易于设计超标量与流水线,寄存器数量多,大量操作在寄存器之间进行。由于下文所讲的CPU核心大部分是讲RISC核心,所以这里就不多介绍了,对于RISC核心的设计下面会详细谈到。
  RISC目前正如日中天,Intel的Itanium也将最终抛弃x86而转向RISC结构。

二、CPU内核结构
  好吧,下面来看看CPU。CPU内核主要分为两部分:运算器和控制器。
(一) 运算器
1、 算术逻辑运算单元ALU(Arithmetic and Logic Unit)
  ALU主要完成对二进制数据的定点算术运算(加减乘除)、逻辑运算(与或非异或)以及移位操作。在某些CPU中还有专门用于处理移位操作的移位器。
  通常ALU由两个输入端和一个输出端。整数单元有时也称为IEU(Integer Execution Unit)。我们通常所说的“CPU是XX位的”就是指ALU所能处理的数据的位数。
2、 浮点运算单元FPU(Floating Point Unit)
  FPU主要负责浮点运算和高精度整数运算。有些FPU还具有向量运算的功能,另外一些则有专门的向量处理单元。
3、通用寄存器组
  通用寄存器组是一组最快的存储器,用来保存参加运算的操作数和中间结果。
在通用寄存器的设计上,RISC与CISC有着很大的不同。CISC的寄存器通常很少,主要是受了当时硬件成本所限。比如x86指令集只有8个通用寄存器。所以,CISC的CPU执行是大多数时间是在访问存储器中的数据,而不是寄存器中的。这就拖慢了整个系统的速度。而RISC系统往往具有非常多的通用寄存器,并采用了重叠寄存器窗口和寄存器堆等技术使寄存器资源得到充分的利用。
  对于x86指令集只支持8个通用寄存器的缺点,Intel和AMD的最新CPU都采用了一种叫做“寄存器重命名”的技术,这种技术使x86CPU的寄存器可以突破8个的限制,达到32个甚至更多。不过,相对于RISC来说,这种技术的寄存器操作要多出一个时钟周期,用来对寄存器进行重命名。
4、 专用寄存器
  专用寄存器通常是一些状态寄存器,不能通过程序改变,由CPU自己控制,表明某种状态。
(二) 控制器
  运算器只能完成运算,而控制器用于控制着整个CPU的工作。
1、 指令控制器
  指令控制器是控制器中相当重要的部分,它要完成取指令、分析指令等操作,然后交给执行单元(ALU或FPU)来执行,同时还要形成下一条指令的地址。
2、 时序控制器
  时序控制器的作用是为每条指令按时间顺序提供控制信号。时序控制器包括时钟发生器和倍频定义单元,其中时钟发生器由石英晶体振荡器发出非常稳定的脉冲信号,就是CPU的主频;而倍频定义单元则定义了CPU主频是存储器频率(总线频率)的几倍。
3、 总线控制器
  总线控制器主要用于控制CPU的内外部总线,包括地址总线、数据总线、控制总线等等。
4、中断控制器
  中断控制器用于控制各种各样的中断请求,并根据优先级的高低对中断请求进行排队,逐个交给CPU处理。
(三) CPU核心的设计
  CPU的性能是由什么决定的呢?单纯的一个ALU速度在一个CPU中并不起决定性作用,因为ALU的速度都差不多。而一个CPU的性能表现的决定性因素就在于CPU内核的设计。
1、超标量(Superscalar)
  既然无法大幅提高ALU的速度,有什么替代的方法呢?并行处理的方法又一次产生了强大的作用。所谓的超标量CPU,就是在集成了多个ALU、多个FPU、多个译码器和多条流水线的CPU上,以并行处理的方式来提高性能。
  超标量技术应该是很容易理解的,不过有一点需要注意,就是不要去管“超标量”之前的那个数字,比如“9路超标量”,不同的厂商对于这个数字有着不同的定义,更多的这只是一种商业上的宣传手段。
2、流水线(Pipeline)
  流水线是现代RISC核心的一个重要设计,它极大地提高了性能。
  对于一条具体的指令执行过程,通常可以分为五个部分:取指令,指令译码,取操作数,运算(ALU),写结果。其中前三步一般由指令控制器完成,后两步则由运算器完成。按照传统的方式,所有指令顺序执行,那么先是指令控制器工作,完成第一条指令的前三步,然后运算器工作,完成后两步,再在指令控制器工作,完成第二条指令的前三步,再在运算器工作,完成第二条指令的后两部……很明显,当指令控制器工作时运算器基本上在休息的,而当运算器在工作时指令控制器却在休息,造成了相当大的资源浪费。解决方法很容易想到,当指令控制器完成了第一条指令的前三步后,直接开始第二条指令的操作,运算单元也是。这样就形成了流水线系统,这是一条2级流水线。
  如果是一个超标量系统,假设有三个指令控制单元和两个运算单元,那么就可以在完成了第一条指令的取址工作后直接开始第二条指令的取址,这时第一条指令在进行译码,然后第三条指令取址,第二条指令译码,第一条指令取操作数……这样就是一个5级流水线。很显然,5级流水线的平均理论速度是不用流水线的4倍。
  流水线系统最大限度地利用了CPU资源,使每个部件在每个时钟周期都工作,大大提高了效率。但是,流水线有两个非常大的问题:相关和转移。
  在一个流水线系统中,如果第二条指令需要用到第一条指令的结果,这种情况叫做相关。以上面哪个5级流水线为例,当第二条指令需要取操作数时,第一条指令的运算还没有完成,如果这时第二条指令就去取操作数,就会得到错误的结果。所以,这时整条流水线不得不停顿下来,等待第一条指令的完成。这是很讨厌的问题,特别是对于比较长的流水线,比如20级,这种停顿通常要损失十几个时钟周期。目前解决这个问题的方法是乱序执行。乱序执行的原理是在两条相关指令中插入不相关的指令,使整条流水线顺畅。比如上面的例子中,开始执行第一条指令后直接开始执行第三条指令(假设第三条指令不相关),然后才开始执行第二条指令,这样当第二条指令需要取操作数时第一条指令刚好完成,而且第三条指令也快要完成了,整条流水线不会停顿。当然,流水线的阻塞现象还是不能完全避免的,尤其是当相关指令非常多的时候。
  另一个大问题是条件转移。在上面的例子中,如果第一条指令是一个条件转移指令,那么系统就会不清楚下面应该执行那一条指令?这时就必须等第一条指令的判断结果出来才能执行第二条指令。条件转移所造成的流水线停顿甚至比相关还要严重的多。所以,现在采用分支预测技术来处理转移问题。虽然我们的程序中充满着分支,而且哪一条分支都是有可能的,但大多数情况下总是选择某一分支。比如一个循环的末尾是一个分支,除了最后一次我们需要跳出循环外,其他的时候我们总是选择继续循环这条分支。根据这些原理,分支预测技术可以在没有得到结果之前预测下一条指令是什么,并执行它。现在的分支预测技术能够达到90%以上的正确率,但是,一旦预测错误,CPU仍然不得不清理整条流水线并回到分支点。这将损失大量的时钟周期。所以,进一步提高分支预测的准确率也是正在研究的一个课题。
  越是长的流水线,相关和转移两大问题也越严重,所以,流水线并不是越长越好,超标量也不是越多越好,找到一个速度与效率的平衡点才是最重要的。

三、CPU的外核
1、解码器(Decode Unit)
  这是x86CPU才有的东西,它的作用是把长度不定的x86指令转换为长度固定的类似于RISC的指令,并交给RISC内核。解码分为硬件解码和微解码,对于简单的x86指令只要硬件解码即可,速度较快,而遇到复杂的x86指令则需要进行微解码,并把它分成若干条简单指令,速度较慢且很复杂。好在这些复杂指令很少会用到。
  Athlon也好,酷睿也好,老式的CISC的x86指令集严重制约了他们的性能表现。
2、一级缓存和二级缓存(Cache)
  一级缓存和二级缓存是为了缓解较快的CPU与较慢的存储器之间的矛盾而产生的,一级缓存通常跟CPU内核同速运行,而二级缓存则是以OnDie或OnBoard的方式以较快于存储器的速度运行。对于一些大数据交换量的工作,CPU的缓存显得尤为重要。

  好了,看到了吧,CPU其实也就这样,并不是很神秘。这篇文章的所有内容都不针对某一种CPU,而是适合于任何CPU,是一些最基本的CPU原理,希望能够对你有所帮助。

 

 

MOVW:将DS:SI的内容送至ES:DI,是复制过去,原来的代码还在。很多书用了“移”这个字,实际上是复制过去。

Linux内核中引导部分一开始有这样一段代码:

45 entry start ! 告知连接程序,程序从start 标号开始执行。
46 start:
47 mov ax,#BOOTSEG !
将ds 段寄存器置为0×7C0;
48 mov ds,ax
49 mov ax,#INITSEG    !
将es 段寄存器置为0×9000;
50 mov es,ax
51 mov cx,#256             !
移动计数值=256 字;
52 sub si,si                    ! 源地址 ds:si = 0×07C0:0×0000
53 sub di,di                    ! 目的地址
es:di = 0×9000:0×0000
54 rep                              ! 重复执行,直到
cx = 0
55 movw                          ! 移动1 个字;

56 jmpi go,INITSEG      ! 间接跳转。这里INITSEG 指出跳转到的段地址。
57 go: mov ax,cs            ! 将ds、es 和ss 都置成移动后代码所在的段处(0×9000)。

! 47–56 行作用是将自身(bootsect)从目前段位置0×07c0(31k)
! 移动到0×9000(576k)处,共256 字(512 字节),然后跳转到

! 移动后代码的go 标号处,也即本程序的下一语句处。

注意,在55行执行完毕之后,0×7c00之后的512字节应当与0×9000之后的512字节一模一样。
然后看56行,这里的go是段内偏移,也就是0×39(十进制的57),而INITSEG=0×9000,所以执行这条语句是跳到0×9000:0×39也就是复制过去的第57行,这样代码就相当于在一个程序里继续执行了。