Ş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.
carriage return / line feed
carriage return
line feed / carriage return
line feed
Belki de bu dosya düzenlerinin kombinasyonlarınıda çözümleyebilmelidir.Mesela birkaç 'carriage return' den sonra gelen 'line feed'gibi.
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.
normal durum
'cr'
'lf'
Programımız normal(ordinary) durumundan başlayacak ve durum boyunca yapacağıişlemler aşağıdaki gibi girdilere bağlı olacak.
Eğer girdi 'cr' yada 'lf' karakterinden farklıbir karakter ise hiç bir değişiklik yapmadan çıktıya yazdır.
Eğer gelen karakter 'cr' ise durumu cr olarak değiştir ve herhangi bir çıktıverme.
Eğer gelen karakter 'lf' ise benzer şekilde durumu lf haline getir ve herhagi bir çıktıverme.
Eğer programımız cr durumunda ise 'cr' karakteri gelmişdemektir öyleyse yapacağımız işlem yine girdiye bağlıolacaktır.
Eğer gelen karakter 'cr' yada 'lf' karakterinden farklıbir karakter ise çıktıolarak 'line feed' karakteri yazdıktan sonra ordinary durumuna geç
Eğer gelen karakter 'cr' ise bu üst üste 'cr' karakteri almışız demektir ve çıktıdosyasına 'linefeed' gönderdikten sonra durumda bir değişiklik olmadan sonraki girdiye geçilecektir.
Eğer gelen karakter 'line feed' ise çıktıdosyasına 'line feed' yazdıktan sonra durumumuzu ordinary olarak değiştirmeliyiz.
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 :
Bu özel karakterlerin dışında bir karakterimiz gelirse durumumuz 'lf' içinde olduğundan bir tane 'lf' karakteri yazdıktan sonra çıktıya aynen gelen karakteri yazarız.Sonra durumumuzu ordinary olarak değiştiririz.Bu cr durumundaki aynıtip karakter aldığımızda yaptığımız işlemin aynısıdır.
Eğer gelen karakter 'cr' ise gelen karakteri çıktıya yazdırmayız.'lf' karakteri ile satır başıyaptıktan sonra durumu normal(ordinary) olarak değiştiririz.
Eğer gelen karakter 'lf' ise durumda herhangi bir değişiklik olmadan çıktıya 'lf' karakteri göndeririz.
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.
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.
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.
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).
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.
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.
ftruncatesistem ç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.
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.