从地址栏输入到页面显示 -深入探究web运行机制(半草稿持续更新中)

服务器特点:的相对性

 

dns解析,tcp/ip网络,必须有ip地址才能数据传输

http封包

osi七层协议

 

定义

主机:连接在网络上,有通过网络进行数据收发的设备。对于web开发而言,主要关心指web服务器,数据库服务器,普通用户计算机,除此还有dns解析服务器,代理服务器,等其它类型。

互联网上主机,要想进行数据传输,就必需有传输目的主机的ip地址。如,普通用户计算机,对于web开发者而言,主要关心的是其浏览器,有时也称web客户端(在与web服务器相对而言时,也简称客户端)。本文在不引起误解的情况下,会混称以下概念:web浏览器,客户端,用户主机。

对于web开发者,在不引起误解时web服务器也经常简称服务器。

 

服务器(端)与客户端,及其特点

服务器(端)与客户端,是一对相对的概念。服务器是可以向其它主机提供一种网络服务的网络设备。繁体中文(台湾)中称伺服器,这个名称更能体现服务器的特点,即“伺”与“服”,即等待其它主机提出服务请求,并向其它主机提供响应。它永远都是被动的向请求方(客户端)提供响应,而不会主动提供服务。服务器提供的响应,一般是一个数据包,通过一定的网络协议把响应数据包发给请求方。(网络协议可以简单的理解为数据传输的格式或规则)。以数据传递的走向来看,客户端是主动方,它主动向服务器提出请求。而服务器则是被动方,只有在接受到客户端的请求时,才向客户端提供相应的响应数据。

服务器可以按所提供服务划分为多种类型,如数据库服务器,域名服务器,web服务器,缓存服务器等多种。

(注:以上从功能的角度对服务器与客户端的进行的解释,非物理实体角度的解释。)

从软件的角度看,一台物理上的服务器就是安装了某种网络服务软件的计算机。

一般情况下,一台物理服务器都提供单一的网络服务,以获得最佳的性能。当然在服务器主机性能有富余时、或降低成本时,也会安装把多个服务器软件安装在同一台物理服务器主机上。

 

Web服务器与浏览器

web服务器是提供web服务的服务器;而主动向web服务器发送请求的主机,即是web客户端。一般情况下,服务器都提供单一的网络服务,以获得最佳的性能,web服务器也是这样;而物理上的 web客户端,绝大多数情况就是我们平时使用的计算机,上面会安装多种软件,包括浏览器、下载工具、输入法程序、即时通讯工具等,浏览器只是其中之一,不过对于web开发者,我们主要关心的只是浏览器,所以在不死混淆时,我们会笼统地把用户端计算机、web客户端与浏览器等价看待。

 

 

Web服务器的主要功能解析

web服务器是安装了web服务软件的一台主机。上面最主要的软件就是web服务器软件,它接受http协议的请求,并返回相应的http响应数据给请求者。 目前最常用的是Apache HTTP Server (简称apache, httpd) internet information services(简称iis)ginxlighttpd

它主要有以下功能模块:http请求数据包分析器,响应数据生成器,响应数据包封装器。

http请求数据包分析器,是分析浏览器发送来的请求数据包,从中拆解出请求字段,并传递给响应数据生成器。拆解出的字段至少包括请求的主机域名,请求端口号,请求文件路径,请求字符串(GET请求字串)等等。

响应数据生成器,主要是根据请求中的字段,生成相应的响应数据。1. 对服务器上静态页面的请求,就直接把请求文件路径字段对应的文件读取出来,传递给响应数据包封装器。一般来说图片、静态html页面,css文件,javascript文件都是这样。 2. 而动态页面的请求,则需要调用安装在web服务器程序里的动态程序引擎,执行相应的web程序,才得到响应数据。web开发者开发程序主要打交道的主要就是这里所说的“动态程序引擎”,web开发者编写的程序正是由它执行的。每种动态程序,如php, jsp, python, asp(.net)等,都有其相应的引擎;其中asp的引擎比较特殊,它内置在iis里,以一个.dll的形式存在,与iis紧密联系。

响应数据包封装器,把动态或静态生成的响应数据,附带上必要的http头字段,构造成http响应数据包。

最终生成的响应包,递交给操作系统,由操作系统发送给客户端。

 

浏览器的主机功能

浏览器至少包含以下重要的功能模块:域名解析客户端,http请求封装器,http响应接收器,排版引擎,客户端脚本解析引擎,页面缓存系统,cookie处理模块,

域名解析客户端,互联网上的主机需要与任何主机进行数据传输,都需要知道对方的ip地址,使用网址访问服务器时,就必须查询到网址域名的ip地址,该项工作就是由域名解析客户端进行。工作过程大致如下:域名解析客户端向dns服务器(操作系统的网络配置里的dns配置)发起域名解析请求,请求包中包括待解析的域名,dns服务器通过一系列查询,得到域名对应的ip地址,返回给域名解析客户端。

http请求封装器根据请求域名的ip地址,请求域名、端口、文件路径、GET字串、POST数据、COOKIE、浏览器描述信息(user-agent, 是否接收zip压缩,保持连接等)等信息,封装成http请求数据包,提交给操作系统,发送给目标服务器。

http响应接收器,接收服务器的响应数据包。

排版引擎,根据html文档,css样式把页面内容显示在浏览器视图区域。

客户端脚本解析引擎,处理客户端动态脚本,主要是javascript,在IE系列浏览器里还有vbscript.

页面缓存系统,打开网页时,如果页面中的一幅图片是刚刚请求过的图片,就不必重新向服务器请求该图片,而是使用之前请求返回的图片文件,这样可以加快整个网页打开速度,还节约带宽。这就是页面缓存系统的功能。

cookie处理模块,cookie机制在浏览器上的实现。

特殊的浏览器:搜索引擎的蜘蛛,即网络爬虫,自动获取网页html代码,用于搜索引擎的索引系统。

 

浏览器几个特性

连接保持,缓存,排版引擎差异,

连接保持,浏览器与服务器进行数据传递(http请求与响应都是数据传递)前,需要建立数据连接,一般是tcp连接,而这一过程通常是个比较耗时的过程。为了减少整个数据传递的总耗时、从而加快页面打开速度,通常会开启“连接保持”的功能,即服务器与浏览器在首次建立tcp连接、并完成一个http数据传递后,并不把这个tcp连接断开,而是暂时保持一段时间(一般在2分钟左右),如果这一段时间内还需要进行数据传输,则直接使用这个tcp连接;这样就节省了建立连接的时间花费。

缓存,已经在上面讲述过(打开网页时,如果页面中的一幅图片是刚刚请求过的图片,就不必重新向服务器请求该图片,而是使用之前请求返回的图片文件,这样可以加快整个网页打开速度,还节约带宽)。缓存在动态文件上有可能造成一些问题,如有一个动态user.php,作用为显示当前用户的状态;当浏览器第一次打开该文件时,用户状态是未登录的,而用户登录后,该页面将显示用户信息;但如果浏览器使用缓存,则还显示未登录的信息。所以就需要在响应数据包的http头中加入一个字段: Cache-Control,标识是缓存有效期 ,用于告诉浏览器当前页面使用缓存方式。如果在http响应头里没有这个字段,浏览器就按其自身默认设置应用缓存,而不同的浏览器默认设置并不相同。正是因此,有时页面刚刚修改过,但某些浏览器显示的还是老内容,点浏览器的“刷新”也不行,只能关闭浏览器窗口,并清空缓存。强制浏览器不使用缓存还可以在每次请求的地址后面加入随机的GET字串,如user.php?a=25seg

