不同编译器下系统开方运算性能测试(已更新Linux版测试)

前两天看到一篇文章提到了用MagicNumber来做神奇的导数开方运算,于是我打算试一试测试系统的函数的性能

 

以上这段简短的程序就是我写的来测试性能的代码。注意:后面的的重写这个数组是为了防止编译器优化把计算的循环给优化掉

 

测试机器是我的笔记本

  • CPU:Intel i73517U  @2.7GHz
  • 内存:16G DDR3L 1600Mhz

性能测试的时候为了准确使用了Intel的性能调试工具,没有使用程序输出的数值。编译全部是windows平台,gcc使用mingw环境。都是debug模式开O2优化,x64代码。

 

VS 2015:557.943ms

CLang 3.7:605.040ms

icc 16.0:762.681ms

gcc 5.2.0:1269ms

 


附加测试:Linux上的性能测试。有朋友说我的测试都是Windows平台,Linux平台下面g++可能性能会更好,于是我有做了另一个测试

测试机器是我的一台服务器

  • CPU Xeon E5-2603 x2 @ 1.80 GHz
  • 内存:16G

由于服务器上没有Intel的性能调试工具的授权,所以使用系统函数来测试时间。系统是Ubuntu

 

CLang 3.4 :1020.149ms

gcc 4.8.4:1355.673ms

所以可以看出来在Linux系统下面gcc的性能有所提升,但是仍然比不了clang

Windows的进程线程优先级与修改

如果你曾经用过windows的任务管理器,就算是没有学习过windows的内核你也应该有听说过“进程优先级“这么一个概念。虽然你可能没有操作过,但是你很有可能在任务管理器里面见过它。今天稍微讲一下Windows的进程优先级以及线程优先级。

Windows是一个抢断式多线程操作系统,在并发的处理的时候最基本的执行单结构是线程,而一个进程内并不是一个执行上不可分割的结构,而是由多个线程组成的。每一个线程在内核中有一个优先级顺序,这个顺序的取值范围是0-31,数字越大优先级越高。如果有任意一个高顺序的线程需要执行,Windows绝对不会执行低优先级的线程。如果低优先级的线程一直不能被执行,那么线程就被饥饿了,这个时候抢占式操作系统的特点就体现出来了。

但是为什么平时都没有注意到呢,这里很大的原因是平时绝大多数工作线程的的优先级都是基本差不多一致的,每一个线程基本都能分配到时间片。但是有的时候我们需要创建一些较高优先级或者较低优先级的线程用来完成一些特殊的任务,我们就要了解一下这个线程的优先顺序到底是如何计算出来的。

虽然说这个优先顺序的取值范围是0-31,但是你并不能直接通过API来设定优先顺序。你能够设定一个进程优先级以及一个线程优先级。下面是一个Windows的优先级对应表:

线程相对

优先级

进程优先级类

Idle

Below Normal

Normal

Above Normal

High

Real-Time

Time-critical

15

15

15

15

15

31

Highest

6

8

10

12

15

26

Above normal

5

7

9

11

14

25

Normal

4

6

8

10

13

24

Below normal

3

5

7

9

12

23

Lowest

2

4

6

8

11

22

Idle

1

1

1

1

1

16

从这个表你可以看出来,实时优先级的进程拥有者极高的数值,通常情况下你不应该把你的进程优先级设置为实时。包括磁盘IO、鼠标显示、音频输出之类的系统功能都在实时优先级中的某些优先级中工作。如果你的线程优先级高于它们,并且需要大量CPU运算很有可能导致整个操作系统无法响应。不过微软在windows vista开始引入UAC之后开始限制了管理员账户的一些权限,现在你必须需要完全的UAC管理员权限才能把进程的优先级设置为实时,否则系统不会报错,但是最高会给你设置到高。

设置优先级的方法也非常简单,一种方法是创建进程或者创建线程的时候进行设置,另一种方法是通过专门的设置优先级的API进行设置。这里只介绍第二种通过专门的API设置的方法。

下面我给一种设置进程优先级的代码

类似的你也可以通过

  • SetProcessPriorityBoost
  • SetThreadPriorityBoost
  • SetThreadPriority
  • SetPrioriytClass

等这一簇函数来完成。更详细资料请参考MSDN

 

GDI高效内存手动绘图

