操作系统硬件之硬盘

世界上第一台磁盘存储设备来自IBM,没错,那个蓝色巨人,几十年前科技的风向标,此设备用磁头来读写数据,用盘片来存储数据,以后的硬盘都是以这样的模式发展。这个磁头可以直接移动到盘片上的任何一块存储单元,从而实现了随机存储。虽然它的总容量只有5MB,但是限于当时的制造工艺水平,一共使用了50个直径为24英寸的盘片,摞起来的体积相当于冰箱那么大。在这些盘片表面都涂有用于存储数据的磁性物质,它们摞起来被固定在一起,在电机的带动下,绕着同一个轴旋转。

1968年由IBM首次提出了“温彻斯特/Winchester”技术,这项技术的精髓是“镀磁盘片在密封空间中高速自转,磁头悬浮在盘片上方,固定在磁头臂上沿盘片径向移动”。磁头不与盘片接触也不应该接触,长期的摩擦会销毁一切数据,盘片高速旋转会产生气流,磁头在这种气流下像飞碟一样悬浮,这样就保证了不会与盘片有摩擦。磁头被固定在磁头臂上,它能沿盘片径向移动,由于磁头和盘片各自的运动,再加上如此近的距离,所以哪怕一点灰尘都会造成磁盘的损伤。于是,磁头、盘片被密封在了一个盒子里。今天的硬盘依然是这样的结构。

当时这样的结构只是一种设想,这种设想在1973年被IBM蓝色巨人实现了,IBM 3340,容量60MB,由两个30MB的存储单元拼合而成。从此硬盘技术的发展便有了成形的结构基础。

硬盘发展到今天,这几十年来都是靠容量不断提升才打败竞争对手存活下来的。可是速度一直是其最大的敌人,家用电脑10年前硬盘主流转速是7200转/分钟,今天依然如此。硬盘的随机存取是靠磁头臂不断移动实现的,磁头臂移动到目标位置的时间称为寻道时间,如果存储的数据不连续,这一块那一片的,磁头就得不断调整位置,这是机械式硬盘不可避免的,这便是硬盘的瓶颈所在,所以一般的硬盘都将寻道时间作为重要参数。

在众多竞争对象当中,也有一款顽强地活了下来,它就是SSD固态硬盘,人家也有几十年的历史了,没想到吧,直到前几年SSD硬盘还是一种非常贵的设备,当时一般只有128G或者256G的固态硬盘用作启动盘,提高运行速度,而且三星这些固态硬盘大厂时不时失火跑水,固态硬盘和内存条这种电子产品都有涨价的时候,被人们戏称理财产品,但是长江存储等国产厂商攻克存储技术后,固态硬盘和内存就白菜价了,国外的那些大厂也不失火了,也不跑水了,虽然目前三星的固态依然贵,但是早已没有了之前的傲慢。有些核心技术不掌握在自己手中,始终就是任人宰割的羔羊。有些跑远了,回来回来,我们这章只讲硬盘,

硬盘的工作原理

操作系统硬件之硬盘_第1张图片

讲硬盘就离不开这张图,目前主流个人电脑硬盘上的转速是7200转/分钟(这个数字几十年没边了)。每个盘片分上下两面,每面都存储数据,每个盘面都各由一个磁头来读取数据,故一个盘片上对应2个磁头。由于盘面与磁头是一一对应的关系,故用磁头号来表示盘面。磁头编号从上到下以0开始计数,所以用磁头0代表第一个盘面。磁头不会自己在盘片上移动,它需要被固定在右边的磁头臂上,在磁头臂的带动下,沿着盘片边缘向圆心的方向来回摆动,注意,摆动的轨迹是个弧,并不是绝对径向地直来直去。一方面这是因为磁头臂是由步进电机驱动的,磁头臂一端是步进电机主轴,另一端是磁头。步进电机每次都会转动一个角度,所以带动磁头臂在“画圆”,而磁头位于磁头臂的另一端,所以也跟着呈钟摆运动,运动轨迹是个弧线,并不是直线。所以,图3-28中磁头臂中标注了“类似于”径向运动,这就是“类似”的原因。另一方面,磁头读取数据也不需要做直来直去的移动,能否找到数据,只跟它最终落点有关,和中间路径形状是没关系的。所以,一方面盘片的自转,另一方面磁头的摆动,这两种动作的合成,使磁头能够读取盘片任意位置的数据。