排版引擎差异,同样的html,在不同浏览器里显示效果并不相同,如IEfirefoxchrome等浏览器之间,甚至IE的不同版本间都有很大差异。

 

 

 

web服务器是安装了web服务软件的一台主机,web开发者的计算机中一般都会安装上相应的web服务器端软件,常称为web server,如apache, iis 等,这台计算机也就是一台web服务器;同时,该主机上也有浏览器,因此,这台物理上的主机,既上服务器端也是客户端。

 

 

实例:简单模拟一个常规静态页面的打开。

服务器www.testsite.net上有这样一个简单网页(如图),其中包含一个html文档及一张引用图片

index.html

<html>

<head>测试网页</head>

<body>

<p>第一行文字</p>

<p>the 2nd line</p>

<img src=”img.jpg” alt=”just a test image” height=”50” width=”40” />

<p>本文档到此就结束了</p>

</body>

</html>

 

img.jpg

 

 

 

 

就这两个文件,放置在站点www.testsite.netweb根目录下,这样使用浏览器访问www.testsite.net/index.html就可以打开这个页面。这个过程至少包括以下几个重要的步骤:

  1. 域名解析。浏览器根据用户输入的网址,从中获取请求的主机名,www.testsite.net,通过域名解析获得该域名对应的ip地址192.168.10.166.
  1. 请求数据库的封装。浏览器根据用户请求,把多个信息封装成为http请求包,这个请求包里一般都包含以下几部分,请求的主机名,请求服务器上的文件路径,请求字符串,cookie,客户端(浏览器)的IP地址等(除此还有,客户端user-agent字符串,是否接受zip压缩等多个字段)
  2. 浏览器(客户端)提交给所在计算机操作系统,由操作系统把封装好的http请求数据包提交给目标主机,这里就是www.testsite.net所在的192.168.10.166. 提交完成后,浏览器一般会在状态栏里显示为“正在等待服务器响应”,并显示进度条表示等待服务器返回响应数据的过程。。
  3. 服务器接收到客户端的请求后,由操作系统把这个请求递交给web服务器(一般是apache, iis 等)。
  4. web服务器对这个请求包进行分析,然后交由www.testsite.net站点进行处理。
  5. web服务器到www.testsite.netweb根目录下寻找index.html,找到该文件后,把此文件的内容读取,然后按照http响应数据包的格式进行封装。其中包括http响应数据头,响应数据内容主体两大部分。响应头里包含了响应状态码,响应数据类型(这次为text/html),要求客户端设置cookie的信息等。
  6. 客户端从服务器响应数据包里,解析出响应数据的类型,为text/html,浏览器于是按照text/html的格式分析其内容,边分析边把页面内容按html格式显示在浏览器的页面里。(不同的排版引擎之间对html的解析稍有不同,所以同样的网页在不同浏览器下看到效果可能稍有不同)
  7. 浏览器解析index.html文件时,发现其中还有对图片img.jpg的引用,于是再发送一次http请求给服务器www.testsite.net,用于请求img.jpg文件。这一过程,如果发生在浏览器全部获取http响应数据之前,就可以解析出还需要其它文档,这里就开始对下一个页面元素的请求;而不是等浏览器还没有读取到完整的html文档后,这样无疑就加快了浏览器打开页面的速度。如果打开一个比较庞大的html文档时,这个影响就很可观了。
  8. 服务器对请求图片的http请求作出响应。这次响应的数据类型是image/jpeg.
  9. 浏览器得到服务器的响应,解析出图片,并把图片在index.html页面上绘制出来。

 

直观查看http数据包的传递,及http

工具:firefox(需要插件live http headers, firebug)

一般我们通过浏览器看到的内容,都是http数据主体的内容,而http头,我们一般是看不到的。因为浏览器向我们隐藏了这些细节,这些细节对于一般使用者是没有意义的。但对于web开发人员,还是十分有必要理解这些的。所以我们就需要借助一些工具达成目的。

可以查看http头的工具很多,包括本机网络数据抓包工具,浏览器插件等都是可行的,以浏览器插件最为简便实用。

 

 

域名与web服务器 /域名解析,域名绑定

域名解析,有时也会说dns解析。互联网上的数据传输依赖于IP地址,但日常使用的却是英文域名,所以首先要把域名解析为IP地址,这项工作是dns服务器进行。而前面说过,服务器的工作都是被动的,这个域名解析过程中的主动方就是需要把域名解析为ip地址的程序,在B/S架构里,就是浏览器(B,browser)

平时我们在浏览器地址栏里输入网站域名,就能打开其网页,看似域名与web服务器是同一套服务,但事实上完全不是这样。域名服务与web服务两人者是隔离的,要把这两者协同工作,我们至少需要完成两项工作:域名解析到web主机ip地址、web主机上设定该域名的站点。

以实际中的例子讲解。举个例子,我们注册了一个域名path8.net,注意,注册域名时,前面没有wwwwww其实是我们注册域下的一个子域名,子域名我们可以任意开设。不过习惯上,在web上,大家都把www的子域名作为主要域名;甚至有些人错误的认为域名前面都要带www,笔者就多次见过这样的情形。笔者参与维护的一个网站为客户开设了子域名服务,于是就有了zhangsan.xxx.yy, lisi.xxx.yy, wangwu.xx.yy等等这样的一系列子域名,但很多用户访问网站时,会输入www.zhangsan.xxx.yy这样的域名,就是在子域名前面再加上www. ,这当然是无效的子域名。然而我们无法给每个访客说明子域名与www的关系(我们无法控制用户的行为,只能适应用户,或者有意引导用户),所以使用了一个巧妙的方法,对于www.subdomain.xxx.yy的请求,在web程序里直接把前面的www.去掉,这样用户即使

多输www.也可以打开正确的页面。扯远了。

每一个子域名都是需要单独设置解析的,域名解析有多种形式,最常见的就是A记录,也就是直接把一个域名解析到一个ip地址上。除了A记录外,还有mx记录,cnametxt记录等。cname记录是把域名解析到另外一个域名上,也称为“别名”,这点非常有用。假设我有100个域名,我想把这100个域名全部指向到一台主机上,但这台主机的ip地址会定期改变。,这样的话,岂不是ip地址一变化,我就要设置100条域名解析信息吗?!如果有更多域名,可想而知是什么情况。借助cname就可以轻松实现这一点,我可以把其中100个域名“别名”。到一个域名(如alias.xxx.yy)上,然后再为这个域名alias.xxx.yy设置一条A记录到ip地址上;以后只需要修改alias.xxx.yyA记录就可以了。cname还经常在cdn加速服务器设置。mx记录是用于邮件系统的,txt一般用于防止“谎称域名而发送防垃圾邮件”的授权验证。如果你看过域名管理的控制面板,应该见过这些。

