ITEEDU

关于"顺序读"功能还有一个重要的问题需要讨论,那就是操作系统给我们的返回结果。前面我们讲过执行过顺序读功能后DOS会在AL寄存器中返回一些数据,这些数据反应了DOS执行功能的情况,具体的说明前面都已给出。我们现在要做的就是"动态"地研究一下这些返回的数据,为此我们又需要编制一个试验程序来跟踪分析,请看FCB6.ASM:

        DATA  SEGMENT
              ASSUME    DS:DATA
      MY_FCB  LABEL     BYTE
       DRIVE  DB        0	;驱动器号,0:当前 1:A 2:B ...
   FILE_NAME  DB        'CONFIG '	;文件名
    EXT_NAME  DB        'SYS'	;扩展名
   CUR_BLOCK  DW        ?	;当前记录块号
    REC_SIZE  DW        ?	;记录长度
   FILE_SIZE  DW        2 DUP(?)	;文件长度,由系统填入
   CREA_DATE  DW        ?	;建立或最后修改的日期,由系统填入
    POSITION  DB        10 DUP(?)	;保留空间,由系统填入
     CUR_REC  DB        ?	;当前记录号
     REL_REC  DW        2 DUP(?)	;相对记录号
        DATA  ENDS
			
        CODE  SEGMENT
              ASSUME    CS:CODE
        MAIN  PROC      FAR
              MOV       AX,DATA	;初始化DS寄存器
              MOV       DS,AX
			
              MOV       AH,0FH	;准备打开文件
              MOV       DX,OFFSET MY_FCB	;DX寄存器指向未打开的FCB
              INT       21H	;打开文件
              OR        AL,AL	;文件打开操作正确吗?
              JNZ       EXIT	;若不正确,转EXIT结束
			
              MOV       BX,DX	;BX寄存器指向已打开的FCB
              MOV       AX,[BX+10H]	;取得文件长度低字
              MOV       DX,[BX+12H]	;取得文件长度高字
              MOV       CX,[BX+0EH]	;取得记录长度
              DIV       CX	;计算文件包含的记录数
              ADD       AX,2	;记录数加2
              MOV       CX,AX	;将增大的记录数作为循环计数
              MOV       BP,DX
  READ_LOOP:
              MOV       AH,14H	;准备从文件中读入数据
              MOV       DX,BX	;DX指向打开了的FCB
              INT       21H	;读入一个记录
              OR        AL,AL	;正确读入了吗?
              JNZ       ERR_EXIT	;若数据未正确读入,转ERR_EXIT
              LOOP      READ_LOOP	;继续读入下一个记录
   ERR_EXIT:
              LOOP      READ_LOOP	;继续读入
       EXIT:
              MOV       AH,4CH	;结束程序
              INT       21H
        MAIN  ENDP
        CODE  ENDS
              END       MAIN
  

下面是这个程序的跟踪分析结果:

C:\ASM\>DEBUG FCB6.EXE[Enter]
g=0 24[Enter]循环计数 数据的余数
AX=0007 BX=0000 CX=0007 DX=001B SP=0000 BP=001B SI=0000 DI=0000
DS=2589 ES=2579 SS=2589 CS=258C IP=0024 NV UP EI PL NZ NA PO NC
258C:0024 B414 MOV AH,14
  

笔者的CONFIG文件有667个字节,我们取记录长度为默认值128字节,这里我们可以看到程序计算出CONFIG文件包含5个记录零1BH个字节。为了能分析出错情况,我们将算出的记录数加了2并将其置入CX寄存器做为循环计数。

g=24 30[Enter]
AX=1403 BX=0000 CX=0002 DX=0000 SP=0000 BP=001B SI=0000 DI=0000
DS=2589 ES=2579 SS=2589 CS=258C IP=0030 NV UP EI PL NZ NA PE NC
258C:0030 E2F2 LOOP 0024
  

一次执行全部循环,可以看到AL寄存器返回的第一的错误码是03H,这个错误码的含义是:只读取了"部分"数据,我们看一看究竟在读取哪个记录时出现了这个错误,列出FCB的内容:

-d0 24
15F1:0000 03 43 4F 4E 46 49 47 20-20 53 59 53 00 00 80 00 .CONFIG?SYS....
15F1:0010 9B 02 00 00 38 21 4C 85-40 02 42 93 9B 61 B8 04 ....8!L.@.B..a..
15F1:0020 06 00 00 00 00 								  .....
注意当前记录号
  

当前记录号为06H,在上一次读取数据时它应该是05H,也就是说程序在读取第六个记录时出现了问题。我们知道这个文件只有5个完整的记录,第六个记录只有1BH个字节,这样一来错误码03H就很好理解了:若一个文件所剩的数据不足一个完整记录时,读取这些数据就会返回03H错误码。换言之,这个错误码告诉我们现在已经读到文件末尾了。我们列出读取到的数据,看看是不是如我们分析的那样:

-des:80     带下划线的数据恰好为1BH个
2579:0080	0D 0A 0D 0A 5B 43 4F 4D-4D 4F 4E 5D 0D 0A 53 57		....[COMMON]..SW		
2579:0090	49 54 43 48 45 53 3D 2F-46 0D 0A 00 00 00 00 00		ITCHES=/F.......		
2579:00A0	00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00		................		
2579:00B0	00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00		................	
2579:00C0	00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00		................		
2579:00D0	00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00		................			
2579:00E0	00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00		................
2579:00F0	00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00		................
  

可以看到DTA中只有前1BH个字节是文件中的内容,其余的都是0。这就是03H错误码所说明的问题。
跟踪至此这个文件的内容已经全部读取过了,这时FCB中的"当前记录号"为06H。在这种情况下如果我们继续执行"顺序读"功能会发生什么情况呢?

-t

AX=1403 BX=0000 CX=0001 DX=0000 SP=0000 BP=001B SI=0000 DI=0000

DS=2589 ES=2579 SS=2589 CS=258C IP=0024 NV UP EI PL NZ NA PE NC
258C:0024 B414 MOV AH,14
-g=24 30
AX=1401 BX=0000 CX=0001 DX=0000 SP=0000 BP=001B SI=0000 DI=0000
DS=2589 ES=2579 SS=2589 CS=258C IP=0030 NV UP EI PL NZ NA PO NC
258C:0030 E2F2 LOOP 0024
  

DOS返回了另一个错误码01H,含义是已经读到了文件末尾。在这种情况下FCB和DTA中的数据会有什么变化呢?

-des:80
2579:0080	0D 0A 0D 0A 5B 43 4F 4D-4D 4F 4E 5D 0D 0A 53 57		....[COMMON]..SW
2579:0090	49 54 43 48 45 53 3D 2F-46 0D 0A 00 00 00 00 00		ITCHES=/F.......
2579:00A0	00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00		................
2579:00B0	00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00		................
2579:00C0	00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00		................
2579:00D0	00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00		................
2579:00E0	00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00		................
2579:00F0	00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00		................
-d0 24
15F1:0000	03 43 4F 4E 46 49 47 20-20 53 59 53 00 00 80 00		.CONFIG?SYS....		
15F1:0010	9B 02 00 00 38 21 4C 85-40 02 42 93 9B 61 B8 04		....8!L.@.B..a..
15F1:0020	06 00 00 00 00										.....
  

可以看到DTA和FCB中的数据没有变化。至此关于错误码01H和03H的详细情况我们就已经全部搞清楚了,并没有什么很深奥的内容。那么错误码02H会在什么情况下返回呢?关于这个错误码我们给出的解释是"数据传输区超界",我们先给出一个假想的解释:当DTA的容量不足以装下一个完整的记录时,DOS就会返回这个错误码。这个解释是否正确,还要靠我们编制程序试验。在这里我不再给出试验程序,大家可以自己编制出来。

有一点还要提醒大家注意,那就是缺省的DTA究竟有多大?回顾一下前面讨论的1AH功能,当使用这个功能设置新的DTA时DOS并未要求程序给出DTA的容量,那么是否可以说DOS会把从程序给出的DTA起始地址直至DTA所在段的末尾全部当做数据传输区呢?笔者分析是这样的。