盘片表面是用于存储数据的磁性介质,为了更有效管理磁盘,这些磁性介质也被“格式化”成易于管理的格局,即将整个盘面划分为多个同心环,以圆心画扇形,扇形与每个同心环相交的弧状区域作为最基本的数据存储单元。这个同心环就称为磁道,而同心环上的弧状区域是扇形的一部分,故称之为扇区,它作为我们向硬盘存储数据的最基本单位,大小是512字节。我们写入的数据最终是写进了磁道上的扇区中。注意啦,我上面说的磁道是个环,不是线,很多教科书上介绍磁道时都简单画了个圆圈,这容易让人误解磁道是条线,线上可无法存储数据,“环”是有横截面的,数据就存储这些“面积”中。磁头臂带动磁头在盘片上方移动,就是在找磁道的位置,盘片高速自转,就是在磁道内定位扇区。

磁道的编号和磁头一样也是从0开始的。相同编号的磁道组成的管状区域就称为柱面。图3-28中,两个盘片上编号相同的磁道,它们之间用灰色直线连接起来的部分,很像柱子的弧形表面,所以称柱面。如果盘片非常多的话,“柱面”就显得非常形象了。为什么会有柱面这个概念呢,我一个盘一个盘的存放不好嘛,这个是为了减少寻道时间的,磁头在磁道间跳转,跳转所需要的时间称为寻道时间,如果待写入的数据小于一个磁道的剩余容量,将来再读出来的时候,磁头只定位到该磁道就行了。这时候的寻道只是一次。如果待写入的数据要占用多个磁道时,除了写的时候要变换磁头到新磁道,将来读出来的时候也需要变换磁道,也就是需要多次寻道才能完成数据的完整读写。既然寻道对机械式硬盘速度影响较大,那原则上就尽量减少寻道次数,柱面中的磁道是相同编号,编号相同则意味着磁道在盘面上的位置相同,要定位到同一柱面中的磁道,所有磁头位置都一样,于是磁头不用再移动了。是不是很赞呢?

按照这种想法写数据:当0面上的某磁道空间不足时,其他数据写入第1面相同编号的磁道上。若新磁道空间还是不足,再写第2面相同编号的磁道上,直到同一柱面上的磁道(所有盘面上的编号相同的磁道)都不够用时才会写到新的柱面上。所以,在这一点上,盘面越多,硬盘越快。

各磁道扇区都是以1为起始编号的,并且只限于本磁道内有效,所以各个磁道间的扇区编号都相同,下限都是以0起,上限就不一定了,取决于各厂商的工艺,不过大多数情况下一个磁道中有63个扇区。

磁头如何找到扇区呢,就算是按照编号查找也得有个对比吧,实际上扇区是有自己的头部的,头部之后才是存储的数据,头部之中包含了扇区自身的信息,所以用磁头号磁道号扇区号3个信息就可以定位到唯一的一个扇区。

硬盘的一点历史

硬盘控制器同硬盘的关系,如同显卡和显示器一样,它们都是专门驱动外部设备的模块电路,CPU只同它们说话,由它们将CPU的话转译给外部设备。这是它们的共同点,但不同的是显卡和显示器是分开的,硬盘控制器和硬盘是连接在一起的,但是实际上很早之前他们也是分开的,后来业界的几个老大合作开发出一种新的接口,这样才将硬盘和硬盘控制器整合在一起,为了突显“整合”之意,硬盘控制器和硬盘终于在一起了,这种接口便称为集成设备电路(Integrated Drive Electronics,IDE)。随着IDE接口标准的影响力越来越广泛,全球标准化协议将此接口使用的技术规范归纳成为全球硬盘标准,这样就产生了ATA(Advanced Technology Attachment),计算机发展非常快,新老交替的现象层出不穷,以至于后辈的出现常常把前辈的名字都给改了。这不,前几年刚出道的硬盘串行接口(Serial ATA,SATA),由于其是串行,所以之前的ATA接口只好称为并行ATA,即(Parallel ATA,PATA)。

PATA这种接口我们现在已经用的非常少了,她的每个接口可以挂载两块硬盘,一块主盘一块从盘,主盘从盘的分工很明显,很多工作都要以主盘为主,比如系统就要装在主盘上。到后来系统兼容性越来越好,以至区别不明显了。一个主板支持这样的4块IDE(PATA)硬盘,所以主板上提供两个IDE插槽。这两个接口也是以0为起始编号的,一个称为IDE0,另一个称为IDE1。过按ATA的说法,这两个插槽称为通道,IDE0叫作Primary通道,IDE1叫作Secondary通道。