上面所说的A记录,mx记录,cnametxt记录,每设置一条都是一条解析。如果为path8.net设置 5A记录linux.path8.net, php.path8.net, mysql.path8.net, path8.net, 另有2cname记录,一条mx记录,那我们就一共需要设置5+2+1=8条解析记录。事实上,我们的域名注册(服务)商,一般不会允许我们设置无限多条解析记录的,限制通常是1020条,如果需要更多条解析,就需要另外购买了。当然我们可以自建dns服务器,用来解析我们的域名,而不再使用域名商的解析服务,这样我们就可以设置任意多条域名了。不过自建服务器费用昂贵,通常没有这个必要。

有人问了,如果我有100万个用户的博客站点,我想为他们每个人开设一个子域名,可以通过cname别名到一个域名上,对这个域名再设置A记录。这种情况下,我就需要100万还多的解析,不自建服务器怎么办呢? 我们的回答是,照样不需要,我们可以设置泛域名解析。泛域名解析就是把所有没有设置的子域名全部都指向一个ip地址S上。如上对path8.net 设置了4个子域名,再设置一个泛域名指向ip地址P上。这样linux.path8.net的解析当然还是ip地址S,而对windows.path8.net, mssql.path8.net, 将全部解析到ip地址P(如果没有设置泛域名解析,这几个子域名将全部解析失败)。当然,泛域名解析需要域名商的支持(多数域名商是支持的)。

对域名解析扯了很多,也不过是简单介绍,对站长入门有一参考价值,但于web开发人员来说,可能显得多余。不过,个人感觉,对web系统运行的背后的知识不了解,知识结构总是显得苍白无力。个人当初学习web开发时,就对这些东西很感好奇,但不知道去哪里学习(个人完全是靠自学计算机方面的东西,除思科ccna的培训之外,可以臭美一下~~)。

 

下面还要对web程序在服务器上的设置做一下简单介绍。这也是web开发的入门级人员有必要了解的。

 

web服务器,当下最常用的是Apache HTTP Server (简称apache, httpd) internet information services(简称iis)。这里不对它们的配置方法介绍,主要是讲web 服务器共同之处。

最初一台web服务器只能设置一个web站点(对应一个域名),也就是说,如果我要开设一个web站点,就要一台web服务器;每增加一个域名,就要新安装一台物理上的服务器,即使是一个访问量很小的站点也是一样——这早已是很古老的事情了——这里面就有一个无法发挥服务器性能及浪费的问题了。

聪明的人类发现问题后,总是会想解决方案的。虚拟主机的概念就此应运而生。它就是在一台物理的服务器、一套web服务器软件、一个ip地址、一个对外服务的端口上,同时运行多个web站点。在同一台服务器上运行多个web站点,从软件开发角度上讲,就是要用一种机制,把这多个站点区分开来。如果你对网络基础有所了解,这一点类似于:在同一个ip地址、一种协议上,可以使用“端口”对外建立多个数据连接。区分多个web站点的方式,就是“虚拟主机技术”,其实原理非常简单的,就是找区分标准。一台服务器同时运行多个站点,那么这些站点的域名是不同的,我们就可以根据这一点做文章了:如果我们开发一套web服务器软件,它根据客户端(浏览器)发来的http请求数据包里的“主机名”——就是请求网站的域名——的不同,向请求方返回相应站点的页面,这样不就可以解决问题了!

事实上虚拟主机就是这个原理,主流的web服务器软件都有这样功能,不需要我们做相应开发了。

早期的web服务都是静态文件,当时的web服务器主要就是为了共享一些文档而用的;动态web程序是后来才的有。web服务器上每个web站点都对应着一个目录,作用就是通过http协议共享这个目录里的文件。

那我们是否可以在web服务器上建立多个站点(每个站点对应一个域名),对多这个站点全部设置为同一个目录呢?答案是肯定的。但这并不是最简便的方式,可以只建立一个站点,而对这个站点绑定多个域名。

了解了以上这些,你可能已经想象到了:web服务器的多站点功能,其实是一个web服务器软件对http请求的分发,把不同的http请求分发给相应的web站点进行处理;而web站点都是对应着服务器上的一个目录。

上面我们对虚拟主机的讨论,是建立在一个假定的基础上:web服务器只有一个ip地址。那如果我们的web服务器有多个ip地址,是否是也可以就此区分多个站点呢?答案是肯定的。这也是虚拟主机的一种实现机制(同样是对http请求的分发机制),不过目前ipv4的地址很缺乏,都“告急”好些年了,尤其在中国,几乎大家都是在“凑合”着用,大多数公司的办公室都是用对外一个ip地址,通过nat在内部使用私网ip地址,甚至听说有些地方adsl拨号获得的ip地址都是私网ip地址了。又扯远了。总之,就是说ip地址是稀缺资源,“基于ip地址划的虚拟主机”代价太高,很少这么用。而“基于主机名划分的虚拟主机”是常用的,几乎所有的虚拟主机提供商都是采用这种技术。国外某些虚拟主机提供商还提供独立ip地址,他们是把这两人种虚拟主机技术结合实施的。

还说“基于主机名划分的虚拟主机”,我们要在一台服务器上设置多个虚拟主机,就要建立多个站点,给它们分别设置不同的域名(域名绑定)。

再回到我们前面讨论过的“为100万个用户分别设置子域名”的问题。这100万个子域名,是否都要逐个设置呢?先不说答案,我们自己从底层原理上分析一下先。

http请求分发的观点分析:web服务器接收到http请求后,从请求包里读出出所请求的域名,如果发现它是该服务器上绑定的域名,就把该请求分发给相应的web站点进行处理;但如果不在已绑定域名之列,那怎么办呢? 对这样的请求,要么正确响应,要么返回给请求浏览器一个错误消息。返回错误,很简单,就是”请求错误,域名未知”这样的错误消息,不知你是否注意过打开某些网址后显示Bad Request (Invalid Hostname) 样的消息时,这就是iis对这要错误返回的消息文本。而要向请求方发回正确的影响该怎么办呢,总得把这个请求交给一个站点处理才可以吧?是这样的,这个对非配置域名也能处理的站点,就是“默认站点”。

分析到这里,你是否已经想到这怎么解决对100万用户分别设置子域名的解决方案了,不错,就是借助web服务器软件的默认站点功能。

是否发现了,这里有一个问题,那就是一台服务器上只能配置一个默认站点,也就是这要的功能,在从虚拟主机商购买的虚拟主机上,是难于实现的(除非你在该虚拟主机上有独立ip地址,再加上虚拟主机商提允许你“配置该ip地址的默认站点”的功能)。想通过配置默认站点的方式给100万会员分别分配子域名,只能在独立主机上实现。

现在假定已经有独立服务器并配置好了默认站点,它被设置到了一个目录上,我们在web开发者的角度,分析一下怎么对不同的子域名返回不同的页面。