笔者这话说的不很肯定,因为笔者没有发现哪本书讨论过这个问题。笔者曾经试着把FCB中的记录长度设为129字节,然后利用缺省DTA读入一个记录,结果并未出现错误,第129个字节也被读入了。由此才得出了这样一个结论。那么什么情况下会出现DTA超界的错误,笔者认为如果把DTA设在接近一个段的边界时就有可能出现这样的错误。

讨论到此关于"顺序读"功能大体上已经全部讲解完毕,按下来的任务就是研究如何将数据写入文件,这要用到DOS的一个新功能--顺序写。

功能号:15H
用 途:将数据写入文件
参 数:DS:DX = 打开的FCB起始地址
调 用:INT 21H
返 回:AL = 0 -- 写成功
AL = 1 -- 盘已满(无法写)
AL = 2 -- 写失败

关于这个功能没有什么需要更多说明的,它和"顺序读"功能在用法上相近。我们给出一个示例程序FCB7.ASM演示这个功能的应用,通过这个程序你可以很好地掌握"顺序读写"功能的应用。这个程序的功能是将一个文本文件的内容完全变成小写后存入另一个文件中:

FCB7.ASM
        DATA  SEGMENT
              ASSUME    DS:DATA
        FCB1  LABEL     BYTE
      DRIVE1  DB        0	;驱动器号,0:当前 1:A 2:B ...
  FILE_NAME1  DB        'CONFIG '	;文件名
   EXT_NAME1  DB        'SYS'	;扩展名
  CUR_BLOCK1  DW        ?	;当前记录块号
   REC_SIZE1  DW        ?	;记录长度
  FILE_SIZE1  DW        2 DUP(?)	;文件长度,由系统填入
  CREA_DATE1  DW        ?	;建立或最后修改的日期,由系统填入
   POSITION1  DB        10 DUP(?)	;保留空间,由系统填入
    CUR_REC1  DB        ?	;当前记录号
    REL_REC1  DW        2 DUP(?)	;相对记录号
			
        FCB2  LABEL     BYTE
      DRIVE2  DB        0	;驱动器号,0:当前 1:A 2:B ...
  FILE_NAME2  DB        'CONFIG '	;文件名
   EXT_NAME2  DB        'TXT'	;扩展名
  CUR_BLOCK2  DW        ?	;当前记录块号
   REC_SIZE2  DW        ?	;记录长度
  FILE_SIZE2  DW        2 DUP(?)	;文件长度,由系统填入
  CREA_DATE2  DW        ?	;建立或最后修改的日期,由系统填入
   POSITION2  DB        10 DUP(?)	;保留空间,由系统填入
    CUR_REC2  DB        ?	;当前记录号
    REL_REC2  DW        2 DUP(?)	;相对记录号
        DATA  ENDS
			
        CODE  SEGMENT
              ASSUME    CS:CODE
        MAIN  PROC      FAR
              MOV       AX,DATA	;初始化DS寄存器
              MOV       DS,AX
			
              MOV       AH,0FH	;打开FCB1指示的文件
              MOV       DX,OFFSET FCB1
              INT       21H
              OR        AL,AL	;打开操作正确吗?
              JNZ       ERR_EXIT	;未正确打开文件,转ERR_EXIT
              MOV       AH,0FH	;打开FCB2指示的文件
              MOV       DX,OFFSET FCB2
              INT       21H
              OR        AL,AL	;打开操作正确吗?
              JZ        READ_REC	;打开正确,转READ_REC
			
              MOV       AH,16H	;建立FCB2指示的文件
              MOV       DX,OFFSET FCB2
              INT       21H
              OR        AL,AL	;文件正确建立了吗?
              JNZ       ERR_EXIT	;文件未正确建立,转ERR_EXIT
   READ_REC:
              MOV       AH,14H	;从FCB1指示的文件中读入一个记录
              MOV       DX,OFFSET FCB1
              INT       21H
              OR        AL,AL	;数据正确读入了吗?
              JZ        L_TO_U	;数据正确读入,转L_TO_U
              CMP       AL,3	;读入了一部分数据吗?
              JNZ       ERR_EXIT	;其它错误,转ERR_EXIT
     L_TO_U:
              MOV       CX,128	;将128个字符转换成小写
              MOV       DI,80H	;DI指向DTA首
  NEXT_CHAR:
              MOV       AL,ES:[DI]	;取得一个字符
              CMP       AL,'A'	;这个字符是"A"吗?
              JB        NOT_UPPER	;比"A"小则转NOT_UPPER
              CMP       AL,'Z'	;这个字符是"Z"吗?
              JA        NOT_UPPER	;比"Z"大则转NOT_UPPER
              ADD       AL,20H	;将字符转换成小写
  NOT_UPPER:
              STOSB     ;将字符送回DTA
              LOOP      NEXT_CHAR	;处理下一个字符
			
              MOV       AH,15H	;选择DOS的15H功能
              MOV       DX,OFFSET FCB2	;DX指向打开的FCB2
              INT       21H	;将处理过的字符写入FCB2指示的文件
              OR        AL,AL	;数据正确写入了吗?
              JNZ       ERR_EXIT	;数据写入不正确,转ERR_EXIT
			
              JMP       READ_REC	;转READ_REC继续处理下一个记录
   ERR_EXIT:
              MOV       AH,4CH	;结束进程
              INT       21H
        MAIN  ENDP
        CODE  ENDS
              END       MAIN
  