SATA这种接口我们现在用的比较多,一个主机可以链接多少SATA硬盘取决于主板上有多少SATA接口,就没有PATA那种限制了,即使主板上安装的是SATA硬盘,它也兼容PATA的编程接口,向上兼容是计算机能源源不断向前发展的根基。所以,后面给出的端口号也将按照PATA这两个通道来分组给出。

Nvme这种接口是一种基于PCIe的高速接口协议,专门用于连接固态硬盘。相比于SATA和SAS,NVMe具有更高的数据传输速率和更低的延迟,能够更好地发挥固态硬盘的性能优势。这种协议比较新所以优势是链接速度比较快,现在的固态硬盘传输速度理论上甚至在7000MB/s

硬盘端口

硬盘的控制器属于IO接口,所以他的控制也是读写端口,端口就是位于IO控制器上的寄存器,此处的端口是指硬盘控制器上的寄存器。我们只讲一些可能会用到的东西,其实硬盘是很复杂的,毕竟是一个非常精密的元器件,她的手册都非常的长。
操作系统硬件之硬盘_第2张图片

端口可以被分为两组,Command Block registers 和 Control Block registers,第二组中的寄存器已经被精简了很多了,而且基本用不到,所以我们只讲第一组。

端口是按照通道给出的,也就是说,大家不要像我当初那样误以为端口是直接针对某块硬盘的,不是这样的,一个通道上的主、从两块硬盘都用这些端口号。要想操作某通道上的某块硬盘,需要单独指定。瞧,上面有个叫device的寄存器,顾名思义,指的就是驱动器设备,也就是和硬盘相关。不过此寄存器是8位的,一个通道上就两块硬盘,指定哪一个硬盘只用1位就够了,寄存器可是很宝贝的资源,不能浪费,所以此寄存器是个杂项,很多设置都需集中在此寄存器中了,其中的第4位,便是指定通道上的主或从硬盘,0为主盘,1为从盘。

端口用途在读硬盘和写硬盘时还是有点区别的,比如拿Primary通道上的0x1F1端口来说,读操作时,若读取失败,里面存储的是失败状态信息,所以称为error寄存器,并且0x1F2端口中存储未读的扇区数。写操作时就变成了feauture寄存器,此寄存器用于写命令的参数。这一切都是为了省钱,一个寄存器可以复用。

Data寄存器在名字上我们就知道它是负责管理数据的,数据的读写还是越快越好,所以此寄存器较其他寄存器宽一些,16位(已经很不错了,表中其他寄存器都是8位的)。在读硬盘时,硬盘准备好的数据后,硬盘控制器将其放在内部的缓冲区中,不断读此寄存器便是读出缓冲区中的全部数据。在写硬盘时,我们要把数据源源不断地输送到此端口,数据便被存入缓冲区里,硬盘控制器发现这个缓冲区中有数据了,便将此处的数据写入相应的扇区中。

读硬盘时,端口0x171或0x1F1的寄存器名字叫Error寄存器,只在读取硬盘失败时有用,里面才会记录失败的信息,在写硬盘时,此寄存器有了别的用途,所以有了新的名字,叫Feature 寄存器。有些命令需要指定额外参数,这些参数就写在Feature寄存器中。

Sector count寄存器用来指定待读取或待写入的扇区数。硬盘每完成一个扇区,就会将此寄存器的值减1,所以如果中间失败了,此寄存器中的值便是尚未完成的扇区。这是8位寄存器,最大值为255,若指定为0,则表示要操作256个扇区。

硬盘中的扇区在物理上是使用 柱面-磁头-扇区 来定位的,简称CHS,但是每次我们都要先算出来在那个柱面,那个扇区,太麻烦了,我们希望磁盘中扇区从0开始依次递增编号,不用考虑扇区所在的物理结构。其实我在描述需求时已经说出了LBA的定义,这是一种逻辑上为扇区址的方法,全称为逻辑块地址(Logical Block Address)。LBA有两种,一种是LBA28,用28位比特来描述一个扇区的地址。最大寻址范围是2的28次方等于268435456个扇区,每个扇区是512字节,最大支持128GB。我们这里为图简单,采用LBA28模式。由于128GB已经不能满足日益增长的存储需求,硬盘越来越大了,得有相匹配的寻址方法与之配套,于是要介绍的另外一种是LBA48,用48位比特来描述一个扇区的地址,最大可寻址范围是2的48次方,等于281474976710656个扇区,乘以512字节后,最大支持131072TB,即128PB。