这个问题,其实还是请求分配的问题。现在的情况是:默认站点对应的是一个特定的目录,也就是这些所有子域名,都是靠同样的处理程序实现的。不同的用户有同的用户id号。如果有这个id号,我们就可以根据这个id号,向浏览器输出相应的页面(一般是从数据库里读取出来相应的数据,并形成动态页面)。就是要根据用户id号来作为区分标准,显示不同的页面,现在我们没有这个id号,但我们可以根据子域名的最前一段进行区分。也就是,我们可以根据用户的用户名(或是让用户自己设定的一个子域名)分配子域名给每个用户,而在服务器端的处理程序上,根据用户子域名,从数据库里查询出该用户的id号,接下来,就是常规的程序了,这里就不需多讲了。

 

这里有一个问题,如果一台web服务器上配置了10个域名,浏览器向这台服务器发送一个http请求包,这个请求包里如果包含的是这10个域名其中之一,是没有问题的,可以正确得到结果。但如果请求的是这10个域名之外的域名,返回什么结果呢?

 

综合讲一下域名及web服务器。域名服务器与web服务器是相互隔离的,我们要建网站,就至少要做两个工作:注册域名及对域名的设置,购置web服务器(可以是实际的服务器也可以是虚拟主机)及对web服务器的设置。详细过程大致如下:

  1. 注册域名。(获得了域名及对域名解析到ip地址的管理权)
  2. 购买web服务器。(获得web服务器ip地址及web服务器主机名——也就是域名——的设置权)
  3. 把域名解析到web服务器的ip地址上。一般使用A记录解析。
  4. 把域名“绑定”到web服务器上。使用apache独立主机的话修改apache配置文件,iis的话在iis管理器里,从虚拟主机商购买的虚拟主机的话在的管理后台里。

后两步的设置工作,是缺一不可的。

可以这样讲,因为 域名服务器与web服务器是相互隔离的,所以要建站,就要对域名及web服务器做双向指向:要让域名知道web服务器的存在(域名解析),还要让主机知道域名的存在(域名绑定)

(未完待续)

------------------------- 以下是计划写入的主题条目,实际可能会有适当增减 -------------------------------

服务器特性:相对性;从来都是被动的,从来不主动向任何对象发送数据,而是只在收到请求后才响应;“伺服器”的名称更体现这特点。

dns解析,tcp/ip网络的数据库传输必需得到对方的ip地址才能进行。

http封包

osi七层及tcp/ip

网络数据传输(局域网广域网交换机路由器)简述

web服务器

防火墙

http请求包的拆解,http请求包的分配(被web服务器分配到相应的站点,默认站点)

HTTP变量赋值,rewrite

web语言脚本解析引擎

http响应包的封装,脚本引擎生成部分,再由web服务器补充

http响应状态码

http无状态性及patch方案

cookie,session,不同语言的session机制,不互通

浏览器解析html文档,载入内嵌媒体(图片,js,css等),cookie

http连接保持

客户端脚本javascript执行,js事件触发,ajax

排版引擎

web server的多站点原理,

程序并发性,冲突:

正在进行的一个操作,受到了另一个操作运行的影响,就有可以造成并发访问问题。

(正在进行的一个操作的结果,影响了另一个操作进行的条件)

在一个操作操作完成所需要的时间段内,有另外一个操作也对该资源进行可能冲突访问(一般是“写”),就很有可能造成并发冲突

 

硬盘分区表的详细结构及自动备份和恢复

本篇题目的发起帖子来自于两个论坛,原帖链接分别如下: http://www.51ct.net/bbs/read.php?tid=500&fpage=1:推荐访问 http://www.cn-dos.net/dosbbs/dispbbs.asp?boardid=9&id=15000

需要参考的两篇资料文章的原链接及内容如下:

===================================================================== 1、硬盘分区结构及windows文件系统结构 =====================================================================

===================================================================== 2、扩展int13h调用详解(点击访问原文)

扩展int13h调用详解 作者:不详·发布日期:2002-12-17·阅读次数:18301

--------------------------------------------------------------------------------

转载自:BBS 水木清华站 (Sat Nov 20 16:12:06 1999)

第一部分 简 介

1,1

一. 硬盘结构简介

1. 硬盘参数释疑

到目前为止, 人们常说的硬盘参数还是古老的 CHS (Cylinder/ Head/Sector)参数. 那么为什么要使用这些参数, 它们的意义是什么? 它们的取值范围是什么? 很久以前, 硬盘的容量还非常小的时候, 人们采用与软盘类似的结 构生产硬盘. 也就是硬盘盘片的每一条磁道都具有相同的扇区数. 由此 产生了所谓的3D参数 (Disk Geometry). 既磁头数(Heads), 柱面数 (Cylinders), 扇区数(Sectors),以及相应的寻址方式.

其中:

磁头数(Heads) 表示硬盘总共有几个磁头,也就是有几面盘片, 最大 为 255 (用 8 个二进制位存储); 柱面数(Cylinders) 表示硬盘每一面盘片上有几条磁道, 最大为 1023 (用 10 个二进制位存储); 扇区数(Sectors) 表示每一条磁道上有几个扇区, 最大为 63 (用 6 个二进制位存储). 每个扇区一般是 512个字节, 理论上讲这不是必须的, 但好象没有取 别的值的.

所以磁盘最大容量为:

255 * 1023 * 63 * 512 / 1048576 = 8024 GB ( 1M = 1048576 Bytes ) 或硬盘厂商常用的单位: 255 * 1023 * 63 * 512 / 1000000 = 8414 GB ( 1M = 1000000 Bytes )

在 CHS 寻址方式中, 磁头, 柱面, 扇区的取值范围分别为 0 到 Heads - 1, 0 到 Cylinders - 1, 1 到 Sectors (注意是从 1 开始).

2. 基本 Int 13H 调用简介

BIOS Int 13H 调用是 BIOS 提供的磁盘基本输入输出中断调用, 它可以 完成磁盘(包括硬盘和软盘)的复位, 读写, 校验, 定位, 诊断, 格式化等功能. 它使用的就是 CHS 寻址方式, 因此最大识能访问 8 GB 左右的硬盘 ( 本文中 如不作特殊说明, 均以 1M = 1048576 字节为单位).

3. 现代硬盘结构简介

在老式硬盘中, 由于每个磁道的扇区数相等, 所以外道的记录密度要远低 于内道, 因此会浪费很多磁盘空间 (与软盘一样). 为了解决这一问题, 进一 步提高硬盘容量, 人们改用等密度结构生产硬盘. 也就是说, 外圈磁道的扇区 比内圈磁道多. 采用这种结构后, 硬盘不再具有实际的3D参数, 寻址方式也改 为线性寻址, 即以扇区为单位进行寻址. 为了与使用3D寻址的老软件兼容 (如使用BIOS Int13H接口的软件), 在硬 盘控制器内部安装了一个地址翻译器, 由它负责将老式3D参数翻译成新的线性 参数. 这也是为什么现在硬盘的3D参数可以有多种选择的原因 (不同的工作模 式, 对应不同的3D参数, 如 LBA, LARGE, NORMAL).

4. 扩展 Int 13H 简介