最近一直想要写几篇文章,但是由于懒癌的发作一直都没有写。今天趁着写代码写的闹心并且无力追番的闲心写一下。

最近在参加一个比赛,里面要求百万的图形能够在10s以内能够进行裁剪显示,这对于GDI绘图来说是基本不现实的,不过经过各种优化我在1秒以内完成这个任务(i7 四核 1.8GHz)。实现这个效果的最关键的就是手动绘图替代掉效率底下的GDI。

如果有DOS下图形编程经验的朋友可能都习惯用最基本的绘点函数来完成一些复杂的绘制,到了windows之后遇到GDI之后却发现绘点函数慢的恐怖。比如要SetPixel来绘制一个调色盘,这个效率基本是要让人疯掉的。

这里先分析一下GDI的效率为什么低下。众所周知,现在的Windows是运行在保护模式的,只有系统内核以及驱动程序才能接触到那些关键的显存,而普通的ring3应用程序必须通过操作系统的API才能调用硬件资源。于是每次绘图的时候程序都会调用gdi的dll,这个动态链接库会接受到调用之后在缓冲队列里面记录下操作,在特定的时候会触发CPU陷阱把缓冲队列的数据交给内核来执行。也就是说大量的GDI绘图指令会导致大量的内核切换的开销。

为了加速绘图微软公司也是有不少解决方案,在windows早期就提出过DirectX用来直接操作这些硬件资源。不过今天并不打算采用那种复杂到恐怖的东西。还是用类似于双缓冲的方式实现

GDI里面的重要的一个概念就是DC,而DC需要一个实际的数据区,这个数据区就是一个位图。如果我们能够直接对这个位图内部的内存进行读写那么就能够非常快的完成绘图操作。

关键的代码有下面这几个,如果有问题可以查询MSDN。我一开始也对这几个函数比较有疑惑,现在稍微记录一下吧。

  • CreateCompatibleDC能够创建一个空的兼容的DC,但是这个DC由于没有缓存区所以大小是1*1的。
  • CreateCompatibleBitmap能够创建一个大小一致的兼容的BMP,由于新创建的DC并没有大小,请注意这里要传入旧的DC
  • SelectObject用来把一个BMP绑定到DC上
  • BitBlt用来复制并且光栅操作
  • SetBitmapBits用来设置一个BMP的内容

还有一些其他的API可能也是有用的,不过由于调用并没有这里方便于是我就不介绍了

代码Demo可以看我当时写的代码:https://github.com/manageryzy/lineCircle/blob/master/lineCycle/draw/drawMemory.cpp

 

减小Visual Studio的解决方案体积

最近,迫于生计,去使用Visual Studio去开发了一些闭源的项目。是用Visual Studio的时候发现了一个现象就是解决方案目录体积过大(明明早就发现了好么,只是在重新炒冷饭好么)。

其实如果你熟悉eclipse或者idea的话,你可能就对解决方案是什么有一个基本的了解了。Visual Studio的解决方案eclipse的WorkSpace。eclipse为了代码的智能提示会把代码进行分析之后储存在解决方案的目录下面的隐藏目录下,当然了,这个目录下面也有当前WorkSpace的所有配置。

而Visual Studio的做法是:把解决方案的配置放在.suo文件里面,而那些肥大的代码提示放在一个SQL数据库文件里面(喂,这么不智能的提示也能算提示么)。如果你想要干掉这个肥大的文件,一个办法就是通过配置让Visual Studio把这个文件储存在系统临时目录下。具体的做法如下(我假设你使用的是中文版的Visual Studio):

  1. 点击“工具”菜单,选择“选项”
  2. 选择“文本编辑器”-“C/C++“-高级
  3. 设定”始终使用回退位置“为TRUE

在进行上面的操作之后肥大的代码提示文件被出掉了。如果要进一步的缩减解决方案的体积不要忘记在每次Build之后Clean你的解决方案

HAL层直接端口IO来绕过atapi层驱动

http://www.jybase.net/windows/20120106743.html

 