可是我们才三个LBA寄存器,一共24位,不够28位怎么办,和device借一点,device寄存器的低4位用来存储LBA地址的第24~27位,第4位用来指定通道上的主盘或从盘,0代表主盘,1代表从盘。第6位用来设置是否启用LBA方式,1代表启用LBA模式,0代表启用CHS模式。另外的两位:第5位和第7位是固定为1的,称为MBS位,

在读硬盘时,端口0x1F7或0x177的寄存器名称是Status,它是8位宽度的寄存器,用来给出硬盘的状态信息。第0位是ERR位,如果此位为1,表示命令出错了,具体原因可见error寄存器。第3位是data request位,如果此位为1,表示硬盘已经把数据准备好了,主机现在可以把数据读出来。第6位是DRDY,表示硬盘就绪,此位是在对硬盘诊断时用的,表示硬盘检测正常,可以继续执行一些命令。第7位是BSY位,表示硬盘是否繁忙,如果为1表示硬盘正忙着,此寄存器中的其他位都无效。另外的4位暂不关注。

在写硬盘时,端口0x1F7或0x177的寄存器名称是command,和上面说过的error和feature寄存器情况一样,只是用途变了,所以换了个名字表示新的用途,它和status寄存器是同一个。此寄存器用来存储让硬盘执行的命令,只要把命令写进此寄存器,硬盘就开始工作了,0xEC,即硬盘识别。0x20,即读扇区。0x30,即写扇区。

通过端口读写硬盘

1、先选择通道,往该通道的sector count寄存器中写入待操作的扇区数。

2、往该通道上的三个LBA寄存器写入扇区起始地址的低24位

3、往device寄存器中写入LBA地址的24~27位,并置第6位为1,使其为LBA模式,设置第4位选择主盘或者从盘

4、往该通道上的command寄存器写入操作命令。

5、读取该通道上的status寄存器,判断硬盘工作是否完成。

6、如果以上步骤是读硬盘,进入下一个步骤。否则,完工。

7、将硬盘数据读出。

如果硬盘已经完成了自己的工作,他已经准备好了数据,一般常用的数据传送方式有以下五种

1、无条件传送

2、查询传送

3、中断传送

4、直接存储器存取(DMA)

5、IO处理机传送

对于第一种方式,数据源设备一定是随时准备好了数据,CPU随时取随时拿都没问题,如寄存器、内存就是类似这样的设备,CPU取数据时不用提前打招呼。那硬盘就不行了,他很慢。

对于第二种方式,是指传输之前,由程序先去检测设备的状态。数据源设备在一定的条件下才能传送数据,这类设备通常是低速设备,比CPU慢很多。CPU需要数据时,先检查该设备的状态,如果状态为“准备好了可以发送”,CPU再去获取数据。硬盘有status寄存器,里面保存了工作状态,所以对硬盘可以用此方式来获取数据。

对于第三种方式,是如果数据源设备将数据准备好后再通知CPU来取,这样效率就高了。通知CPU可以采用中断的方式,当数据源设备准备好数据后,它通过发中断来通知CPU来拿数据,避免了CPU轮询机制,效率较高。

对于第四种方式,也是针对第三种的缺陷,第三种中断的处理方式需要保存现场,压栈这些操作,DMA控制器可以完全解放CPU,不让CPU参与传输,完全由数据源设备和内存直接传输。CPU直接到内存中拿数据就好了。

对于第五种方式,CPU还需要完成数据交换,组合,校验等工作,干脆一不做二不休,再引入一个硬件吧。于是,I/O处理机诞生啦,听名字就知道它专门用于处理IO,并且它其实是一种处理器,只不过用的是另一套擅长IO的指令系统,随时可以处理数据。IO处理机之前会被集合在南桥芯片下,但是最新的一下处理器会集合有南桥和北桥的功能,只能说CPU的集成度越来越高了。