虽然现代硬盘都已经采用了线性寻址, 但是由于基本 Int 13H 的制约, 使 用 BIOS Int 13H 接口的程序, 如 DOS 等还只能访问 8 G 以内的硬盘空间. 为了打破这一限制, Microsoft 等几家公司制定了扩展 Int 13H 标准 (Extended Int13H), 采用线性寻址方式存取硬盘, 所以突破了 8 G 的限制, 而且还加入了对可拆卸介质 (如活动硬盘) 的支持.

二. Boot Sector 结构简介

1. Boot Sector 的组成

Boot Sector 也就是硬盘的第一个扇区, 它由 MBR (Master Boot Record), DPT (Disk Partition Table) 和 Boot Record ID 三部分组成.

MBR 又称作主引导记录占用 Boot Sector 的前 446 个字节 ( 0 to 0x1BD ), 存放系统主引导程序 (它负责从活动分区中装载并运行系统引导程序). DPT 即主分区表占用 64 个字节 (0x1BE to 0x1FD), 记录了磁盘的基本分区 信息. 主分区表分为四个分区项, 每项 16 字节, 分别记录了每个主分区的信息 (因此最多可以有四个主分区). Boot Record ID 即引导区标记占用两个字节 (0x1FE and 0x1FF), 对于合法 引导区, 它等于 0xAA55, 这是判别引导区是否合法的标志. Boot Sector 的具体结构如下图所示 (参见 NightOwl 大侠的文章):

0000 |------------------------------------------------| | | | | | Master Boot Record | | | | | | 主引导记录(446字节) | | | | | | | 01BD | | 01BE |------------------------------------------------| | | 01CD | 分区信息 1(16字节) | 01CE |------------------------------------------------| | | 01DD | 分区信息 2(16字节) | 01DE |------------------------------------------------| | | 01ED | 分区信息 3(16字节) | 01EE |------------------------------------------------| | | 01FD | 分区信息 4(16字节) | |------------------------------------------------| | 01FE | 01FF | | 55 | AA | |------------------------------------------------|

2. 分区表结构简介

分区表由四个分区项构成, 每一项的结构如下:

BYTE State : 分区状态, 0 = 未激活, 0x80 = 激活 (注意此项) BYTE StartHead : 分区起始磁头号 WORD StartSC : 分区起始扇区和柱面号, 底字节的低6位为扇区号, 高2位为柱面号的第 9,10 位, 高字节为柱面号的低 8 位 BYTE Type : 分区类型, 如 0x0B = FAT32, 0x83 = Linux 等, 00 表示此项未用 BYTE EndHead : 分区结束磁头号 WORD EndSC : 分区结束扇区和柱面号, 定义同前 DWORD Relative : 在线性寻址方式下的分区相对扇区地址 (对于基本分区即为绝对地址) DWORD Sectors : 分区大小 (总扇区数)

注意: 在 DOS / Windows 系统下, 基本分区必须以柱面为单位划分 ( Sectors * Heads 个扇区), 如对于 CHS 为 764/255/63 的硬盘, 分区的 最小尺寸为 255 * 63 * 512 / 1048576 = 7.844 MB.

3. 扩展分区简介

由于主分区表中只能分四个分区, 无法满足需求, 因此设计了一种扩展 分区格式. 基本上说, 扩展分区的信息是以链表形式存放的, 但也有一些特 别的地方. 首先, 主分区表中要有一个基本扩展分区项, 所有扩展分区都隶属于它, 也就是说其他所有扩展分区的空间都必须包括在这个基本扩展分区中. 对于 DOS / Windows 来说, 扩展分区的类型为 0x05. 除基本扩展分区以外的其他所有扩展分区则以链表的形式级联存放, 后 一个扩展分区的数据项记录在前一个扩展分区的分区表中, 但两个扩展分区 的空间并不重叠. 扩展分区类似于一个完整的硬盘, 必须进一步分区才能使用. 但每个扩 展分区中只能存在一个其他分区. 此分区在 DOS/Windows 环境中即为逻辑盘. 因此每一个扩展分区的分区表 (同样存储在扩展分区的第一个扇区中)中最多 只能有两个分区数据项(包括下一个扩展分区的数据项). 扩展分区和逻辑盘的示意图如下:

|-----------------------| -------- | 主扩展分区(/dev/hda2) | ^ |-----------------------| | | 扩 展 | 分区项 1 |--\ | | |------------| | | | 分区表 | 分区项 2 |--+--\ | |-----------------------| | | | | | | | | | 逻辑盘 1 (/dev/hda5) |<-/ | | | | | | |-----------------------| | 主 | 扩展分区 2 |<----/ |-----------------------| 扩 | 扩 展 | 分区项 1 |--\ | |------------| | 展 | 分区表 | 分区项 2 |--+--\ |-----------------------| | | 分 | | | | | 逻辑盘 2 (/dev/hda6) |<-/ | 区 | | | | |-----------------------| | | | 扩展分区 3 |<----/ | |-----------------------| | | 扩 展 | 分区项 1 |--\ | | |------------| | | | 分区表 | 分区项 2 | | | |-----------------------| | | | | | | | 逻辑盘 3 (/dev/hda7) |<-/ | | | |

|-----------------------| ---------

三. 系统启动过程简介

系统启动过程主要由一下几步组成(以硬盘启动为例):

1. 开机 :-) 2. BIOS 加电自检 ( Power On Self Test -- POST ) 内存地址为 0ffff:0000 3. 将硬盘第一个扇区 (0头0道1扇区, 也就是Boot Sector) 读入内存地址 0000:7c00 处. 4. 检查 (WORD) 0000:7dfe 是否等于 0xaa55, 若不等于 则转去尝试其他启动介质, 如果没有其他启动介质则显示 "No ROM BASIC" 然后死机. 5. 跳转到 0000:7c00 处执行 MBR 中的程序. 6. MBR 首先将自己复制到 0000:0600 处, 然后继续执行. 7. 在主分区表中搜索标志为活动的分区. 如果发现没有活动 分区或有不止一个活动分区, 则转停止. 8. 将活动分区的第一个扇区读入内存地址 0000:7c00 处. 9. 检查 (WORD) 0000:7dfe 是否等于 0xaa55, 若不等于则

显示 "Missing Operating System" 然后停止, 或尝试 软盘启动. 10. 跳转到 0000:7c00 处继续执行特定系统的启动程序. 11. 启动系统 ...

以上步骤中 2,3,4,5 步是由 BIOS 的引导程序完成. 6,7,8,9,10 步由MBR中的引导程序完成.

一般多系统引导程序 (如 SmartFDISK, BootStar, PQBoot 等) 都是将标准主引导记录替换成自己的引导程序, 在运行系统启动程序 之前让用户选择要启动的分区. 而某些系统自带的多系统引导程序 (如 lilo, NT Loader 等) 则可以将自己的引导程序放在系统所处分区的第一个扇区中, 在 Linux 中即为 SuperBlock (其实 SuperBlock 是两个扇区).

注: 以上各步骤中使用的是标准 MBR, 其他多系统引导程序的引导 过程与此不同.

第二部分 技术资料

第一章 扩展 Int13H 技术资料

一. 简介 设计扩展 Int13H 接口的目的是为了扩展 BIOS 的功能, 使其支持 多于1024柱面的硬盘, 以及可移动介质的琐定, 解锁及弹出等功能.

