11.11. Dosyalarla Çalışmak

Şimdiye kadar basit dosya işlemlerini gerçekleştirdik. Dosyaların nasıl açılıp kapatılacağınıönbellek kullanarak nasıl okuma yazma yapılacağınıartık biliyoruz. Fakat UNIX bize dosyalar üzerinde bundan çok daha fazla fonksiyonellik sağlar.Bu bölümde bunlardan bir kaç tanesini göreceğiz ve sonlara doğru bir dosya dönüştürme aracı yapacağız.

Şimdi yapacaklarımıza sondan yani dosya dönüştürme aracıdan başlayalım. Çünkü ileride ürün olarak ne yapacağınıbilmek programlamayı daha basit hale getirir.

Unix altında yazmışolduğum ilk programlardan bir tanesi de TUC (ftp://ftp.int80h.org/unix/tuc/)adlıyazıdosyasıdönüştürme programıidi.Diğer bir işletim sisteminin yazıdosyasınıUNIX işletim sistemininde kullanılan biçime dönüştürüyordu. Diğer bir deyişle diğer işletim sistemininde kullanılan satır sonu düzenini UNIX'in newline düzenine dönüştürüyordu. Çıktılarıfarklıbir dosyaya yazıyordu. Ek olarak da UNIX formatındaki yazılarıMS-DOS yazıdosyasıhaline getirebiliyordu.

Tuc programınıçok defa kullanmıştım , ama genelde diğer sistemden UNIX'e dönüştürme maksatlıkullandık geri dönüştürme işlemini çok fazla gerçekleştirmedik. Her bir dosya dönüştürdüğümüzde çıktıdosyasını farklıbir dosya olarak bulundurmak yerine girdi üzerine yazmak için aşağıdaki gibi bir kod takımıkullandık.

% tuc myfile tempfile % mv tempfile myfile

Eğer bir ftuc gibi hızlıbir dönüştürme aracım olsa çok güzel olacaktımesala :

% ftuc myfile

Bu bölümde ftuc(Fast tuc) programınıassembly dilinde yazacağız (Orjinal Tuc C de derlenmişti).Ayrıca çekirdeğin dosyalar üzerindeki çeşitli servislerini inceleyeceğiz.

İlk bakışta bu dosya dönüşümü çok basit gibi görünüyor , çünkü tek yapmamız gereken 'carriage return' karakterlerini çıkarmak değil mi ?

Eğer bu soruya evet diye cevap veriyorsanız tekrar düşünün : Bu yöntem çoğu zaman doğru sonuç vermekle birlikte (En azından MS-DOS dosyalarında ) kimi zamanlar hatalara neden olacaktır.Problem bütün UNIX dışıişletim sistemlerinin düzeninin 'carriage return / line feed' sıralamasında olmamasıdır. Bazıları'carriage return' karakterini 'line feed' olmadan kullanabilirler.Bazıları'carriage return' karakterinden sonra boşluk karakterleri sonra 'line field' karakterleri şeklinde düzene sahip olabilirler. Bu yazıdönüştürücümüz bütün bu olasılıkları göz önünde bulundurmalıdır.

Belki de bu dosya düzenlerinin kombinasyonlarınıda çözümleyebilmelidir.Mesela birkaç 'carriage return' den sonra gelen 'line feed'gibi.

11.11.1. Sonlu durum makineleri

Dijital elektronik devrelerinin tasarımcılarıtarafından 'sonlu durum makineleri' tekniğini kullanarak bu problemi çözebiliriz. Dijital bir devre olan sonlu durum makinesi çıktısısadece girdiye bağlıolan değil ayrıca bir önceki girdilerine göre değer alan makine demektir.Mesela mikroişlemci bir örnek olabilir.Bizim assembly kodumuz makina diline çevrilirken bazıassemly kodlarıbir byte makina kodu oluşturur bazılarıise birden fazla byte makina kodu oluşturabilir. Bir mikroişlemci hafızadan baytlarıbirer birer çekerken bazıkodlar hiçbir çıktıvermeden sadece mikroişlemcinin durumunda değişikliğe neden olur.Kod tam olarak işlemciye alındığında bazılarıçıktıişlemi gerçekleştiriken bazıkodlar ise sadece yazmaçlarda değişiklik yapar.

Bütün bunlardan dolayıyazılımların hepsi mikroişlemcideki durumlar değişimi manzumesidir. Yani 'sınırlıdurum makineleri' tekniği yazılım tasarımıiçin çok uygundur.

Programımız normal(ordinary) durumundan başlayacak ve durum boyunca yapacağıişlemler aşağıdaki gibi girdilere bağlı olacak.

Eğer programımız cr durumunda ise 'cr' karakteri gelmişdemektir öyleyse yapacağımız işlem yine girdiye bağlıolacaktır.

Bizim yazıdönüştürme programımız 3 durumlu bir sonlu durum makinesi olarak düşünülebilir.Durumları0'dan 2'ye kadar olan sayılar ile ifade edecek olursak bunlarıdaha kolay kullanabilmek için sembolik isimler atayabiliriz.Eğer bunlarıbirleştirmek istiyorsak bir değil iki tane 'lf' karakteri yazdırmalıyız.

Son olarak öncesinde 'cr' olmayan 'line feed' karakterleri durumunun 'lf' olmasınısağlayacak . Bu şartlar dosya zaten UNIX formatında olduğu zaman gerçekleşir yada bir 'cr ' karakterinden sonra birden fazla 'lf ' geldiğinde gerçekleşebilir. Bir ihtimal de satırın lf / cr düzeninde bitmesidir. Bütün bu olasılıklarınasıl derlendireceğimizi görelim :

11.11.1.1. Son durum

Yukarıda tanımlamışolduğumuz sonlu durum makinasıbütün dosya için çalışırken dosyanın sonundaki dosya son satırıihmal edildi. Bu ancak dosya sonu bir 'cr' yada 'lf' karakteri ile biterse gerçekleşir.Tuc programınıyazarken bunu dikkate almadık sadece bazı durumlarda son satırıçıkarabileceğini göstermek istedik.

Bu problem bütün dosya işlendikten sonra son durumu kontrol ederek düzeltilebilir. Eğer durum son anda ordinary değil ise tek yapmamız gereken 'lf' karakterini çıktıdosyasına yazmaktır.

Not: Biz 'sonlu durum makinası' şeklinde bir algoritmanın tarifini yaptık. Bu işi gerçekleştirecek bir devre yapmak çok kolaydır. Tabiki bunu devre halinde yapmak assembly kodunu yazmaktan çok daha pahalıya mal olacaktır.

11.11.1.2. Çıktısayacı

Dosya dönüştürme programımız iki karakteri bir karakter olarak çıkartma ihtimali olduğundan çıktıiçin bir sayaca ihtiyacımız olacaktır.Başlangıç olarak 0 atayıp her bir çıktıişlemi sonrasında sayıyı1 artıracağız.Programın sonunda bu sayac çıktıdosyasıiçin hangi boyutta bir yere ihtiyacımız olduğunu bize söyleyecek.

11.11.2. Yazılımsal sonlu durum makinası

Sonlu durum makinalarında çalışmanın en zor kısmıproblemi analiz edip nasıl bir sistem olamasıgerektiğini tasarlamaktır.Bu gerçekleştikten sonra yazılım neredeyse hazır demektir.

C gibi yüksek seviyeli dillerde çeşitli ana yaklaşımlar vardır. Bir tanesi switch ifadesi kullanarak gerekli fonksiyonları çağırmaktır.

switch (state) { default: case REGULAR: regular(inputchar);break; case CR: cr(inputchar);break; case LF: lf(inputchar);break; }

Diğer bir yöntem ise fonksiyonların adreslerini gösteren işaretçiler tanımlamaktır.

(output[state])(inputchar);

Bir diğeri ise durum değişkenini fonksiyon adresini gösteren bir işaretçi olarak tanımlamaktır.

(*state)(inputchar);

Bu yaklaşım bizim programımızda kullanacağımız yaklaşımdır. Çünkü bunu assembly dilinde kullanmak çok kolaydır ve hızlıdır.Basitçe prosedürün adresini EBX yazmacında saklayacağız ve tek yapmamız gereken :

call ebx

Fonksiyonumuzun adresini programımızın içinde tutmaktansa mikro işlemcinin yazmaçlarında tutmak daha hızlıolabilir .Çünkü işlemci hafızaya ulaşmak için her seferinde ayrıbir işlem gerçekleştirmek zorunda kalacaktır .Olabilir diyoruz çünkü günümüz mikroişlemcilerinin önbellekleme sistemleri bu işlemi neredeyse eşit hale getiriyor.

11.11.3. Hafıza eşlenik dosyaları

Programımız tek dosya üzerinde çalışacağından önceden kullandığımız gibi girdi dosyasından okuyup çıktıdosyasına yazma işlemi yapamayız.Unix bir dosyayıyada dosya parçasınıhafızaya eşlemeye izin verir.Bunu gerçekleştirmek için dosyalarıokuma yazma bayraklarıile açmamız gereklidir. Sonra 'mmap' fonksiyonu ile dosyayı hafızaya eşleyebiliriz. Mmap'ın güzel bir özelliği de sanal bellek ile çalışmasıdır.Böylece ne kadar dosya eşlemesi yapsak da bellekte bir azalma söz konusu olmayacak ve assembly kodlarısanki normal hafızaya erişiyor gibi rahatça işlem görebilecektir.Dosyanın hafıza üzerindeki görüntüsü değiştirildiğinde değişiklikler sistem tarafından dosyaya yazılacaktır. Dosyayıaçık tutmaya bile ihtiyaç yoktur. Dosya görüntü ile bağlıolduğu sürece yazma ve okuma işlemlerini rahatlıkla yapabiliriz.

Intel 32 bit işlemcileri 4 gb kadar hafıza erişimi yapabilir. FreeBSD bunun yarısınıdosya eşleme için kullanmaya olanak sağlar.

Basit olmasımaksadıile burada sadece tamamıhafızaya eşlenmiş dosyaların dönüşümünü yapacağız. Bunlar muhtemelen 2gb geçmeyecek yazı dosyalarıolacak.Eğer böyle bir durum oluşacak olursa programımız orjinal TUC programınıtavsiye eden bir mesaj gönderecektir.

syscalls.master kopyasınıincelediğinizde içinde mmap isimli iki farklısistem çağrısıbulacaksınız. Bunun sebebi UNIX'in gelişimidir.Bir klasik BSD mmap fonksiyonu '71' vardır. Bir de POSIX standartında tanımlıbir sistem çağrısımmap '191' vardır.FreeBSD bunun ikisine de destek verir çünkü eski programlar BSD versiyonunu kullanır. Bizim kullandığımız gibi bazıprogramlar POSIX versiyonunu kullanır.

syscalls.master dosyasında POSIX versiyonu aşağıdaki gibidir :

197 STD BSD { caddr_t mmap(caddr_t addr, size_t len, int
        prot, \ int flags, int fd, long pad, off_t pos);}

Bu fonksiyon yukarıda bahsettiğimiz mmap(2) den biraz farklıdır. Çünkü mmap(2) C versiyonunu tasvir eder.

Farklılık C versiyonunda bulunmayan long pad argümanıdır.FreeBSD sistem çağrıları64 bit argümanlardan sonra 32 bit pad ekler.Bu durumda off_t 64 bitlik bir değerdir.

Hafıza eşleme ile işimiz bittiğinde aradaki bağıkaldırmak için munmap sistem çağrısınıkullanırız.

Tip: mmap hakkında daha detaylı bilgi için : W. Richard Stevens’ Unix Network Programming, Volume 2, Chapter 12 (http://www.int80h.org/cgi-bin/isbn?isbn=0130810819).

11.11.4. Dosya boyutunu belirleme

Mmap sistem çağrısına işleyeceğimiz dosyanın tümüne ne kadar byte hafıza ayıracağınısöylemek için dosyanın boyutunu tesbit etmemiz gerekir.

Bu maksatla kullanacağımız sistem çağrısı'fstat' tır.fstat açık olan dosyaların durumlarıhakkında bilgi verir.Bu bilgiye dosya boyutu da dahildir.

Yine syscalls.master dosyasıiçinde fstat sistem çağrısını bulursanız iki tane biri POSIX standartı(189) ve biri geleneksel BSD versiyonu (92) olan tanımlar göreceksiniz.

189 STD POSIX { int fstat(int fd, struct stat *sb);
        }

Bu çok basit kullanımıolan bir sistem çağrısıdır : İlk önce stat yapısının adresini gönderip sonra açık olan dosyanın fd(file descriptor) numarasınıgönderdiğimizde dosyanın o anki durumuna ait bütün bilgileri stat yapısına kaydeder.

11.11.5. Dosya boyutu değiştirmek

Programımız cr / lf karakterlerini birleştirebileceği için çıktı dosyamız girdi dosyamızdan daha küçük olabilir. Fakat okuduğumuz dosya ile yazdığımız dosya aynıyerde olduğu için dosya boyutunu değiştirerek bu problemin üstesinden gelebiliriz.

ftruncate
sistem çağrısıbunu gerçekleştirmemizi sağlar. İsminin aksine bu sistem çağrısıdosya boyutunu küçültebilirken aynızamanda büyütedebilir.

Yine diğerlerinde olduğu gibi bu sistem çağrısınında iki versiyonu var. BSD versiyonu :

201 STD BSD { int ftruncate(int fd, int pad, off_t
        length);}

Dikkat ederseniz burada da pad değişkeni var.

11.11.6. Ftuc

Artık ftuc programızıyazmak için gerekli herşeyi biliyoruz. İlk olarak yeni sistem çağrılarımızı system.inc dosyamıza ekliyelim. Bazısabitler ve yapılar tanımlayarak dosyanın ilk kısmına yakın bir yere ekleyelim :

;;;;;;;Açma bayrakları %define O_RDONLY 0 %define O_WRONLY 1 %define O_RDWR 2 ;;;;;;;mmap bayrakları %define PROT_NONE 0 %define PROT_READ 1 %define PROT_WRITE 2 %define PROT_EXEC 4 ;; %define MAP_SHARED 0001h %define MAP_PRIVATE 0002h ;;;;;;;stat yapısı struc stat st_dev resd 1 ;= 0 st_ino resd 1 ;= 4 st_mode resw 1 ;= 8, size is 16 bits st_nlink resw 1 ;= 10, ditto st_uid resd 1 ;= 12 st_gid resd 1 ;= 16 st_rdev resd 1 ;= 20 st_atime resd 1 ;= 24 st_atimensec resd 1 ;= 28 st_mtime resd 1 ;= 32 st_mtimensec resd 1 ;= 36 st_ctime resd 1 ;= 40 st_ctimensec resd 1 ;= 44 st_size resd 2 ;= 48, size is 64 bits st_blocks resd 2 ;= 56, ditto st_blksize resd 1 ;= 64 st_flags resd 1 ;= 68 st_gen resd 1 ;= 72 st_lspare resd 1 ;= 76 st_qspare resd 4 ;= 80 endstruc  

Yeni sistem çağrılarınıda ekleyelim :

%define SYS_mmap 197 %define SYS_munmap 73 %define SYS_fstat 189 %define SYS_ftruncate 201 

İleride kullanacağımız makrolarıtanımlayalım :

%macro sys.mmap 0 system SYS_mmap %endmacro %macro sys.munmap 0  system SYS_munmap %endmacro %macro sys.ftruncate 0 system SYS_ftruncate %endmacro %macro sys.fstat 0 system SYS_fstat %endmacro  

system.inc dosyasındaki gerekli değişiklikleri yaptıktan sonra artık kodumuzu yazabiliriz.

;;;;;;; Fast Text-to-Unix Conversion (ftuc.asm) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Started:	21-Dec-2000
;; Updated:	22-Dec-2000
;;
;; Copyright 2000 G. Adam Stanislav.
;; All rights reserved.
;;
;;;;;;; v.1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
%include	'system.inc'
section	.data
	db	'Copyright 2000 G. Adam Stanislav.', 0Ah
	db	'All rights reserved.', 0Ah
usg	db	'Usage: ftuc filename', 0Ah
usglen	equ	$-usg
co	db	"ftuc: Can't open file.", 0Ah
colen	equ	$-co
fae	db	'ftuc: File access error.', 0Ah
faelen	equ	$-fae
ftl	db	'ftuc: File too long, use regular tuc instead.', 0Ah
ftllen	equ	$-ftl
mae	db	'ftuc: Memory allocation error.', 0Ah
maelen	equ	$-mae
section	.text
align 4
memerr:
	push	dword maelen
	push	dword mae
	jmp	short error
align 4
toolong:
	push	dword ftllen
	push	dword ftl
	jmp	short error
align 4
facerr:
	push	dword faelen
	push	dword fae
	jmp	short error
align 4
cantopen:
	push	dword colen
	push	dword co
	jmp	short error
align 4
usage:
	push	dword usglen
	push	dword usg
error:
	push	dword stderr
	sys.write
	push	dword 1
	sys.exit
align 4
global	_start
_start:
	pop	eax		; argc
	pop	eax		; program name
	pop	ecx		; file to convert
	jecxz	usage
	pop	eax
	or	eax, eax	; Too many arguments?
	jne	usage
	; Open the file
	push	dword O_RDWR
	push	ecx
	sys.open
	jc	cantopen
	mov	ebp, eax	; Save fd
	sub	esp, byte stat_size
	mov	ebx, esp
	; Find file size
	push	ebx
	push	ebp		; fd
	sys.fstat
	jc	facerr
	mov	edx, [ebx + st_size + 4]
	; File is too long if EDX != 0 ...
	or	edx, edx
	jne	near toolong
	mov	ecx, [ebx + st_size]
	; ... or if it is above 2 GB
	or	ecx, ecx
	js	near toolong
	; Do nothing if the file is 0 bytes in size
	jecxz	.quit
	; Map the entire file in memory
	push	edx
	push	edx		; starting at offset 0
	push	edx		; pad
	push	ebp		; fd
	push	dword MAP_SHARED
	push	dword PROT_READ | PROT_WRITE
	push	ecx		; entire file size
	push	edx		; let system decide on the address
	sys.mmap
	jc	near memerr
	mov	edi, eax
	mov	esi, eax
	push	ecx		; for SYS_munmap
	push	edi
	; Use EBX for state machine
	mov	ebx, ordinary
	mov	ah, 0Ah
	cld
.loop:
	lodsb
	call	ebx
	loop	.loop
	cmp	ebx, ordinary
	je	.filesize
	; Output final lf
	mov	al, ah
	stosb
	inc	edx
.filesize:
	; truncate file to new size
	push	dword 0		; high dword
	push	edx		; low dword
	push	eax		; pad
	push	ebp
	sys.ftruncate
	; close it (ebp still pushed)
	sys.close
	add	esp, byte 16
	sys.munmap
.quit:
	push	dword 0
	sys.exit
align 4
ordinary:
	cmp	al, 0Dh
	je	.cr
	cmp	al, ah
	je	.lf
	stosb
	inc	edx
	ret
align 4
.cr:
	mov	ebx, cr
	ret
align 4
.lf:
	mov	ebx, lf
	ret
align 4
cr:
	cmp	al, 0Dh
	je	.cr
	cmp	al, ah
	je	.lf
	xchg	al, ah
	stosb
	inc	edx
	xchg	al, ah
	; fall through
.lf:
	stosb
	inc	edx
	mov	ebx, ordinary
	ret
align 4
.cr:
	mov	al, ah
	stosb
	inc	edx
	ret
align 4
lf:
	cmp	al, ah
	je	.lf
	cmp	al, 0Dh
	je	.cr
	xchg	al, ah
	stosb
	inc	edx
	xchg	al, ah
	stosb
	inc	edx
	mov	ebx, ordinary
	ret
align 4
.cr:
	mov	ebx, ordinary
	mov	al, ah
	; fall through
.lf:
	stosb
	inc	edx
	ret

Uyarı: Bu programıMS-DOS yada windows dosya biçimi ile formatlanmışbölümler üzerindeki dosyalarda kullanmayınız.FreeBSD altında bu şekilde bağlanmışsürücüler hakkında gizli bir hata vardır.Eğer dosya belli bir boyutun üzerinde ise mmap fonksiyonu hafızayısıfırlar ile dolduracaktır.Sonra bunlarıdosyanın üzerine yazacaktır.