关于文件的保护的话题,笔记在前几期的黑防中进行了一些个人的分析,根据IRP的下
发流程,从系统的文件驱动ntfs.sys到atapi.sys的dispatch hook和深度的AtapiStartIo
hook,共记三篇。可以说,从 IRP 发送到文件驱动开始,到下发至 atapi 的 dispatch,以
及 atapi 的一些内部处理都大致讲了一遍。
如果读者没有忘记前面几期的内容,那么应该知道 atapi 后是在 HAL.DLL 中进行端口
IO 的处理了,那么本次笔者就带大家来初步的了解 HAL 中所做的一些工作,以及如何自己
实现直接的端口IO,从而绕过 atapi这层驱动。
如果读者还记得第6期《Atapi的深度 HOOK》一文的内容,那么就会知道IdeReadWrite
这个函数起着“承上启下”的作用,这个函数的“下方”就是IO端口的操作了,而“上方”
则是 atapi 层。那么要了解如何实现直接端口 IO,这个函数对我们来说是至关重要的。首
先这个函数的原型是:
NTSTATUS IdeReadWrite(UCHAR devExt_ach, PVOID  Srb)
下面给出一些关键的汇编代码:

.text:00011239                 push    dword ptr [esi+24h] ; 0x1F7,状态
寄存器
.text:0001123C       call ds:__imp__READ_PORT_UCHAR@4 ; 
.text:00011242                 test    al, al
.text:00011244                 jns     short loc_1124D
……
.text:0001124D loc_1124D:                              ; CODE XREF: 
.text:0001124D                 test    al, 40h         ; 测试第6位(准备
位)
.text:0001124F                 jnz     short loc_11263
……
.text:00011263
.text:00011263 loc_11263:                              ; CODE XREF:
IdeReadWrite(x,x)+3Dj
.text:00011263                 mov     eax, [edi+18h]  ; 设备可以接受命令
.text:00011266                 mov     [esi+84h], eax
.text:0001126C                 mov     eax, [edi+10h]
.text:0001126F                 mov     [esi+88h], eax

.text:00011275                 mov     byte ptr [esi+0C4h], 1
.text:0001127C                 mov     eax, [edi+10h]  ;
Srb.DataTransferLength
.text:0001127F                 add     eax, 1FFh
.text:00011284                 shr     eax, 9          ; 除以 512,扇区大

.text:00011287                 push    eax             ; Value
.text:00011288                 push    dword ptr [esi+10h] ; Port = 0x1F2
.text:0001128B                 call    ebx ; WRITE_PORT_UCHAR(x,x) ; 写
扇区计数寄存器
……
.text:000112A7                 mov     dword ptr [ebp+devExt_ach], eax ;
从 CDB指令中算出起始扇区

.text:000112CA                 push    ecx             ; Value = 块地址
24~27
.text:000112CB                 push    dword ptr [esi+20h] ; Port = 0x1F6
.text:000112CE                 call    ebx ; WRITE_PORT_UCHAR(x,x) ;
WRITE_PORT_UCHAR(x,x)
.text:000112D0                 push    dword ptr [ebp+devExt_ach] ; Value
= 000de1cf
.text:000112D3                 push    dword ptr [esi+14h] ; Prot = 0x1F3
.text:000112D3                                         ; 块地址0~7位
.text:000112D6                 call    ebx ; WRITE_PORT_UCHAR(x,x) ;
WRITE_PORT_UCHAR(x,x)
.text:000112D8                 mov     eax, dword ptr [ebp+devExt_ach]
.text:000112DB                 shr     eax, 8
.text:000112DE                 push    eax             ; Value
.text:000112DF                 push    dword ptr [esi+18h] ; Port = 0x1F4
.text:000112DF                                         ; 块地址8~15位
.text:000112E2                 call    ebx ; WRITE_PORT_UCHAR(x,x) ;
WRITE_PORT_UCHAR(x,x)
.text:000112E4                 mov     eax, dword ptr [ebp+devExt_ach]
.text:000112E7                 shr     eax, 10h
.text:000112EA                 push    eax
.text:000112EB                 push    dword ptr [esi+1Ch] ; Port = 0x1F5
.text:000112EB                                         ; 块地址16~23位
.text:000112EE                 jmp     short loc_1135B
.text:0001135B loc_1135B:                              ; CODE XREF: 
.text:0001135B                 call    ebx ; WRITE_PORT_UCHAR(x,x) ;

 

上面的汇编代码就算是结合注释想理解也很困难。因为它涉及到了IDE接口的一
些技术。在IDE读写时,先要对一些IDE的寄存器进行设置,最后才是调用HAL 中的