rd_disk_m_16:
	mov esi,eax                    ;备份eax,也就是LBA地址
	mov di,cx                      ;备份cx,也就是待读入的扇区数
	;1、设置要读取的扇区数
	mov dx,0x1f2                   ;要写入的端口号要放在dx中
	mov al,cl
	out dx,al
	mov eax,esi                    ;恢复eax
	;2、将LBA地址存入0x1f3-0x1f6
	mov dx,0x1f3                   ;LBA地址7-0位
	out dx,al
	
	mov cl,8                       ;右移操作数
	shr eax,cl                     ;右移8位
	mov dx,0x1f4                   ;写入端口 LBA 15-8
	out dx,al

	shr eax,cl                     ;写入端口 LBA 13-16
	mov dx,0x1f5
	out dx,al
	
	shr eax,cl                     ;只需要LBA地址的24-27位
	and al,0x0f                    ;将al的前四位清零
	or al,0xe0                     ;将al的前四位设置为1110,即表示LBA模式
	mov dx,0x1f6                   ;写入到端口
	out dx,al
	
	;3、向0x1f7端口写入读命令0x20
	mov dx,0x1f7      
	mov al,0x20
	out dx,al
	
	;4、检测硬盘状态
.not_ready:
	;统一端口,写时表示命令字,读时表示读入硬盘状态
	nop
	in al,dx
	and al,0x88            ; 第4位为1表示硬盘已经准备好数据传输
						   ; 第7位位1表示硬盘忙
						   ; 如果硬盘准备好了,那么al的结果是0x08,否则位0x80
	cmp al,0x08            ; 下面这两行汇编可以表示为,如果结果不是0x80那么跳转
		
	jnz .not_ready
	
	;5、从0x1f0端口读数据
	mov ax,di     ; di为要读取的扇区数,这里我们只读取一个扇区,
				  ;一个扇区512字节,每次读取一个子,所以需要256次读取 
	mov dx,256
	mul dx        ; 乘法指令,被乘数隐藏在ax中
	mov cx,ax     ; cx中是循环次数
	mov dx,0x1f0

.go_on_ready:
	in ax,dx                ; 数据寄存器是16位的,所以这里每次读取两个字节
	mov [bx],ax             ;
	add bx,2                ;
	loop .go_on_ready       ;
	ret

通过中断 0x13 读写硬盘

该段汇编代码如下:

_start:
    mov     edx, 0          ; 初始化 EDX 寄存器,用于指定设备号和扇区号
    mov     ecx, 0          ; 初始化 ECX 寄存器,用于指定磁道号
    mov     ebx, 0          ; 初始化 EBX 寄存器,用于指定目标地址

    mov     ah, 2           ; AH 寄存器设置为 2,表示读取指定扇区数据
    mov     al, 1           ; AL 寄存器设置为 1,表示读取一个扇区
    push    edx             ; 将扇区号压到栈中
    mov     dword [esp+4], ebx ; 将目标地址压到栈中
    mov     ecx, 0          ; 磁道号设置为 0
    mov     edx, 0          ; 设备号为 0 硬盘
    mov     ebx, 0x80       ; 通过设置 LBA 模式(7位),EBX 寄存器的高一位为 1

    int     0x13            ; 调用 BIOS 中断读取扇区数据

    jc      error           ; 如果 CF 标志被设置,表示读取失败,跳转到 error 标签处

    jmp     $               ; 跳转回当前位置

error:
    ; 处理错误

该代码使用 BIOS 中断 0x13 读取 SATA 硬盘 0 头 0 道 0 扇区的数据,然后将数据存储到目标地址(EBX 寄存器中指定的内存地址)中。通过 AH 寄存器设置为 2,表示读取指定扇区数据,而 AL 寄存器设置为 1,表示只读取一个扇区。将扇区号压到栈中,再将目标地址压到栈中,使用 0x80 的值将 EBX 寄存器的高一位设置为 1,以启用 LBA 模式。如果操作失败,将跳转到 error 标签处处理错误。

标地址(EBX 寄存器中指定的内存地址)中。通过 AH 寄存器设置为 2,表示读取指定扇区数据,而 AL 寄存器设置为 1,表示只读取一个扇区。将扇区号压到栈中,再将目标地址压到栈中,使用 0x80 的值将 EBX 寄存器的高一位设置为 1,以启用 LBA 模式。如果操作失败,将跳转到 error 标签处处理错误。

你可能感兴趣的:(Linux,OS,Linux)