<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<atom:link href="http://gentoo-zh.org/extern.php?action=feed&amp;tid=600&amp;type=rss" rel="self" type="application/rss+xml" />
		<title><![CDATA[Gentoo中文社区 / 对于硬盘驱动的理解]]></title>
		<link>http://www.gentoo-zh.org/viewtopic.php?id=600</link>
		<description><![CDATA[对于硬盘驱动的理解 最近发表的帖子。]]></description>
		<lastBuildDate>Sat, 03 Dec 2022 08:21:50 +0000</lastBuildDate>
		<generator>FluxBB</generator>
		<item>
			<title><![CDATA[对于硬盘驱动的理解]]></title>
			<link>http://www.gentoo-zh.org/viewtopic.php?pid=641#p641</link>
			<description><![CDATA[<p>目录</p><p>如何读写硬盘</p><p>&#160; 读写操作</p><p>&#160; 硬盘控制器端口及作用</p><p>&#160; 硬盘中断</p><p>&#160; 硬盘分区信息的获取</p><p>&#160; 如何读写文件</p><p>&#160; TASK_HD<br />如何读写硬盘<br />读写操作</p><p>&#160; 第一次看到linux 0.12关于读写硬盘几行代码时候，感觉很费解。<br />复制代码</p><p>do_hd = intr_addr;<br />outb_p(hd_info[drive].ctl,HD_CMD);<br />port=HD_DATA;<br />outb_p(nsect,++port);<br />outb_p(sect,++port);<br />outb_p(cyl,++port);<br />outb_p(cyl&gt;&gt;8,++port);<br />outb_p(0xA0|(drive&lt;&lt;4)|head,++port);<br />outb(cmd,++port)</p><p>复制代码</p><p>&#160; 我还是不明白怎么这样就可以读写硬盘了。但是代码到此就结束了。</p><p>&#160; 一直好奇程序是如何控制硬件的，这些指令就是一个个电信号在cpu中流动，怎么就能把硬盘中的数据拿到内存中呢？</p><p>&#160; 正好在同一个学期开设了《计算机组成原理》和《微机原理与接口技术》这两门课程，那个时候才了解到端口的意思，了解到cpu寻址、数据传输的流程。</p><p>&#160; 往端口写了数据和指令，剩下的我们只能相信硬件制造商的设计和生产能力了，然后默默等待硬件的回应。我记得当时自己疑惑了一段时间，苦于没有人来提醒这一点，可能会的人感觉这根本不是问题吧。<br />硬盘控制器端口及作用</p><p>&#160; Linux 0.12当时操作的硬盘是CHS寻址模式，起始扇区编号是1。对于《实现》来说，用bochs自带的工具bximage命令生成的虚拟硬盘是LBA寻址模式的，起始扇区编号是0。CHS模式和LBA模式的端口号和操作方式都一样，只是有一些端口代表的意义不一样了,来看一下LBA寻址模式的端口作用，借用书中的表9.1。</p><p>表1&#160; &#160; &#160; &#160; &#160; &#160;LBA寻址模式的硬盘端口及其作用 <br />I/O端口 &#160; &#160; 读时 &#160; &#160; 写时&#160; <br />primary &#160; &#160; secondary<br />1F0H &#160; &#160; 170H &#160; &#160;  Data <br />1F1H&#160; &#160; &#160; 171H&#160; &#160; &#160;  Error &#160; &#160;  Features<br />1F2H&#160; &#160; &#160; 172H&#160; &#160; &#160;  Sector count <br />1F3H &#160; &#160; 173H&#160; &#160; &#160;  LBA low <br />1F4H&#160; &#160; &#160; 174H&#160; &#160; &#160;  LBA mid <br />1F5H&#160; &#160; &#160; 175H&#160; &#160; &#160;  LBA high <br />1F6H&#160; &#160; &#160; 176H&#160; &#160; &#160;  Device <br />1F7H&#160; &#160; &#160; 177H&#160; &#160; &#160;  Status &#160; &#160;  Command<br />3F6H &#160; &#160; 376H&#160; &#160; &#160;  Alternate status &#160; &#160;  Device control</p><p> </p><p>&#160; 其中Device寄存器比较特殊，它用来指明寻址模式。来看一下格式。 </p><p>表2&#160; &#160; &#160; &#160; &#160; &#160;Device寄存器各个bit为的意义<br />Bit位 &#160; &#160; 值 &#160; &#160; 意义<br />7 &#160; &#160; 1 &#160; &#160;  <br />6 &#160; &#160; L &#160; &#160; 0表示CHS模式，1表示LBA模式<br />5 &#160; &#160; 1 &#160; &#160;  <br />4 &#160; &#160; DRV &#160; &#160; 0表示主盘，1表示从盘<br />3 &#160; &#160; HS3 &#160; &#160; </p><p>如果是L=0，CHS模式，那么这四位的值表示磁头号</p><p> </p><p>如果L=1，LBA模式，那么这四位的值表示LBA的24到27位<br />2 &#160; &#160; HS2<br />1 &#160; &#160; HS1<br />0 &#160; &#160; HS0</p><p> </p><p>&#160; 从上面的代码可以很清楚的看到如何读写硬盘，往相应的端口写上我们要读多少个扇区，读哪个扇区，哪个柱面，哪个磁头，哪个硬盘，然后告诉硬盘我们的需求cmd，读或者写。 </p><p>&#160; 另外，CHS模式下，硬盘扇区编号从1开始编号。LBA模式下，从0开始编号。<br />硬盘中断</p><p>&#160; 我们怎么知道硬盘的工作做完了没有呢？只能等待硬盘产生中断信号，通过8259A告诉cpu，这个中断信号是哪个硬件产生的。</p><p>&#160; 在书中，用的是微内核，所有的进程都给TASK_HD(硬盘驱动)发送读写硬盘的命令，而不是自己调用硬盘驱动中的读写函数。所以中断产生后，仅仅需要通知TASK_HD这个进程，TASK_HD会把硬盘准备好的数据读到发出读请求进程指定的内存位置。<br />硬盘分区信息的获取</p><p>&#160; 前面说了如何向硬盘发送命令，让它读写哪些扇区，但是这些参数都是我们提前计算好的。如何计算这些参数？我们又是如何知道该读写那个扇区呢？</p><p>&#160; 之所以把分区信息的介绍放到读写文件这一小节中，是因为我觉得分区信息和文件关联很大。我们要读写文件，才需要知道分区信息，如果我们不需要按照文件形式来读写硬盘，那么知不知道分区信息就无所谓啦，凭我们的大脑记住要读取的数据在第几个分区，到时候直接汇编操作寄存器就好啦。</p><p>&#160; 那为什么要分区呢？似乎不分区把所有的数据都杂糅在一起，电脑也可以正常运行啊。我百度了一下，大概是由于为了把操作系统和数据分开吧。试想，如果所有的东西和操作系统共处一个空间，那么操作系统崩溃了，这个空间的所有数据的记录索引在重新安装操作系统后都会失效，尽管数据本身依然很正常，但是由于记录索引丢失，我们却没法找到他们。如果分区了，那么最多操作系统的所在分区的数据拿不到了，其他分区数据的记录索引还在。</p><p>&#160; 如何获取分区信息?</p><p>&#160; 在硬盘的0号扇区(MBR扇区)偏移0x1BE处保存的有一张硬盘主分区表。只有四个表项，也就是说一个硬盘只能记录四个主分区，据说是因为当初IBM认为一个PC上装4个操作系统(只有主分区上能安装操作系统)就够用了。如果想要更多的分区，那么需要在格式化的时候指明一个(只能有一个主分区记录能用于扩展分区)表项用作扩展分区，扩展分区并不能直接使用，在这个扩展分区里面我们还要划分出逻辑分区，每一个逻辑分区的起始扇区记录的分区表只能使用两个表项。</p><p>&#160; 对于操作系统而言，每个分区都被当做一个独立的设备对待。</p><p>&#160; 那么书中如何记录分区信息呢？看一下保存数据的结构体：<br />复制代码</p><p>struct part_info {<br />&#160; &#160; u32&#160; &#160; base;&#160; &#160; /* # of start sector (NOT byte offset, but SECTOR) */<br />&#160; &#160; u32&#160; &#160; size;&#160; &#160; /* how many sectors in this partition */<br />};</p><p>/* main drive struct, one entry per drive */<br />struct hd_info<br />{<br />&#160; &#160; int&#160; &#160; &#160; &#160; &#160; &#160; open_cnt;<br />&#160; &#160; struct part_info&#160; &#160; primary[NR_PRIM_PER_DRIVE];//计算后NR_PRIM_PER_DRIVE = 5<br />&#160; &#160; struct part_info&#160; &#160; logical[NR_SUB_PER_DRIVE];// 计算后NR_SUB_PER_DRIVE = 64<br />};</p><p>复制代码</p><p>&#160; 书中根设备编号是0x322，可以知道子设备号是0x22，一开始很困惑，这么大的子设备号，难道要分0x22个分区？或者说系统怎么就知道0x22表示的是根分区呢？</p><p>&#160; 还得再看一段代码</p><p>logidx = (p-&gt;DEVICE - MINOR_hd1a) % NR_SUB_PER_DRIVE;<br />sect_nr += p-&gt;DEVICE &lt; MAX_PRIM ?<br />&#160; &#160; &#160; &#160; hd_info[drive].primary[p-&gt;DEVICE].base :<br />&#160; &#160; &#160; &#160; hd_info[drive].logical[logidx].base; </p><p>&#160; 先将设备号减去第一个逻辑设备的编号得到设备号在logical数组的下标。当然，可能这个设备号不是逻辑设备，而是主分区。没关系，下一步判断p-&gt;DEVICE 是不是小于MAX_PRIM，如果小于，说明是主分区，直接用p-&gt;DEVICE在primary数组中取值就可以了。</p><p>&#160; 原来是这样，你想怎么样编号就怎么样编号，只要你自己能找到映射关系就可以了。</p><p>&#160; 获取信息的步骤：</p><p>&#160; &#160; device = 0，style = P_PRIMARY<br />&#160; &#160; 调用获取分区信息函数<br />&#160; &#160; 如果style == P_ EXTENDED执行第10步<br />&#160; &#160; 读取设备device的起始扇区，提取0x1BE处的4个表项到part_tbl<br />&#160; &#160; 令i=0<br />&#160; &#160; 判断第i个分区表项part_tbl[ i ]<br />&#160; &#160; 如果是主分区，记录起始扇区sect_start和扇区数目setcs到相应的primary[i+1]。<br />&#160; &#160; 如果是扩展分区，记录起始扇区sect_start和扇区数目setcs到相应的primary[i+1]，令device += i+1，style = P_ EXTENDED跳到第2步<br />&#160; &#160; 如果i&gt;=4，结束；否则i++，执行第6步<br />&#160; &#160; 扩展分区的起始扇区ext_start_sect = primary[device].base(这个值在第8步中已经计算出来了)，求出该扩展分区的第一个逻辑分区的编号，nr_1st_sub = (device-1) * NR_SUB_PER_PART，计算该扩展分区第0个逻辑分区的起始地址s= ext_start_sect<br />&#160; &#160; 令i=0(由于是递归调用，此处i的值并不影响第5步的i)<br />&#160; &#160; 读取第nr_1st_sub+i个逻辑分区的起始扇区s，提取0x1BE处的2个表项(逻辑分区只使用分区表的两个表项)到part_tbl<br />&#160; &#160; 记录逻辑分区的信息到logical[nr_1st_sub+i]<br />&#160; &#160; s = ext_start_sect + part_tbl[1].start_sect<br />&#160; &#160; 如果i&gt;=16，本次递归结束，返回到第8步；否则i++，执行第12步</p><p>&#160; 感觉文字叙述理解起来可能比较模糊，但是比代码实现起来还是省事一些，像读分区起始扇区，一句话带过，知道怎么做就可以了，如果用代码描述，可能还要牵扯到其他知识点。<br />如何读写文件</p><p>&#160; 其实对于硬盘驱动而言，没有文件这个概念，只有扇区。硬盘驱动能接受的参数就是要读写的起始扇区，读写扇区个数。文件这个概念由上层的文件系统来处理。</p><p>&#160; 这个时候，我们会想起来inode结构体中有两个记录是i_dev和i_start_sect，这两个元素把上层文件系统和硬盘关联起来了。当我们要读某某个文件的时候，文件系统告诉硬盘驱动读目录区，把文件的inode号找到，再读indoe到内存中，这个时候就有了文件在哪个分区i_dev，数据存放在第i_start_sect号扇区，及之后一共的x800个扇区中，这个i_start_sect的值是相对于分区i_dev为起始偏移的。</p><p>&#160; 知道i_dev和i_start_sect之后，硬盘驱动可以做什么呢？首先将以i_dev分区为起始偏移的i_start_sect转化为相对于整个硬盘。怎么转化呢？上面获取分区信息的时候，每个分区的起始扇区都被记录，我们找到i_dev的起始扇区，加上i_start_sect就是相对于整个硬盘的了。</p><p>&#160; 这样就把文件读进来了。至于读文件哪一段的内容，其实还是上层的文件系统来记录处理的，还记得file结构体中有一个元素是pos，这个值就是用来标明要读写的内容在文件中的偏移。将pos/SECT_SIZE再加上上面计算的文件相对于整个硬盘的偏移，就是要读写的某一段数据了。</p><p>&#160;  所谓块设备的名称也许就是这样由来的吧，一次最少处理的数据是一个扇区(当然，不同的硬盘给出的接口肯定不一样)。<br />TASK_HD</p><p>&#160; 这样一来，TASK_HD的任务就是很简单了啊，接收TASK_FS发送的读写请求，将针对于i_dev设备的i_start_sect转化为相对于整个硬盘的扇区号，再加上pos/SECT_SIZE，然后读写这个扇区交给TASK_FS就什么都不管了。进入下一个循环。</p>]]></description>
			<author><![CDATA[dummy@example.com (batsom)]]></author>
			<pubDate>Sat, 03 Dec 2022 08:21:50 +0000</pubDate>
			<guid>http://www.gentoo-zh.org/viewtopic.php?pid=641#p641</guid>
		</item>
	</channel>
</rss>