WRITE_PORT_xxxx 或者 READ_PORT_xxxx 系列的函数读写数据。下面给出这些 IDE 寄存器的
相关介绍:
1.Task File Registers命令寄存器组 
I/O 地址  读(主机从硬盘读数据)     写(主机数据写入硬盘) 
1F0H    数据寄存器        数据寄存器 
1F1H    错误寄存器(只读寄存器)      特征寄存器 
1F2H    扇区计数寄存器       扇区计数寄存器 

1F3H    扇区号寄存器或LBA块地址0~7    扇区号或LBA 块地址0~7 
1F4H    磁道数低 8位或 LBA块地址 8~15   磁道数低 8位或 LBA块地址8~15 
1F5H    磁道数高 8位或 LBA块地址 16~23  磁道数高8位或LBA块地址 16~23 
1F6H    驱动器/磁头或LBA块地址 24~27   驱动器/磁头或LBA块地址24~27 
1F7H    状态寄存器        命令寄存器 
特殊 Task File Registers的位含义
0x1F6: 7~5位,010,第4位 0 表示主盘,1 表示从盘,3~0位,0
状态寄存器(0x1F7):
  第八位:忙。1表示设备正忙
第七位:准备。1 表示设备可以接受命令
第六位:错误。1 表示写错误发生
第五位:寻址。1表示完成寻道操作
第四位:请求。1 表示请求主机数据传输
第三位:校验。1 表示已校正磁盘
第二位:索引。这个不重要
第一位:错误。1 表示前次命令时发生错误
  命令寄存器(0x1F7)
  0x20为读, 0x30 为写
2.Control/Diagnostic Registers控制/诊断寄存器 
I/O 地址     读        写 
3F6H    交换状态寄存器(只读寄存器)  设备控制寄存器(复位) 
3F7H    驱动器地址寄存器
特殊控制/诊断寄存器的位含义
0x3F6 = 0x80 (0000 1RE0): R=reset, E=0 =enable interrupt
有了这些资料,再加上笔者的注释,不难知道上面的流程:先对设备进行查询是否可以
接受指令,可以则对设备的命令组寄存器进行设置,其中有对SRB 结构中的CDB 结构进行一
些计算,得出LBA 等数据,看过上期文章的读者对这几个结构应该不会陌生。
于是可以仿照这个过程写出我们的初始代码,如下: 
WRITE_PORT_UCHAR(0x3F6, 0x0c);
 WRITE_PORT_UCHAR(0x3F6, 0x08);
 WRITE_PORT_UCHAR(0X1F2, 1);
 WRITE_PORT_UCHAR(0x1F3, 0);
 WRITE_PORT_UCHAR(0X1F4, 0);
 WRITE_PORT_UCHAR(0X1F5, 0);
 WRITE_PORT_UCHAR(0x1F6, 0xE0);

 

关于其中的 1、2 行代码作用分别是重置设备和启用中断。剩下的代码作用是从磁盘的
0 扇区开始读出一个扇区的内容,即MBR。
接下来应该看 IdeReadWrite 剩下的代码了。但是笔者在浏览了 IDA 反汇编出来的代码
后,却在发现 IdeReadWrite 的磁盘读操作分支中只进行一些简单的处理后调用了
WRITER_PORT_UCHAR,之后再调用了 PCIIDEX!BmArm 来启动 DMA 通道,并进行数据的传输,
当然这不是笔者想要的,我们要做的是直接 IO,于是我们只能依靠资料来进行推测并尝试
着去写代码。或许笔者比较幸运,在几次尝试后就成功了。代码如下:
 WRITE_PORT_UCHAR(0x1F7, 0x20);
  while((READ_PORT_UCHAR((PUCHAR)0x1f7)&0xf) != 0x08)
  KeStallExecutionProcessor(0X96);
 READ_PORT_BUFFER_USHORT(0x1F0, (PUSHORT)buffer, 512/2); 
上面的代码笔者解释下。在向 0x1F7 端口写入 0x20 命令(也就是读命令)后,用
READ_PORT_UCHAR命令从0x1F7端口中读出设备的状态,并且比较第四位,从而判断是否已
经完成数据的传输。这里注意,当0x1F7 用做读时,为状态寄存器,当0x1F7用做写时,当
做命令寄存器,在上面给出的 IDE 介绍中也可以看出这一点。如果数据传输完成将会被读出
到 0x1F0 端口处,用READ_PORT_BUFFER_USHORT读取到自己分配的内存区即可。
其中调用 READ_PORT_BUFFER_USHORT时,第一次笔者用了READ_PORT_UCHAR 读出来的数
据是跳着读的,即读出前一位数据后,跳过中间一位数据,再读后一位。笔者百思不得其解,
希望有人能解答。
最后运行的结果,与WinHex读出的 MBR相比较发现是一致的,如图1。
 至此笔者从最初的只是想看看ntfs流程,到 atapi,最后再到端口IO。共计四篇文章,
