Jak pisać programy w języku asembler?

Część 11 - Pamięć jest nietrwała, czyli jak posługiwać się plikami

Jak wiemy, wszystkich danych nie zmieścimy w pamięci. A nawet jeśli zmieścimy, to pozostaną tam tylko do najbliższego wyłączenia prądu. Dlatego trzeba je zapisywać do pliku, a potem umieć je z tego pliku odczytać. W tej części zajmiemy się właśnie operacjami na plikach.

Do operowania na plikach posłużymy się kilkoma funkcjami przerwania 21h:

Wszystkie te funkcje ustawiają flagę carry (CF=1), gdy wystąpił jakiś błąd.
Po szczegóły (w tym kody błędów) odsyłam do Listy Przerwań Ralfa Brown'a.

Przykładowe użycie tych funkcji:


(przeskocz przykłady)
Utworzenie pliku i zapisanie czegoś do niego:
	mov	ah, 3ch		; numer funkcji - utworzenie
	mov	dx, nazwa	; adres nazwy pliku
	xor	cx, cx		; atrybuty. Zero oznacza normalny plik.
	int	21h		; utworzenie pliku
	jc	blad		; sprawdzamy, czy nie ma błędu.

	mov	[uchwyt], ax
	mov	bx, ax		; zapisujemy uchwyt

	mov	ah, 40h		; numer funkcji - zapis
				; BX = uchwyt do pliku
	mov	cx, 1024	; liczba bajtów do zapisania
	mov	dx, bufor	; adres bufora, z którego bierzemy bajty
	int	21h		; zapis do pliku
	jc	blad		; sprawdzamy, czy nie ma błędu.

	mov	ah, 3eh		; numer funkcji - zamknięcie pliku
				; BX = uchwyt do pliku
	int	21h		; zamykamy plik
	jc	blad		; sprawdzamy, czy nie ma błędu.

Otwarcie istniejącego pliku, odczytanie i zapisanie czegoś do niego:

	mov	ah, 3dh		; numer funkcji - otwieranie pliku
	mov	al, 00010010b	; wyłączny dostęp do odczytu i zapisu
	mov	dx, nazwa	; adres nazwy pliku
	int	21h		; utworzenie pliku
	jc	blad		; sprawdzamy, czy nie ma błędu.

	mov	[uchwyt], ax
	mov	bx, ax		; zapisujemy uchwyt

	mov	ah, 3fh		; numer funkcji - odczyt
				; BX = uchwyt do pliku
	mov	cx, 1024	; liczba bajtów do odczytania
	mov	dx, bufor	; adres bufora, do którego czytamy
	int	21h		; czytamy z pliku
	jc	blad		; sprawdzamy, czy nie ma błędu.

	; .... operacje na bajtach z pliku, na przykład
	xor	byte [bufor], 0ffh

	mov	ah, 40h		; numer funkcji - zapis
				; BX = uchwyt do pliku
	mov	cx, 1024	; liczba bajtów do zapisania
	mov	dx, bufor	; adres bufora, z którego bierzemy bajty
	int	21h		; zapis do pliku
	jc	blad		; sprawdzamy, czy nie ma błędu.

	; Zauważcie, że zapisane bajty wylądowały po odczytanych, gdyż nie
	; zmieniliśmy pozycji w pliku, a ostatnia operacja (odczyt) zostawiła
	; ją tuż po odczytanych bajtach

	mov	ah, 3eh		; numer funkcji - zamknięcie pliku
				; BX = uchwyt do pliku
	int	21h		; zamykamy plik
	jc	blad		; sprawdzamy, czy nie ma błędu.

A teraz prawdziwy przykład. Będzie to nieco uszczuplona (pominąłem wczytywanie nazwy pliku) wersja mojego programu na_male.asm. Program ten zamienia wszystkie wielkie litery w podanym pliku na ich małe odpowiedniki. Reszta znaków pozostaje bez zmian. Jedna rzecz jest warta uwagi - nigdzie nie zmieniam rejestru BX, więc ciągle w nim jest uchwyt do pliku i nie muszę tego uchwytu zapisywać do pamięci.
A teraz kod:


(przeskocz na_male.asm)
; Program zamienia wszystkie litery w podanym pliku z wielkich na male.
;
; Autor: Bogdan D.
; kontakt: bogdandr (at) op (dot) pl
;
; nasm -O999 -o na_male.com -f bin na_male.asm
; fasm na_male.asm na_male.com

org 100h

start:
	mov	dx, info
	mov	ah, 9
	int	21h

	mov	ax, 3d02h	; otwórz do odczytu i zapisu,
				; zabroń wszystkim dostępu
	mov	dx, plik	; adres nazwy pliku
	int	21h

	jnc	otw_ok
	call	plik_blad	; uruchamiamy tę procedurę, gdy wystąpił błąd
	jmp	zamk_ok		; jeśli nie udało się nam nawet otworzyć
				; pliku, to od razu wychodzimy z programu.


otw_ok:
	mov	bx, ax		; zapisujemy uchwyt do pliku
	mov	bp, 400h	; BP = rozmiar bufora.

czytaj:
	mov	ah, 3fh		; funkcja czytania
				; BX = uchwyt
	mov	dx, bufor	; adres bufora, dokąd czytamy
	mov	cx, bp		; kilobajt - rozmiar bufora
	int	21h		; odczyt

	jnc	czyt_ok
	call	plik_blad	; uruchamiamy tę procedurę, gdy wystąpił błąd

czyt_ok:
	xor	di, di		; DI będzie wskaźnikiem do bufora.
				; Na początku go zerujemy.
	cmp	ax, cx		; Czy liczba bajtów odczytana (AX) =
				; = liczba żądana (CX) ?
	jne	przy_eof	; jeśli nie, to plik się skończył

zamiana:
	mov	dl, [bufor+di]	; wczytujemy znak z bufora do DL

	cmp	dl, "A"
	jb	znak_ok
	cmp	dl, "Z"
	ja	znak_ok

	or	dl, 20h		; jeśli okazał się wielką literą, zamieniamy
				; go na małą
	mov	[bufor+di], dl	; i zapisujemy w miejsce,
				; gdzie poprzednio był

znak_ok:
	inc	di		; przechodzimy do innych znaków
	loop	zamiana		; aż przejdziemy przez cały bufor
				; (CX=BP=400h)

	mov	dx, ax		; DX = liczba przeczytanych bajtów
	mov	ax, 4201h	; idź do ... od pozycji bieżącej.
				; aby zapisać zmienione litery,
				; musimy przejść
				; się w pliku o 1 kilobajt wstecz.
				; Do CX:DX wpisujemy odległość
	neg	dx		; DX = -DX
;	dec	cx		; CX po wyjściu z pętli jest zerem,
				; więc wykonanie DEC zrobi z niego -1.
	mov	cx, 0ffffh	; CX = -1
				; CX:DX = -DX = -liczba przeczytanych bajtów

				; BX = uchwyt
	int	21h		; wykonujemy przeskok w pliku

	jnc	idz_ok
	call	plik_blad

idz_ok:				; po udanym przeskoku

	mov	dx, bufor	; DX = adres bufora, skąd będziemy brać dane
				; do zapisania
				; BX = uchwyt
	mov	ah, 40h		; funkcja zapisz
	mov	cx, bp		; CX = BP = 400h = długość bufora.
	int	21h		; zapisujemy

	jmp	short czytaj	; i idziemy czytać nową partię danych.

przy_eof:			; gdy jesteśmy już przy końcu pliku.

;	xor	di, di		; DI już = 0 (wcześniej to zrobiliśmy)

	mov	bp, ax		; BP = liczba przeczytanych znaków
	mov	cx, ax		; CX = liczba przeczytanych znaków

zamiana2:
	mov	dl, [bufor+di]	; pobieramy znak z bufora do DL

	cmp	dl, "A"
	jb	znak_ok2
	cmp	dl, "Z"
	ja	znak_ok2

	or	dl, 20h		; jeśli okazał się wielką literą,
				; zamieniamy go na małą
	mov	[bufor+di], dl	; i zapisujemy w miejsce,
				; gdzie poprzednio był

znak_ok2:
	inc	di		; przechodzimy do innych znaków
	loop	zamiana2	; aż przejdziemy przez cały bufor
				; (CX = BP = liczba bajtów)

	mov	dx, bp
