Die nachfolgende Zusammenstellung betrifft den sogenannten DOS-Interrupt INT 21h.
Die Beschreibung gilt bis MS-DOS Version 6 einschließlich. Ich hatte mir diese Zusammenstellung gemacht, als ich mich seinerzeit mit dem 8086-Assembler beschäftigt hatte.
Aufruf und Fehlercodes bei den READ- und WRITE-FunktionenDer Aufruf läuft nach folgendem Schema ab:
Die CP/M-kompatiblen Funktionen verwenden AL zur Kennzeichnung von Fehlern. Die neueren Funktionen melden mit dem gesetzten Carryflag (CF), dass ein Fehler aufgetreten ist. Falls ja, steht im Register AX eine Fehlernummer. Fehlercodes bei den READ- und WRITE-Funktionen (READ-Funktionen: 14H, 21H, 27H und WRITE-Funktionen: 15H, 22H, 28H):
Aufbau des Attributbytes:
Directoryeinträge:
Das 1.Byte des Dateinamens hat folgende Sonderbedeutung: DTA / FCBDie CP/M kompatiblen Diskfunktionen verwenden File Control Blocks (FCB)s.Da der FCB keine Möglichkeit der Pfadangabe hat, beziehen sich diese Funktionen immer auf das aktuelle Directory. Als Wildcard-Angabe für die CP/M-kompatiblen Funktionen ist nur das ? möglich, nicht das *. Sämtlicher Datentranfers findet bei den FCB-behafteten Funktionen über die Disk Transfer Address (DTA) statt. Die DTA ist die Anfangsadresse eines beliebigen Puffers im Hauptspeicher. Die maximale Größe ist 64 K Byte. Effektiv genutzt werden kann nur der Bereich zwischen dem Offset der DTA und 0FFFFH. Hatte z.B. die DTA die Adresse 3000:FFF0H so könnten maximal 16 Bytes genutzt werden. Als Default-DTA-Adresse gibt MS-DOS DS:80H vor. Da die Programme bei DS:100H beginnen, ist der Puffer somit 128 Bytes lang. Die DTA-Adresse befindet sich im Program-Segment Prefix (PSP), welches den Anfang eines jeden Programmes bildet. Im PSP befinden sich an den Offsets 5CH und 6CH ebenfalls zwei ungeöffnete FCBs. Vor Verwendung bitte die hier gemachten Angaben zum FCB prüfen, ggf. korrigieren! normaler FCB: drive db ? ;gewähltes Laufwerk, 0= aktuelles, 1=A, usw. fname db 8 dup (' ') ;Dateiname, linksbündig mit Space aufgefüllt fext db 3 dup (' ') ;Erweiterung, linksbündig mit ;Space aufgefüllt curblk dw 0 ;Nr. des aktuellen Blockes recln dw 0 ;logische Satzlänge (default: 80H) fsize dd 0 ;Dateilänge in Bytes fdate db 0,0 ;Dateidatum gepackt, jjjj jjjj mmmt tttt ftime db 0,0 ;Dateizeit gepackt, hhhh hmmm mmms ssss reserv db 8 dup (0) ;intern von DOS benutzt currec db 0 ;aktueller Satz in curblk relrec dd 0 ;relative Satznr. bei Randomverarbeitung ;keine Vorbelegung durch OPEN, ;benutzt werden: ;4 Byte bei reclen >64 bytes ;3 byte bei reclen <=64 Bytes erweiterter FCB: dieser Kopf wird beim erweiterten FCB vor dem normalen FCB platziert: flag db 0ffh ;Muss-Kennzeichner für ;erweiterten FCB dummy db 5 dup (' ') ;reserviert für zukünftige ;Benutzung fattr db ? ;Attributbyte spezieller FCB für Funktion 17 (Umbenennen von Dateien) drive db ? ;gewähltes Laufwerk, ;0= aktuelles, 1=A, usw. afname db 8 dup (' ') ;alter Dateiname, linksbündig aext db 3 dup (' ') ;alte Erweiterung, linksbündig db 5 dup (0) ;reserviert nfname db 8 dup (' ') ;neuer Dateiname, linksbündig next db 3 dup (' ') ;neue Erweiterung, linksbündig db 9 dup (0) ;reserviert Beispiel zur FCB-Behandlung:comment # Zweck: Rename eines Unterverzeichnisses Aufruf: rendir Altname Neuname Beide Dateinamen dürfen nur die reinen Dateinamen sein, da mit einem FCB gearbeitet wird Quelle: c't 1988, Heft 6, Seite 204 mit Modifikationen # .286c dbl macro text ;;define a line db text,cr,lf endm jmps macro to ;;kurzer Sprung jmp short to endm prstr MACRO TEXT ;;PRINT STRING CALL PRMSG DB TEXT,0 ENDM cr equ 0dh lf equ 0ah dos equ 21h lparm equ 0080h ;Parameterlänge parm equ 0081h ;Parameter code segment assume cs:code, ds:code, es:code org 100h start: mov cl,byte ptr cs:lparm xor ch,ch jcxz leer ;kein Parameter cld ;Altname als 1. Parameter suchen mov di,parm mov al,' ' repe scasb ;führende blanks überlesen je leer ;nur blanks ! lea si,[di-1] ;Anfang Altname nach DI repne scasb ;nächstes Blank als Ende Altname suchen jne leer ;Neuname fehlt mov byte ptr[di-1],0 ;Markieren Ende Altname repe scasb ;weitere blanks überlesen je leer ;kein Neuname angegeben lea dx,[di-1] ;Anfang Neuname nach DX add di,cx ;hinter Neuname gehen mov byte ptr[di],0 ;Markieren Ende Neuname jmps name_to_fcb leer: prstr 'Usage: RENDIR Altname Neuname' jmp ret1 ;Altname in FCB eintragen name_to_fcb: lea di,fcb.fname call tofcb jc fdrive ;Fehler im Namen ;Neuname in FCB eintragen mov si,dx lea di,fcb.nfname call tofcb jc fdrive ;Fehler im Namen ;Rename versuchen mov fcb.attr,10h ;Attribut SubDir mov ah,17h ;Rename mit FCB lea dx,fcb int dos or al,al ;fehlerfrei? jnz nicht_ok ;nein jmp ret0 fdrive: or al,al ;Fehlermeldung ermitteln jne fdrive1 ;und ausgeben jmp mdrive fdrive1: prstr 'alter oder neuer Name zu lang !' jmp ret1 nicht_ok: call prmsg dbl 'SubDir ist nicht im aktuellen Directory' db 'oder den neuen Namen gibt es schon' db 0 jmp ret1 mdrive: prstr 'Laufwerks- bzw. Pfadangabe nicht möglich !' jmp ret1 tofcb proc near comment # Übernahme von Name und Erweiterung aus der Kommandozeile in den FCB input <si>=Beginn des Dasteinamens, mit 0 beendet <di>=Ziel für Dateiname in FCB output cf=0 Name und Erweiterung in FCB eingetragen cf=1 : al=0 Name enthält Laufwerks- oder Pfadangaben al=1 Nume+Erw. zu lang (>11 Byte) # mov cx,8 ;max. Länge für Name lea bx,[di+8] ;Offset für Erweiterung toloop: lodsb or al,al ;Ende des Namens? je toret ;ja. fetig mit cf=0 cmp al,'.' ;Erweiterung? jne to10 mov di,bx ;ja- ab jetzt in Erweiterung eintragen mov cx,3 jmps toloop to10: cmp al,':' ;Laufwerk ? je toerror cmp al,'\' ;Pfad? je toerror stosb loop toloop lodsb or al,al ;Ende mit 0? jz toret ;ja, dann OK mov al,1 ;Name oder Erweiterung zu lang jmps tofehler toerror: mov al,0 tofehler: stc toret: ret tofcb endp ;--------------- sfcb struc ;Struktur erweiterter FCB db 0ffh ;Flag für erweitereten FCB db 5 dup (0) attr db ? drive db 0 fname db ' ' ;alter Name fext db ' ' ;alte Erweiterung db 5 dup (0) nfname db ' ' ;neuer Name nfext db ' ' ;neue Erweiterung db 15 dup (0) sfcb ends fcb sfcb >< ;--------------------- ;Unterprogrammsammlung ;--------------------- ;PRINT MESSAGE FOLLOWING CALL PRMSG AND ENDING WITH 0 PRMSG: mov word ptr PRMSG2,di POP di CALL PSTR PUSH di ;Returnadr. mov di,0 ;mod PRMSG2 EQU $-2 RET ;PRINT MESSAGE ADRESSED BY di, ENDING WITH 0 PSTR: mov dl,[di] INC di cmp dl,0 jz pstr99 ;FALLS FERTIG mov ah,02 int 21h jmp short PSTR pstr99: ret ;--------------- ;diverse Programmbeendigungen ret0: mov al,0 jmps raus ret1: mov al,1 ;Programmende raus: prstr <cr,lf> mov ah,4ch int dos code ends end start HandlesUm eine Dateinummer zu erhalten, muss die Datei mit einer UNIX-kompatiblen Funktion (3CH, 3DH, 5AH, 5BH oder bei langen Dateinamen mit einer der Unterfunktionen 71xxh ) eröffnet bzw. eingerichtet werden. Hierzu übergibt man den Funktionen den Dateipfad. MS-DOS liefert als Ergebnis den HANDLE (=Dateinummer) zurück. Alle weiteren Zugriffe zu dieser Datei, bis hin zum Schließen, werden durch die Angabe des Handles gesteuert.5 Handles sind vordefiniert: Handle Bezeichnung Gerät ---------------------------------------- 0 STDIN Standard_Input Tastatur 1 STDOUT Standard_Output Bildschirm 2 STDERR Standard_Error Bildschirm 3 STDAUX Standard_Auxiliary RS-232 4 STDPRN Standard_Printer Drucker Beispielmacros zur Handle-Dateibehandlung:Zur Anwendung vergleiche das Beispielprogramm. Die Beispielmacros sind in der Datei firm.mac enthalten, die unter Routinensammlung für COM-Programme eingesehen und heruntergeladen werden kann.Bei den Beispielmacros wird davon ausgegangen, dass die Handlenummern in der durch den Parameter fhand spezifizierten Speicherstelle stehen. Eine Angabe der Handlenummer als Direktwert ist somit bei diesen Macros nicht möglich. dos equ 21h ;ldreg is an "inner" macro. ;It allows the caller of a macro to specify a register ;or a literal value as the parameter to the macro. ;This macro loads the correct register specified ;by the caller of the macro with the contents of ;the register, literal or ;memory location also specified. ;No code will be generated if the two ;parameters are ;the same. ldreg macro destreg,source ifdif <destreg>,<source> mov destreg,source endif endm ;--------------------------- ;handle-dateibehandlung ;--------------------------- fopen macro fname,acode ;;open file ifdif <dx>,<fname> mov dx,offset fname ;;address of file name endif ldreg al,acode ;;access code ;;0=read, ;;1=write, ;;2=read & write mov ah,3dh ;;funtion call open file int dos ;;returns fhandle in ax endm fcrat macro fname,attrb ;;create a file (eine alte Datei ifdif <dx>,<fname> ;;gleichen Namens wird ;;dabei gelöscht). Die erzeugte Datei ;;ist geoeffnet fuer Lesen und Schreiben. mov dx,offset fname ;;address of file name endif ldreg cx,attrb ;;attribute mov ah,3ch ;;funtion call create file int dos ;;returns fhandle in ax endm fwrit macro fhand,wbuff,count ;;write to file ldreg bx,fhand ;;address of file name ldreg cx,count ;;attribute ifdif <dx>,<wbuff> mov dx,offset wbuff ;;address of write buffer endif mov ah,40h ;;funtion write file int dos endm fclose macro fhand ;;close file ldreg bx,fhand ;;file handle mov ah,3eh ;;function call close file int dos endm fread macro fhand,rbuff,count ;;read from file ldreg bx,fhand ;;get file handle ldreg cx,count ;;get this many byte(s) ifdif <dx>,<rbuff> mov dx,offset rbuff ;;address of read buffer endif mov ah,3fh ;;function read handle int dos endm PSP und Umgebungsblock (Environment Block)Der PSP ist 256 Bytes lang und beginnt stets vor dem Programm.Beispiel eines PSP (in diesem Fall stehen ab Adresse 00A2 Überreste alter Befelszeilen): D:\dos\ML\Test>debug test.com datei1.dat datei2.dat PARAMETER -d0 ff 18A0:0000 CD 20 FF 9F 00 9A F0 FE-1D F0 4F 03 F3 12 8A 03 . ........O..... 18A0:0010 F3 12 17 03 F3 12 E2 12-01 01 01 00 02 FF FF FF ................ 18A0:0020 FF FF FF FF FF FF FF FF-FF FF FF FF 30 09 4C 01 ............0.L. 18A0:0030 B3 17 14 00 18 00 A0 18-FF FF FF FF 00 00 00 00 ................ 18A0:0040 05 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 18A0:0050 CD 21 CB 00 00 00 00 00-00 00 00 00 00 44 41 54 .!...........DAT 18A0:0060 45 49 31 20 20 44 41 54-00 00 00 00 00 44 41 54 EI1 DAT.....DAT 18A0:0070 45 49 32 20 20 44 41 54-00 00 00 00 00 00 00 00 EI2 DAT........ 18A0:0080 20 20 64 61 74 65 69 31-2E 64 61 74 20 64 61 74 datei1.dat dat 18A0:0090 65 69 32 2E 64 61 74 20-50 41 52 41 4D 45 54 45 ei2.dat PARAMETE 18A0:00A0 52 0D 41 52 41 4D 45 54-45 52 0D 00 00 00 00 00 R.ARAMETER...... 18A0:00B0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 18A0:00C0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 18A0:00D0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 18A0:00E0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 18A0:00F0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ Hinweise zur folgenden Tabelle:
NAME=Parameter Sein Ende wird durch ein weiteres Nullbyte hinter der abschließenden Null der letzten Zeichenkette markiert. Falls ein Prozess ohne Environment auskommt, besteht der Umgebungsblock aus einem Wort mit dem Inhalt 0000h. Der erste String im Umgebungsblock ist der Name des verwendeten Kommandointerpreters. Weitere Eintragungen im Umgebungsblock stammen aus dem SET-Kommando wie in in der AUTOEXEC.BAT bzw. deren Nachfolgern angewendet wird. Ab MS-DOS 3.0 folgen auf den Umgebungsblock weitere Initalisierungsparameter. Deren Anzahl wird durch ein Wort angekündigt. Es folgt der Dateiname des aufgerufenen Programmes, ggf. ergänzt um Pfadnamen und Laufwerksnamen.
Das folgende Beispiel veranschaulicht, wie man mit DEBUG den Sachverhalt überprüfen kann. Auf Adresse 2ch des PSP steht die Segmentadresse des Umgebungsblockes (zuerst das wertniedrige Byte, dann das werthohe Byte). D:\DOS\ML\TEST>debug test.com PARAMETER -d20 2f 1932:0020 FF FF FF FF FF FF FF FF-FF FF FF FF 30 09 4C 01 ............0.L. -d0930:0 100 0930:0000 43 4F 4D 53 50 45 43 3D-43 3A 5C 57 49 4E 44 4F COMSPEC=C:\WINDO 0930:0010 57 53 5C 53 59 53 54 45-4D 33 32 5C 43 4F 4D 4D WS\SYSTEM32\COMM 0930:0020 41 4E 44 2E 43 4F 4D 00-41 4C 4C 55 53 45 52 53 AND.COM.ALLUSERS 0930:0030 50 52 4F 46 49 4C 45 3D-43 3A 5C 50 52 4F 47 52 PROFILE=C:\PROGR 0930:0040 41 7E 32 00 41 50 50 44-41 54 41 3D 43 3A 5C 55 A~2.APPDATA=C:\U 0930:0050 73 65 72 73 5C 72 6F 6C-66 5C 41 70 70 44 61 74 sers\rolf\AppDat(usf.) Bei 0930:0449 steht die Anzahl der folgenden Zeichenketten (zuerst das wertniedrige, dann das werthohe Byte). Hier ist es eine einzige Zeichenkette. Sie enthält den Namen des aufgerufenen Programms. Hinter der abschließenden Null auf Adresse 0930:0453 stehen Reste aus vorhergegangenen Aktivitäten. 0930:0430 45 52 50 52 4F 46 49 4C-45 3D 43 3A 5C 55 73 65 ERPROFILE=C:\Use 0930:0440 72 73 5C 72 6F 6C 66 00-00 01 00 54 45 53 54 2E rs\rolf....TEST. 0930:0450 43 4F 4D 00 5C 4B 34 2E-43 4F 4D 00 FF EB 65 F6 COM.\K4.COM...e. |