我们使用了两个FCB打开两个文件,第一个FCB用于"源文件"CONFIG.SYS,第二个用于"目的文件"CONFIG.TXT。程序从"源文件"中读入数据,转换成小写后写入"目的文件",如此循环,直至读数据时DOS返回了错误码01H为止。这个程序的思路很清楚,无需过多解释。大家可以在DOS下直接执行这个程序,看看有何结果。

不知道各位读者看到的结果和笔者看到的是否一样,执行此程序后,当前目录下出现了一个新的文件--CONFIG.TXT,但这个文件的长度为0,它没有实质内容。

出现这样一个结果的原因稍有些复杂,我们先来看看如何解决这个问题。很简单,执行完所有的"顺序写"操作后将"目的文件"CONFIG.TXT关闭即可。DOS提供的关闭文件功能如下所述:

功能号:10H
用 途:关闭一个已打开的文件
参 数:DS:DX = 打开的FCB起始地址
调 用:INT 21H
返 回:AL = 0 -- 成功关闭文件
AL = 0FFH -- 关闭失败

这个功能和"打开文件"功能正相反,但AL返回的状态码含义是一样的。我们现在利用这个功能将上面的程序中标号ERR_EXIT后面部分改成下面这样:

   ERR_EXIT:
              MOV       AH,10H	;利用DOS的10H功能
              MOV       DX,OFFSET FCB2	;将FCB2指示的文件关闭
              INT       21H
			
              MOV       AH,4CH	;结束进程
              INT       21H
  

重新编译程序并执行,这时就会看到当前目录下出现的CONFIG.TXT文件长度不再是0了。而且用TYPE命令确实可以看到其中的字符全部是小写。

不知道你的"源文件"有多大,如果你的CONFIG文件长度不是恰好凑够整数个记录的话那么你会发现生成的目的文件长度和源文件不等。这个问题我们暂且忽略,现在先来讨论一下为什么将文件关闭后就能获得正确的结果。

"关闭文件"其实有两个作用,第一是将DOS的文件缓冲区中的内容写入磁盘中;第二是在磁盘的目录表中建立一个具有正确特征信息的目录项。我先来谈一谈DOS的文件缓冲区是怎么回事。如果你很熟悉如何建立CONFIG文件,那么在你建立一个新的配置文件时一定不会忘记使用"BUFFERS=xx"设置DOS的缓冲区数。很多DOS手册都指出了设置合适的缓冲区数可以提高DOS的磁盘操作效率,这个道理从何而来?我们现在就要搞清楚DOS的缓冲区是什么样的结构。

在系统启动的过程中DOS会根据CONFIG文件中设置的缓冲区数在内存中保留一部分空间做为缓冲区,每个缓冲区有528个字节长,其中16个字节用于保存一些控制信息,DOS用另外512个字节保存从磁盘中读入或写出的数据。