简单讨论了三个层面对文件读写的响应,不敢说对读者有很大帮助,但只要读者能从这四
篇文章得到一些益处,笔者就满足了。
本文所有代码在XPSP3+WDK中编译通过,并在XPSP3上成功运行。本文只实现了对硬盘
的读操作,并未实现写操作。示例代码也非常的简单,可以说只是提供了思路,并未经过
严格的测试。希望本文起到抛砖引玉的作用,期待更精彩的文章出现。行文仓促,加之硬
件设备复杂,资料少,所以纰漏难免,欢迎批评指正。

 

通过IO口对硬盘绝对扇区读写

http://read.pudn.com/downloads79/sourcecode/hack/crack/301277/%E7%A1%AC%E7%9B%98%E7%9B%B4%E6%8E%A5IO.asm__.htm

通过IO口对硬盘绝对扇区读写2006-09-20 17:18对硬盘进行操作的常用端口是1f0h~1f7h号端口,各端口含义如下:
端口号     读还是写   具体含义
1F0H       读/写      用来传送读/写的数据(其内容是正在传输的一个字节的数据)
1F1H       读         用来读取错误码
1F2H       读/写      用来放入要读写的扇区数量
1F3H       读/写      用来放入要读写的扇区号码
1F4H       读/写      用来存放读写柱面的低8位字节
1F5H       读/写      用来存放读写柱面的高2位字节(其高6位恒为0)
1F6H       读/写      用来存放要读/写的磁盘号及磁头号
                    第7位     恒为1
                    第6位     恒为0
                    第5位     恒为1
                    第4位     为0代表第一块硬盘、为1代表第二块硬盘
                    第3~0位    用来存放要读/写的磁头号
1f7H       读         用来存放读操作后的状态
                    第7位     控制器忙碌
                    第6位     磁盘驱动器准备好了
                    第5位     写入错误
                    第4位     搜索完成
                    第3位     为1时扇区缓冲区没有准备好
                    第2位     是否正确读取磁盘数据
                    第1位     磁盘每转一周将此位设为1,
                    第0位     之前的命令因发生错误而结束
         写         该位端口为命令端口,用来发出指定命令
                    为50h     格式化磁道
                    为20h     尝试读取扇区
                    为21h     无须验证扇区是否准备好而直接读扇区
                    为22h     尝试读取长扇区(用于早期的硬盘,每扇可能不是512字节,而是128字节到1024之间的值)
                    为23h     无须验证扇区是否准备好而直接读长扇区
                    为30h     尝试写扇区
                    为31h     无须验证扇区是否准备好而直接写扇区
                    为32h     尝试写长扇区
                    为33h     无须验证扇区是否准备好而直接写长扇区
注:当然看完这个表你会发现,这种读写端口的方法其实是基于磁头、柱面、扇区的硬盘读写方法,不过大于8G的硬盘的读写方法也是通过端口1F0H~1F7H来实现的^_^

一个通过对硬盘输入输出端口操作来读写硬盘的实例
让我们来看一个关于INT13H读写硬盘程序实例。在例子中详细说明了硬盘的读写操作所用到的端口,并且把通过INT13H读出的主引导区得到的数据和通过输入输出读主引导区得到的数据进行比较,从而证实这两种操作功能相同,程序片段如下:

mov dx,1f6h ; 要读入的磁盘号及磁头号
mov al,0a0h ;磁盘0,磁头0
out dx,al

mov dx,1f2h ;要读入的扇区数量
mov al,1 ;读一个扇区
out dx,al

mov dx,1f3h ;要读的扇区号
mov al,1 ;扇区号为1
out dx,al

mov dx,1f4h ;要读的柱面的低8位
mov al,0 ; 柱面低8位为0
out dx,al

mov dx,1f5h ; 柱面高2位
mov al,0 ; 柱面高2位为0(通过1F4H和1F5H端口我们可以确定
; 用来读的柱面号是0)
out dx,al

