如何写DOS下的设备驱动程序(三)

来源:互联网 发布:mysql控制台闪退 编辑:程序博客网 时间:2024/05/17 09:34

本文我们拟给出一个RAM DISK的实际的设备驱动程序,并分析、解释,为下面我们完成DOS下的U盘驱动程序做准备,在提供源代码之前,我想我们必须先了解一下硬盘的结构,为了以后方便,我们索性直接介绍U盘的文件系统结构,因为它和硬盘基本是一样的。

    和硬盘一样,U盘上的数据可大致分为5个部分:MBR区(Main Boot Record 主引导记录)、DBR区(DOS Boot Record. DOS引导记录)、FAT区(File Allocation Table 文件分配表)、FDT区(File Directory Table 文件目录表)和DATA区(数据区)。

    主引导记录

    主引导记录在绝对扇区号MBR_LBA=0的位置,占一个扇区,其中前446个字节(偏移地址:0--01bdh)为MBR,紧接着的64个字节(偏移地址:01beh--01fdh)是分区信息表DPT(Disk Partition Table),每个分区信息表项占16个字节,所以一个硬盘(U盘)最多可以有4个分区,最后的2个字节(偏移地址:01feh--01ffh)为55h和aah,是MBR结束的标志。

    partition  struc
    bootflag   db   ?             ;80h--活动分区;0--非活动分区
    s_head     db   ?             ;该分区起始磁头号
    s_sector   db   ?             ;低6位为该分区起始扇区号,高2位为磁柱号的bit 9,10
    s_cylinder db   ?             ;起始磁柱号的bit0--7
    type       db   ?             ;分区类型,01--FAT12,04--FAT16,06--bigFAT16,
                                  ;07--NTFS,0BH--FAT32,83H--LINUX,82H--LINUX NATIVE,
                                  ;83H--LINUX SWAP
    e_head     db   ?             ;结束磁头号
    e_sector   db   ?             ;结束扇区号
    e_cylinder db   ?             ;结束磁柱号
    relative   dd   ?             ;分区起始扇区数(相对于LBA=0)(线性寻址)
    sectors    dd   ?             ;该分区的扇区总数
    partition  ends

    mbr        struc
    bootcode   db   446 dup(?)    ;启动代码
    partition1 db   size partition dup(?)
    partition2 db   size partition dup(?)
    partition3 db   size partition dup(?)
    partition4 db   size partition dup(?)
    endflag    dw   0aa55h

    mbr        ends

    之所以我们要介绍这部分,是因为我们必须通过这个分区表才能找到我们的DOS引导记录DBR,从而开始我们的工作。

    DOS引导记录(DBR)

    DOS引导记录的绝对扇区号为:DBR_LBA=mbr.relative,可能这样一说有助于理解mbr.relative这个字段,因为上面在定义中实在是没有说清楚。DOS引导记录我们通常又叫分区引导记录,或者叫操作系统引导记录(OS Boot Record),总之我们知道指的是这部分就好了,还要说明的是,前面说的MBR在软盘中是没有的,只有在硬盘中才有,还有我们要做的RAM DISK也是没有MBR的,直接就是DBR(因为他们不会像硬盘一样分很多个区)。

    DBR中除了有一段引导操作系统的代码外,还存着一个重要的表,就是我们在上一篇文章的最后特别提到的BPB(BIOS Parameter Block),引导操作系统的代码不是我们关注的重点,我们想要的就是这个BPB表,下面我们给出DBR的定义

    dbr                   struc
    jmp_bot               db   3 dup(?)        ;一条JMP指令,跳转到启动代码
    oem_name              db   8 dup(?)        ;操作系统名称
    bpb_bytesPerSector    dw   ?               ;每扇区的字节数
    bpb_secPerClus        db   ?               ;每簇扇区数
    bpb_reservedSec       dw   ?               ;保留扇区数,从DBR到FAT的扇区数
    bpb_numFAT            db   ?               ;FAT的个数
    bpb_rootEntry         dw   ?               ;介质标识,同上文中的介质描述符,U盘、硬盘为0f8h
    bpb_secPerFAT         dw   ?               ;每个FAT占的扇区数
    bpb_secPerTrack       dw   ?               ;每磁道扇区数
    bpb_heads             dw   ?               ;磁头数
    bpb_hiddenSec         dd   ?               ;隐藏扇区数,从MBR到DBR的扇区数
    bpb_totalSec          dd   ?               ;分区总山区数
    drvNum                db   ?               ;盘符,软盘使用00h...硬盘使用80h...
    reserved1             db   ?               ;保留
    bootSig               db   ?               ;扩展引导标志,29h
    volID                 db   4 dup(?)        ;盘序列号
    volLable              db   11 dup(?)       ;卷标,如"MSDOS      "
    fileSysType           db   8 dup(?)        ;文件系统类型,如"FAT16   "
    bootCode              db   448 dup(?)      ;引导代码
    endFlag               dw   0aa55h          ;结束标志
    dbr                   ends

    前面我们在定位MBR和DBR时,都是用的绝对扇区号,那么现在可以说什么是逻辑扇区号了,我们看到,一个分区的第一个扇区是DBR分区引导记录,那么这个扇区就是这个分区里逻辑扇区号为0的扇区,我们可以推算出,DBR所在的扇区的绝对扇区号是隐藏扇区数(bpb_hiddenSec)这个字段,所有我们有如下公式:

    绝对扇区号 = 逻辑扇区号 + 隐藏扇区数

    下面我们再说DBR中的内容就不会再使用绝对扇区号,而是用逻辑扇区号了。

    FAT起始扇区号 = 保留扇区数(bpb_reservedSec)
    根目录起始扇区号 = FAT起始扇区号 + FAT个数(bpb_numFAT) + 每个FAT的扇区数(bpb_secPerFAT)
    数据区起始扇区号 = 根目录起始扇区号 + (32 X 根目录目录项数(bpb_rootEntry)) / 每扇区字节数(bpb_bytesPerSector)

    另外,DBR位于0柱面,1磁道,1扇区,其逻辑扇区号为0,进而我们可以推算出:

    扇区号 =(绝对扇区号 % 每磁道扇区数)+1
    磁头号 =(绝对扇区号 / 每磁道扇区数)% 磁头数
    磁道号 =(绝对扇区号 / 每磁道扇区数)/ 磁头数

    其实,了解到这里,我们已经掌握了必要的知识,我们介绍MBR,目的是为了通过MBR找到DBR,我们介绍DBR,主要是为了介绍其中的BPB,我们介绍BPB是为了能够进行逻辑扇区和绝对扇区的转换,以及可以通过CHS计算出逻辑扇区,或者通过逻辑扇区,算出CHS,至于其它的比如FAT、根目录的位置、数据区的位置等,于我们写驱动程序关系不大。

    下面我们来关心我们本文的重点,一个RAM DISK的驱动程序

    RAM DISK设备驱动程序

    我们不做非常完整,完善的RAM DISK,我们的目的仅仅是示范,为后面我们要完成的U盘的驱动程序热热身,这个驱动程序从内存中得到一个100K的空间,构造一个RAM DISK,RAM DISK不像前面介绍的硬盘或U盘的构造那么复杂,它不需要MBR,因为它没有更多的分区,所以它的第一个扇区就是DBR,在这个DBR中实际也不需要引导代码,但需要有一个完整的BPB,RAM DISK不是硬盘或者软盘,没有机械动作,所以也不需要格式化,总之,我们心里要清楚,我们是在用一块内存来仿真磁盘的动作。

    完整程序在下面地址下载:

    http://blog.hengch.com/source/ramdisk.asm

    在这个驱动程序中,我们实现了下面几个命令:

    Command 0:Initialization,初始化,这是必须实现的
    Command 1:Media Check,介质检查
    Command 2:Get BPB,得到BPB
    Command 4:Input,输入
    Command 8:Output,输出
    Command 9:Output Verify,带校验输出
    Command 15:Removable,可移动介质
    下面我们一个一个简单做一下说明。

    Initialization,初始化

    在DOS装入设备驱动程序时将发出该请求,除此之外,初始化命令不会再被调用,所以,大多数驱动程序都把初始化部分写在整个驱动程序的最后,这样在执行完初始化命令后,就可以释放掉初始化程序所占的内存,以使驱动程序尽可能少地占用内存。

    我们先要说说我们这个RAM DISK的情况,我们准备建立一个数据区为100K的RAM DISK,我们计划在驱动程序后面为这个RAM DISK分配内存空间,我们要求这块内存的起始地址要与16byte对齐,这样,我们就可以以xxxx:0000的形式来表示这个这个位置,那么这个位置也就是RAM DISK的第一个扇区DBR,所以我们可以用xxxx:0000h--xxxx:01ffh来表示这个扇区中的每一个字节,由于每个扇区为512字节,也是与16byte对齐的,所以,每个扇区都可以表达成yyyy:0000h--yyyy:01ffh的形式。在驱动程序的工作区,有一个RAM DISK information(请看注释)的区域,这个区域记录着我们这个RAM DISK的基本信息,有些是静态的,有些是动态的。

    下面简单介绍一下这个RAM DISK information:

    静态信息

    res_count=5:额外扇区数量,指数据区以外的扇区,包括DBR、FAT、目录区
    ram_par=6560:RAM DISK的分段地址数量,每16个字节可以有一个分段地址,RAM DISK一共有205个扇区,每个扇区512个字节,共有205 X 512个字节地址,这些地址以16为一个分段,一共有(205 X 512)/16=6560个分段地址,这个分段地址数加上RAM DISK的第一个分段地址,就是我们RAM DISK所需内存后面的分段地址,也就是DOS可以给别的程序分配的内存地址。
    disk:RAM DISK的起始段地址

    动态信息:

    start、disk、total三个变量是传送给cvt2seg的参数,cvt2seg根据这三个参数转换出该start扇区的段地址,并根据扇区数total转换出字节数,返回时,DS为扇区段地址,SI=0,CX为字节数。
    buf_ofs和buf_seg用于存放INPUT和OUTPUT命令的数据传输地址的,save子程序将从命令请求头中把相应的数据放到这两个变量中。
    verify仅仅作为一个标志在执行OUTPUT Verify时使用,用来告诉OUTPUT命令是否做校验

    我们说过,DOS的请求头对每一个命令都是不一样的,但前13个字节是一样的,这13个字节我们在《驱动程序(一)》中已经做了定义,下面是Initialization请求头13个字节以后的定义(后面的几个命令也不再定义前13个字节):

    rh0             struc           ;Initialization (Command 0)
    rh0_rh          db      size rh dup(?)  ;Fixed portion
    rh0_nunits      db      ?       ;number of units
    rh0_brk_ofs     dw      ?       ;offset address for break
    rh0_brk_seg     dw      ?       ;segment address for break
    rh0_bpb_tbo     dw      ?       ;offset address of pointer to BPB array
    rh0_bpb_tbs     dw      ?       ;segment address of pointer to BPB array
    rh0_drv_ltr     db      ?       ;first available drive(block device only)
    rh0             ends

    除了rh0_drv_ltr这个字段告诉我们可用的驱动器号外,其余5项均是需要Initialization返回给DOS的。

  • rh0_nunits=1,单元个数当然只有1个
  • rh0_brk_ofs和rh0_brk_seg是让我们返回RAM DISK的起始地址,在程序的最后部分有一个常量定义:disk_start定义为当前地址,然后后面的内容已经没有意义,这一位置就是RAM DISK的起始内存地址,为了能够让这个位置的偏移是0,前面有几条伪指令:
    if      ($-start_address) mod 16
    org     ($-start_address)+16-(($-start_address) mod 16)
    endif
    这一段指令仅仅是为了disk_start能够是与16字节对齐的,这样RAM DISK的起始位置就可以表达成xxxx:0000的形式,不知道我说明白了没有
  • rh0_bpb_tbo和rh0_bpb_tbs是要求Initialization返回BPB的地址。在程序的开始部分已经定义了一个与该RAM DISK相适应的BPB,如下:
    bpb_ss          dw      512      ;512 byte sector size(每个扇区512字节)
    bpb_au          db      1        ;cluster size is 1 sector(每簇一个扇区)
    bpb_rs          dw      1        ;1 (boot) reserved sector(1个保留扇区DBR)
    bpb_nf          db      1        ;1 FAT only(1个FAT)
    bpb_ds          dw      48       ;files in the file directory(目录的最大文件数)
    bpb_ts          dw      205      ;sectors=100KB + 5 overhead(全部扇区数)
    bpb_md          db      0feh     ;media descriptor(介质描述符)
    bpb_fs          dw      1        ;FAT sectors in each FAT(每个FAT的扇区数)
    bpb_ts为全部扇区数,包括100K数据去需要200个扇区,DBR一个扇区,FAT一个扇区和目录三个扇区,DBR一个扇区不用解释;FAT我们使用FAT12,每个簇有一个扇区,一共有200个簇,需要300个字节,所以一个扇区足够;目录数最大48项,每项32个字节,故需1536字节,需要占3个扇区。所以bpb_ts=205。
    bpb_md在《驱动程序(一)》里已经说过,硬盘、U盘都是0f8h,这个0feh按DOS资料介绍应该是单面软盘,这里借用这个标识可以让DOS了解这是一个FAT12的盘。
    其它项我想不需要解释。

    Initialization除了返回这些DOS需要的内容外,还需要做一些事情,如下:

  • 初始化DBR、FAT和目录区,也就是全部清为0
  • 建立分区引导记录DBR,主要是建立BPB
    在驱动程序的工作区(Work Space)已经定义好了现成的DBR,在BPB的上面一点,标号是boot_rec,其中BPB占13个字节,跳转指令3个字节,厂商标识8个字节,共计24个字节搬移到RAM DISK的第一个扇区中即可
  • 建立文件分配表FAT
    根据FAT的规范,起始数据区的簇号是2,前两个簇的位置用于存放磁盘标识,这两个簇占三个字节,第一个字节填介质描述符0feh,后两个字符无意义,填0ffh
  • 建立文件目录
    文件目录表不用做过多处理,因为初始化时已经清0,目录项首字符为0表示没有目录项

    至此,初始化工作完成,记得在返回DOS前,一定要让ES:BX指向当初调用初始化程序时的请求头,否则会乱套。

    Media Check  介质检查

    介质检查主要是为防止软盘在读取过程中换盘而设置的,通过检查介质描述符来检查是否更换了软盘(尽管这个方法并不可靠),介质检查的状态可以返回以下值:
    -1:介质已经被变动过
     0:不知道
    +1:介质没有变动
    由于RAM DISK不可能出现更换的情况,所以永远返回+1。
    该命令请求头的定义如下:
    rh1             struc           ;Media Check(Command 1)
    rh1_rh          db      size rh dup(?)  ;Fixed portion
    rh1_media       db      ?       ;media descriptor from DPB
    rh1_md_stat     db      ?       ;media status returned by device driver
    rh1             ends
    我们只需要在rh1_md_stat中简单地返回1即可。

    Get BPB    得到BPB

    该命令的请求头定义如下:

    rh2             struc           ;Get BPB(Command 2)
    rh2_rh          db      size rh dup(?)  ;Fixed portion
    rh2_media       db      ?       ;media descriptor from DPB
    rh2_buf_ofs     dw      ?       ;offset address of data transfer area
    rh2_buf_seg     dw      ?       ;segment address of data transfer area
    rh2_pbpbo       dw      ?       ;offset address of pointer to BPB
    rh2_pbpbs       dw      ?       ;segment address of pointer to BPB
    rh2             ends
    在任何时候,如果DOS认为介质发生了变化,就会调用该命令以获得新的BPB,根据资料,命令应该把BPB传送到rh2_buf_ofs和rh2_buf_seg所指定的缓冲区中,同时把BPB的的偏移地址和段地址放到rh2_pbpbo和rh2_pbpbs中,但实际上,仅把指针放到rh2_pbpbo和rh2_pbpbs中即可。

    Input    输入

    该命令的请求头定义如下:

    rh4             struc           ;INPUT(Command 4)
    rh4_rh          db      size rh dup(?)  ;Fixed portion
    rh4_media       db      ?       ;media descriptor 
    rh4_buf_ofs     dw      ?       ;offset address of data transfer area
    rh4_buf_seg     dw      ?       ;segment address of data transfer area
    rh4_count       dw      ?       ;transfer count(sectors for block)(bytes for character)
    rh4_start       dw      ?       ;start sector number(block only)
    rh4             ends

    Input、Output和Output Verify三个命令的请求头都是一样的,所以在下面两个命令中不再解释。

    该命令告诉驱动程序从哪个扇区读取多少个扇区的数据,所以我们使用cvt2seg计算得到数据的起始地址后向rh4_buf_ofs和rh4_buf_seg表示的传送即可。

    Output    输出

    该命令的请求头与Input命令一样,其区别是把rh4_buf_ofs和rh4_buf_seg表示的缓冲区中的数据,写到起始扇区为rh4_start的地方,长度为rh4_count个扇区。

    Output Verify    带校验的输出

    这个命令和OUTPUT的区别就是,在把缓冲区里的数据写入到RAM DISK后,再把写入的数据读出来放回缓冲区,这样DOS可以通过比较数据来判断写入是否正确。

     Removable Media    可移动的介质

    该命令没有附加的请求头,该命令要求如果介质是可移动的,则返回的rh_status中的BUSY位不要置1,如果介质是不可移动的,则将BUSY位置1。在《驱动程序(一)》中我们已经介绍过,BUSY位是bit 9,因为RAM DISK是不可移动的,所以我们把BUSY位置1,然后返回。

 

    好了,程序就解释完了,把程序编译链接,如果用masm6.11,再连接的时候加上/tiny参数可直接声称COM格式的文件,如果使用的MASM版本较低,需要在连接成exe文件后,使用exe2bin改成COM格式文件,然后再把文件名改成ramdisk.sys,最后在config.sys中加上这样一行:device=ramdisk.sys,当然,ramdisk.sys前要加上路径,重新启动你的DOS,你就会获得一个100KB的RAM DISK。

    DOS在装入该驱动程序时会显示:

    The Whowin Group 100k RAM DISK
        Drive=E:

    当然也可能是别的盘符,根据你机器的情况而不同。

    最后要注意的是,驱动程序的调试非常麻烦,尤其是初始化部分,所以程序一定不能错,否则,你可能会不知所措。

 

原创粉丝点击