在上一篇中我们提到磁面、磁道、柱面、扇区等概念,我们再回顾一下:
  
  硬盘的每个盘片都有两个盘面(Side),即上、下盘盘面,安照顺序从上至下从“0”开始依次编号。磁盘在格式化时被划分成许多同心圆,这些同心圆轨迹叫磁道(Track)。磁道从外向内从0开始顺序编号。所有盘面上的同一磁道构成的一个圆柱,通常称做柱面(Cylinder,每个柱面上的磁头由上而下从“0”开始编号。
  
  操作系统以扇区(Sector)形式将信息存储在硬盘上,每个扇区包括512个字节的数据和一些其他信息。
  
  这些通常用来表示硬盘的物理地址,现在随着硬盘容量增大我们用逻辑地址即线性地址(LBA)。因此我们需要了解CHSLBA之间的转换。
  
  C/H/SLBA地址的对应关系:
  
  从CHSLBA
  
  假设用C表示当前柱面号,H表示当前磁头号,Cs表示起始柱面号,Hs表示起始磁头号,Ss表示起始扇区号,PS表示每磁道有多少个扇区,PH表示每柱面有多少个磁道,则有以下对应关系:
  
  LBA=C-Cs*PH*PS+H-Hs*PS+S-Ss
  
  一般情况下,CS=0HS=0SS=1PS=63PH=255
  
  那么以下可以根据公司计算,如下:
  
  C/H/S=0/0/1,代入上述公式中得到LBA=0
  C/H/S=0/0/63,代入上述公式中得到LBA=62
  C/H/S=1/0/1,代入上述公式中得到LBA=63
  C/H/S=220/156/18,代入上述公式中得到LBA=3544145
  
  从LBACHS
  
  在这里先介绍两种运算DIVMODDIV做整除运算,即被除数除以除数所得的商的整数部分;比如5 DIV 3=133 DIV 6=5
  
  MOD是余运算,MOD运算则取商的余数;比如:5 MOD 3=233 MOD 6=5
  
  DIVMOD是一对搭档,一个取整一个取余。各个变量按照上面的进行假设,那么有:
  C=LBA DIV (PH*PS) + Cs
  H=(LBA DIV PS) MOD PH + Hs
  S=LBA MOD PS + Ss
  
  如果上述不用MOD运算,只用DIV运算可以如下:
  C= LBA DIV (PH*PS) + Cs
  H=LBA DIV PS –(C-Cs) * Ps + Ss
  S=LBA- (C-Cs) * PH * PS - (H-Hs) * PS + Ss
  
  那么按照这个规律则有:
  LBA=0,相应地C/H/S=0/0/1
  LBA=62,相应地C/H/S=0/0/63
  LBA=63,相应地C/H/S=1/0/1
  LBA=62,相应地C/H/S=0/0/63
  LBA=3544145,相应地C/H/S=220/156/18
  
  通过以上转换,大家对CHSLBA之间的转换有一定的了解了。

 

使用INT 13 时。要用到CHS(磁道磁头扇区)这3个参数,可是从FAT表中只知道扇区数,如何求CHS参数呢?

 

FAT(32)分区的引导扇区中,可以找到隐藏扇区数(偏移量一般为1CH,如果
这个分区实际位置在8G之后(含跨),或分区类型是0CH/0EH(在分区表/链表中),或这是
主分区,上述隐藏扇区数就是分区起始地址(LBA,否则,隐藏扇区数加上此分区所在扩展分区子链的位置,就等于分区起始地址(LBA.
    知道了分区起始LBA之后,再用INT13H/AH=08H去磁盘磁头数/每道扇区数,即可将LBA
转换为CHS.(H,S两参数在FAT引导扇区中也有记录,但由于是直接用INT13H访问扇区,应以INT13H/08H参数为准为宜)

 

硬盘的chs模式是指chs(Cylinder/Head/Sector)模式,很久以前, 硬盘的容量还非常小的时候, 人们采用与软盘类似的结构生产硬盘. 也就是硬盘盘片的每一条磁道都具有相同的扇区数. 由此产生了所谓的3D参数 (Disk Geometry). 既磁头数(Heads), 柱面数(Cylinders), 扇区数(Sectors per track),以及相应的寻址方式. 其中: 磁头数(Heads) 表示硬盘总共有几个磁头,也就是有几面盘片, 最大为 256 ( 8 个二进制位存储);
柱面数(Cylinders) 表示硬盘每一面盘片上有几条磁道, 最大为 1024( 10 个二进制位存储);
扇区数(Sectors per track) 表示每一条磁道上有几个扇区, 最大为63 ( 6 个二进制位存储).
每个扇区一般是 512个字节, 理论上讲这不是必须的, 但好象没有取别的值的. 所以磁盘最大容量为: 256 * 1024 * 63 * 512 / 1048576 = 8064 MB ( 1M = 1048576 Bytes )或硬盘厂商常用的单位:
256 * 1024 * 63 * 512 / 1000000 = 8455 MB ( 1M = 1000000 Bytes )
CHS 寻址方式中, 磁头, 柱面, 扇区的取值范围分别为 0 Heads-1,0 Cylinders-1, 1 Sectors per track (注意是从 1 开始).
CHS 寻址方式中, 有以下几种尺寸单位:
扇区 (Sector) = 512 字节 (一般情况下)
磁道 (Track) = (Sectors per track) 扇区
柱面 (Cylinder)= (Sectors per track) * Heads 扇区 这种方式会浪费很多磁盘空间 (与软盘一样). 为了解决这一问题, 进一步提高硬盘容量, 人们改用等密度结构生产硬盘. 也就是说, 外圈磁道的扇区比内圈磁道多. 采用这种结构后, 硬盘不再具有实际的3D参数, 寻址方式也改为线性寻址, 即以扇区为单位进行寻址.
为了与使用3D寻址的老软件兼容 (如使用BIOS Int13H接口的软件), 在硬盘控制器内部安装了一个地址翻译器, 由它负责将老式3D参数翻译成新的线性参数. 这也是为什么现在硬盘的3D参数可以有多种选择的原因 (不同的工作模式, 对应不同的3D参数, LBA, LARGE, NORMAL). CHS模式只能识别大硬盘的前面8G.lba使用的线性寻址,突破了1024柱面的限制,能访问8G以外的空间了。

 

经常看到一些帖子把零磁道的概念搞错了,发个帖子想纠正一下在好多人记忆中根深蒂固的错误概念。
零磁道概念问题,涉及硬盘的物理结构和CHS 3D寻址方法。老硬盘一个磁道上分63个扇区,更老的还没63个扇区,物理磁头也就6-8个吧(3-4片盘片),再多硬盘里就装不下了。使用8位寻址方式,8位二进制数的最大值是2560-255),用于表达磁头数;而扇区只有63个(1-63),用6位就可以表达了,还有二位就给柱面去用了,柱面数用10位来表达,达到10240-1023)。这也就是所谓的C(柱面)H(磁头)S(扇区) 3D参数。一般书写时按照CHS的次序来做,如001第一个00柱面,第二个0是的磁头(盘面)第三个11扇区。硬盘的实际物理磁头只有4个或6个,现在有些硬盘只有一个磁头,仅用一面。如果有块硬盘正好符合上面的8位寻址方式的最大值,可以想象这个硬盘有128片盘片,每个盘面上有1024个磁道,每个磁道上有63个扇区。这就是它的CHS 3D参数。顺便说一下由于受8位寻址的限制,采用8位寻址时的最大寻址空间为8.4G。(1024*256*63*512/1024/1024)目前主板的BIOS已采用28位寻址,最大寻址137G,最前沿的主板BIOS采用48位寻址,已突破了137G的瓶颈。每个盘面上同一位置的磁道就称为柱面。如001,表示:0柱面,是指位于128个盘片上的零磁道,但没讲是在那片盘上;0磁头对应于0盘面,其实用盘面来理解更确切,1扇区,现在定位已完成。011,在刚才位置的反面。这些是不要我们操心的,硬盘内部的参数和主板BIOS之间会达成协议,正确发出寻址信号,正确定位磁道和扇区位置。
0
01指的是MBR所在扇区,零磁道001-0063为真正的零磁道,它的位置一做好就不能变了,它的读取是主板中BIOS通过INT19去读它的信息。011DBR所在位置,这个扇区坏了,MSOS就会说是零磁道损坏,其实TMD鬼才说它是零磁道,都是MS闯的祸。它坏了是可以用PCTOOLS移的。明白了吧!现在你说那个是零磁道啊!如果坚持0110磁道,那岂不是要有2560磁道?至少物理盘片的开始要称作零磁道吧。哈哈,如果是这样定义叫硬盘怎样去找扇区?那就要大乱了。
老硬盘由于每道上只有63个扇区,因此,外圈疏,内圈密,没有很好地利用盘面,现在磁盘内外圈密度相同,圈子上也不再是63个扇区了,不再用CHS 3D参数,现在都用LAB逻辑寻址方式,也有称线性寻址。传统意义上的零磁道,只是磁道上的一小段了,但有一点是相同的,一个硬盘必须有一个唯一的起点,那就是LAB 0扇区,对应于CHS001。它坏了一般的软件就没办法了,只是以前听高人说过,有些零磁道损坏的硬盘可以通过用3K来对物理扇区和逻辑扇区重定位,重新确定一个新的起点。好象也不是所有硬盘都能能这样。用PCTOOLS等软件来修复零磁道损坏的硬盘一文,一直在误导读者,流毒广深也。不过归根到底还是M$的错。

 

硬盘的基本知识


硬盘的DOS管理结构 
1.
磁道,扇区,柱面和磁头数 
  硬盘最基本的组成部分是由坚硬金属材料制成的涂以磁性介质的盘片,不同容量硬盘的盘片数不等。每个盘片有两面,都可 
记录信息。盘片被分成许多扇形的区域,每个区域叫一个扇区,每个扇区可存储128×2N次方(N0.1.2.3)字节信息。在DOS 
中每扇区是128×22次方=512字节,盘片表面上以盘片中心为圆心,不同半径的同心圆称为磁道。硬盘中,不同盘片相同半径 
的磁道所组成的圆柱称为柱面。磁道与柱面都是表示不同半径的圆,在许多场合,磁道和柱面可以互换使用,我们知道,每个磁 
盘有两个面,每个面都有一个磁头,习惯用磁头号来区分。扇区,磁道(或柱面)和磁头数构成了硬盘结构的基本参数,帮这些 
参数可以得到硬盘的容量,基计算公式为: 
存储容量=磁头数×磁道(柱面)数×每道扇区数×每扇区字节数 
要点:(1)硬盘有数个盘片,每盘片两个面,每个面一个磁头 
   (2)盘片被划分为多个扇形区域即扇区 
   (3)同一盘片不同半径的同心圆为磁道 
   (4)不同盘片相同半径构成的圆柱面即柱面 
   (5)公式: 存储容量=磁头数×磁道(柱面)数×每道扇区数×每扇区字节数 
   (6)信息记录可表示为:××磁道(柱面),××磁头,××扇区 
2.
 
  DOS进行分配的最小单位。当创建一个很小的文件时,如是一个字节,则它在磁盘上并不是只占一个字节的空间, 
而是占有整个一簇。DOS视不同的存储介质(如软盘,硬盘),不同容量的硬盘,簇的大小也不一样。簇的大小可在称为磁盘 
参数块(BPB)中获取。簇的概念仅适用于数据区。 
本点:(1DOS进行分配的最小单位。 
   (2)不同的存储介质,不同容量的硬盘,不同的DOS版本,簇的大小也不一样。 
   (3)簇的概念仅适用于数据区。 
3.
扇区编号定义:绝对扇区与DOS扇区 
  由前面介绍可知,我们可以用柱面/磁头/扇区来唯一定位磁盘上每一个区域,或是说柱面/磁头/扇区与磁盘上每一个扇区有 
一一对应关系,通常DOS柱面/磁头/扇区这样表示法称为绝对扇区表示法。但DOS不能直接使用绝对扇区进行磁盘上的 
信息管理,而是用所谓相对扇区“DOS扇区相对扇区只是一个数字,如柱面140,磁头3,扇区4对应的相对扇区号 
2757。该数字与绝对扇区柱面/磁头/扇区具有一一对应关系。当使用相对扇区编号时,DOS是从柱面0,磁头1,扇区1开始 
(注:柱面0,磁头0,扇区1没有DOS扇区编号,DOS下不能访问,只能调用BIOS访问),第一个DOS扇区编号为0,该磁道上剩余 
的扇区编号为116(设每磁道17个扇区),然后是磁头号为2,柱面为017个扇区,形成的DOS扇区号从1733。直到该柱面的 
所有磁头。然后再移到柱面1,磁头1,扇区1继续进行DOS扇区的编号,即按扇区号,磁头号,柱面号(磁道号)增长的顺序连续 
地分配DOS扇区号。 
公式:记DH--第一个DOS扇区的磁头号 
    DC--第一个DOS扇区的柱面号 
    DS--第一个DOS扇区的扇区号 
    NS--每磁道扇区数 
    NH--磁盘总的磁头数 
   则某扇区(柱面C,磁头H,扇区S)的相对扇区号RS为: 
RS
NH×NS×CDC)+NS×HDH)+(SDS 
   若已知RSDCDHDSNSNH 
S
=(RS MOD NS)+DS 
H
=((RS DIV NSMOD NH)+DH 
C
=((RS DIV NSDIV NH)+DC 
要点:(1)以柱面/磁头/扇区表示的为绝对扇区又称物理磁盘地址 
   (2)单一数字表示的为相对扇区或DOS扇区,又称逻辑扇区号 
   (3)相对扇区与绝对扇区的转换公式 
4.DOS
磁盘区域的划分 
  格式化好的硬盘,整个磁盘按所记录数据的作用不同可分为主引导记录(MBR:Main Boot Record),Dos引导记录(DBR:Dos 
Boot Record
),文件分配表(FAT:File Assign Table),根目录(BD:Boot Directory)和数据区。前5个重要信息在磁盘的外 
磁道上,原因是外圈周长总大于内圈周长,也即外圈存储密度要小些,可靠性高些。 
要点:(1)整个硬盘可分为MBRDBRFATBD和数据区。 
   (2MBRDBRFAT,和BD位于磁盘外道。 
5.MBR 
  MBR位于硬盘第一个物理扇区(绝对扇区)柱面0,磁头0,扇区1处。由于DOS是由柱面0,磁头1,扇区1开始,故MBR不属于 
DOS
扇区,DOS不能直接访问。MBR中包含硬盘的主引导程序和硬盘分区表。分区表有4个分区记录区。记录区就是记录有关分区信 
息的一张表。它从主引导记录偏移地址01BEH处连续存放,每个分区记录区占16个字节。 
分区表的格式 
分区表项的偏移 意义   占用字节数 
   00 引导指示符 1B 
   01 分区引导记录的磁头号 1B 
   02 分区引导记录的扇区和柱面号 2B 
   04 系统指示符 1B 
   05 分区结束磁头号 1B 
   06 分区结束扇区和柱面号 2B 
   08 分区前面的扇区数 4B 
   0C 分区中总的扇区数 4B 
4
个分区中只能有1个活跃分区,即C盘。标志符是80H在分区表的第一个字节处。若是00H则表示非活跃分区。例如: 
80
 01 01 00 0B FE 3F 81 3F 00 00 00 C3 DD 1F 00 
00 00 01 82 05 FE BF 0C 02 DE 1F 00 0E 90 61 00 
00
 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00
 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
要点:(1MBR位于硬盘第一个物理扇区柱面0,磁头0,扇区1处。不属于DOS扇区, 
   (2)主引导记录分为硬盘的主引导程序和硬盘分区表。 
6.DBR 
  DBR位于柱面0,磁头1,扇区1,即逻辑扇区0DBR分为两部分:DOS引导程序和BPBBIOS参数块)。其中DOS引导程序完成 
DOS
系统文件(IO.SYSMSDOS.SYS)的定位与装载,而BPB用来描述本DOS分区的磁盘信息,BPB位于DBR偏移0BH处,共13字节。 
它包含逻辑格式化时使用的参数,可供DOS计算磁盘上的文件分配表,目录区和数据区的起始地址,BPB之后三个字提供物理格 
式化(低格)时采用的一些参数。引导程序或设备驱动程序根据这些信息将磁盘逻辑地址(DOS扇区号)转换成物理地址(绝对 
扇区号)。BPB格式 
序号 偏移地址 意义 
1 03H
0AH OEM 
2 0BH
0CH 每扇区字节数 
3 0DH 
每簇扇区数 
4 0EH
0FH 保留扇区数 
5 10H FAT
备份数 
6 11H
12H 根目录项数 
7 13H
14H 磁盘总扇区数 
8 15H 
描述介质 
9 16H
17H FAT扇区数 
10 18H
19H 每磁道扇区数 
11 1AH
1BH 磁头数 
12 1CH
1FH 特殊隐含扇区数 
13 20H
23H 总扇区数 
14 24H
25H 物理驱动器数 
15 26H 
扩展引导签证 
16 27H
2AH 卷系列号 
17 2BH
35H 卷标号 
18 36H
3DH 文件系统号 
DOS
引导记录公式: 
文件分配表保留扇区数 
根目录保留扇区数+FAT的个数×每个FAT的扇区数 
数据区根目录逻辑扇区号+(32×根目录中目录项数+(每扇区字节数-1))DIV每扇区字节数 
绝对扇区号逻辑扇区号+隐含扇区数 
扇区号(绝对扇区号MOD每磁道扇区数)+
磁头号(绝对扇区号DIV每磁道扇区数)MOD磁头数 
磁道号(绝对扇区号DIV每磁道扇区数)DIV磁头数 
要点:(1DBR位于柱面0,磁头1,扇区1,其逻辑扇区号为
   (2DBR包含DOS引导程序和BPB 
   (3BPB十分重要,由此可算出逻辑地址与物理地址。 
7.
文件分配表 
  文件分配表是DOS文件组织结构的主要组成部分。我们知道DOS进行分配的最基本单位是簇。文件分配表是反映硬盘上所 
有簇的使用情况,通过查文件分配表可以得知任一簇的使用情况。DOS在给一个文件分配空间时总先扫描FAT,找到第一个可 
用簇,将该空间分配给文件,并将该簇的簇号填到目录的相应段内。即形成了簇号链FAT就是记录文件簇号的一张表。 
FAT
的头两个域为保留域,对FAT12来说是3个字节,FAT来说是4个字节。其中头一个字节是用来描述介质的,其余字节为FFH 
。介质格式与BPB相同。 
第一个字节的8位意义: 
7
 6 5 4 3 2 1  
└─────-   │┌0非双面 
  └┤ 
  1双面 
 │┌0不是8扇区 
 └┤ 
 18扇区 
│┌0不是可换的 
└┤ 
1是可换的 
FAT
结构含义 
FAT12 FAT16 
意义 
000H 0000H 
可用 
FF0H
FF6H FFF0HFFF6H 保留 
FF7H FFF7H 
 
FF8H
FFFH FFF8HFFFFH 文件最后一个簇 
×××H ××××H 
文件下一个簇 
对于FAT16,簇号×2作偏移地址,从FAT中取出一字即为FAT中的域。 
逻辑扇区号=数据区起始逻辑扇区号+(簇号-2×每簇扇区数 
簇号=(逻辑扇区号-数据区起始逻辑扇区号)DIV每簇扇区数+
要点:(1FAT反映硬盘上所有簇的使用情况,它记录了文件在硬盘中具体位置(簇)。 
   (2)文件第一个簇号(在目录表中)和FAT的该文件的簇号串起来形成文件的簇号链,恢复被破坏的文件就是根 
据这条链。 
   (3)由簇号可算逻辑扇区号,反之,由逻辑扇区号也可以算出簇号,公式如上。 
   (4FAT位于DBR之后,其DOS扇区号从1开始。 
8.
文件目录 
  文件目录是DOS文件组织结构的又一重要组成部分。文件目录分为两类:根目录,子目录。根目录有一个,子目录可以有 
多个。子目录下还可以有子目录,从而形成树状的文件目录结构。子目录其实是一种特殊的文件,DOS为目录项分配32 
节。目录项分为三类:文件,子目录(其内容是许多目录项),卷标(只能在根目录,只有一个。目录项中有文件(或子目 
录,或卷标)的名字,扩展名,属性,生成或最后修改日期,时间,开始簇号,及文件大小。 
目录项的格式 
字节偏移 意义 占字节数 
00H 
文件名 8B 
08H 
扩展名 3B 
0BH 
文件属性 1B 
0CH 
保留 10B 
16H 
时间 2B 
18H 
日期 2B 
1AH 
开始簇号 2B 
1CH 
文件长度 4B 
目录项文件名区域中第一个字节还有特殊的意义:00H代表未使用 
05H
代表实际名为E5H 
EBH
代表此文件已被删除 
目录项属性区域的这个字节各个位的意义如下: 7 6 5 4 3 2 1 0 
                      未 修 修 子 卷 系 隐 只 
                      用 改 改 目 标 统 藏 读 
                        标 标 录   属 属 属 
                        志 志     性 性 性 
注意:WINDOWS的长文件名使用了上表中所说的保留这片区域。 
要点:(1)文件目录是记录所有文件,子目录名,扩展名属性,建立或删除最后修改日期。文件开始簇号及文件长度的一张 
      登记表
   (2DOSDIR列出的内容训是根据文件目录表得到的。 
   (3)文件起始簇号填在文件目录中,其余簇都填在FAT中上一簇的位置上。 
9.
物理驱动器与逻辑驱动器 
  物理驱动器指实际安装的驱动器。 
  逻辑驱动器是对物理驱动器格式化后产生的。 
要点:同上。

 

自磁芯大战以来,病毒从DOS时代的病毒发展到WINDOS系统的病毒,从变形、加密到智能化现在的病毒是让人防不胜防,现在网络上病毒大肆泛滥,给人们带来的很大的危害,本人在此仅做抛砖引玉,介绍病毒的原理,希望大家共同研究交流。(本文参考了网络上的部分文章,并引用了部分内容。)
要学DOS下的病毒,首先你必须要学会或掌握汇编语言。DOS下病毒一般分为引导型病毒、文件型病毒、混合型病毒等。大部分病毒是感染COMEXE文件,因此你必须了解COM文件和EXE文件结构。
.COM文件结构及原理
.COM
文件比较简单,.COM文件包含程序的一个绝对映象―――就是说,为了运行程序准确的处理器指令和内存中的数据,MS-DOS通过直接把该映象从文件拷贝到内存而加载.COM程序,它不作任何改变。为加载一个.COM程序,MS-DOS首先试图分配内存,因为.COM程序必须位于一个64K的段中,所以.COM文件的大小不能超过65,024(64K减去用于PSP256字节和用于一个起始堆栈的至少256字节)。如果MS-DOS不能为程序、一个PSP、一个起始堆栈分配足够内存,QQ:9750406则分配尝试失败。否则,MS-DOS分配尽可能多的内存(直至所有保留内存),即使.COM程序本身不能大于64K。在试图运行另一个程序或分配另外的内存之前,大部分.COM程序释放任何不需要的内存。
分配内存后,MS-DOS在该内存的头256字节建立一个PSP,如果PSP中的第一个FCB含有一个有效驱动器标识符,则置AL00h,否则为0FFhMS-DOS还置AH00h0FFh,这依赖于第二个FCB是否含有一个有效驱动器标识符。建造PSP,MS-DOSPSP后立即开始(偏移100h)加载.COM文件,它置SS,DSESPSP的段地址,接着创建一个堆栈.为创建一个堆栈,MS-DOSSP0000h,若已分配了至少64K内存;否则,它置寄存器为比所分配的字节总数大2的值.最后,它把0000h推进栈(这是为了保证与在早期MS-DOS版本上设计的程序的兼容性)。MS-DOS通过把控制传递偏移100h处的指令而启动程序.程序设计者必须保证.COM文件的第一条指令是程序的入口点。注意,因为程序是在偏移100h处加载,因此所有代码和数据偏移也必须相对于100h.汇编语言程序设计者可通过置程序的初值为100h而保证这
一点(例如通过在原程序的开始使用语句org 100h).
EXE文件结构
 EXE 文件比较复杂,每个EXE文件都有一个文件头,结构如下:
       EXE文件头信息    
    ―――――――――――――――――――
     偏移量    意义         
     00h-01h MZ‘EXE文件标记    
     2h-03h 文件长度除512的余数 
     04h-05h ...............商 
     06h-07h 重定位项的个数      
     08h-09h 文件头除16的商    
     0ah-0bh 程序运行所需最小段数
     0ch-0dh ..................
     oeh-0fh 堆栈段的段值 (SS)   
     10h-11h ........sp     
     12h-13h 文件校验和      
     14h-15h IP          
     16h-17h CS          
     18h-19h ............ 
     1ah-1bh ............     
     1ch   ............       
 ―――――――――――――――――――――――――
.EXE
文件包含一个文件头和一个可重定位程序映象。文件头包含MS-DOS用于加载程序的信息,例如程序的大小和寄存器的初始值。文件头还指向一个重定位表,该表包含指向程序映象中可重定位段地址的指针链表。文件头的形式与EXEHEADER结构对应:
EXEHEADER STRUC
exSignature dw 5A4Dh ;.EXE
标志
exExraBytes dw ? ;
最后(部分)页中的字节数
exPages dw ? ;
文件中的全部和部分页数
exRelocItems dw ? ;
重定位表中的指针数
exHeaderSize dw ? ;
以字节为单位的文件头大小
exMinAlloc dw ? ;
最小分配大小
exMaxAlloc dw ? ;
最大分配大小
exInitSS dw ? ;
初始SS
exInitSP dw ? ;
初始SP
exChechSum dw ? ;
补码校验值
exInitIP dw ? ;
初始IP
exInitCS dw ? ;
初始CS
exRelocTable dw ? ;
重定位表的字节偏移量
exOverlay dw ? ;
覆盖号
EXEHEADER ENDS
程序映象,包含处理器代码和程序的初始数据,紧接在文件头之后。它的大小以字节为单位,等于.EXE文件的大小减去文件头的大小,也等于exHeaderSize的域的值乘以16MS-DOS通过把该映象直接从文件拷贝到内存加载.EXE程序然后调整定位表中说明的可重定位段地址。
定位表是一个重定位指针数组,每个指向程序映象中的可重定位段地址。文件头中的exRelocItems域说明了数组中指针的个数,exRelocTable域说明了分配表的起始文件偏移量。每个重定位指针由两个16位值组成:偏移量和段值。 为加载.EXE程序,MS-DOS首先读文件头以确定.EXE标志并计算程序映象的大小。然后它试图申请内存。首先,它计算程序映象文件的大小加上PSP的大小再加上EXEHEADER结构中的exMinAlloc域说明的内存大小这三者之和,如果总和超过最大可用内存块的大小。则MS-DOS停止加载程序并返回一个出错值。否则面,它计算程序映象的大小加上PSP的大小再加上EXEHEADER结构中exMaxAlloc域说明的内存大小之和,如果第二个总和小于最大可用内存块的大小,则MS-DOS 分配计算得到的内存量。否则,它分配最大可用内存块。分配完内存后,MS-DOS确定段地址,也称为起始段地址,MS-DOS从此处加载程序映象。如果exMinAlloc域和exMaxAlloc域中的值都为零,则MS-DOS把映象尽可能地加载到内存最高端。否则,它把映象加载到紧挨着PSP域之上。接下来,MS-DOS读取重定位表中的项目调整所有由可重定位指针说明的段地址。对于重定位表中的每个指针,MS-DOS寻找程序映象中相应的可重定位段地址,并把起始段地址加到它之上。一旦调整完毕,段地址便指向了内存中被加载程序的代码和数据段。 MS-DOS在所分配内存的最低部分建造256字节的PSP,把ALAH设置为加载 .COM程序时所设置的值。MS-DOS使用文件头中的值设置SPSS,调整SS初始值,把起始地址加到它之上。MS-DOS还把ESDS设置为PSP的段地址.最后,MS-DOS从程序文件头读取CSIP的初始值,把起始段地址加到CS 上,把控制转移到位于调整后地址处的程序。

三、引导型病毒原理
了解引导型病毒的原理,首先要了解引导区的结构。软盘只有一个引导区,称为DOS BOOT SECTER ,只要软盘做了格式化,就会存在。其作用为查找盘上有无IO.SYS DOS.SYS,若有则引导,若无则显示‘NO SYSTEM DISK...’等信息。硬盘有两个引导区,在001扇区的称为主引导区,内有主引导程序和分区表,主引导程序查找激活分区,该分区的第一个扇区即为DOS BOOT SECTER。绝大多数病毒感染硬盘主引导扇区和软盘DOS引导扇区。

***3.5”软盘格式***
3.5”
软盘是双面的,所以零磁道有正反两面,正面为0-17扇区,
反面是18-35扇区。
0
扇区: Boot area (引导扇区)
1 - 9
扇区: 1st FAT area (第一张文件分配表)
10 - 18
扇区: 2st FAT area (第二张文件分配表)
19 - 32
扇区: Root dir area(也叫 File Directory Table,FDT)
文件目录表(根目录)
33-2879
扇区: Data area (数据区)

***硬盘的主引导记录结构***
硬盘的主引导记录结构
偏移 机器码 符号指令 说明
0000
 FA CLI ;屏蔽中断
0001
 33C0 XOR AX,AX
0003
 8ED0 MOV SS,AX ;(SS)=0000H
0005
 BC007C MOV SP,7C00 ;(SP)=7C00H
0008
 8BF4 MOV SI,SP ;(SI)=7C00H
000A
 50 PUSH AX
000B
 07 POP ES ;(ES)=0000H
000C
 50 PUSH AX
000D
 1F POP DS ;(DS)=0000H
000E
 FB STI
000F
 FC CLD
0010
 BF0006 MOV DI,0600
0013
 B90001 MOV CX,0100 ;512字节
0016
 F2 REPNZ
0017
 A5 MOVSW ;主引导程序把自己从0000:7C00处搬到
;0000:0600
,Dos分区的引导程序腾
;
出空间
0018
 EA1D060000 JMP 0000:061D ;跳到0000:061D处继续执行,实际上就是
;
执行下面的MOV指令(001D偏移处)
001D
 BEBE07 MOV SI,07BE ;07BE-0600=01BE,01BE是分区表的首址
0020
 B304 MOV BL,04 ;分区表最多4,即最多4个分区
0022
 803C80 CMP BYTE PTR [SI],80 ;80H表示活动分区
0025
 740E JZ 0035 ;找到活动分区则跳走
0027
 803C00 CMP BYTE PTR [SI],00 ;00H为有效分区的标志
002A
 751C JNZ 0048 ;既非80H亦非00H则分区表无效
002C
 83C610 ADD SI,+10 ;下一个分区表项,每项16字节
002F
 FECB DEC BL ;循环计数减一
0031
 75EF JNZ 0022 ;检查下一个分区表项
0033
 CD18 INT 18 ;4个都不能引导则进入ROM Basic
0035
 8B14 MOV DX,[SI]
0037
 8B4C02 MOV CX,[SI+02] ;取活动分区的引导扇区的面,柱面,扇区
003A
 8BEE MOV BP,SI ;然后继续检查后面的分区表项
003C
 83C610 ADD SI,+10
003F
 FECB DEC BL
0041
 741A JZ 005D ;4个都查完则去引导活动分区
0043
 803C00 CMP BYTE PTR [SI],00 ;00H为分区有效标志
0046
 74F4 JZ 003C ;此分区表项有效则继续查下一个
0048
 BE8B06 MOV SI,068B ;068B-0600=018B,"无效分区"字符串
004B
 AC LODSB ;从字符串中取一字符
004C
 3C00 CMP AL,00 ;00H表示串尾
004E
 740B JZ 005B ;串显示完了则进入死循环
0050
 56 PUSH SI
0051
 BB0700 MOV BX,0007
0054
 B40E MOV AH,0E
0056
 CD10 INT 10 ;显示一个字符
0058
 5E POP SI
0059
 EBF0 JMP 004B ;循环显示下一个字符
005B
 EBFE JMP 005B ;此处为死循环
005D
 BF0500 MOV DI,0005 ;读入活动分区的引导扇,最多试读5
0060
 BB007C MOV BX,7C00
0063
 B80102 MOV AX,0201
0066
 57 PUSH DI
0067
 CD13 INT 13 ;
0069
 5F POP DI
006A
 730C JNB 0078 ;读盘成功则跳走
006C
 33C0 XOR AX,AX
006E
 CD13 INT 13 ;读失败则复位磁盘
0070
 4F DEC DI
0071
 75ED JNZ 0060 ;不到5次则再试读
0073
 BEA306 MOV SI,06A3 ;06A3-0600=00A3,"Error loading"
0076
 EBD3 JMP 004B ;去显示字符串,然后进入死循环
0078
 BEC206 MOV SI,06C2 ;06C2-0600=00C2,"Missing.."
0076
 EBD3 JMP 004B ;去显示字符串,然后进入死循环
0078
 BEC206 MOV SI,06C2 ;06C2-0600=00C2,"Missing.."
007B
 BFFE7D MOV DI,7DFE ;7DFE-7C00=01FE,即活动分区的引导扇
;
区的最后两字节的首址
007E
 813D55AA CMP WORD PTR [DI],AA55;最后两字节为AA55H则有效
0082
 75C7 JNZ 004B ;无效则显示字符串并进入死循环
0084
 8BF5 MOV SI,BP
0086
 EA007C0000 JMP 0000:7C00 ;有效则跳去引导该分区
0080
 49 6E 76 61 6C Inval
0090
 69 64 20 70 61 72 74 69-74 69 6F 6E 20 74 61 62 id partition tab
00A0
 6C 65 00 45 72 72 6F 72-20 6C 6F 61 64 69 6E 67 le.Error loading
00B0
 20 6F 70 65 72 61 74 69-6E 67 20 73 79 73 74 65 operating syste
00C0
 6D 00 4D 69 73 73 69 6E-67 20 6F 70 65 72 61 74 m.Missing operat
00D0
 69 6E 67 20 73 79 73 74-65 6D 00 00 FB 4C 38 1D ing system...L8.
00E0
 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00F0
 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0100
 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0110
 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0120
 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0130
 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0140
 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0150
 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0160
 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0170
 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0180
 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0190
 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
01A0
 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
01B0
 00 00 00 00 00 00 00 00-00 00 00 00 00 00 80 01 ................;分区表
01C0
 01 00 06 0F 7F 9C 3F 00-00 00 F1 59 06 00 00 00 ......?....Y....
01D0
 41 9D 05 0F FF 38 30 5A-06 00 40 56 06 00 00 00 A....80Z..@V....
01E0
 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
01F0
 00 00 00 00 00 00 00 00-00 00 00 00 00 00 55 AA ..............U.


使用INT 13H02功能调用把位于硬盘保留扇区中001扇区处的硬盘主引导记录读到内存的ES:BX处。现在把读出程序代码进行如下分析:

1、移动主引导记录程序
0E74:7C00
 33C0 XOR AX,AX ;AX清零
0E74:7C02
 8ED0 MOV SS,AX ;SS清零
0E74:7C04
 BC007C MOV SP,7C00 ;SP=7C00,堆栈设在07C00H
0E74:7C07
 FB STI ;开中断
0E74:7C08
 50 PUSH AX
0E74:7C09
 07 POP ES ;ES=0
0E74:7C0A
 50 PUSH AX
0E74:7C0B
 1F POP DS ;DS=0
0E74:7C0C
 FC CLD
0E74:7C0D
 BE1B7C MOV SI,7C1B ;源地址为07C1BH
0E74:7C10
 BF1B06 MOV DI,061B ;目的地址为0061BH
0E74:7C13
 50 PUSH AX
0E74:7C14
 57 PUSH DI
0E74:7C15
 B9E501 MOV CX,01E5 ;移动01E5字节
0E74:7C18
 F3 REPZ ;将主引导记录从07C1B-07DFF
0E74:7C19
 A4 MOVSB ;移至0061B-007FF
0E74:7C1A
 CB RETF ;转移到0061B,继续执行程序

2、顺序查找四个硬盘分区表,寻找自举标志
0E74:061B
 BEBE07 MOV SI,07BE ;SI指向硬盘分区表1的自举标志

0E74:061E B104 MOV CL,04 ;查找四个分区
0E74:0620
 382C CMP [SI],CH
0E74:0622
 7C09 JL 062D ;如果[SI]的第7位为1,即为自
;举标志,转062DH
0E74:0624
 7515 JNZ 063B ;如果[SI]不为0,出错,转063BH
0E74:0626
 83C610 ADD SI,+10 ;依次检验四个分区表,直至找到

0E74:0629 E2F5 LOOP 0620 ;自举标志
0E74:062B
 CD18 INT 18 ;找不到自举标志,进入BOOT
;常处理程序。
0E74:062D
 8B14 MOV DX,[SI] ;保存自举驱动器号于DL
0E74:062F
 8BEE MOV BP,SI ;保存自举分区地址指针于BP
0E74:0631
 83C610 ADD SI,+10 ;继续检验自举分区后的分区
0E74:0634
 49 DEC CX ;自举标志,直至四个分区都
0E74:0635
 7416 JZ 064D ;检查完
0E74:0637
 382C CMP [SI],CH ;若其余的自举标志不为0,出错

0E74:0639 74F6 JZ 0631 

3、出错,写屏幕程序段
0E74:063B
 BE1007 MOV SI,0710 ;错误信息输出,死循环
0E74:063E
 4E DEC SI
0E74:063F
 AC LODSB
0E74:0640
 3C00 CMP AL,00
0E74:0642
 74FA JZ 063E
0E74:0644
 BB0700 MOV BX,0007
0E74:0647
 B40E MOV AH,0E
0E74:0649
 CD10 INT 10
0E74:064B
 EBF2 JMP 063F

硬盘主引导记录程序的功能是读出自举分区的BOOT程序,并把控制转移到分区BOOT程序。整个程序流程如下:
1
 将本来读入到07C00H处的硬盘主引导记录程序移至061BH处;
 顺序读入四个分区表的自举标志,以找出自举分区,若找不到,转而执行INT18HBOOT异常执行中断程序;
 找到自举分区后,检测该分区的系统标志,若为32FAT表或16FAT表但支持13号中断的扩展功能,就转到执行13号中断的41号功能调用进行安装检验,检验成功,就执行42号扩展读功能调用把BOOT区程序读入到内存07C00H处,成功,跳到第步,若读失败或系统标志为其它,就调用13号中断的读扇区功能调用把BOOT读到07C00H
 用13号中断的读扇区功能时,用两种方式分别进行5次试读。第一种方式是直接从自举分区的头扇区读入BOOT程序,若读成功,但结束标志不是55AA,则改用第二种方式,又如果用第一种方式试读五次均不成功,就改用第二种方式。若两种方式试读均失败,就转到出错处理程序;  读入BOOT区程序成功,转至07C00H处执行BOOT程序。 

NT下对I/O地址的访问
Windows NT 操作系统设置的进程模式会使运行在其中的应用程序访问I/O地址的指令引起保护性的失败。这使得应用程序需要附以一个设备驱动程序进行I/O操作。设备驱动程序运行在内核模式,这使得在这种状态的中运行的进程可以执行I/O操作。

---- Windows 95/98 是仅为 Intel 类型机器设计的,没有额外复杂的I/O需求,而Windows NT 被设计成可以在不同机器机构上进行移植。这使得Windows NT 的系统模式要求驱动程序的编写者要考虑一台机器可能有多种类型的总线,这可能需要在总线之间传递地址。这种模式还要区别I/O空间和内存空间。在多总线的机器中每一总线可以既支持内存又支持I/O循环。

---- 根据定义,I/O寄存器或者端口访问是通过I/O循环实现的。然而,在一些系统中外部总线的I/O空间可以被映像到进程内存空间。硬件抽象层(Hardware Abstract Layer)决定这些。要访问I/O寄存器,驱动程序编写者必须知道寄存器在那一总线,它的I/O空间地址在那条总线。一条总线是由其接口类行 (如 ISA PCI 等)和编号(从零开始)决定的。

---- 下面是一个假象设备访问I/O的例子,接口类型:ISA 编号 0 地址 0xE700。设备描述如下: Offset Size Usage 0 1 Command register 1 1 Status register 2 2 Word data register 4 4 Dword data register

---- 用开发NT 设备驱动程序的工具包DriverDorks 可以用以下 步骤访问设备:
----
建立一个KIoRange的对象映像设备寄存器。
   KIoRange DeviceIos; Status = DevceIos.Initialize(
     Isa, //
总线类型
     0, //
总线号
     0xE700, //
总线地址
     8, //
设备数
     TRUE //
映像到系统空间(如果端口是内存映像的)
     );
   if(NT_SUCCESS(status)) //
建立成功

---- 可以用KIoRange 的成员函数访问寄存器:
     //
寄存器偏移量
     #define COMMAND 0
     #define STATUS 1
     #define WDATA 2
     #define DDATA 3

     //读状态寄存器
     UCHAR DeviceStatus = DeviceIos.inb(STATUS);

     //写命令寄存器
     DeviceIos.outb(COMMAND,CMD_RESET);

     //20个字到端口
     DeviceIos.outw(WDATA,buffer,20);

---- 另外也可以建立KIoRegister 的对象来访问设备:
     KIoRegister CommandReg = DeviceIos[COMMAND];
     KIoRegister StatusReg = DeviceIos[STATUS];
     CommandRge=(UCHAR)RESET;  //
RESET命令
     UCHAR status=StatusReg; //
读状态寄存器
    
如果在同一函数中频繁访问寄存器用KioRegiser 比用KIoRange 的成员函数的性能好一些。无论如何,数据类型必须正确(UCHAR,USHORT,ULONG),这些决定了到总线上数据的实际大小.

硬盘的 dos 管理结构
  1. 磁道,扇区,柱面和磁头数
   硬盘最基本的组成部分是由坚硬金属材料制成的涂以磁性介质的盘片,不同容量硬盘的盘片数不等。每个盘片有两面,都可记录信息。盘片被分成许多扇形的区域,每个区域叫一个扇区,每个扇区可存储 128 × 2 n 次方( n 0.1.2 .3 )字节信息。在 dos 中每扇区是 128 × 2 2 次方= 512 字节,盘片表面上以盘片中心为圆心,不同半径的同心圆称为磁道。硬盘中,不同盘片相同半径 的磁道所组成的圆柱称为柱面。磁道与柱面都是表示不同半径的圆,在许多场合,磁道和柱面可以互换使用,我们知道,每个磁 盘有两个面,每个面都有一个磁头,习惯用磁头号来区分。扇区,磁道(或柱面)和磁头数构成了硬盘结构的基本参数,帮这些 参数可以得到硬盘的容量,基计算公式为:
 
存储容量=磁头数×磁道(柱面)数×每道扇区数×每扇区字节数
 
要点:( 1 )硬盘有数个盘片,每盘片两个面,每个面一个磁头
     2 )盘片被划分为多个扇形区域即扇区
     3 )同一盘片不同半径的同心圆为磁道
     4 )不同盘片相同半径构成的圆柱面即柱面
     5 )公式: 存储容量=磁头数×磁道(柱面)数×每道扇区数×每扇区字节数
     6 )信息记录可表示为:××磁道(柱面),××磁头,××扇区


  2.

   dos 进行分配的最小单位。当创建一个很小的文件时,如是一个字节,则它在磁盘上并不是只占一个字节的空间, 而是占有整个一簇。 dos 视不同的存储介质(如软盘,硬盘),不同容量的硬盘,簇的大小也不一样。簇的大小可在称为磁盘 参数块( bpb )中获取。簇的概念仅适用于数据区。
  
本点:( 1 dos 进行分配的最小单位。
      2 )不同的存储介质,不同容量的硬盘,不同的 dos 版本,簇的大小也不一样。
      3 )簇的概念仅适用于数据区。

  3. 扇区编号定义:绝对扇区与 dos 扇区
   由前面介绍可知,我们可以用柱面 / 磁头 / 扇区来唯一定位磁盘上每一个区域,或是说柱面 / 磁头 / 扇区与磁盘上每一个扇区有 一一对应关系,通常 dos 柱面 / 磁头 / 扇区这样表示法称为绝对扇区表示法。但 dos 不能直接使用绝对扇区进行磁盘上的 信息管理,而是用所谓相对扇区“ dos 扇区相对扇区只是一个数字,如柱面 140 ,磁头 3 ,扇区 4 对应的相对扇区号 2757 。该数字与绝对扇区柱面 / 磁头 / 扇区具有一一对应关系。当使用相对扇区编号时, dos 是从柱面 0 ,磁头 1 ,扇区 1 开始 (注:柱面 0 ,磁头 0 ,扇区 1 没有 dos 扇区编号, dos 下不能访问,只能调用 bios 访问),第一个 dos 扇区编号为 0 ,该磁道上剩余 的扇区编号为 1 16 (设每磁道 17 个扇区),然后是磁头号为 2 ,柱面为 0 17 个扇区,形成的 dos 扇区号从 17 33 。直到该柱面的 所有磁头。然后再移到柱面 1 ,磁头 1 ,扇区 1 继续进行 dos 扇区的编号,即按扇区号,磁头号,柱面号(磁道号)增长的顺序连续 地分配 dos 扇区号。
   
公式:记 dh --第一个 dos 扇区的磁头号
             dc
--第一个 dos 扇区的柱面号
             ds
--第一个 dos 扇区的扇区号
             ns
--每磁道扇区数
             nh
--磁盘总的磁头数
  则某扇区(柱面 c ,磁头 h ,扇区 s )的相对扇区号 rs 为:
    rs
nh × ns × c dc )+ ns × h dh )+( s ds
  若已知 rs dc dh ds ns nh
    s
=( rs mod ns )+ ds
    h
=(( rs div ns mod nh )+ dh
    c
=(( rs div ns div nh )+ dc
   
要点:( 1 )以柱面 / 磁头 / 扇区表示的为绝对扇区又称物理磁盘地址
       2 )单一数字表示的为相对扇区或 dos 扇区,又称逻辑扇区号
       3 )相对扇区与绝对扇区的转换公式

 4.dos 磁盘区域的划分
   格式化好的硬盘,整个磁盘按所记录数据的作用不同可分为主引导记录( mbr:main boot record ), dos 引导记录( dbros boot record ),文件分配表( fat:file assign table ),根目录( bd:boot directory )和数据区。前 5 个重要信息在磁盘的外 磁道上,原因是外圈周长总大于内圈周长,也即外圈存储密度要小些,可伤心性高些。
   
要点:( 1 )整个硬盘可分为 mbr dbr fat bd 和数据区。
       2 mbr dbr fat ,和 bd 位于磁盘外道。

 5.mbr
    mbr
位于硬盘第一个物理扇区(绝对扇区)柱面 0 ,磁头 0 ,扇区 1 处。由于 dos 是由柱面 0 ,磁头 1 ,扇区 1 开始,故 mbr 不属于 dos 扇区, dos 不能直接访问。 mbr 中包含硬盘的主引导程序和硬盘分区表。分区表有 4 个分区记录区。记录区就是记录有关分区信
息的一张表。它从主引导记录偏移地址 01beh 处连续存放,每个分区记录区占 16 个字节。
分区表的格式 分区表项的偏移 意义   占用字节数
   00
引导指示符 1b
   01
分区引导记录的磁头号 1b
   02
分区引导记录的扇区和柱面号 2b
   04
系统指示符 1b
   05
分区结束磁头号 1b
   06
分区结束扇区和柱面号 2b
   08
分区前面的扇区数 4b
   0c
分区中总的扇区数 4b
   4
个分区中只能有 1 个活跃分区,即 c 盘。标志符是 80h 在分区表的第一个字节处。若是 00h 则表示非活跃分区。例如:
   80 01 01 00 0b fe 3f 81 3f 00 00 00 c 3 dd 1f 00
   00 00 01 82 05 fe bf 0c 02 de 1f 00 0e 90 61 00
   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  
要点:( 1 mbr 位于硬盘第一个物理扇区柱面 0 ,磁头 0 ,扇区 1 处。不属于 dos 扇区,
      2 )主引导记录分为硬盘的主引导程序和硬盘分区表。

  6.dbr
  dbr
位于柱面 0 ,磁头 1 ,扇区 1 ,即逻辑扇区 0 dbr 分为两部分: dos 引导程序和 bpb bios 参数块)。其中 dos 引导程序完成 dos 系统文件( io.sys msdos.sys )的定位与装载,而 bpb 用来描述本 dos 分区的磁盘信息, bpb 位于 dbr 偏移 0bh 处,共 13 字节。 它包含逻辑格式化时使用的参数,可供 dos 计算磁盘上的文件分配表,目录区和数据区的起始地址, bpb 之后三个字提供物理格 式化(低格)时采用的一些参数。引导程序或设备驱动程序根据这些信息将磁盘逻辑地址( dos 扇区号)转换成物理地址(绝对 扇区号)。
   bpb
格式  序号 偏移地址 意义
            1 03h
0ah oem
            2 0bh
0ch 每扇区字节数
            3 0dh
每簇扇区数
            4 0eh
0fh 保留扇区数
            5 10h fat
备份数
            6 11h
12h 根目录项数
            7 13h
14h 磁盘总扇区数
            8 15h
描述介质
            9 16h
17h fat 扇区数
            10 18h
19h 每磁道扇区数
            11 1ah
1bh 磁头数
            12 1ch
1fh 特殊隐含扇区数
            13 20h
23h 总扇区数
            14 24h
25h 物理驱动器数
            15 26h
扩展引导签证
            16 27h
2ah 卷系列号
            17 2bh
35h 卷标号
            18 36h
3dh 文件系统号
            dos
引导记录公式:
  
文件分配表保留扇区数
  
根目录保留扇区数+ fat 的个数×每个 fat 的扇区数
  
数据区根目录逻辑扇区号+( 32 ×根目录中目录项数+(每扇区字节数- 1 )) div 每扇区字节数
  
绝对扇区号逻辑扇区号+隐含扇区数
  
扇区号(绝对扇区号 mod 每磁道扇区数)+ 1
  
磁头号(绝对扇区号 div 每磁道扇区数) mod 磁头数
  
磁道号(绝对扇区号 div 每磁道扇区数) div 磁头数
  
要点:( 1 dbr 位于柱面 0 ,磁头 1 ,扇区 1 ,其逻辑扇区号为 0
      2 dbr 包含 dos 引导程序和 bpb
      3 bpb 十分重要,由此可算出逻辑地址与物理地址。

  7. 文件分配表
   文件分配表是 dos 文件组织结构的主要组成部分。我们知道 dos 进行分配的最基本单位是簇。文件分配表是反映硬盘上所 有簇的使用情况,通过查文件分配表可以得知任一簇的使用情况。 dos 在给一个文件分配空间时总先扫描 fat ,找到第一个可 用簇,将该空间分配给文件,并将该簇的簇号填到目录的相应段内。即形成了簇号链 fat 就是记录文件簇号的一张表。 fat 的头两个域为保留域,对 fat12 来说是 3 个字节, fat 来说是 4 个字节。其中头一个字节是用来描述介质的,其余字节为 ffh 。介质格式与 bpb 相同。
   
第一个字节的 8 位意义:
    7 6 5 4 3
 2 1
   
└───── - │┌ 0 非双面
   
1 └┤
   
1 双面
   
│┌ 0 不是 8 扇区
   
└┤
   
1 8 扇区
   
│┌ 0 不是可换的
   
└┤
   
1 是可换的
    fat
结构含义
    fat12 fat16
意义
    000h 0000h
可用
    ff0h
ff6h fff0h fff6h 保留
    ff7h fff7h

    ff8h
fffh fff8h ffffh 文件最后一个簇
    ××× h ×××× h
文件下一个簇
   
对于 fat16 ,簇号× 2 作偏移地址,从 fat 中取出一字即为 fat 中的域。
   
逻辑扇区号=数据区起始逻辑扇区号+(簇号- 2 ×每簇扇区数
   
簇号=(逻辑扇区号-数据区起始逻辑扇区号) div 每簇扇区数+ 2
要点:( 1 fat 反映硬盘上所有簇的使用情况,它记录了文件在硬盘中具体位置(簇)。
   ( 2 )文件第一个簇号(在目录表中)和 fat 的该文件的簇号串起来形成文件的簇号链,恢复被破坏的文件就是根 据这条链。
   ( 3 )由簇号可算逻辑扇区号,反之,由逻辑扇区号也可以算出簇号,公式如上。
   ( 4 fat 位于 dbr 之后,其 dos 扇区号从 1 开始。

 8. 文件目录
   文件目录是 dos 文件组织结构的又一重要组成部分。文件目录分为两类:根目录,子目录。根目录有一个,子目录可以有 多个。子目录下还可以有子目录,从而形成树状的文件目录结构。子目录其实是一种特殊的文件, dos 为目录项分配 32 节。目录项分为三类:文件,子目录(其内容是许多目录项),卷标(只能在根目录,只有一个。目录项中有文件(或子目 录,或卷标)的名字,扩展名,属性,生成或最后修改日期,时间,开始簇号,及文件大小。
  
目录项的格式   字节偏移 意义 占字节数
                  00h
文件名 8b
                  08h
扩展名 3b
                  0bh
文件属性 1b
                  0ch
保留 10b
                  16h
时间 2b
                  18h
日期 2b
                  1ah
开始簇号 2b
                  1ch
文件长度 4b
  
目录项文件名区域中第一个字节还有特殊的意义: 00h 代表未使用  05h 代表实际名为 e5h ebh 代表此文件已被删除
目录项属性区域的这个字节各个位的意义如下: 7 6 5 4 3 2 1 0
                      未 修 修 子 卷 系 隐 只
                      用 改 改 目 标 统 藏 读
                         标 标 录   属 属 属
                         志 志     性 性 性
 
注意: windows 的长文件名使用了上表中所说的保留这片区域。
 
要点:( 1 )文件目录是记录所有文件,子目录名,扩展名属性,建立或删除最后修改日期。文件开始簇号及文件长度的一张 登记表 .
    2 dos dir 列出的内容训是根据文件目录表得到的。
    3 )文件起始簇号填在文件目录中,其余簇都填在 fat 中上一簇的位置上。

9. 物理驱动器与逻辑驱动器
   物理驱动器指实际安装的驱动器。 逻辑驱动器是对物理驱动器格式化后产生的 硬盘逻辑锁巧解在谈论具体的解决方法前,先讲述一下被 " 逻辑锁 " 锁住的硬盘为什么不能用普通办法启动的原因:计算机在引导 dos 系统时将会搜索所有逻辑盘的顺序,当 dos 被引导时,首先要去找主引 导扇区的分区表信息,位于硬盘的零头零柱面的第一个扇区的 obeh 地址开始的地方,当 分区信息开始的地方为 80h 时表示是主引导分区,其他的为扩展分区,主引导分区被定义 为逻辑盘 c 盘,然后查找扩展分区的逻辑盘,被定义为 d 盘,以此类推找到 e f g..... " 逻辑锁 " 就是在此下手,修改了正常的主引导分区记录将扩展分区的第一个逻辑盘指向 自己, dos 在启动时查找到第一个逻辑盘后,查找下个逻辑盘总是找到是自己,这样一来 就形成了死循环,这就是使用软驱 , 光驱,双硬盘都不能正常启动的原因。实际上这 " 逻辑锁 " 只是利用了 dos 在启动时的一个小小缺陷,便令不少高手都束手无策。知道了 " 逻辑 " " 上锁 " 原理,要解锁也就比较容易了。以前我看到有位朋友采用 " 热拔插 " 硬盘电源的方法来处理:就是在当系统启动时,先不给被锁的硬盘插上电源线,等待启动完成后再给硬盘 " 热插 " 上电源线,这时如果硬盘没有烧坏的话,系统就可以控制硬盘了。当然这是一种非常危险的方法,大家不要轻易尝试,下面介绍两种比较简单和安全的处理方法。

   方法一:修改 dos 启动文件 首先准备一张 dos6.22 的系统盘,带上 debug pctools5.0 fdisk 等工具。然后在一台正常的机器上,使用你熟悉的二进制编辑工具( debug pctools5.0 ,或者 windows 下的 ultraedit 都行)修改软盘上的 io.sys 文件(修改前记住改该文件的属性为正常),具体是在这个文件里面搜索第一个 "55aa" 字符串,找到以后修改为任何其他数值即可。用这张修改过的系统软盘你就可以顺利地带着被锁的硬盘启动了。不过这时由于该硬盘正常的分区表已经被黑客程序给恶意修改了,你无法用 fdisk 来删除和修改分区,而且仍无法用正常的启动盘启动系统,这时你可以用 debug 来手工恢复。使用 debug 手工修复硬盘步骤如下:

a:\>debug
-a
-xxxx:100 mov ax,0201
读一个扇区的内容
-xxxx:103 mov bx,500
设置一个缓存地址
-xxxx:106 mov cx,0001
设置第一个硬盘的硬盘指针
-xxxx:109 mov dx,0080
读零磁头
-xxxx: 10c int 13
硬盘中断
-xxxx:10e int 20
-xxxx:0110
退出程序返回到指示符
-g
运行
-d500
查看运行后 500 地址的内容

这时候会发现地址 6be 开始的内容是硬盘分区的信息,发现此硬盘的扩展分区指向自己,这就使 dos windows 启动时查找硬盘逻辑盘进去死循环,在 debug 指示符下用 e 命令修改内存数据 具体如下:

e6be
xx.0 xx.0 xx.0...............
.............................
.......................55 aa
55 aa
表示硬盘有效的标记,不要修改, xx0 表示把以前的数据 "xx" 改成 0

再用硬盘中断 13 把修改好的数据写入硬盘就可以了,具体如下:
a:\>debug
a 100
表示修改 100 地址的汇编指令
-xxxx:100 mov ax,0301
写硬盘一个扇区
-xxxx:
这里直接按回车
-g
运行
-q
退出

   然后运行 fdisk/mbr (重置硬盘引导扇区的引导程序 ) ,再重新启动电脑就行了。 怎么样?用这种方法处理够简单的吧?而且这种方法还有一个好处就是可以保住盘上的 数据!如果你不需要保数据的话,还有更加简单的处理方法:

   方法二:巧设 bios ,用 dm 解锁大家知道 dm 软件是不依赖于主板 bios 的硬盘识别安装软件,(所以在不能识别大硬盘的老主板上也可用 dm 来安装使用大容量硬盘)。就算在 bios 中将硬盘设为 "none" dm 也可识别并处理硬盘。首先你要找到和硬盘配套的 dm 软件(找 js 要或去网上荡),然后把 dm 拷到一张系统盘上。接上被锁硬盘,开机,按住 del 键,进 cmos 设置,将所有 ide 硬盘设为 none (这是关键所在 ! ),保存设置,重启动,这时系统即可 " 带锁 " 启动。启动后运行 dm ,你会发现 dm 可以绕过 bios ,识别出硬盘,选中该硬盘,分区格式化,就 ok 了。这么简单?不过这种 方法的弱点是硬盘上的数据将全部丢失。

如何开发自己的操作系统的引导程序?
当你打开计算机时发生了什么?
  1.电源打开;2.BIOS开始执行;3.引导程序开始执行。
  引导程序的规定:你要有一个普通的二进制文件(COM 格式);大小是512个字节;最后两个字节一定是0AA55h;它能被载入到内存地址0x7C00
  工具:
  NASM――是一个免费的汇编工具(有DOS/windows/Linux三种版本)
  PARTCOPY2.0――DOS下可自由往磁盘拷贝数据的软件
  举例:
  1.Just hang……
  这个简单的引导程序只能挂起:
  hang:
   jmp hang
  times 512($-$$)2 db 0
  dw 0AA55h
  连接这个引导程序:
  nasm f bin o hang.bin hang.asm
  现在你需要一张格式化磁盘,传送hang.bin到磁盘的引导扇区
  partcopy hang.bin 0 200 f0
  “0”的意思是指从hang.bin文件的顶端开始传送
  “200”的意思是指拷贝200个字节
  插入磁盘和重新启动机器,测试这个引导程序。
  2.一个实模式下的引导程序
  上面的程序非常简单,下面介绍一个稍微复杂一点的程序。
  bits 16
  org 0x7C00
  start:
  cli;关中断
  mov ax,0x9000;设置堆栈址:0x90000
  mov ss,ax
  mov sp,0
  sti;开中断
  l1:push ds
  mov dl,0
  重新设置磁盘控制器
  mov ax,0
  int 13h
  pop ds
   jc fail
  push es
  mov ax,0x1000ES:BX=10000
  mov es,ax
  mov bx,0
  mov ah,2;读磁盘扇区
  mov al,5;读入5个扇区
  mov cx,2;柱面号=0,扇区号=2
  mov dx, 0;磁头号=0,驱动器号=0
  int 13hES:BX=来自磁盘上的数据
  pop es
  jc l1
  mov ax,0x10000;设置段寄器
  mov es,ax
  mov ds,ax
  push ax
  mov ax,0
  push ax
  retf
  fail:
  jmp fail
  times 512($-$$)2 db 0
  dw 0AA55h
  连接这个引导程序:
  nasm f bin o boot.bin boot.asm
  传送boot.bin到磁盘的引导扇区
  partcopy boot.bin 0 200 f0
  为了使程序可以看到,在编译下面程序
  mov ax,1000h;修改段寄存器
  mov ds,ax
  mov es,ax
  mov si,msg;打印 "JIPPIKAYE!"
  call putstr
  hang:;挂起
  jmp hang
  putstr:
  lodsb
  or al,al
  jz short putstrd
  mov ah,0x0E
  mov bx,0x0007
  int 0x10
  jmp putstr
  putstrd:
  retn
  msg db 'JIPPIKAYE!',13,10,0
  连接和传送:
  nasm f bin o boot.bin boot.asm
  partcopy boot.bin 0 200 f0 200
  在partcopy中最后一个参数“200”意思是指磁盘的偏移地址插入磁盘和重新启动机器,你会看到“JIPPIKAYE”然后挂起。 


Windows 2000的引导过程

事实上,Windows 2000的引导过程是从安装时候就已经开始的。

那我们首先从windows 2000的安装说起。
windows 2000 setup运行时,它向硬盘上写入MBR(主引导记录),同时在这个磁盘驱动器的第一个可引导分区(就是我们在fdisk后激活的分区)写入引导扇区,引导扇区的内容根据不同的文件系统格式而变化(FAT或者是NTFS)。如果你的机器上曾装有MS操作系统并建立了引导扇区的话,windows 2000 setup将检测它要覆盖的引导扇区是否有效,如果有效的话,windows 2000 setup安装程序将把引导扇区的内容复制到这个分区的根目录中的文件bootsect.dos中。Setup程序在写完引导扇区后,将把windows 2000所用的文件拷贝到硬盘,包括两个引导文件NtldrNtdetect.com。另外,setup还会在引导分区的根目录中建立引导菜单文件boot.ini
例:
[boot loader]
timeout=3
default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
[operating systems]
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows "
multi(0)disk(0)rdisk(0)partition(2)\WINDOWS="Windows Server 2000" /fastdetect

这是我的机器上的boot.ini文件,该内容显示装了两个操作系统,win98win2000,后面的那个参数/fastdetect最常见,是安装系统时默认的,它的作用是使ntdetect忽略秉性和串行设备的枚举。Boot.ini文件中的相关参数还有很多,各有不同的功能,因为与本文没太大关系,所以不作具体介绍,有兴趣的朋友可以到网上找找有关资料。

Windows 2000的启动:
当你按下机器上的power键,计算机就开始启动了,首先是上电自检,通过后bios引导计算机去读取硬盘上的MBR,根据MBR中的信息,找到引导分区,将引导分区内的引导扇区的代码读入内存并把控制权交给该代码。引导扇区代码的作用是向Windows 2000提供磁盘驱动器(硬盘)的结构和格式信息并且从磁盘根目录中读取Ntldr文件,在引导扇区代码将Ntldr加载到内存后,它把控制权交给Ntldr的入口点。如果引导扇区代码在根目录中没有找到Ntldr文件的话,若文件系统为FAT格式,则显示:“Boot:无法找到Ntldr”,若引导文件系统是NTFS格式,则显示:“NTLDR丢失。然后,Ntldr使用内建的文件系统代码从根目录读取boot.ini文件(Ntldr内建代码与引导扇区文件系统代码不同的是,Ntldr文件系统代码可以读取子目录)。此时,Ntldr清除屏幕,如果boot.ini中存在不止一种引导选项,则显示引导选择菜单,如果在boot.ini制定的超时范围内未有任何动作的话,Ntldr会选择默认的选项。引导选项确定后,Ntldr加载和执行Ntdetect.com(这是一个使用系统bios进行查询计算机基本设备和设置信息的16位实模式程序)。然后,Ntldr开始清除屏幕并显示:“Starting Windows……”进度栏。这个进度栏保持空白,直到Ntldr开始加载引导驱动程序(假如有100个引导驱动程序,则每加载一个文件,进度条增加1%)。在进度条的下面是信息:“For troubleshooting and advanced startup options for windows 2000 , press F8 .”如果此时按下F8键,会出现高级启动菜单,包括:已知的最近正确模式(last known good),安全模式(safe mode),调试模式(debug mode)等等等等。
此后,Ntldr加载合适的内核和HAL映像文件(缺省为Ntoskrnl.exeHAL.dll),读入SYSTEM注册表hive文件(hive文件是一种包含注册表子树的文件)以确定该加载哪些引导驱动程序,加载引导驱动程序,为Ntoskrnl.exe的执行准备CPU寄存器。之后,Ntldr调用Ntoskrnl.exe并由它开始初始化执行程序子系统并引导系统-启动(system-start)设备驱动程序,在一系列的初始化工作完成后Ntoskrnl.exe为系统本机应用程序作准备并运行smss.exe
smss
的主要任务是:初始化注册表,创建系统环境变量,加载Win32子系统(Win32k.sys)的内核模式部分,启动子系统进程Crss,启动登陆进程winlogon。然后,winlogon开始执行其启动步骤,如创建初始的窗口和桌面对象等等。然后它创建服务控制管理器(SCM)进程(Winnt\System32\Services.exe),它加载所有的标记为自动启动(auto-start)的服务程序和设备驱动程序和本机安全验证子系统(Lsass)进程(Winnt\system32\Lsass.exe)。当一切加载成功且用户在控制台成功登陆后,SCM则认为系统引导成功,注册表中 已知最近正确配置(HKLM\SYSTEM\select\LastKnownGood)由\CurrentControlSet替代。反之,如果用户在引导的时候选择高级菜单中的已知最近正确模式(LastKnownGood)或者加载时驱动程序返回一个严重的或者关键的错误,系统会以LastKnownGood的值作为CurrentControlSet 的值。
之后,我们便看到了熟悉的桌面。至此,windows 2000的引导过程结束。
限于篇幅,本文只简单的讲述一下windows 2000操作系统引导的大体过程,一些细节方面的东东请看我整理的其他windows 2000操作系统方面的文章。


不同WINDOWS平台下磁盘逻辑扇区的直接读写
一、概述
   
DOS操作系统下,通过BIOSINT13DOSINT25(绝对读)、INT26(绝对写)等功能调用实现对磁盘逻辑扇区或物理扇区的读写是很方便的,C语言中还有对应上述功能调用的函数:biosdiskabsreadabswrite等。但在WINDOWS操作系统下编写WIN32应用程序时却再也不能直接使用上述的中断调用或函数了。那么,在WINDOWS操作系统下能不能实现磁盘扇区的直接读写呢?如何实现磁盘扇区的读写呢?为了解决这些问题,笔者查阅了一些相关资料后发现,WINDOWS操作系统也提供了读写磁盘扇区的方法,只是在不同的版本中有着不同的方式和使用限制。最后,笔者编写了一个磁盘扇区直接读写类,不敢独专,特提供出来,希望能对大家有所帮助。
   
注:这里INT13表示INT 13H,其它类同。
二、一个读取软盘扇区的例子
    WINDOWS
操作系统对所有的存储设备实行了统一管理,而且为了安全起见,操作系统还不允许在WIN32应用程序(工作在Ring3级)中直接调用中断功能,如INT13INT21INT25INT26等。但它同时也提供了一些服务来弥补这种缺憾,在WIN95/98中,VWIN32服务就是其中一种。VWIN32服务是通过一个VXD来实现的,它提供了设备IO功能,通过它,使用API函数DeviceIoControl便可以实现WIN32应用程序和磁盘设备驱动程序间的通信,从而实现对磁盘的存取。VWIN32提供的服务是一系列的控制命令字,它们实现诸如DOS操作系统下的INT13INT25INT26INT21等功能调用。下面是它定义的一些控制命令字:
   VWIN32_DIOC_DOS_IOCTL     (1) 
实现INT21 功能
   VWIN32_DIOC_DOS_INT25     (2) 
实现INT25 功能
   VWIN32_DIOC_DOS_INT26     (3) 
实现INT26 功能  
   VWIN32_DIOC_DOS_INT13     (4) 
实现INT13 功能
   VWIN32_DIOC_DOS_DRIVEINFO (6) 
实现INT21 730x 功能  

如果要对磁盘进行读写,只要使用DeviceIoControl执行相应命令即可,下面的例子用来读取软盘的一个扇区(使用INT13):
   
第一步:打开VWIN32服务,HANDLE hDev=CreateFile("\\\\.\\VWIN32",0,0,0,0,FILE_FLAG_DELETE_ON_CLOSE,NULL)
   
第二步:填充中断所用到的相关寄存器。这里将寄存器放在一个结构中,结构定义如下(有关INT13使用的寄存器情况,请参阅相关资料):
      typedef struct INT13Regs{
  PVOID buffer;   // ebx
寄存器

        BYTE Drive;    // 磁盘号 dl
 BYTE Head;      //
磁头号 dh
 WORD EDX_High;  // edx
寄存器 

 BYTE Sector;    //起始扇区 cl
 BYTE Track;     //
磁道号   ch
 WORD ECX_High;  //ecx
寄存器

 BYTE Number;    //要读写的扇取数 al
 BYTE CMD;       //
命令:2--,3--,5--格式化 ah
 WORD EAX_High;  //eax
寄存器

 DWORD EDI;       // edi 寄存器
        DWORD ESI;       // esi
        DWORD EFLAG;     // flags
      }INT13_REGISTERS;

      unsigned char Buffer[512];//定义缓冲区,放置读取扇区数据
      INT13_REGISTERS reg={0};//
定义寄存器结构变量
 
      reg.buffer =(void *)Buffer;
      reg.Drive =0;//0-
软盘A  1-软盘B 0x80-硬盘c
      reg.Head =0;
      reg.Track=0;
      reg.Sector=1;
      reg.Number=1;
      reg.CMD=2;  //
读取
   
第三步:调用设备IO API函数DeviceIoControl执行4号命令(即VWIN32_DIOC_DOS_INT13), BOOL b_ret=DeviceIoControl(hDev,4,&reg,sizeof(INT13_REGISTERS),&reg,sizeof(INT13_REGISTERS),&lpRet,0);
如果其返回值不等于零,调用成功,进一步处理....否则调用失败。
   
第四步:关闭服务,CloseHandle(hDev);

三、限制或局限
   
上面是使用INT13读取软盘扇区的完整步骤,在WIN95/98下它是可以工作的。那么,是否将上面的寄存器结构中的Drive置为0x80就可以读取逻辑硬盘C盘的扇区了呢?回答是否定的。INT13用来存取硬盘的功能在WINDOWS中被忽略了。另外,INT25INT26虽然可以存取硬盘,但是它们不能工作在FAT32格式的硬盘上。下面的列表将详细列举与磁盘操作相关的中断调用的限制情况(不特殊说明,指的是在WIN95/98操作系统下):

     中断功能             限制及使用情况

      INT13             不可以读写硬盘,仅支持软盘
    INT25/INT26        
不可以读/FAT32硬盘,支持FAT12FAT16
   INT21(440DH-41H/61H)
不可用(文档资料中说支持FAT12FAT16FAT32,实际上没有实现)
   INT21(7305H)        
可以读写软盘、硬盘,支持FAT12FAT16FAT32,但要求WIN95OSR2及以后版本

    值得一提的是上表中的INT21--7305H功能是专门提供用来支持FAT32的,并且用来替换INT25/INT26,对应的控制命令字是6(即VWIN32_DIOC_DOS_DRIVEINFO),它和INT13INT25INT26等中断功能的一个显著区别是:它不使用寄存器来传递参数(INT21--440DH-41H/61H类同),而是使用一个称为DISKIO的结构,寄存器EBX用来保存指向该结构的地址。DISKIO的定义如下:
     typedef struct _DISKIO {
      DWORD  dwStartSector;   // 要读写的起始扇区号
      WORD   wSectors;        // 要读写的扇区数
      DWORD  dwBuffer;        // 用来保存读/写数据的缓冲区
     }DISKIO, * PDISKIO;

另外,在使用该功能时还需要特别设置一些寄存器,如ECX必须为-1,用ESI来表示读写。下面的例子是使用该功能来实现上面的例子功能,即读软盘A的一个扇区。首先定义一个新的寄存器结构供本例使用:
     typedef struct _DIOC_REGISTERS{
       DWORD EBX;
       DWORD EDX;
       DWORD ECX;
       DWORD EAX;
       DWORD EDI;
       DWORD ESI;
       DWORD Flags;
      }DIOC_REGISTERS; 

其实该结构和上面的INT13_REGISTERS是一样的,只不过INT13_REGISTERS将寄存器细分开了,可读性更强些。本例从步骤上说和上面的例子相同,只有寄存器设置一步在内容上有差异。
   
第一步:打开VWIN32服务。
   
第二步:设置寄存器。
     DIOC_REGISTERS reg = {0};
     DISKIO         dio;
     unsigned char Buffer[512];
     //
设置参数结构
     dio.dwStartSector = 0;//
注意:和上例不同,不是1,从0开始编号
     dio.wSectors      = 1;
     dio.dwBuffer      = (DWORD)Buffer;
      //
设置寄存器
     reg.EAX = 0x7305;    //
功能上类似于INT25,绝对读
     reg.EBX = (DWORD)&dio;//
参数结构的地址
     reg.ECX = -1;//
必须是-1     
     reg.EDX = 1;  //
注意:和上例不同,驱动器编号变了,0--缺省 1--A2--B3--C 
     reg.ESI = 0;  //ESI
bit0表示读写,0--读、1--

    在写状态时SIbit1--bit12bit15必须是0bit13bit14bit15共同来表示所写数据的类型,具体见下表:
           15 14 13
类型描述
           0  0  0 
其它或不知道
           0  0  1  FAT
数据 
           0  1  0 
目录数据 
           0  1  1 
一般数据 
           1  x  x 
保留。bit15必须是

     第三步:调用APIBOOL b_ret=DeviceIoControl(hDev,6,&reg, sizeof(DIOC_REGISTERS),&reg,sizeof(DIOC_REGISTERS),&cb,0);
    
第四步:关闭服务。

可以发现,两种方法读到的数据完全一致。

四、WIN2000中的磁盘扇区读写
   
WINNTWIN2000中磁盘被看做一种标准设备,可以使用CreateFile象打开文件一样打开并存取。CreateFile支持两种方式的磁盘设备--逻辑磁盘(格式为"\\.\C:")和物理磁盘(格式为"\\.\PHYSICALDRIVEx",其中x为数字),例如打开A:盘进行读取操作,只要这样:
     HANDLE hDev=CreateFile("\\\\.\\A:",GENERIC_READ,FILE_SHARE_WRITE,0,OPEN_EXISTING,0,0);
如果得到的句柄有效,就可以使用ReadFile来读取了,
     ReadFile(hDev,Buffer,512,&dwRet,0);
读取结束要关闭该句柄,
     CloseHandle(hDev)

这比WIN95/98下的磁盘扇区读取方便多了。
   
另外,上面的例子是操作逻辑磁盘的,它包括软驱、硬盘分区等;物理磁盘指的是实际的硬盘,它不关心该硬盘被分成几个区,硬盘的编号是从0开始的,"\\.\PHYSICALDRIVE0"表示第一块硬盘,其它依此类推。大家可能马上会想起,利用这种机制可以对硬盘的分区表进行存取了。确实如此,此时便可以对硬盘的主引导扇区(独立存在的一个扇区,包含分区表信息,不同于磁盘分区的BOOT区)进行操作了。
     unsigned char Buffer[512]={0};
     HANDLE hDev=CreateFile("\\\\.\\PHYSICALDRIVE0",GENERIC_WRITE,FILE_SHARE_WRITE,0,OPEN_EXISTING,0,0);
     WriteFile(hDev,Buffer,512,&dwRet,0);
     CloseHandle(hDev);

危险!!!千万别这么做!!!

五、一个自适应的磁盘读写类
  
由上面的例子可以看出,不同的操作系统下对磁盘扇区的读写有不同的方式,为了能够在各类操作系统下能够使用统一的方法读写磁盘扇区,特设计了一个通用类。该类的设计思想如下:首先编写各类操作系统下的磁盘扇区存取函数,然后通过GetVersionEx来判断操作系统,进而选取对应的函数来实现磁盘扇区的读写。由上面的分析可知,WINDOWS操作系统对INT13的支持是最差的,所以在这里只使用INT25INT26INT21--7305等中断调用来实现。类的定义如下:
class CDiskInfo{
public:
 CDiskInfo();
 ~CDiskInfo();
private:
 HANDLE hDev;
        DWORD dwCurrentPlatform;
        void GetPlatform();  //
取得操作系统,并存入变量dwCurrentPlatform
   
 BOOL Win2000_AccessSectors(WORD CMD,BYTE bDrive,DWORD dwStartSector,WORD wSectors,LPBYTE lpSectBuff);//
用于WIN2000WINNT等操作系统,
 BOOL Int25_ReadSectors(BYTE bDrive,DWORD dwStartSector,WORD wSectors,LPBYTE lpSectBuff);
        BOOL Int26_WriteSectors(BYTE bDrive,DWORD dwStartSector,WORD wSectors,LPBYTE lpSectBuff);//
用于WIN95以前的操作系统
        BOOL Int21_AccessSectors(WORD CMD,BYTE bDrive,DWORD dwStartSector,WORD wSectors,LPBYTE lpSectBuff);//7305
功能实现,用于WIN95OSR2WIN98等操作系统

public:
  //
对外统一提供ReadWrite操作,类内部根据平台选用适合的函数调用
 BOOL ReadSectors(BYTE bDrive,DWORD dwStartSector,WORD wSectors,LPBYTE lpSectBuff);
 BOOL WriteSectors(BYTE bDrive,DWORD dwStartSector,WORD wSectors,LPBYTE lpSectBuff);
};

该类对外提供了两个接口,即ReadSectorsWriteSectors,其参数是一样的,分别是要读写的磁盘编号bDrive,要存取磁盘的开始扇区号dwStartSector,要读取的扇区数wSectors和读写扇区数据的缓冲区lpSectBuff。这里磁盘编号是从1开始的,即1代表A:2代表B:3代表C:,依此类推。扇区的编号从0开始。使用时也很简单,只要作如下声明即可:
        BYTE Buffer[1024];      
  CDiskInfo A;
        BOOL bRet=A.ReadSectors(1,0,2,Buffer);
   
详细情况见附带的类文件及测试程序。
六、补充说明
   
严格来说,在对磁盘进行读写时,应该遵循以下顺序:打开设备(WIN95/98下为VWIN32服务,WIN2000下为磁盘设备)、锁卷、验证卷的有效性、读/写、开锁卷、关闭设备。这里为了描述上的简洁,忽略了锁卷/开锁卷及验证有效性等操作。有兴趣的朋友可以自行添加。
   
另外,该类仅实现了逻辑驱动器的读写,要想实现诸如对物理硬盘的主引导扇区的读写,还需要其它技术,如thunk技术,即编写两个动态库,一个是WIN32动态库,一个是WIN16动态库(thunk技术只可以用动态库实现),其中WIN16动态库转到DPMI模式,调用INT13(或者扩展INT13)来实现物理磁盘扇区的读写。有关thunk技术请参阅相关文档资料。

在对硬盘分区表进行跟踪时,可以发现一个问题:对于一个4.3GB的硬盘,可以正确地找到它的所有逻辑分区,而对于一个10.3GB的硬盘,我们只能够找到近8GB范围内的逻辑分区。同样,在采用传统方式编程来获取硬盘参数时,如果硬盘是4.3GB的,能够得到正确结果,但如果硬盘是10.3GB或更大的,程序就好象成了“近视眼”,只能“看到”8.4GB的容量。这是为什么呢?

为了解决这个问题,首先应该了解硬盘的物理结构。我们可以把硬盘看成一个圆柱体,然后将圆柱体沿垂直于中心轴的方向切成多块薄圆片,对于一个圆片,它有上下两面,每面上都有多个同心圆,每个同心圆又可以分成数量相等的扇形区域,数据就存放在这些扇形区域中。我们把一个圆片不同的上下两面叫做“磁头”(磁面),标号从0开始,把这些同心圆叫做“柱面”(磁道),标号从0开始,把扇形区域叫“扇区”,标号从1开始。对于标准格式化的磁盘,每个扇区一共是512个字节,所以硬盘的容量可以这样计算:

容量=柱面数 * 磁头数 *(扇区数/柱面)* 512(字节/扇区)

其实对于所有标准格式化的磁盘,上面的公式都成立。例如对于软盘,共有2个磁头(磁面),每磁头80条磁道,每磁道18个扇区,所以容量就是:

2 * 80 * 18 * 512 = 1.44MB

了解了硬盘的物理结构后,再来简要谈谈有关ATA界面和INT 13界面。ATA界面是寄存器驱动式并行总线,传输数据时,BIOS先向ATA中特定寄存器写入数据的开始地址和长度,再把相应的读写等命令写入特定寄存器,完成相应操作;INT 13界面其实也是靠寄存器来驱动的,它先将所有的参数包括数据地址等设置好(赋值给寄存器),再调用INT 13中断完成操作。

对于ATA界面,寄存器定义如下:

                  柱面低位寄存器          8bit

                  柱面高位寄存器          8bit

                      扇区寄存器          8bit

                  设备磁头寄存器          4bit

如果采用传统的CHSCylinder Head Sector)寻址,其最大寻址容量是:

28+8*28-1* 24 * 512 = 65536*255*16*512=136.9GB

如果采用LBALogic Block Addressing)寻址,其最大寻址容量是:

2(8+8) * 28 * 24 * 512 = 65536*256*16*512=137.4GB

对于INT 13界面,寄存器定义如下:

                  柱面地位寄存器          8bit

             柱面高位/扇区寄存器          2bit/6bit

                      磁头寄存器          8bit

如果采用传统的CHSCylinder Head Sector)寻址,最大寻址容量是:

210 * (26-1) * 28 * 512 = 8.456GB

如果采用LBALogic Block Addressing)寻址,最大寻址容量是:

210 * 26 * 28 * 512 = 8.590GB

注意:两种寻址方式的计算公式之所以有差别,是因为CHS寻址方式的扇区首地址是从1开始的,而LBA寻址方式的扇区首地址是从0开始的。

至此,为何存在8.4GB容量限制的原因已经清楚了。概括起来,原因有两个方面:一方面,主板的BIOS程序太旧,无法支持大容量的硬盘,报告给操作系统的参数也就不具备可靠性,解决这一问题的方法就是通过刷新BIOS的方法升级BIOS;另一方面是因为INT 13的局限。为了解决这个软问题,以超越容量限制,人们又定义了新的扩展INT 13,它不再使用操作系统的寄存器传递硬盘参数,而是使用存储在操作系统内存中保存着64LBA地址的地址包。如果硬盘支持ATA,就把地址包低28位传给ATA界面,否则就先转换成CHS参数,再传给ATA界面。由此一来,采用扩展INT 13可以使最大寻址容量达到137GB左右。

现在开始研究如何在C语言中使用扩展INT 13来操作大容量硬盘。为了使说明更加简单,这里只例举读、写、获取硬盘参数三种操作的实现方法,其它操作如“测试硬盘扇区”等,请读者自己查阅有关资料。

对于扩展INT 13中断,参数如下:

中断号

功能

调用寄存器

返回寄存器

备注

INT 13

AH=41H

检测扩展中断功能是否安装

AH = 41h

BX = 55AAh

DL = 驱动器号

(80HFFH)

失败:AH=1

CF置位

成功:AH=版本号

CF=0 BX=AA55H

 

INT 13

AH=42H

磁盘扩展读操作

AH = 42H

DL = 驱动器号

DS:SI=指向LBA地址包的指针

失败:AH=错误号

CF置位

成功:AH=0

CF=0

地址包定义:

偏移   大小        描述

00H    字节  地址包大小

01H    字节 保留(为0

02H        传输包个数

04H    双字 指向数据指针

08H     4    起始地址

其中LBA=((柱面*磁头/柱面+磁头)*扇区/柱面)+扇区-1

INT 13

AH=43H

磁盘扩展写操作

AH=43H

AL=写标志

DL = 驱动器号

DS:SI=指向LBA地址包的指针

失败:AH=错误号

CF置位

成功:AH=0

CF=0

同上

INT 13

AH=48H

获取磁盘参数

AH=48H

DL=驱动器号

DS:SI=指向保存参数缓冲区的指针

失败:AH=错误号

CF置位

成功:AH=0

CF=0

参数缓冲区定义:

偏移 大小           描述

00H        缓冲区大小

02H        信息标志位

04H 双字     物理柱面数

08H 双字     物理磁头数

0CH 双字 物理每柱扇区数

10H 4       扇区总数

18H      每扇区字节数

 

有了上面的参数,我们就可以方便地编写程序,来操作大容量硬盘。这里举了几个例子来说明如何在C语言中编写程序实现操作。笔者用的C语言是Borland C++ 3.1的编译器。

[程序一] 检测扩展INT 13是否安装

#include <dos.h>

#include <stdio.h>

void main()

{

REGS regs;

SREGS sregs;

regs.h.ah=0x41;regs.x.bx=0x55aa; /*设置寄存器*/

regs.h.dl=0x80;

int86x(0x13,&regs,&regs,&sregs); /*调用中断*/

if (regs.x.bx= =0xaa55) /*对结果作判断*/

    printf ("Int 13 Extensions Installed!\n");

else

    printf ("Int 13 Extensions Not Installed!\n");

}

在使用扩展INT 13中断之前,最好用上面的程序判断一下,看BIOS是否支持扩展INT13,这样就可以保证你的程序正确无误。一般现在新的支持LBA模式的主板和Win98自带的DOS7操作系统是支持扩展INT 13的。

[程序二] 获得硬盘物理参数

#include <dos.h>

#include <math.h>

#include <stdio.h>

 

typedef unsigned char BYTE; /*定义字节数据类型名*/

typedef unsigned short WORD; /*定义字数据类型名*/

typedef unsigned long DWORD; /*定义双字数据类型名*/

 

void main ()

{

    union REGS regs;

    struct SREGS sregs;

    struct {

        WORD size; /*地址包大小*/

        WORD inforflags; /*信息标志*/

        DWORD cylns; /*物理柱面数*/

        DWORD heads; /*物理磁头数*/

        DWORD sects; /*物理每柱扇区数*/

        DWORD tslow; /*扇区总数低八位*/

        DWORD tshi; /*扇区总数高八位*/

        WORD  bps; /*每扇区字节数*/

    } package; /*LBA地址包定义*/

    regs.h.ah=0x48;

    regs.h.dl=0x80;

    sregs.ds=FP_SEG(&package);/*获得package的段地址*/

    regs.x.si=FP_OFF(&package);/*获得package的偏移量*/

    int86x(0x13,&regs,&regs,&sregs);/*调用中断*/

    printf ("Total Cylinders in this disk is : %ld\n",package.cylns);/*输出信息*/

    printf ("Total Heads     in this disk is : %ld\n",package.heads);

    printf ("Total Sect/Cyln in this disk is : %ld\n",package.sects);

    printf ("Low  bit of Total Sectors    is : %ld\n",package.tslow);

    printf ("High bit of Total Sectors    is : %ld\n",package.tshi);

    printf ("Bytes per Sector of the disk is : %d\n",package.bps);

    printf ("Total Space amount by l-bit  is : %.1f GB\n",(double)(package.tslow*512.0/pow(10,9)));

}

对于上面的程序,读者可以把它和上表的参数比较,这样可以更加深刻地了解表一的各个参数的含义,也可以更好地读懂这段程序。读者也可以自己把上面的程序转化成函数,在必要的时候直接调用就可以了。

[程序三 磁盘扩展读写操作]

#include <dos.h>

#include <stdio.h>

#define EXT_READ 0x42

#define EXT_WRITE 0x43

#define HDD 0x80

unsigned long CYLINDERS, HEADS, SECTORS;

 

typedef unsigned char BYTE;

typedef unsigned short WORD;

typedef unsigned long DWORD;

 

int get_parameter (int drive) /*读取硬盘参数以便于LBA数据的计算*/

{

      union REGS regs;

      struct SREGS sregs;

      struct {

           WORD size;

           WORD inforflags;

           DWORD cylns;

           DWORD heads;

           DWORD sects;

           DWORD tslow;

           DWORD tshi;

           WORD  bps;

      } package;

      regs.h.ah=0x48;

      regs.h.dl=drive;

      sregs.ds=FP_SEG(&package);

      regs.x.si=FP_OFF(&package);

      int86x(0x13,&regs,&regs,&sregs);

      CYLINDERS = package.cylns;/*将得到的数据赋值给全局变量*/

      HEADS = package.heads;

      SECTORS = package.sects;

      if (regs.h.ah) return (regs.h.ah);

      else return 0;

}

 

int iodisk(unsigned char drv, unsigned char cmd, unsigned char *buffer,

unsigned long startlow, unsigned long starthi, unsigned short copyblk)

{

      union REGS regs;

      struct SREGS sregs;

      struct {

           unsigned char len; /*package的大小*/

           unsigned char res; /*保留字节*/

           unsigned short nob; /*package的个数*/

           unsigned short bufoff; /*数据缓冲区偏移量*/

           unsigned short bufseg;/*数据缓冲区段地址*/

           unsigned long  slow; /*扇区起始地址低位*/

           unsigned long  shi; /*扇区起始地址高位*/

           } package;

 

           package.len=sizeof(package);

           package.res=0;

           package.nob=copyblk;

           package.bufoff=FP_OFF(buffer);

           package.bufseg=FP_SEG(buffer);

           package.slow=startlow;

           package.shi=starthi;

           regs.h.ah=cmd;

           regs.h.dl=drv;

           regs.h.al=0;

           sregs.ds=FP_SEG(&package);

           regs.x.si=FP_OFF(&package);

           int86x(0x13,&regs,&regs,&sregs);

           if (regs.h.ah) return (regs.h.ah);

           else return (0);

}

 

int  IoSectorEx (unsigned char drv, unsigned char cmd, unsigned long cylinder,unsigned long head, unsigned long sector, unsigned long copys, unsigned char *buffer)

{

      int err;

      unsigned long lba;

      lba = ((cylinder * HEADS + head) * SECTORS ) + sector - 1; /*CHS地址转化成LBA的扇区地址*/

      err = iodisk (drv, cmd, buffer, lba, 0 , copys);/*调用函数读写扇区*/

      return err;

}

void main ()

{

unsigned char buffer[512]; /*定义扇区数据区*/

get_parameter(HDD);/*调用函数获得硬盘参数*/

IoSectorEx (HDD, EXT_READ, 0, 0, 1, 1, buffer); /*调用函数读取0柱面0磁头1扇区的内容*/

printf ("%X\n",buffer[511]);/*输出所读扇区的最后一个字节*/

}

上面的[程序三]中定义了三个被调用函数,get_parameter()函数是通过扩展INT 13来读取硬盘物理参数的函数,设置这个函数主要是为了更加方便地计算LBA扇区地址的值;iodisk()是最通用的扇区读写操作函数,它可以利用LBA扇区的地址直接操作扇区;而IoSectorEx()函数则是利用提供CHS参数的方式来操作扇区的,中间是一个转化过程。

如果你需要的话,你完全可以在你的程序中使用上面的函数。对于扩展INT 13的其它磁盘操作功能,在此就不一一叙述了,其实你只要认真查阅有关资料, 你完全可以写一个功能更强大的函数来实现你所需要的磁盘操作。

 

参考文献

《细说IDE硬盘的容量限制》(http://www.pchome.net/,dwell