登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

DOS编程技术

讨论在纯DOS下的编程技术

 
 
 

日志

 
 
关于我

1984年大学毕业,1985年底有机会开始接触PC机,1986年开始在PC机上做开发工作,曾接触过MS-DOS、CP/M、UNIX、VMS、LINUX、iRMX等众多的操作系统并在上面从事技术开发,擅长做底层与硬件相关的软件开发,目前主要在DOS和LINUX平台下工作,主要从事软件,在硬件开发上也有一定造诣,亦有在8051系列、6502系列(凌阳)、z80系列、ARM、X86等各类平台下开发软硬件的经历。更详细情况可以参考http://resume.whowin.net

从命令行加载设备驱动程序  

2008-09-02 09:31:09|  分类: 设备驱动 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
    前面有文章提到过,在利用DOSUSB编写U盘的设备驱动程序时,遇到了一些困难,其主要问题就是DOSUSB是一个内存驻留程序,需要在命令行加载,而U盘的设备驱动程序是在config.sys中加载的,在加载驱动程序之前,DOSUSB还没有加载,所以无法使用,当然在加载我们编写的U盘驱动程序时就没有办法进行初始化,无奈之下只好开始编写自己的DOSUSB程序,不过这个过程相对要复杂,漫长的多,由于有其它工作要做,显然在近期无法完成。
    不过我们应该还有别的途径解决这个问题,我们遇到的问题是因为驱动程序的加载必须先于命令行,使DOSUSB的加载晚于U盘设备驱动程序的加载,那么如果我们能够从命令行加载设备驱动程序,这个问题不就解决了嘛!
    我们先来讨论一下这个做法的可行性,在这篇文章之前,我已经写了三篇关于设备驱动程序的文章,文章中已经介绍了设备驱动程序的结构和设备驱动程序的加载过程,下面我们简单回顾一下。
    设备驱动程序的结构:
  • 设备头,有指向下一个驱动程序的指针,设备属性,策略和中断例程的入口以及设备名
  • 策略例程,存储调用时的请求头的地址
  • 中断例程,根据请求头的内容执行相应的命令程序
    设备驱动程序的加载过程:
  • 将设备驱动程序调入内存
  • 构造一个初始化命令的请求头并调用设备驱动程序执行初始化命令
  • 如果是块设备,处理好DOS下的一些列表格,包括驱动器参数块DPB、当前目录结构CDS等
  • 根据返回的要求驻留部分的大小,为需要驻留部分分配内存
  • 将此设备驱动程序加入设备驱动程序链NUL设备的后面
    必须要说明的是,实际过程比这个要复杂一些,比如对块设备要分别调入设备驱动程序中的每一个块设备等等,我们为了说明问题,不把事情整那么复杂。
    加载驱动程序的前1、2、4步骤应该不是很困难,一般DOS在调入一个程序时,总是把可以分配的所有内存都分配给这个程序,在程序退出时在根据需要释放,我们也可以采取类似的办法把驱动程序整个文件调入内存;按照数据结构的要求构造一个初始化命令的请求头也应该不复杂,把ES:BX指向这个请求头就可以根据设备驱动程序的设备头分别调用设备驱动程序的策略和中断例程,这样,设备驱动程序在完成初始化命令后会在请求头里给我们返回它所希望驻留内存部分的大小,我们可以根据这个返回值重新调整内存将驱动程序驻留内存。
    第五部分我们应该也是可以实现的,我们在《如何写DOS下的设备驱动程序(二)》一文中写了一个程序可以列出所有的驱动程序,这个程序中我们利用DOS int 21h的52h功能成功第找到了NUL的设备头,既然我们可以找到这个设备头,当然就可以把我们这个驱动程序链接在后面;所以看上去从命令行加载设备驱动程序这个想法似乎是可行的。
    第三部分实际上是要对DOS内部有更深入的了解才可以做到,请参阅我的另外两篇文章:
    《DOS下驱动器参数块(DPB)》
    《当前目录结构(CDS)的格式说明》
    我想看完这两篇文章,问题也就解决了。

    实际上困难还没有完,我们在考虑调入驱动程序的时候不能忘记我们自己的程序,即我们调入驱动程序的程序,如果我们的设备驱动程序从config.sys中加载,DOS会处理自己的加载过程,那么在这里我们必须自己想清楚。
    首先,DOS在把我们的程序调入内存时,会像对待普通程序一样调入,也就是说会把所有的内存的控制权交给我们,但如果这样我们就无法再为设备驱动程序分配内存了,所以,我们要先剪裁自己,释放多余的内存后才能调入设备驱动程序;其二,DOS在调入我们的程序的时候,会把我们的程序尽量放在内存低端,然后我们调入设备驱动程序,这个驱动程序应该在我们的地址上方,那么我们程序退出时会怎样呢?会在DOS可分配内存和设备驱动程序之间留下一小块内存垃圾,这显然是不妥当的,因为我们每调入一个驱动程序就会留下这样一块垃圾;对这个问题我的解决方案是改变DOS的内存分配机制,在高端内存处为我们的程序分配一块内存,然后自己把自己的程序移动到这段内存,然后转到高端地址运行,并将DOS内存分配策略改回从低端分配,然后为驱动程序分配内存,这样在我们的程序退出时,内存会比较干净。
    说到这里,看上去我们已经把所有的问题都想明白了,其实不尽然,我们要写一个COM文件来从命令行调入设备驱动程序,我们知道,COM文件总是在100h这个地址上开始运行,从0--0ffh这一段DOS会建立一个叫做程序段前缀PSP的东西,这个东西可不是吃素的,如果我们不认真第处理好这个东西,那我们的程序是没有办法完美地退出的,而PSP这个东东又不是简单第搬移过去就行的,总之要记住,当我们把程序搬移到高端地址时,一定要记得处理好PSP。
    到了这里我们已经把大部分的问题都考虑到了,还有一些细节我们跳过去,比如命令行参数什么的,我们之调入那些没有参数的设备驱动程序,还有devicehigh=xxx.sys之类的我们不去考虑,我们只把设备驱动程序调入低端地址,下面我们描述一下我们要编的这个程序的要求:
  • 只调入没有参数的设备驱动程序,即只做device=xxx.sys,而不做device=xxx.sys yyy /z这种形式的设备驱动程序调入
  • 只把设备驱动程序调入低端地址,不考虑调入高端地址的情况
  • 对于块设备,不考虑设备驱动程序中有多个块设备的情况,默认一个设备驱动程序中只有一个块设备
  • 对于块设备,不考虑诸如扇区大小、是否有驱动器号可以分配等错误处理,降低程序复杂性
  • 在调入驱动程序时,不考虑环境变量,这样可以简化PSP处理和程序复杂性
  • 由于DOS个版本对驱动程序的调入有一定差异,我们只保证在DOS 6.22下完成(一般在DOS 5.0以上都不会有问题),不能保证在DOS 5.0一下的版本中能正常运行,也不能保证在DOS7.0以上的版本中能够正常运行(因为我从来不在这个版本下玩DOS,不太了解有没有什么变化)
    好,我们现在进一步理清楚我们的想法
  1. 我们要做一个.com的可执行程序,其功能是从命令行调入一个设备驱动程序,为了简单说明问题,我们对这个程序的功能做了一些限制,如上。
  2. 我们把这个程序的名称叫做loaddrv,考虑到其中有一些涉及DOS内部的复杂的操作,源程序使用汇编完成,源程序名为loaddrv.asm,可执行文件为loaddrv.com。
  3. 我们之所以强调采用.com的文件格式,是因为.com的文件是内存的直接映像,不像.exe文件那样需要进行重定位,这样在我们把程序自身从低地址搬移到高端地址(这个问题前面有交代),并继续运行时会省去很多的麻烦。
  4. 程序的执行方法是:loaddrv xxxxxx.sys,其中,xxxxxx.sys为设备驱动程序的名称。
    这篇文章的目的并不是完成一个完整的从命令行加载设备驱动程序的程序,写这篇文章的目的是为了日后我们可以在DOSUSB之后加载我们自己写的一个简单的U盘驱动程序,所以我们做了很多简化,不过在这个程序的基础上增加驱动程序中多设备单元的加载,以及接受驱动程序的参数两个功能,就基本上是一个完整的东西,读者自己完成吧。
    该文所涉及的源程序在下面下载:
    http://blog.whowin.net/source/loaddrv.zip
    源程序中有比较详细的注释,我们把其中比较大的步骤写在这里,以便让大家更加清晰思路。
    程序在结构上可以分成两个大部分,第一大部分是程序调入后正常运行的,这部分的最后把自身搬移到了内存的高端地址区域,然后赚到高端地址继续运行;第二大部分就是在高端地址运行的部分,加载驱动程序的主要工作是在第二大部分完成的。
    第一大部分的程序,从标号start开始,在源文件的最后,之所以写在最后,是因为这部分在低端地址运行完成后,没有必要搬移到高端地址,可以丢弃,放在后面,丢弃起来比较方便。
    第一大部分的程序步骤:
  1. 检查是否有命令行参数
    这部分利用PSP(程序段前缀),偏移地址从80h开始处的命令行参数来判断在loaddrv后面是否跟有驱动程序文件名。有关PSP的各种资料相对比较多,文中就不说了。
  2. 将参数全部转换成大写字母
  3. 跳过命令行参数前面的无效字符,找到设备驱动程序文件名的第一个字符
  4. 找到驱动程序文件名的结尾处,并计算出文件名的长度
  5. 把完整的驱动程序文件名搬移到缓冲区中,并在末尾加0
    把驱动程序文件名移到变量drvFilename中,在末尾加0构造成DOS可接受的文件名格式,以备在后面加载驱动程序时使用
  6. 检查文件是否存在
    这里有一个细节,由于在DOS中子目录实际上是以文件的形式存储的,所以仅仅判断文件可以打开是不够的,还要判断它不是子目录才行,具体见程序中的注释。20年前,我记得我发表过一篇文章,大致的名字叫《子目录的读取和修改》说的就是这个事,可惜我没有找到这篇文章的原稿。
  7. 裁剪程序自身占用的内存
    裁剪自身,释放不需要的内存空间,以便为自身在高端地址分配空间。因为DOS在调入程序时会把系统可用的内存的控制权全部交给程序,所以这一步必须要有,否则分配不到内存。
  8. 为本程序在高端地址分配内存
    分配内存方面,请注意DOS有一个内存分配策略问题,设置DOS的分配策略可以让DOS在分配内存时选择从低地址开始分配还是从高地址开始分配,使用DOS int 21h的58h功能调用。
  9. 在高端内存区建立新的PSP
    不是简单地把程序搬移过去就可以的,一定要为准备搬移过去的内存块建立新的PSP,使用DOS int21h的55h功能调用,要注意的是,DOS使用PSP的段地址作为进程ID,当我们在高端地址建立PSP时,DOS将把它建成是低端地址PSP的子进程,这在我们一会释放掉低端内存以后将会出现问题,因为新的PSP的父进程将消失,所以,我们在这里要修改一下PSP偏移为16h的字段,把原PSP的父进程填进去。
  10. 将程序搬移到到段内存
    我们从PSP的偏移80h处开始搬移,80h处正好是命令行参数的长度字段
  11. 修正内存控制块
    在另一篇介绍内存控制块MCB的文章中对这个东西有所介绍,当我们在高端地址为自己分配内存后,MCB中记录的这块内存是归属低端PSP的,使用DOS int 21h的4ch功能退出程序时,DOS会扫描内存MCB链,把所有属于这个PSP的内存块全部释放掉,为避免麻烦,我们必须修改一下这块内存的归属
  12. 把当前PSP设为新的PSP
    在开始在高端地址执行之前,让高端地址的PSP成为当前PSP,也就是当前进程,使用DOS int 21h的50h功能调用
  13. 在高端内存设置新的堆栈
  14. 使程序在高端地址继续运行
    把要执行的地址压入堆栈,然后用一条retf指令开始在新地址执行
    第二大部分的步骤如下:
  1. 释放原低端地址中程序所占用的内存
  2. 释放环境变量所占用的内存
    当DOS把一个程序调入内存并运行时,并不仅仅给程序分配一块内存然后把控制权交给程序这么简单,实际上它会先给这个程序分配一块内存放置环境变量,按道理,当我们在本地找不到要加载的设备驱动程序时,应该沿着环境变量指定的路径一直找下去,但为了简化程序,我们的程序中没有做这个工作,所以我们的程序中并没有用到环境变量,在这里仅仅要说明的时,如果使用DOS调用去退出程序,DOS会帮助你完成所有的工作,但如果自己释放程序,一定要记得把相关的环境变量内存块释放掉,免得产生内存垃圾。
  3. 获得系统可分配的最大内存段落
    这里技巧了一把,因为我们不知道系统可以分配的内存有多少,所以我们以最大值来分配内存,DOS调用肯定会出错,出错的同时,我们得到了系统可分配的最大内存
  4. 为设备驱动程序分配所有可以分配的内存
  5. 把设备驱动程序调入内存
    在这里我们采用了DOS调用的4B03H功能,这个调用明显给我们减少了很多麻烦,如果我们使用打开文件、读文件的方法,很难想象是否能考虑周全。
  6. 从DOS内部信息表中获取相关信息
    使用DOS调用中的一个为公开的调用52h来得到DOS的内部信息表,这个调用有另外的文章介绍,大家可以参考,如果没有这个调用,真不知道如何获得这些参数,我们需要偏移为[20h]、[21h]、[22h]、[10h]和[00h]处的数据,其含义可以参阅《DOS未公开功能52h说明》一文,在这里我们仅仅是先存储起来备用。
  7. 执行设备驱动程序的初始化命令
    在这个步骤中还要包括三个小步骤:构造设备请求头、建立适当的堆栈、调用设备驱动程序初始化功能。我们首先把要求驱动程序返回的地址压入堆栈,我们再把驱动程序中中断例程的入口地址压入堆栈,最后把驱动程序的策略例程入口地址压入堆栈,我们知道,调用驱动程序份两个步骤,首先把ES:BX指向请求头,然后调用驱动程序的策略例程,带驱动程序返回后,在调用驱动程序的中断例程,当我们安排号堆栈后,使用retf指令正好可以执行驱动程序的策略例程,当策略例程返回时会执行一条retf指令,这条指令并没有返回到我们的程序而是直接返回到了驱动程序的中断例程,等到中断例程在返回时,才返回到我们第一个压入堆栈的地址。
  8. 检查设备驱动程序初始化命令的执行情况
  9. 检查驱动程序支持的设备单元数量
  10. 计算驻留部分的内存
  11. 检查是否为块设备
    如果不是块设备,比较简单一些,直接跳到第15步即可
  12. 安装块设备前的检查
    至少要检查程序中的两点:是否有可分配的驱动器号和扇区大小是否超出系统允许范围
  13. 在驱动程序的后面建立一个DPB
    什么是DPB?请参阅文章《DOS下驱动器参数块(DPB)》。我们把DPB建立在驱动程序的后面,记得在将驱动程序驻留内存时一定要加上这块内存
  14. 设置CDS
    什么是CDS?请参阅文章《当前目录结构(CDS)的格式说明》。CDS的空间在DOS启动时已经预留好了,我们只要填一下就可以了。
  15. 将驱动程序驻留内存
  16. 修改驱动程序的MCB,使其属于自身
    和程序第一大部分的步骤11的原因一样,我们要适当第修改一下MCB。关于MCB,请参阅文章《内存控制块MCB说明》。
  17. 修改DOS内部参数的块设备数量参数
  18. 最后把驱动程序链接到NUL设备的后面
  19. 退出
    检验程序
    这个程序的实用性并不是很强,很多多于一个设备单元的设备驱动程序都无法加载,但作为今后加载我们自己的U盘驱动已经足够了,我们在《如何写DOS下的设备驱动程序(三)》这篇文章中编写了一个RAM DISK的设备驱动程序,这个程序和我们要完成的U盘的驱动程序最为相似,我们就以加载它为例来检验程序。
    首先在加载驱动程序前,先看一下内存的情况,结果如下:
C:>mem
Memory Type        Total  =   Used  +   Free
----------------  -------   -------   -------
Conventional         640K      121K      519K
Upper                  0K        0K        0K
Reserved               0K        0K        0K
Extended (XMS)    61,952K       64K   61,888K
----------------  -------   -------   -------
Total memory      62,592K      185K   62,407K

Total under 1 MB     640K      121K      519K

Largest executable program size       519K (531,440 bytes) 
Largest free upper memory block         0K       (0 bytes) 
MS-DOS is resident in the high memory area.
C:>
    然后我们开始加载RAMDISK.SYS驱动程序:
C:>loaddrv ramdisk.sys
Load Device Driver From Command Line.
Dos Ver5.0 or newer.

The Whowin Group 100k RAM Disk
  Drive = F:
C:>
    我们在看一下内存情况
C:>mem
Memory Type        Total  =   Used  +   Free
----------------  -------   -------   -------
Conventional         640K      228K      412K
Upper                  0K        0K        0K
Reserved               0K        0K        0K
Extended (XMS)    61,952K       64K   61,888K
----------------  -------   -------   -------
Total memory      62,592K      292K   62,300K

Total under 1 MB     640K      228K      412K

Largest executable program size       412K (421,504 bytes) 
Largest free upper memory block         0K       (0 bytes) 
MS-DOS is resident in the high memory area.
C:>
    我们看到,空闲内存从原来的519K变成了现在的412K,少了107K,要知道,我们调入的驱动程序是一个空间为100K的RAM DISK,其数据空间就有100K,加上驱动程序、目录区、引导区等,107K应该是合理的。
    我们在《如何写DOS下的设备驱动程序(二)》一文中曾经编写过一个程序可以列出所有的DOS下的驱动程序,我把这个程序叫showddh,我们来运行一下这个程序。
    C:>showddh
    Device Driver Entry (011C:0048)

    Next Device Driver     : 1E41:0000
    Device Attribute       : 8004
    Device Strategy offset : 0dc6
    Device Interrupt offset: 0dcc
    Device Driver Name     : NUL    


    Device Driver Entry (1E41:0000)

    Next Device Driver     : 0C5B:0000
    Device Attribute       : 2000
    Device Strategy offset : 003f
    Device Interrupt offset: 004a
    Device Driver Name     :       


    Device Driver Entry (0C5B:0000)

    Next Device Driver     : 02C1:0000
    Device Attribute       : 68c0
    Device Strategy offset : 00fe
    Device Interrupt offset: 0109
    Device Driver Name     :  [ HNi


    Device Driver Entry (02C1:0000)

    Next Device Driver     : 027B:0000
    Device Attribute       : c000
    Device Strategy offset : 6974
    Device Interrupt offset: 697f
    Device Driver Name     : SCSIMGR$


    Device Driver Entry (027B:0000)

    Next Device Driver     : 025D:0000
    Device Attribute       : a000
    Device Strategy offset : 004c
    Device Interrupt offset: 0057
    Device Driver Name     : XMSXXXX0

    ............
    没有列出所有的驱动程序,因为太长了,其中在NUL设备下面的被标成红色的部分就是我们加载的设备驱动程序。

    后记:
    老实说,这篇文章写得很艰难,也很生涩,不知道能有多少人能够看得进去,没有办法,所有的问题都无法回避,而且单靠这篇文章根本搞不懂源程序,所以如果希望搞懂它,一定要看下面这几篇文章:
  1. 《如何写DOS下的设备驱动程序(一)》
  2. 《如何写DOS下的设备驱动程序(二)》
  3. 《如何写DOS下的设备驱动程序(三)》
  4. 《DOS下驱动器参数块(DPB)》
  5. 《当前目录结构(CDS)的格式说明》
  6. 《内存控制块MCB说明》
  7. 《DOS未公开功能52h说明》
     另外,免不了还得找一些有关PSP的资料。


 



  评论这张
 
阅读(6628)| 评论(2)

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018