如果把硬盘也抽象成类似内存一样的高楼,撇开扇区的“概念”。 内存里面的一层楼住着:8个bit位=1个字节。硬盘里面的一层楼住着:4096bit位=512个字节。之前因为我们是没有操作系统的虚拟计算机,为了让CPU执行我们的代码。迫不得已把我们的代码放到了硬盘的引导区,让BIOS自检完成之后。自动把引导区的代码加载到0x07c00的位置运行。但如果我们写的程序,万一大于512个字节怎么办?比如3000个字节?甚至很多。当然前提是内存里面也得能放下。这个时候,引导区肯定就放不下了,如果用之前的方法,我们的程序就没办法运行了。为了解决这个问题。我们需要思考一个问题:大家想象:BIOS程序是怎么样把我们512个字节的引导区代码加载到内存0x07c00处,开始执行的?虽然不知道原理。但根据这个原理,我们可以想象一副这样的图。
当BIOS,把硬盘的第一扇区,也就是最开始512个字节加载到0x07C00的位置。引导区的程序指令就被运行起来了,这个过程跟奥运火炬传递一样。而加载器要把“真正的程序”加载到内存得肯定得知道几件事情。1.首先得知道“真正的程序”在硬盘上的起始扇区?2.这个程序占用了硬盘多少个扇区。3.内存中的什么地方是空闲的?4.怎么样把它们从硬盘上搬到内存里面。5.搬进来之后,让CPU如何跳到那块程序代码区执行指令。现在我们讲的内容对应着《x86》这本书的第八章,大家可以私下来结合着书,网上可以下载到电子书看。这节课我们需要进一步抽象了。至于为什么这么设计,或者为何如此定义理解,这是我们的计算机先驱者抽象的概念。当然这并不是最终心态,Windows上操作系统的可执行程序(exe)文件格式。里面的抽象概念和细节要比这个复杂的多,也是我们未来要研究的课程目标之一。站到CPU,内存,硬盘的角度去看待什么标号,变量,函数,指针,屏幕显示字符,段寄存器,通用寄存器,它完全不理解。它只是傻乎乎的一堆开关不停的变化切换电流运行的线路,听你的指挥,通过什么寄存器组合定位到哪个内存地址,把什么数据从硬盘,内存,寄存器,运算器里面搬来搬去的。操作系统本身就是一个很大的程序,而且这个程序抽象出来很多新的概念。什么文件夹,文件目录,进程,线程,内核,注册表,启动项,时钟,窗体,对话框,定时器,多任务多线程……
之所以这些课程有人认为比较难以理解,就是抽象概念太多了。本质就是一堆1和0.我们以前在《穿越计算机的迷雾》里面学习的非门,与门,或门,最后抽象了很多概念寄存器,计数器,控制器,运算器,存储器等。又利用抽象出来的概念再次组合不断的发明新的名词,新的知识点。其实任何行业,任何知识都是如此。按照现代的科学技术,无论是计算机还是人类,所有的活动,生命,死亡,诞生,疾病,一切的一切,都是电子,质子,中子的变化。那么人的意识,思维,想法,又是什么呢?回到主题,所以说,学习计算机,跟学习哲学很像!!
聪明的朋友可能会想到了,以后我们抽象的东西会越来越多。越来越接近人脑中现实世界已经抽象好的概念,即使一些完全不懂计算机的爷爷奶奶也会把“电子计算机”理解成“电脑”,这何尝不是一种抽象。操作系统把一个硬盘分成几大区,跟家里面不同的柜子一样。C盘安装系统。 D盘安装常用软件 E盘存储一些照片,视频,音乐等。每个盘就像一个独立的王国,每个王国下面又可以新建文件夹,好像不同的城市。每个城市还有村落。当有不同的程序同时运行的时候,抽象出进程的概念。可当我们最初使用电脑的时候,如果没有这些知识。是无法感受到这个过程其实就是不断的对硬盘和内存运算器寄存器不停的写数据,读数据继而演化出如此众多的概念名词。这对于我个人也是一个非常大的挑战。坦白来说,这比讲其他编程语言程序设计要难的多。因为有很多硬件层面的知识点,我们只能靠自己的想象力。
先来看一张图在这里,我们就得再引出一个概念了:端口。(port)你可以简单把它理解成一个“住址”。为了后面的解释更加简单。我们把处理器比作“皇帝”。输入输出控制设备集中器芯片比作“服侍皇帝的身边太监”“住址”(端口)都是统一标号的:0x01. 代表太后寝宫0x0002. 代表王爷府0x0003. 代表御膳房………………………………………………0x01 "向太后问安"0x0002 "召见"0x0003 "用膳"………………………………………………"皇帝"先告诉"太监"一个"住址",以及要办的事情,比如召见某位“辅佐大臣“剩下的事情就是“太监”去处理了。当然上述比喻逻辑上并没有那么完美,也不一定完全适用,参考理解而已。
内存的读写单位是字节。硬盘的读写单位是扇区。要读就至少得读一个扇区,要写也至少得写一个扇区。不能说,我只读一个扇区的几个字节。所以,内存和硬盘之间的数据交互是以扇区单位交互的。我在备课到这个地方的时候,心里嘀咕一个问题,固态硬盘是没有“扇区”的概念。很多历史性技术问题,可能已经处于淘汰甚至早就应该被淘汰了。但迫不得已为了兼容历史的包袱,依然在沿用,归根到底还是“成本”。比如现在发明了一种新的燃油,“氦油”,可以取代汽油,同样的做功,成本只需要汽油的70%但由于目前所有已生产的汽车发动机技术,都不能使用这种“氦油”。厂家也不会为了这个问题,单独重新设计所有型号的汽车发动机,再更换。毕竟有些型号早就停售了。所以想使用新的“氦油”,就得用支持“氦油”的发动机技术,假如现在全球有5亿辆汽车,全部都得更换的话。从汽车流水线,到工人培训,以及慢慢淘汰回收市场的旧汽车到真正彻底停止销售汽油,是需要很长时间的,很大的成本。计算机硬件技术发展太快。在历史上,往往一个优秀的产品设计出来没过多久,结果技术进步了,有了更好的设计……所以导致了很多历史的“包袱”,其实很多行业都存在这种情况。不光是硬件,包括一些软件技术也是,已经被淘汰了,出来更好的设计。比如微软关于VC++里面的MFC框架,微软自己也放弃了。但有很多以前的软件技术使用的MFC框架开发的,直到今天还在维护升级。再比如有的一些软件,只能在Windows xp上运行。 重新开发的话成本太高,所以依然有的地方迫不得已使用Windows xp系统,甚至更早的Windows 98.
给大家说这些事情,就是想让大家不用太过于纠结,较真。世界上唯一不变的就是一直在改变。有个概念的笼统认识,最后我们的汇编代码能正常运行就OK。学习计算机体系结构的目的,不是让你去设计自己的 CPU(新的 ISA 或微架构),打败 Intel 和 ARM;也不是参与到 CPU 设计团队,改进现有的微架构;而是明白现代的处理器的能力与特性(例如流水线、多发射、分支预测、乱序执行等等指令级并行手段,内存局部性与 cache,多处理器的内存模型、能见度、重排序等等),在编程的时候通过适当组织代码和数据来发挥 CPU 的效能。
学习操作系统的目的,不是让你去发明自己操作系统内核,打败 Linux;也不是成为内核开发人员;而是理解操作系统为用户态进程提供了怎样的运行环境,作为程序员应该如何才能充分利用好这个环境,哪些做法是有益的,哪些是做无用功,哪些则是帮倒忙。
学习X86这门课程,也不是让大家以后真的用汇编语言,在没有操作系统的电脑上,去写代码,为了打印一行文字,都费老大的劲。没有大家没有一个正确的学习心态和把握,很容易陷入到无穷无尽没意义的研究中,继而浪费掉大量的时间,做了太多的无用功。大家一定要明白一件事情,我们最终的目的是:为了能够做网站,做电脑软件,做手机软件这叫做计算机应用,解决自己遇到的技术问题,这是可以兑现成金钱,价值,被人尊重的东西。什么知识点,应该浅尝而止。什么知识点,应该重点了解。把时间和精力放到有意义的地方。
要从硬盘上读写数据。需要向硬盘控制器发送磁头号,柱面号和扇区号,用这样的方式跟硬盘沟通太费劲了,这种最原始,最费劲的方法叫做CHS模式。正如最早的时候用机器语言跟计算机说话,用像素点的方式在屏幕上拼凑字符。后来有了汇编语言和汇编器。有了Ascll码,直接写到显卡的某个指定位置,如何变成像素点显示到屏幕上,那是显卡的工作,与我们没关系。
CHS模式太麻烦。又发明了一种模式,跟读写内存比较像。这种方法叫做LBA28。使用28个比特位来表示逻辑区号。也就是0x0000000到0xFFFFFFF.最多可以表示:268435456个扇区,每个扇区512字节。所以LBA28可以管理128GB的硬盘。后来硬盘越来越大,28个比特位定位扇区显然不能满足了。一堆指定规则的人,又商量,干脆用48个比特位来定位扇区吧。所以,又有了LBA48,采用48个比特位定位扇区,可以管理131072TB的硬盘容量了。
我们今天的电脑,硬盘控制器和“太监”之间一共有8个端口。这8个门端口的名字叫做,这都是人定义的,没有为什么!0x1F00x1F10x1F20x1F30x1F40x1F50x1F60x1F7假设我们要从硬盘的扇区1开始读,并且读1个扇区长度。要读的位置用LBA28的方式来表示。0000 0000 0000 0000 0000 0000 00010x1f3端口存放的是0~7位.0x1f4端口存放的是8~15位0x1f5端口存放的是16~23位最后4位在0x16f端口里面要读的长度放到1F2里面。
mov dx,0x1F2mov al,0x01out dx,almov dx,0x1F3mov al,0x01out dx,almov dx,0x1F4mov al,0x00out dx,almov dx,0x1F5mov al,0x00out dx,almov dx,0x1F6mov al,0xE0out dx,almov dx,0x1F7mov al,0x20 ;读命令out dx,al0x1F7是命令端口,又是状态端口。发送0x20,硬盘里面的“管理员”就开始做一些准备工作了,并且把会0x1F7里面的第7个位置变成1代表自己很忙。0010 0000 =我要拿东西1010 0000 =我开始准备了0010 1000 =我准备好了等“硬盘管理员”把一些准备工作做好之后,再把0x1F7里面的第7个位置变成0,代表忙完了。并且会把0x1F7端口的第3个位置变成“1”,代表,我已经把准备工作做好了。wait:in al,dx ;把1F7端口里面的数据读入到al里面and al,0x88 ; 请求读 al=0010 0000 准备中 al=1010 0000 准备完毕 al=0010 1000cmp al,0x08jnz wait 不为0的时候跳转。and al,0x880x88的二进制是1000 1000。用这条指令保留住寄存器AL中的第7位和第3位,其他无关的位都清零如果寄存器AL中的二进制数是00001000(0x08),那就说明可以退出等待状态,继续往下操作,否则继续等待。
写入第1扇区db 'I',0x7,' ',0x7,'M',0x7,'i',0x7,'s',0x7,'s',0x7,' ',0x7,'Y',0x7,'o',0x7,'u',0x7times 512-($-$$) db 0写入第0扇区(引导区)
一切都准备工作都就绪了。0x1f0是硬盘接口的数据端口,并且是16位的。一旦硬盘控制器比较闲,准备工作也做好了。就可以连续从这个端口写入或者读取数据。假如现在从硬盘第一个扇区,读取256个字节。读取的数据存放到段寄存器DS:BX指定的位置。mov cx,256mov bx,0x1f0readw:in ax,bxmov [bx],axadd bx,2loop readw0x1F1是端口的错误寄存器,里面保留着“硬盘驱动器”最后一次执行工作的时候状态。(错误原因。)