mov dx,1f7h ;命令端口
mov al,20h ; 尝试读取扇区
out dx,al
still_going:
in al,dx
test al,8 ;扇区缓冲是否准备好
jz still_going ;如果扇区缓冲没有准备好的话则跳转,直到准备好才向下执行。

mov cx,512/2 ;设置循环次数(512/2次)
mov di,offset buffer
mov dx,1f0h ;将要传输的一个字节的数据
rep insw ;传输数据

; ——

mov ax,201h ;以下是用INT13H读硬盘的0磁头、0柱面、1扇区
mov dx,80h
mov cx,1
mov bx,offset buffer2
int 13h

mov cx,512 ;以下部分用来比较2种方法读出的硬盘数据
mov si,offset buffer
mov di,offset buffer2
repe cmpsb
jne failure
mov ah,9
mov dx,offset readmsg
int 21h
jmp good_exit
failure:
mov ah,9
mov dx,offset failmsg
int 21h
good_exit: ;以下部分用来结束程序
mov ax,4c00h ;退出程序
int 21h

readmsg db ‘The buffers match. Hard disk read using ports.$’
failmsg db ‘The buffers do not match.$’
buffer db 512 dup (‘V’)
buffer2 db 512 dup (‘L’)

附:
我的“硬盘绝对扇区检测”功能代码:
平台:Fedora Core 5
NASM version 0.98.39
gcc 版本 4.1.0 20060304 (Red Hat 4.1.0-3)
编译命令:
由于有端口读写,所以需要在root下运行
nasm -f elf *.asm
gcc *.o
./a.out
代码:
global main
extern printf
extern ioperm
section .data
dmsg db ‘The buffers match. The sector is good.’,0Dh,0Ah,0
failmsg db ‘The buffers do not match. The sector is bad.’,0Dh,0Ah,0
buf_d times 512 db ‘V’
ct1 db ‘buf_director’,0Dh,0Ah,0
buf_s times 512 db ‘K’
ct2 db ‘buf_source’,0Dh,0Ah,0
buf_g times 512 db ‘G’
ct3 db ‘buf_ghost’,0Dh,0Ah,0
disk db 0b0h ;要读入的磁盘号及磁头号
;第7位 恒为1
;第6位 恒为0
;第5位 恒为1
;第4位 为0代表第一块硬盘、为1代表第二块硬盘
;第3~0位 用来存放要读/写的磁头号
secnum db 1 ;要读入的扇区数量,读一个扇区
secno db 1 ;要读的扇区号,扇区号为1
cylin_l db 0 ;要读的柱面的低8位,柱面低8位为0
cylin_h db 0 ;柱面高2位,柱面高2位为0
section .text
main:
mov ax,ds
mov es,ax

push word 1 ;打开 1f0h-1f7h 端口的读写权限
push dword 08h
push dword 1f0h
call ioperm
add esp,10 ;清空栈

push dword buf_g ;备份数据
pop eax
mov edi,eax
call read
push dword buf_g ;打印
call printf
pop eax

push dword buf_s ;写入数据
pop eax
mov esi,eax
call write
push dword buf_s ;打印
call printf
pop eax

push dword buf_d ;读出数据
pop eax
mov edi,eax
call read
push dword buf_d ;打印
call printf
pop eax

push dword buf_g ;恢复数据
pop eax
mov esi,eax
call write

mov cx,512 ;比较数据,测试是否坏道
push dword buf_d
pop eax
mov esi,eax
push dword buf_s
pop eax
mov edi,eax
repe cmpsb
jne failure

push dword dmsg ;成功,无坏块
call printf
pop eax
jmp exit
failure:
push dword failmsg ;失败,坏块
call printf
pop eax
exit:
ret
read:
mov dx,1f6h ;要读入的磁盘号及磁头号
mov al,[disk]
out dx,al

mov dx,1f2h ;要读入的扇区数量
mov al,[secnum]
out dx,al

mov dx,1f3h ;要读的扇区号
mov al,[secno]
out dx,al

mov dx,1f4h ;要读的柱面的低8位
mov al,[cylin_l]
out dx,al

mov dx,1f5h ;柱面高2位
mov al,[cylin_h]
out dx,al

