home *** CD-ROM | disk | FTP | other *** search
/ Oakland CPM Archive / oakcpm.iso / cpm / asmutl / meyertut.ark / MEYER06.TXT < prev    next >
Encoding:
Text File  |  1987-12-04  |  20.0 KB  |  459 lines

  1.                      CP/M Assembly Language
  2.                        Part VI: Disk Files
  3.                           by Eric Meyer
  4.  
  5.      By now you have a good understanding of the basics of 8080
  6. assembly language. It's time to learn to do something truly
  7. useful with it: how to read and write disk files.
  8.  
  9.  
  10. 1. BDOS File Access
  11.      The CP/M BDOS is set up to make access to disk files as easy
  12. as possible. You don't have to worry about where the file is on
  13. the disk, or any of the mechanics of reading and writing blocks
  14. of data. A small handful of BDOS functions take care of all these
  15. tasks:
  16.  
  17. 1.  SET DMA -- designate a 128 byte buffer to hold data;
  18. 2.  OPEN a file -- locate file and prepare to read or write; or
  19.      MAKE a file -- create a new file to write;
  20. 3.  READ or WRITE -- a record (128 bytes) of data (repeat as many
  21.      times as needed);
  22. 4.  CLOSE a file -- finish updating a file written to.
  23.  
  24.      Involved here are two "data structures", used to communicate
  25. with these BDOS functions: the DMA and the FCB.
  26.      The DMA (Direct Memory Access) is simply a small area of
  27. memory where the BDOS is to put (or find) the data that will be
  28. read (or written) when the file is accessed.
  29.      Data is read in units of 128 bytes, called "records". There
  30. are eight of these in 1K. You simply declare a region of memory
  31. to be the DMA area, by passing its address to the BDOS with the
  32. appropriate function call.
  33.      As it happens, the default CP/M DMA is at address 0080h near
  34. the bottom of memory, and you can just use that if you only need
  35. one DMA at a time; otherwise, you need to set your own DMA, which
  36. is as simple as:
  37.  
  38. LXI    D,MYDMA  ;point to the region you want
  39. MVI    C,26     ;function 26 sets the DMA
  40. CALL   BDOS     ;ask BDOS to do it
  41.  
  42.      (Remember declare BDOS EQU 0005H.)  You can reserve space
  43. for this (or any other) purpose with the assembler instruction
  44. DS, which we may not have mentioned before, e.g.,:
  45.  
  46. MYDMA:  DS     128      ; one record for DMA
  47.  
  48. sets up a 128-byte buffer at that point for use as a DMA. The
  49. initial contents of a DS block are undefined (unpredictable).
  50.  
  51.  
  52. 2. The FCB
  53.      The FCB (File Control Block) is a structure used by the BDOS
  54. to keep track of your position in a file when it's open.
  55. Basically it is a working copy of the directory entry for the
  56. file, and if you've ever snooped around a disk directory with DU
  57. or some such utility, it will look very familiar:
  58.  
  59. FCB:   d F I L E N A M E T Y P e x x x
  60.        x x x x x x x x x x x x x x x x
  61.        c r r r
  62.  
  63.      The FCB is 36 bytes long. The first byte ("d") is the number
  64. of the drive (00=logged drive, 01=A:, 02=B:, etc). The next 11
  65. bytes are the filename and type. The next byte ("e") is the
  66. current extent number (an "extent" is a unit of 16K; long files
  67. have several extents).
  68.      All the "x" bytes are used by the BDOS to keep track of the
  69. physical location of data on the disk. Their values don't concern
  70. us. The next byte ("c") is the current record number, within the
  71. current extent. And finally, the three bytes "rrr" are for a
  72. random record address (not ordinarily used when accessing a file
  73. sequentially, as we will be doing here).
  74.      You can set up any number of FCBs in your own program, to
  75. read and write files as needed. There is also a default CP/M FCB
  76. at address 005Ch, which is set up when you invoke a program with
  77. the name of any argument you give. For example, when you enter
  78. WordStar with a command like A>ws mytext.fil<cr>, CP/M sets up
  79. the default FCB at 005Ch in the following way:
  80.  
  81. 005C:  0 M Y T E X T _ _ F I L 0 x x x
  82.        x x x x x x x x x x x x x x x x
  83.        0 0 0 0
  84.  
  85.      Thus WordStar can look at this location to see whether you
  86. have given it a filename to open and edit. In this case it sees
  87. that you have: on the logged drive ("00"), in this case A:, the
  88. file MYTEXT.FIL. It can read this file from the beginning (note
  89. extent 0, record 0) just by pointing to this FCB and asking the
  90. BDOS to open the file.
  91.      A further complication arises when you run a program with
  92. two arguments, typically an input and an output file, for
  93. example:
  94.  
  95. A>pip newfil.txt=b:oldfil.txt<cr>
  96.  
  97.      The first file goes into the FCB at 005Ch; the second name,
  98. if present, is put at 006Ch.
  99.  
  100. 005C:  0 N E W F I L _ _ T X T 0 x x x
  101. 006C:  2 O L D F I L _ _ T X T 0 x x x
  102.        0 0 0 0
  103.  
  104.      As you can see, the second name is sitting in the middle of
  105. the default FCB!  What PIP has to do is to copy the second
  106. filename out to another FCB, which it constructs elsewhere. Then
  107. the default FCB at 005Ch can be used to write NEWFIL.TXT, and the
  108. other FCB (in PIP someplace) can be used to read B:OLDFIL.TXT.
  109.  
  110.  
  111. 3. Opening an Existing File
  112.      In order to read an existing disk file, all you have to do
  113. is "open" it. After constructing an appropriate FCB (or using the
  114. default one, if possible), you simply:
  115.  
  116. LXI    D,FCB   ;point to the FCB with D-E reg.
  117. MVI    C,15    ;Function 15 opens a file
  118. CALL   BDOS    ;ask BDOS to do it
  119.  
  120.      At this point, if the specified file existed, it can now be
  121. accessed with BDOS read/write functions, described below.
  122. However, it's also possible that the file didn't exist, in which
  123. case you do not want to go on and try to read or write to it!
  124.      All the BDOS file functions leave a "return code" in the A
  125. register, which you can then examine. For the OPEN, CLOSE, and
  126. MAKE functions, a code from 00-03 indicates success, while FFh
  127. indicates an error. So immediately after the BDOS call you should
  128. check this return code, and if you see FFh, exit with an error
  129. message of some sort. For example, you could do this:
  130.  
  131. CPI    0FFH    ;is the error code FF?
  132. JZ     IOERR   ;jump to i/o error routine if so
  133.  
  134.      In the case of the OPEN function, an error generally means
  135. the file was not found.
  136.  
  137.  
  138. 4. Making a New File
  139.      If you're creating a new file, the process is very similar:
  140. after creating the appropriate FCB, you simply:
  141.  
  142. LXI    D,FCB   ;point to the FCB with D-E
  143. MVI    C,22    ;Function 22 makes a new file
  144. CALL   BDOS    ;ask BDOS to do it
  145.  
  146.      Again, you had better check to see that the return code is
  147. not FFh before continuing to write data to your new file. An
  148. error means there was no room in the directory for another
  149. filename.
  150.  
  151.  
  152. 5. Closing a File
  153.      This is a bit out of order here, but: after you have
  154. finished writing data to a file, you need to "close" the file, to
  155. ensure that all the new data has actually been written to disk,
  156. and the directory has been updated accordingly. By now you can
  157. probably guess how this is done:
  158.  
  159. LXI    D,FCB   ;point to the FCB with D-E
  160. MVI    C,16    ;Function 16 closes a file
  161. CALL   BDOS    ;ask BDOS to do it
  162.  
  163.      Afterwards, check the return code. If it was FFh, the disk
  164. (or possibly the directory) filled up and some of your data could
  165. not be written.
  166.      NOTE: you only need to close a file that has been written
  167. to. Files opened for reading only need not (and probably
  168. shouldn't) be closed.
  169.  
  170.  
  171. 6. Reading and Writing Sequentially
  172.      Once a file is "open" (or "made") it can be read or written
  173. to. This can be done either randomly or sequentially. Random
  174. access is used when quick access to data is needed, and you are
  175. going to maintain some table or index to tell you in what record
  176. in the file a given individual entry can be found. (This is often
  177. done by database programs.)  Sequential access is far more
  178. common, though, and just goes from beginning to end of the file,
  179. reading or writing the whole thing. All you do is:
  180.  
  181. LXI   D,FCB                       LXI    D,FCB
  182. MVI   C,20   ;Read Seq.   OR      MVI    C,21   ;Write Seq.
  183. CALL  BDOS                        CALL   BDOS
  184.  
  185.      Each time the record of data in the current DMA (for the
  186. moment, at 0080h) is read from (or written to) the file, and the
  187. record count is updated to point to the next.
  188.      Again there are return codes. A return code of 00 indicates
  189. success. For READ, an error code of 01 means you've passed the
  190. end of the file, and there are no more records to read. Any other
  191. error code indicates a physical error (such as disk full).
  192.  
  193.  
  194. 7. Character Input: GETCH
  195.      Although the BDOS organizes disk i/o in terms of records of
  196. 128 bytes, this is almost never the unit of interest to you. If
  197. you are reading a text file, for example, you will be interested
  198. in the individual character, or possibly line, of text. Thus it
  199. will be convenient to write a pair of functions to "hide" the
  200. BDOS i/o activity, and just pretend that you're reading and
  201. writing one character at a time. We will call them GETCH and
  202. PUTCH. The usage will be:
  203.  
  204. ;Reading a character            ;Writing a character
  205. CALL  GETCH    ;get char in A   MVI   A,xx     ;put char in A
  206. JC    IOERR    ;jump if error   CALL  PUTCH    ;write to file
  207. CPI   1AH      ;is it EOF?      JC    IOERR    ;jump if error
  208. JZ    ISEOF    ;jump if EOF
  209.  
  210.      For now, both routines can only be used to read/write ONE
  211. file at a time. Here is the read routine:
  212.  
  213. ;Routine to get character from open file at GCFCB
  214. ;Returns char or EOF in A, and C for Error
  215. GETCH:  PUSH   H       ;save registers
  216.         PUSH   D       ;  (so GETCH will be easy
  217.         PUSH   B       ;   to use)
  218.         LDA    GCFLG   ;check EOF flag
  219.         CPI    0       ;is it clear?
  220.         JNZ    GCEOF   ;if not, at EOF
  221.         LDA    GCPOS   ;get position count
  222.         CPI    80H     ;is it up to 128?
  223.         JC     GCCHR   ;no, just go get char
  224.         LXI    D,GCDMA ;yes, need to read another record
  225.         MVI    C,26    ;set the DMA to GCDMA
  226.         CALL   BDOS    ;ask BDOS to do it
  227.         LXI    D,GCFCB ;use the FCB at GCFCB
  228.         MVI    C,20    ;read a record sequentially
  229.         CALL   BDOS    ;ask BDOS to do it
  230.         CPI    1       ;check return code
  231.         JZ     GCEOF   ;oops, 1: no more (end of file)
  232.         JNC    GCERR   ;argh, >1: physical error
  233.         STA    GCPOS   ;0: read successful, set GCPOS to 0
  234. GCCHR:  LXI    H,GCDMA ;point to DMA with H-L
  235.         MOV    E,A     ;move GCPOS into E
  236.         MVI    D,0     ;now D-E is 16-bit version of GCPOS
  237.         DAD    D       ;GCPOS+DMA points to next char
  238.         MOV    A,M     ;get that char into A
  239.         LXI    H,GCPOS ;point to GCPOS with H-L
  240.         INR    M       ;increment GCPOS to point to next
  241.         CPI    1AH     ;is it ^Z?
  242.         JZ     GCEOF   ;if so, record it
  243.         STC
  244.         CMC            ;clear Carry
  245.         JMP    GCRET   ;and return the character
  246. GCEOF:  MVI    A,1AH   ;EOF, get a ^Z
  247.         STA    GCFLG   ;set flag for future reference
  248.         STC
  249.         CMC            ;clear Carry
  250.         JMP    GCRET   ;return the ^Z
  251. GCERR:  MVI    A,1AH   ;ERROR, get a ^Z
  252.         STA    GCFLG   ;set flag
  253.         STC            ;and set Carry
  254. GCRET:  POP    B       ;restore
  255.         POP    D       ;  the
  256.         POP    H       ;    registers
  257.         RET            ;and return
  258. GCFLG:  DS     1       ;flag says EOF reached
  259. GCPOS:  DS     1       ;keep track of position in record
  260. GCDMA:  DS     128     ;DMA for GETCH, 128 bytes
  261. GCFCB:  DS     36      ;FCB for GETCH
  262.  
  263.      Let's discuss how this works. The key is the byte variable
  264. GCPOS, which runs from 00. . .7FH to keep track of which is the
  265. next byte in the record (in the DMA) to be read. GCPOS starts out
  266. at 80H, which indicates that a new record must be read in.
  267.      Once this is done, GCPOS is reset to 0. Then each time a
  268. character is needed, it is found by adding GCPOS to the DMA
  269. address, and GCPOS is incremented to point to the next.
  270.      When GCPOS reaches 80H again, the whole record has been
  271. used, and a new one must once again be read in.
  272.      Ordinarily, GETCH simply returns with the character read in
  273. A, and the C flag clear.
  274.      But once the end of the file has been reached (either no
  275. more records, or the EOF character 1Ah), GETCH returns with an
  276. EOF. (Note the variable GCFLG, a flag that is set non-zero once
  277. this occurs, so that GETCH will continue to return EOFs
  278. thereafter.)  And if an error is encountered trying to read the
  279. file, the C flag is returned.
  280.      You have seen most of these instructions before, but there
  281. are a couple of new ones here.
  282.      LDA and STA are rather like MOV A,M and MOV M,A:  they fetch
  283. or store the value in A to a memory address, but to the address
  284. you specify directly, instead of to the address in H-L.
  285.      Thus STA GCPOS stores the value in A to address GCPOS, and
  286. LDA GCPOS gets it back.
  287.      STC and CMC are the instructions to "set Carry" and
  288. "complement Carry"; they only affect the Carry flag.
  289.      There is no simple "clear Carry" instruction, which is what
  290. we really want to do here; you have to first set it, then
  291. complement it. ("Complement" means, roughly, "reverse".)
  292.  
  293.  
  294. 8. Character Output: PUTCH
  295.      Here now is the complementary character output routine:
  296.  
  297. ;Routine to write character in A to open file at PCFCB
  298. ;Returns C for write Error
  299. PUTCH:  PUSH   H       ;save registers
  300.         PUSH   D       ;  (so PUTCH will be
  301.         PUSH   B       ;   easy to use)
  302.         MOV    C,A     ;save the character in C
  303.         LDA    PCPOS   ;get position count
  304.         CPI    80H     ;is it up to 128?
  305.         JC     PCCHR   ;no, just go write char
  306.         PUSH   B       ;preserve character in C
  307.         LXI    D,PCDMA ;need to write the record out
  308.         MVI    C,26    ;set the DMA to PCDMA
  309.         CALL   BDOS    ;ask BDOS to do it
  310.         LXI    D,PCFCB ;use the FCB at PCFCB
  311.         MVI    C,21    ;write a record sequentially
  312.         CALL   BDOS    ;ask BDOS to do it
  313.         POP    B       ;restore character in C
  314.         CPI    0       ;check return code
  315.         JNZ    PCERR   ;argh, >0: physical error
  316.         STA    PCPOS   ;0: write successful, set PCPOS to 0
  317. PCCHR:  LXI    H,PCDMA ;point to DMA with H-L
  318.         MOV    E,A     ;move PCPOS into E
  319.         MVI    D,0     ;now D-E is 16-bit version of PCPOS
  320.         DAD    D       ;PCPOS+DMA points to next char
  321.         MOV    M,C     ;put outgoing char in its place
  322.         LXI    H,PCPOS ;point to PCPOS with H-L
  323.         INR    M       ;increment PCPOS to point to next
  324.         SUB    A       ;clear Carry flag, all is OK
  325.         JMP    PCRET
  326. PCERR:  STC            ;write error, set Carry
  327. PCRET:  POP    B       ;restore
  328.         POP    D       ;  the
  329.         POP    H       ;    registers
  330.         RET            ;and return
  331. PCPOS:  DS     1       ;keep track of position in record
  332. PCDMA:  DS     128     ;DMA for PUTCH, 128 bytes
  333. PCFCB:  DS     36      ;FCB for PUTCH
  334. ;
  335.  
  336.      By and large, this is very similar to GETCH.
  337.      Note the need to PUSH and POP the outgoing character, so it
  338. isn't lost when we do the BDOS calls to write a record
  339. periodically.
  340.      Also note that PCPOS starts off at 0, not 80H, because you
  341. don't need to write a record until it's full (whereas in GETCH,
  342. we needed to begin by reading a record).
  343.  
  344.  
  345. 9. Using GETCH and PUTCH
  346.      To use these routines properly, we will need a few routines
  347. to open, make, and close files properly for them. First, for
  348. GETCH, we need a routine to open the file for reading:
  349.  
  350. ;Routine to open a file for GETCH (takes DE=FCB)
  351. ;Returns C if file not found
  352. GCOPEN: MVI    A,80H    ;initialize GCPOS to 80H
  353.         STA    GCPOS    ;so first record will be read
  354.         SUB    A        ;and zero the EOF flag
  355.         STA    GCFLG
  356.         XCHG            ;put FCB address in HL
  357.         LXI    D,GCFCB  ;we'll move it into GCFCB
  358.         MVI    B,12     ;the drive/filename is 12 bytes
  359. GCL1:   MOV    A,M      ;fetch a byte from FCB
  360.         STAX   D        ;store it in GCFCB
  361.         INX    D        ;point to next
  362.         INX    H        ;in both places
  363.         DCR    B        ;count down on 12 bytes
  364.         JNZ    GCL1     ;loop if more
  365.         SUB    A        ;now get a 0
  366.         STAX   D        ;and put it into extent ("e")
  367.         STA    GCFCB+32 ;and also into record ("r")
  368.         LXI    D,GCFCB  ;point to start of GCFCB again
  369.         MVI    C,15     ;function 15 opens a file
  370.         CALL   BDOS     ;ask BDOS to do it
  371.         CPI    0FFH     ;check return code
  372.         CMC             ;now Carry is set if it was FFH
  373.         RET             ;return with Carry set if error
  374.  
  375.      This routine simply copies the filename from the FCB address
  376. given to it in the D-E register, into the GCFCB that will be used
  377. by GETCH; then it tries to open the file, returning with the C
  378. flag set if it could not.
  379.      There is one new instruction here. STAX D (and its relative,
  380. STAX B) are just like MOV M,A, except that they use the D-E (or
  381. B-C) registers as pointers instead of H-L. Similarly, there are
  382. instructions LDAX D and LDAX B, which load values as does MOV
  383. A,M. (Unfortunately, these arbitrary names make them look
  384. completely different.)
  385.      The routine to make a new file for use by PUTCH will be very
  386. similar. For now, it simply erases any pre-existing file of the
  387. given name; later we may add code to preserve such a file
  388. instead, possibly renaming it to a ".BAK" file.
  389.  
  390. ;Routine to make a file for PUTCH (takes DE=FCB)
  391. ;Returns C if cannot make file
  392. PCOPEN: SUB    A        ;initialize PCPOS to 0
  393.         STA    PCPOS    ;for beginning to write with PUTCH
  394.         PUSH   D        ;preserve FCB address
  395.         MVI    C,19     ;function 19 ERASES a file
  396.         CALL   BDOS     ;ask BDOS to do it
  397.         POP    H        ;recover FCB address into HL
  398.         LXI    D,PCFCB  ;we'll move it into PCFCB
  399.         MVI    B,12     ;the drive/filename is 12 bytes
  400. PCL1:   MOV    A,M      ;fetch a byte from FCB
  401.         STAX   D        ;store it in PCFCB
  402.         INX    D        ;point to next
  403.         INX    H        ;in both places
  404.         DCR    B        ;count down on 12 bytes
  405.         JNZ    PCL1     ;loop if more
  406.         SUB    A        ;now get a 0
  407.         STAX   D        ;and put it into extent ("e")
  408.         LXI    H,20     ;point ahead 20 bytes
  409.         DAD    D        ;HL now points to record ("r")
  410.         MOV    M,A      ;put 0 there, too
  411.         LXI    D,PCFCB  ;point to start of GCFCB again
  412.         MVI    C,22     ;function 22 makes a file
  413.         CALL   BDOS     ;ask BDOS to do it
  414.         CPI    0FFH     ;check return code
  415.         CMC             ;now Carry is set if it was FFH
  416.         RET             ;return with Carry set if error
  417.  
  418.      Note the use of BDOS function 19 to delete any file with the
  419. same name before we try to make the new file. We don't care about
  420. any error that may result: if the file did exist, it's now gone;
  421. if it didn't, the attempt to erase it failed, but that's OK too.
  422. Use function 19 with caution; we'll say no more about it here.
  423.      Finally, we need a routine to clean up after PUTCH, when
  424. we're all done writing, and close the file. The task is
  425. complicated by the fact that there may still be characters
  426. sitting in the PCDMA buffer, that need to be written before we
  427. close the file. Here is the routine:
  428.  
  429. ;Routine to close a file for PUTCH (no arguments)
  430. ;Returns C if cannot close file
  431. PCLOSE: MVI    A,1AH    ;get a 1AH (EOF char)
  432.         CALL   PUTCH    ;and write it to the file
  433.         LDA    PCPOS    ;now look at position in PCDMA
  434.         CPI    0        ;is it 00?
  435.         JZ     PCLFIL   ;if so, no data, just close file
  436.         LXI    D,PCDMA  ;need to write the last record out
  437.         MVI    C,26     ;set the DMA to PCDMA
  438.         CALL   BDOS     ;ask BDOS to do it
  439.         LXI    D,PCFCB  ;use the FCB at PCFCB
  440.         MVI    C,21     ;write a record sequentially
  441.         CALL   BDOS     ;ask BDOS to do it
  442.         CPI    0        ;check return code
  443.         JNZ    PCLERR   ;warn if error
  444. PCLFIL: LXI    D,PCFCB  ;all set, point to the FCB
  445.         MVI    C,16     ;function 16 closes a file
  446.         CALL   BDOS     ;ask BDOS to do it
  447.         CPI    0FFH     ;examine return code
  448.         CMC             ;Carry now set if was 0FFH
  449.         RET             ;return
  450. PCLERR: STC             ;set Carry for write error
  451.         RET             ;and return
  452.  
  453.      By now this should all be pretty self-explanatory.
  454.  
  455.  
  456. 10. Things To Come
  457.      Take a deep breath!  Next time we'll use these routines to
  458. construct "filter" programs that read and process text files.
  459.