;	dec	cx		; CX po wyjściu z pętli jest zerem, więc
				; wykonanie DEC zrobi z niego -1.
	mov	cx, 0ffffh	; CX = -1
				; CX:DX = -DX
	mov	ax, 4201h	; idź do ... od pozycji bieżącej.
	neg	dx		; DX = -DX.
				; CX:DX = -DX = -liczba przeczytanych bajtów

				; BX = uchwyt
	int	21h		; wykonujemy przeskok w pliku

	jnc	idz_ok2
	call	plik_blad

idz_ok2:			; po udanym przeskoku

	mov	dx, bufor	; zapiszemy do pliku resztę danych.
				; DX = adres bufora.
				; BX = uchwyt
	mov	cx, bp		; CX = liczba bajtów uprzednio odczytanych
	mov	ah, 40h		; funkcja zapisu do pliku
	int	21h		; zapisujemy

	jnc	zamk		; gdy nie ma błędu, to zamkniemy plik
	call	plik_blad

zamk:
	mov	ah, 3eh
				; BX = uchwyt
	int	21h		; zamykamy nasz plik
	jnc	zamk_ok
	call	plik_blad

zamk_ok:
	mov	ax, 4c00h
	int	21h		; wyjście...


plik_blad:			; procedura wyświetla informację o tym, że
				; wystąpił błąd i wypisuje numer tego błędu
	push	ax
	push	bx

	mov	dx, blad_plik
	mov	bx, ax
	mov	ah, 9
	int	21h

	mov	ax, bx
	call	pl

	pop	bx
	pop	ax

	ret

pl:                   	; procedura wypisuje liczbę (4 znaki szesnastkowe)

	mov	bx, ax
	shr	ax, 12
	call	pc2

	mov	al, bh
	and	al, 0fh
	call	pc2

	mov	al, bl
	shr	al, 4
	and	al, 0fh
	call	pc2

	mov	al, bl
	and	al, 0fh
	call	pc2

	ret

pc2:
;we: AL - cyfra hex
;wy: wyświetla cyfrę, niszczone ax

	cmp	al, 9
	mov	ah, 0eh
	ja	hex
	or	al, "0"
	jmp	short pz
hex:
	add	al, "A"-10
pz:
	int	10h

	ret

bufor: 		times 400h db 0		; bufor wielkości jednego kilobajta
;plik: 		times 80 db 0
plik		db "aaa.txt",0		; nazwa pliku

info db "Program zamienia wielkie litery w pliku na male.",10,13,"$"
input1		db "Podaj nazwe pliku do przetworzenia: $"
zla_nazwa 	db 10,13,"Zla nazwa pliku.$"
blad_plik 	db 10,13,"Blad operacji na pliku. Kod: $"

Ten program chyba nie był za trudny, prawda? Cała treść skupia się na odczytaniu paczki bajtów, ewentualnej ich podmianie i zapisaniu ich w to samo miejsce, gdzie były wcześniej.

Pliki są podstawowym sposobem przechowywania danych. Myślę więc, że się ze mną zgodzicie, iż opanowanie ich obsługi jest ważne i nie jest to aż tak trudne, jakby się mogło wydawać.


Poprzednia część kursu (Alt+3)
Kolejna część kursu (Alt+4)
Spis treści off-line (Alt+1)
Spis treści on-line (Alt+2)
Ułatwienia dla niepełnosprawnych (Alt+0)



Ćwiczenia

  1. Napisz program, który wykona po kolei następujące czynności:
    1. Utworzy nowy plik
    2. Zapisze do niego 256 bajtów o wartościach od 00 do FF (nie musicie zapisywać po 1 bajcie)
    3. Zamknie ten plik
    4. Otworzy ponownie ten sam plik
    5. Zapisze odczytane bajty w nowej tablicy 256 słów w taki sposób:
      		00 00 00 01 00 02 00 03 00 04 .... 00 FD 00 FE 00 FF	
      czyli każdy oddzielony bajtem zerowym (należy przeczytać wszystkie bajty, po czym ręcznie je przenieść gdzie indziej i wzbogacić)
    6. Zamknie otwarty plik
    7. Usunie ten plik