mov dx,1f7h ;命令端口
mov al,20h ; 尝试读取扇区
;1f7H 读 用来存放读操作后的状态
;第7位 控制器忙碌
;第6位 磁盘驱动器准备好了
;第5位 写入错误
;第4位 搜索完成
;第3位 为1时扇区缓冲区没有准备好
;第2位 是否正确读取磁盘数据
;第1位 磁盘每转一周将此位设为1,
;第0位 之前的命令因发生错误而结束
;写 该位端口为命令端口,用来发出指定命令
;为50h 格式化磁道
;为20h 尝试读取扇区
;为21h 无须验证扇区是否准备好而直接读扇区
;为22h 尝试读取长扇区(用于早期的硬盘,每扇可能不是512字节,而是128字节到1024之间的值)
;为23h 无须验证扇区是否准备好而直接读长扇区
;为30h 尝试写扇区
;为31h 无须验证扇区是否准备好而直接写扇区
;为32h 尝试写长扇区
;为33h 无须验证扇区是否准备好而直接写长扇区
out dx,al
still_going:
in al,dx
test al,8 ;扇区缓冲是否准备好
jz still_going

mov cx,512/2 ;设置循环次数(512/2次)
mov dx,1f0h ;将要传输的一个字节的数据
rep insw ;传输数据
ret

write:
mov dx,1f6h ;要写入的磁盘号及磁头号
mov al,[disk]
out dx,al

mov dx,1f2h ;要写入的扇区数量
mov al,[secnum]
out dx,al

mov dx,1f3h ;要写的扇区号
mov al,[secno]
out dx,al

mov dx,1f4h ;要写的柱面的低8位
mov al,[cylin_l]
out dx,al

mov dx,1f5h ;柱面高2位
mov al,[cylin_h]
out dx,al

mov dx,1f7h ;命令端口
mov al,30h ;尝试写入扇区
out dx,al
still_going_2:
in al,dx
test al,40h ;第6位 磁盘驱动器是否准备好了
jz still_going_2

write_again:
mov cx,512/2 ;设置循环次数(512/2次)
mov dx,1f0h ;将要传输的一个字节的数据
rep outsw ;传输数据

; in al,dx
; test al,20h ;第5位 写入错误
; jz write_again
ret

附2:
硬盘绝对扇区检测程序代码–shell

#!/bin/bash
#$1 起始扇区号,扇区号LBA从 0 开始计
#$2 待测试扇区数
#$3 测试次数

if [ $# -ne 3 ]
then
echo “Useage: ./testdisk StartSectorNo SectorNum TestTimes”
exit $E_BADARGS
fi
let “z = 65” #初始写 ‘A’

a=1
while [ $a -le “$3” ]
do
a=$(($a+1))
######################生成用来测试写入的文件#########################################
let “t = $z / 64”
let “t = $t * 10 + ( $z % 64 / 8 ”
let “t = t * 10 + $z % 8”
rm -f tmp*
touch tmp
echo -ne “$t” >> ./tmp
let “z += 1”
if [ $z = 91 ]
then
let “z = 65”
fi
let “i = 1”
while [ $i -ne 10 ]
do
touch tmp2
cat ./tmp >> ./tmp2
cat ./tmp >> ./tmp2
mv tmp2 tmp
let “i += 1”
done
mv tmp tmp2
let “i = 0”
touch tmp
while [ $i -ne $2 ]
do
cat ./tmp2 >> ./tmp
let “i += 1”
done
############################开始测试写入&&读出&&比较####################################
dd if=./tmp of=/dev/hdb bs=512 count=$2 seek=$1
dd if=/dev/hdb of=./tmp_b bs=512 count=$2 skip=$1
if diff tmp tmp_b >> log
then
echo “***the sector from $1 to $[$1+$2-1] is good*****At the test times: $[$a-1]**********”
else
let “b = 0”
while [ $b -le $(($2-1)) ]
do
b=$(($b+1))
dd if=tmp2 of=/dev/hdb bs=512 count=1 seek=$[$1+$b-1]
dd if=/dev/hdb of=./tmp_s bs=512 count=1 skip=$[$1+$b-1]
if diff tmp2 tmp_s >> log
then
echo “*******the sector $[$1+$b-1] is good*****At the test time: $[$a-1]************************”
else
echo “*******the sector $[$1+$b-1] is bad******At the test time: $[$a-1]************************”
fi
done
fi
done