二. 数据结构

1. 数据类型约定 BYTE 1 字节整型 ( 8 位 ) WORD 2 字节整型 ( 16 位 ) DWORD 4 字节整型 ( 32 位 ) QWORD 8 字节整型 ( 64 位 )

2. 磁盘地址数据包 Disk Address Packet (DAP) DAP 是基于绝对扇区地址的, 因此利用 DAP, Int13H 可以轻松地逾 越 1024 柱面的限制, 因为它根本就不需要 CHS 的概念. DAP 的结构如下:

struct DiskAddressPacket { BYTE PacketSize; // 数据包尺寸(16字节) BYTE Reserved; // ==0 WORD BlockCount; // 要传输的数据块个数(以扇区为单位) DWORD BufferAddr; // 传输缓冲地址(segment:offset) QWORD BlockNum; // 磁盘起始绝对块地址 };

PacketSize 保存了 DAP 结构的尺寸, 以便将来对其进行扩充. 在 目前使用的扩展 Int13H 版本中 PacketSize 恒等于 16. 如果它小于 16, 扩展 Int13H 将返回错误码( AH=01, CF=1 ). BlockCount 对于输入来说是需要传输的数据块总数, 对于输出来说 是实际传输的数据块个数. BlockCount = 0 表示不传输任何数据块. BufferAddr 是传输数据缓冲区的 32 位地址 (段地址:偏移量). 数据 缓冲区必须位于常规内存以内(1M). BlockNum 表示的是从磁盘开始算起的绝对块地址(以扇区为单位), 与分区无关. 第一个块地址为 0. 一般来说, BlockNum 与 CHS 地址的关系 是: BlockNum = cylinder * NumberOfHeads + head * SectorsPerTrack + sector - 1;

其中 cylinder, head, sector 是 CHS 地址, NumberOfHeads 是磁盘 的磁头数, SectorsPerTrack 是磁盘每磁道的扇区数. 也就是说 BlockNum 是沿着 扇区->磁道->柱面 的顺序记数的. 这一顺 序是由磁盘控制器虚拟的, 磁盘表面数据块的实际排列顺序可能与此不同 (如为了提高磁盘速度而设置的间隔因子将会打乱扇区的排列顺序).

3. 驱动器参数数据包 Drive Parameters Packet 驱动器参数数据包是在扩展 Int13H 的取得驱动器参数子功能调用中 使用的数据包. 格式如下: struct DriveParametersPacket { WORD InfoSize; // 数据包尺寸 (26 字节) WORD Flags; // 信息标志 DWORD Cylinders; // 磁盘柱面数 DWORD Heads; // 磁盘磁头数 DWORD SectorsPerTrack; // 每磁道扇区数 QWORD Sectors; // 磁盘总扇区数 WORD SectorSize; // 扇区尺寸 (以字节为单位) }; 信息标志用于返回磁盘的附加信息, 每一位的定义如下:

0 位: 0 = 可能发生 DMA 边界错误 1 = DMA 边界错误将被透明处理 如果这位置 1, 表示 BIOS 将自动处理 DMA 边界错误, 也就是说 错误代码 09H 永远也不会出现.

1 位: 0 = 未提供 CHS 信息 1 = CHS 信息合法 如果块设备的传统 CHS 几何信息不适当的话, 该位将置 0.

2 位: 0 = 驱动器不可移动 1 = 驱动器可移动

3 位: 表示该驱动器是否支持写入时校验.

4 位: 0 = 驱动器不具备介质更换检测线 1 = 驱动器具备介质更换检测线

5 位: 0 = 驱动器不可锁定 1 = 驱动器可以锁定 要存取驱动器号大于 0x80 的可移动驱动器, 该位必须置 1 (某些驱动器号为 0 到 0x7F 的设备也需要置位)

6 位: 0 = CHS 值是当前存储介质的值 (仅对于可移动介质), 如果 驱动器中有存储介质, CHS 值将被返回. 1 = CHS 值是驱动器支持的最大值 (此时驱动器中没有介质).

7 - 15 位: 保留, 必须置 0.

三. 接口规范

1. 寄存器约定 在扩展 Int13H 调用中一般使用如下寄存器约定:

ds:di ==> 磁盘地址数据包( disk address packet ) dl ==> 驱动器号 ah ==> 功能代码 / 返回码

在基本 Int13H 调用中, 0 - 0x7F 之间的驱动器号代表可移动驱动器 0x80 - 0xFF 之间的驱动器号代表固定驱动器. 但在扩展 Int13H 调用中 0x80 - 0xFF 之间还包括一些新出现的可移动驱动器, 比如活动硬盘等. 这些驱动器支持先进的锁定,解锁等功能. ah 返回的错误码除了标准 Int13H 调用规定的基本错误码以外,又增加 了以下错误码:

B0h 驱动器中的介质未被锁定

B1h 驱动器中的介质已经锁定

B2h 介质是可移动的

B3h 介质正在被使用

B4h 锁定记数溢出

B5h 合法的弹出请求失败

2. API 子集介绍 1.x 版的扩展 Int13H 调用中规定了两个主要的 API 子集.

第一个子集提供了访问大硬盘所必须的功能, 包括 检查扩展 In13H 是否存在( 41h ), 扩展读( 42h ), 扩展写( 43h ), 校验扇区( 44h ), 扩展定位( 47h ) 和 取得驱动器参数( 48h ). 第二个子集提供了对软件控制驱动器锁定和弹出的支持, 包括 检查扩展 Int13H 是否存在( 41h ), 锁定/解锁驱动器( 45h ), 弹出驱动器( 46h ), 取得驱动器参数( 48h ), 取得扩展驱动器改变状态( 49h ), int 15h. 如果使用了调用规范中不支持的功能, BIOS 将返回错误码 ah = 01h, CF = 1.

3. API 详解

1) 检验扩展功能是否存在 入口: AH = 41h BX = 55AAh DL = 驱动器号

返回: CF = 0 AH = 扩展功能的主版本号 AL = 内部使用 BX = AA55h CX = API 子集支持位图 CF = 1 AH = 错误码 01h, 无效命令

这个调用检验对特定的驱动器是否存在扩展功能. 如果进位标志置 1 则此驱动器不支持扩展功能. 如果进位标志为 0, 同时 BX = AA55h, 则 存在扩展功能. 此时 CX 的 0 位表示是否支持第一个子集, 1位表示是否 支持第二个子集. 对于 1.x 版的扩展 Int13H 来说, 主版本号 AH = 1. AL 是副版本号, 但这仅限于 BIOS 内部使用, 任何软件不得检查 AL 的值.

2) 扩展读 入口: AH = 42h DL = 驱动器号 DS:DI = 磁盘地址数据包(Disk Address Packet)

返回: CF = 0, AH = 0 成功 CF = 1, AH = 错误码

这个调用将磁盘上的数据读入内存. 如果出现错误, DAP 的 BlockCount 项中则记录了出错前实际读取的数据块个数.

3) 扩展写 入口: AH = 43h AL 0 位 = 0 关闭写校验 1 打开写校验 1 - 7 位保留, 置 0 DL = 驱动器号 DS:DI = 磁盘地址数据包(DAP) 返回: CF = 0, AH = 0 成功 CF = 1, AH = 错误码

这个调用将内存中的数据写入磁盘. 如果打开了写校验选项, 但 BIOS 不支持, 则会返回错误码 AH = 01h, CF = 1. 功能 48h 可以检测BIOS是否 支持写校验. 如果出现错误, DAP 的 BlockCount 项中则记录了出错前实际写入的数 据块个数.

4) 校验扇区 入口: AH = 44h DL = 驱动器号 DS:DI = 磁盘地址数据包(Disk Address Packet)

返回: CF = 0, AH = 0 成功 CF = 1, AH = 错误码

这个调用校验磁盘数据, 但并不将数据读入内存.如果出现错误, DAP 的 BlockCount 项中则记录了出错前实际校验的数据块个数.

5) 锁定/解锁驱动器 入口: AH = 45h AL = 0 锁定驱动器 = 1 驱动器解锁 = 02 返回锁定/解锁状态 = 03h-FFh - 保留 DL = 驱动器号

返回: CF = 0, AH = 0 成功 CF = 1, AH = 错误码

这个调用用来缩定指定驱动器中的介质. 所有标号大于等于 0x80 的可移动驱动器必须支持这个功能. 如果 在支持可移动驱动器控制功能子集的固定驱动器上使用这个功能调用, 将 会成功返回. 驱动器必须支持最大255次锁定, 在所有锁定被解锁之前, 不能在物理上 将驱动器解锁. 解锁一个未锁定的驱动器,将返回错误码 AH= B0h. 如果锁定一 个已锁定了255次的驱动器, 将返回错误码 AH = B4h. 锁定一个没有介质的驱动器是合法的.

6) 弹出可移动驱动器中的介质 入口: AH = 46h AL = 0 保留 DL = 驱动器号

返回: CF = 0, AH = 0 成功 CF = 1, AH = 错误码

这个调用用来弹出指定的可移动驱动器中的介质. 所有标号大于等于 0x80 的可移动驱动器必须支持这个功能. 如果 在支持可移动驱动器控制功能子集的固定驱动器上使用这个功能调用, 将 会返回错误码 AH = B2h (介质不可移动). 如果试图弹出一个被锁定的介质 将返回错误码 AH = B1h (介质被锁定). 如果试图弹出一个没有介质的驱动器, 则返回错误码 Ah = 31h (驱动器 中没有介质). 如果试图弹出一个未锁定的可移动驱动器中的介质, Int13h会调用 Int15h (AH = 52h) 来检查弹出请求能否执行. 如果弹出请求被拒绝则返回错误码(同 Int15h). 如果弹出请求被接受,但出现了其他错误, 则返回错误码 AH = B5h.

7) 扩展定位 入口: AH = 47h DL = 驱动器号 DS:DI = 磁盘地址数据包(Disk Address Packet)

返回: CF = 0, AH = 0 成功 CF = 1, AH = 错误码

这个调用将磁头定位到指定扇区.

8) 取得驱动器参数 入口: AH = 48h DL = 驱动器号 DS:DI = 返回数据缓冲区地址

返回: CF = 0, AH = 0 成功 DS:DI 驱动器参数数据包地址, (参见前面的文章) CF = 1, AH = 错误码

这个调用返回指定驱动器的参数.

9) 取得扩展驱动器介质更换检测线状态 入口: AH = 49h DL = 驱动器号

返回: CF = 0, AH = 0 介质未更换 CF = 1, AH = 06h 介质可能已更换

这个调用返回指定驱动器的介质更换状态. 这个调用与 Int13h AH = 16h 子功能调用相同, 只是允许任何驱动器 标号. 如果对一台支持可移动介质功能子集的固定驱动器使用此功能,则永远 返回 CF = 0, AH = 0. 简单地将可移动介质锁定再解锁就可以激活检测线, 而无须真正更换介质.

10) Int 15h 可移动介质弹出支持 入口: AH = 52h DL = 驱动器号 返回: CF = 0, AH = 0 弹出请求可能可以执行 CF = 1, AH = 错误码 B1h 或 B3h 弹出请求不能执行

这个调用是由 Int13h AH=46h 弹出介质功能调用内部使用的.

=================================

crshen网友提供的第一个版本的自动备份和恢复硬盘分区表的C源程序:

 #include &lt;stdio.h&gt; #include &lt;bios.h&gt;

#define READ 2 #define WRITE 3

void ShowHelp() { printf("\nHD SECTOR BACKUP &amp; RESTORE TOOLS by CRSHEN\n\n\ Usage: hdsect.exe drive head track sector filename parameter\n\ drive: 1=first HD 2=second HD and so on\n\ head track sector MUST be integer\n\ parameter: /b means backup /r means restore, *case sensitive*\n"); }

void main(int argc,char *argv[]) { int drive=0x80,head=0,track=0,sector=1,nsects=1; unsigned char buf[512]; FILE *fp;

drive=0x80+atoi(argv[1])-1; head=atoi(argv[2]); track=atoi(argv[3]); sector=atoi(argv[4]);

if (argc!=7) { ShowHelp(); exit(2); } if (strcmp(argv[6],"/b")==0) { if (biosdisk(READ,drive,head,track,sector,nsects,buf)) { printf("Failed to read from sector !\n"); exit(1); } if ((fp=fopen(argv[5],"wb"))!=NULL) { fwrite(buf,512,1,fp); fclose(fp); printf("Backup sector to file %s done !\n",argv[5]); exit(0); } else { printf("Open File %s Failed !\n",argv[5]); exit(1); } } if (strcmp(argv[6],"/r")==0) { if ((fp=fopen(argv[5],"rb"))!=NULL) { fread(buf,512,1,fp); fclose(fp); } else { printf("Open File %s Failed !\n",argv[5]); exit(1); } if (biosdisk(WRITE,drive,head,track,sector,nsects,buf)) { printf("Failed to write to sector !\n"); exit(1); } else { printf("Restore sector from file %s done !",argv[5]); exit(0); } } } 

=================================================

crshen网友提供的第二个版本的自动备份和恢复硬盘分区表的C源程序:
以下内容为程序代码:

#include <stdio.h>
#include <bios.h>
#include <dos.h> 

#define READ 2
#define WRITE 3
#define READ_EXT 0x42
#define WRITE_EXT 0x43
#define HD1st 0x80

void ShowHelp();
/*显示帮助信息*/

short _diskop (unsigned char drv, unsigned char cmd,unsigned char verify,unsigned char * buffer, unsigned long startlow, unsigned short copyblks);
/*采用扩展int 13读写扇区*/

short _OldINT13(unsigned char cmd,unsigned char * buffer,unsigned char drv,unsigned char head,unsigned char sector,unsigned char cylinder);
/*老的int 13中断读写扇区,兼容旧机子*/

long cal_add(FILE *fp, int ignore);
/*累加法校验文件*/

void main(int argc,char *argv[])
{
  unsigned char buf[512],tempbuf[512];
  unsigned long StartSector;
  long add;
  int ch,mark,test;
  FILE *fp;

  if (argc!=3)
  {
    ShowHelp();
    exit(6);             /*返回6,帮助信息*/
  }
  /*下面为备份分区信息*/
  if (strcmp(argv[1],"/backup")==0)
  {
    if (biosdisk(READ,HD1st,0,0,1,1,buf))
    {
        printf("Failed to read from sector 0!\n");
        exit(3);           /*返回3,读0扇区出错*/
    }
    if ((fp=fopen(argv[2],"wb"))!=NULL)
        fwrite(buf,512,1,fp);
    else
    {
        printf("Failed to creat file:%s\n",argv[2]);
        exit(1);           /*返回1,磁盘写保护?*/
    }
    for (mark=450;buf[mark]!=0 && mark<512;mark+=16)
    {
        StartSector=buf[mark+7]*16777216L+buf[mark+6]*65536L+buf[mark+5]*256L+buf[mark+4];
        test=_diskop (HD1st, READ_EXT, 0, tempbuf, StartSector, 1);
        if (test!=0)
      {
          printf("READ_EXT ERROR Number: %d\n",test);       /*查看出错信息代码*/
          _OldINT13(READ,tempbuf,HD1st,buf[mark-3],buf[mark-2],buf[mark-1]); /*出错可能为主板不支持扩展int 13,改用老的int 13*/
      }
        fwrite(tempbuf,512,1,fp);
    }
    fclose(fp);

    fp=fopen(argv[2],"ab+");       /*在文件尾部写入校验信息*/
    add=cal_add(fp, 0);
    fseek(fp,0L,SEEK_END);
    ch=add&0xFF;
    fputc(ch,fp);
    add/=0x100;
    ch=add&0xFF;
    fputc(ch,fp);       /*校验信息为文件字节累积和的低4位*/
    fclose(fp);
    exit(0);       /*返回0,操作成功*/
  }
  /*下面为恢复分区信息*/
  if (strcmp(argv[1],"/restore")==0)
  {
    if ((fp=fopen(argv[2],"rb"))!=NULL)
    {
        add=cal_add(fp, 2);
        fseek(fp,-2L,SEEK_END);
        ch=add&0xFF;
        if (ch!=fgetc(fp))         /*首先校验文件正确性*/
      {
          printf("File %s may be wrong !\n",argv[2]);
          exit(5);     /*返回5,文件校验出错*/
      }
        add/=0x100;
        ch=add&0xFF;
        if (ch!=fgetc(fp))
      {
          printf("File %s may be wrong !\n",argv[2]);
          exit(5);
      }
        rewind(fp);
        fread(buf,512,1,fp);
    }
    else
    {
        printf("Failed to open file:%s\n",argv[2]);
        exit(2);     /*返回2,备份文件不存在*/
    }
    if (biosdisk(WRITE,HD1st,0,0,1,1,buf))
    {
        printf("Failed to write to sector 0!\n");
        exit(4);     /*返回4,写扇区0出错*/
    }
    for (mark=450;buf[mark]!=0 && mark<512;mark+=16)
    {
        fread(tempbuf,512,1,fp);
        StartSector=buf[mark+7]*16777216L+buf[mark+6]*65536L+buf[mark+5]*256L+buf[mark+4];
        /*printf("Write sector...\n");*/
        /**/
        test=_diskop (HD1st, WRITE_EXT, 1, tempbuf, StartSector, 1);
        if (test!=0)   /*写扇区出错*/
      {
          test=_diskop (HD1st, WRITE_EXT, 2, tempbuf, StartSector, 1);
          if (test==1)   /*校验与不校验下,两种写入方式均提示invalid function,考虑为主板不支持扩展int 13*/
            _OldINT13(WRITE,tempbuf,HD1st,buf[mark-3],buf[mark-2],buf[mark-1]);
      }
    }
    fclose(fp);
    exit(0);       /*返回0,操作成功*/
  }
  /*下面为重新设置文件校验值*/
  if (strcmp(argv[1],"/checksum")==0)
  if ((fp=fopen(argv[2],"rb+"))!=NULL)
  {
    add=cal_add(fp, 2);
    fseek(fp,-2L,SEEK_END);
    if (fgetc(fp)==0x55 && fgetc(fp)==0xAA)
        printf("Can not checksum of this file (end of 55AA)\n");
    else
    {
        fseek(fp,-2L,SEEK_END);
        ch=add&0xFF;
        fputc(ch,fp);
        add/=0x100;
        ch=add&0xFF;
        fputc(ch,fp);
        fclose(fp);
        printf("Re_Check file: %s done\n",argv[2]);
    }
  }
  else
  {
    printf("Failed to open file:%s\n",argv[2]);
    exit(2);
  }
}

short _OldINT13(unsigned char cmd,unsigned char * buffer,unsigned char drv,unsigned char head,unsigned char sector,unsigned char cylinder)
{
  asm push ds
    asm mov ah,cmd
    asm mov al,1h
    asm mov bx,buffer
    asm mov ch,cylinder
    asm mov cl,sector
    asm mov dh,head
    asm mov dl,drv
    asm int 13h
    asm pop ds
    asm jc error
    return (1);
  error:
    return (-1);
}

short _diskop (unsigned char drv, unsigned char cmd,unsigned char verify,unsigned char * buffer, unsigned long startlow, unsigned short copyblks)
{   /* 磁盘存取数据包结构 */
  struct disk_address_packet
  {
    unsigned char size_of_packet;
    unsigned char reserved;
    unsigned short number_of_blocks_to_transfer;
    unsigned short transfer_buffer_offset;
    unsigned short transfer_buffer_segment;
    unsigned long starting_absolute_block_low;
    unsigned long starting_absolute_block_high;
  };
  struct disk_address_packet dap;
  unsigned char av;
  unsigned char * pt = (unsigned char *) &dap;
  dap.size_of_packet = sizeof (dap);
  dap.reserved = 0;
  dap.number_of_blocks_to_transfer = copyblks;
  dap.transfer_buffer_offset = FP_OFF(buffer);
  dap.transfer_buffer_segment = FP_SEG(buffer);
  dap.starting_absolute_block_low = startlow;
  dap.starting_absolute_block_high = 0; /* 忽略高位 */

  asm push ds
    asm push si
    asm mov ah, cmd
    asm mov al,verify
    asm mov dl, drv
    asm lds si, DWORD PTR pt
    asm int 13h
    asm pop si
    asm pop ds
    asm mov av, ah
    asm jc error
  /*return (dap.number_of_blocks_to_transfer); 返回成功读写的扇区数目*/
    return(0);
  error:
  /*ErrorNum = av;出错信息代码*/
    return (av);
}

long cal_add(FILE *fp, int ignore)
{
  long filelength,add=0;
  fseek(fp,0L,SEEK_END);
  filelength=ftell(fp)-ignore;
  rewind(fp);
  while (filelength--!=0)
    add+=fgetc(fp);   /*文件字节累积求和*/
  return(add);
}

void ShowHelp()
{
  printf("\n=== PATITION TABLE BACKUP & RESTORE TOOLS by CRSHEN ===\n\n\
  Usage: patition.exe </backup|/restore|/checksum> filename\n");
}

----------------------

from http://nufans.net/dvbbs/dispbbs.asp?boardID=1&ID=544&page=3