11.8. Önbelleklendirilmişgirdi çıktı

Kodumuzun verimliliğini arttırmak için girişve çıkışlara önbellekleme işlemi yapabiliriz. Bir girişönbelleği oluşturur ve bir byte dizisini bir seferde okuruz. Ayrıca bir de çıkışiçin önbellek oluşturduk. Tam dolu oluncaya kada çıktılarıiçinde tuttuk. Sonunda çekirdeğe yazma komutu göndererek

stdout
'a yazmasını sağladık.

Program girdi işlemi bittiğinde sonlanır. Yine de en son durumda programıkapatmadan çıktıönbelleğindeki bütün tutulan verileri yazmasınısağlamalıyız. Aksi takdirde çıktılarımız önbelleği içinde bulunsa bile hiç bir zaman ekrana aktarılmayacaktır. Bunu asla unutmayın yoksa ileride çıktıların birazıneden eksik diye kara kara düşünürsünüz.

%include ’system.inc’ %define BUFSIZE 2048 sect2 .data hex db ’0123456789ABCDEF’ sect2 .bss ibuffer resb BUFSIZE obuffer resb BUFSIZE sect2 .text global _start _start: sub eax, eax sub ebx, ebx sub ecx, ecx mov edi, obuffer .loop: ;stdin'den bir byte okur call getchar ;16'lıtabana çevir mov dl, al shr al, 4 mov al, [hex+eax] call putchar mov al, dl and al, 0Fh mov al, [hex+eax] call putchar mov al, ’ ’ cmp dl, 0Ah jne .put mov al, dl .put: call putchar jmp short .loop align 4 getchar: or ebx, ebx jne .fetch call read .fetch: lodsb dec ebx ret read: push dword BUFSIZE mov esi, ibuffer push esi push dword stdin sys.read add esp, byte 12 mov ebx, eax or eax, eax je .done sub eax, eax ret align 4 .done: call write ;çıktıönbelleğini boşaltır(flush). push dword 0 sys.exit align 4 putchar: stosb inc ecx cmp ecx, BUFSIZE je write ret align 4 write: sub edi, ecx ;önbelleğin başlangıcı push ecx push edi push dword stdout sys.write add esp, byte 12 sub eax, eax sub ecx, ecx ;burada önbellek boşaltılmışoluyor ret

Kodumuzda .bss adıile oluşturulmuşüçüncü bir bölüm var. Bu bölüm çalıştırılabilir dosyamız içine dahil değil bu yüzden herhangi bir başlangıç değeri atanmıyor .db yerine resb kullandık.Böylece hafızadan bizim girdi çıktıönbellekleri için yer ayırmışolduk.

Sistemin yazmaçlarıdeğiştirmemesini bir avantaj olarak kullanabiliriz.Artık bundan sonra yazmaçlarıistediğiniz gibi kullanabilirsiniz.Böyle olmasaydıglobal değişkenleri .data bölümüne kaydetmek zorunda kalacaktık.Bu ayrıca Unix düzeninin neden Microsoft Windows düzeninden farklıolduğunu gösterir. Unix altında yazmaçların kullanımıbize bırakılmıştır.

Ayrıca yukarıdaki kodda EDI ve ESI yazmaçlarınıiki tane işaretçi gibi bir sonraki byte'ın yazılacağıyada okunacağıyeri belirtmek için kullandık.Ek olarak EBX ve ECX yazmaçlarınıher iki önbellekte tutulan byte sayısınıhafızada tutmak için kullandık.Böylece ekrana ne zaman çıktıvereğimizi yada daha fazla girişin olup olmadığınıtesbit edebiliriz.

Kodumuzun nasıl çalıştığına bir bakalım:

% nasm -f elf hex.asm % ld -s -o hex hex.o % ./hex Hello, World! Here I come! 48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 0A 48 65 72 65 20 49 20 63 6F 6D 65 21 0A ^D %

Beklediğiniz gibi değil mi ? Program ^D tuşuna basmadan ekrana çıktıvermiyor. Bu problemi üç satırlık kodla çözebiliriz. Bunun için her bir satırbaşıkarakteri 0A gördüğünde çıktıvermesini sağlamalıyız.Eklenmişkodlar '>' karakteri ile başlıyor.

%include ’system.inc’ %define BUFSIZE 2048 sect2 .data hex db ’0123456789ABCDEF’ sect2 .bss ibuffer resb BUFSIZE obuffer resb BUFSIZE sect2 .text global _start _start: sub eax, eax sub ebx, ebx sub ecx, ecx mov edi, obuffer .loop: ;std'in den bir byte oku call getchar ;16'lıtabana çevir mov dl, al shr al, 4 mov al, [hex+eax] call putchar mov al, dl and al, 0Fh mov al, [hex+eax] call putchar mov al, ’ ’ cmp dl, 0Ah jne .put mov al, dl .put: call putchar >cmp al, 0Ah >jne .loop >call write jmp short .loop align 4 getchar: or ebx, ebx jne .fetch call read .fetch: lodsb dec ebx ret read: push dword BUFSIZE mov esi, ibuffer push esi push dword stdin sys.read add esp, byte 12 mov ebx, eax or eax, eax je .done sub eax, eax ret align 4 .done: call write ;çıktıönbelleğini yazdır push dword 0 sys.exit align 4 putchar: stosb inc ecx cmp ecx, BUFSIZE je write ret align 4 write: sub edi, ecx ;önbelleğin başlangıcı push ecx push edi push dword stdout sys.write add esp, byte 12 sub eax, eax sub ecx, ecx ;artık önbellek boşaldı ret

Şimdi programımızıçalıştırırsak.

% nasm -f elf hex.asm % ld -s -o hex hex.o % ./hex Hello, World! 48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 0A Here I come! 48 65 72 65 20 49 20 63 6F 6D 65 21 0A ^D %

Not: Önbelleklenmişgirişçıkış işlemi yaklaşımıgizli bir tehlike içeriyor.Önbellekleme işleminin karanlık taraflarınıtartıştığımızda bunu nasıl çözeğimizi göreceğiz.

644 byte yer kaplayan bir program için fena değil.

11.8.1. Karakteri okuma işlemini nasıl geri alabilirim ?

Not: Bu bölüm gelişmişkonular ihtiva etmektedir,genellikle derleyiciler teorisine aşina programcılar tarafından anlaşılabilir.Eğer istiyorsanız sonraki üniteleri okuduktan sonra buraya tekrar dönebilirsiniz.

Bizim programımız gerektirmese de bazıproramların ileriye dönük okuma yapmalarıgerekebilir.Başka bir deyişle , program bir sonraki karakterin ne olduğuna bakarak işlemi gerçekleştirecek olabillir.Mesela aradığımız karakter gelirse işlemeye devam edeceğiz değilse işlemi bitireceğiz.

Diyelim ki yazıformatında bir girişi gramer bakımından inceliyorsunuz(Bu tip problemlerle programlama dili dizayn edenler sık sık karşılaşır). Eğer bulunduğunuz yerden bir sonraki karakter bir harf yada sayıise kelimeniz devam ediyordur ama boşluk karakteri yada benzeri bir karakter var ise kelimeniz orada bitmiştir.

Bu durumda şöyle bir soru ile karşıkarşıya bulunuyoruz. Giriş kuyruğundan okumuşolduğum bir karakteri ileride tekrar okumak üzere kuyruğun önüne nasıl geri koyabilirim?

Çözümlerden bir tanesi karakteri hafızada bir yere kaydedip bir bayrak ayarlamaktır. Bundan sonra getchar fonksiyonunuzun içeriğini değiştirip eğer bayrak kaldırılmışise oradan okuma yapmasını sağlarız.Okuma sonrasında bayrağıtekrar 0 durumuna getirir. Böylece normal okuma işlemine devam edilir. Ama bu yöntem okumayı yavaşlatacaktır.

C dilinde sırf bu maksatla ungetc( ) fonksiyonu tanımlanmıştır.Bu kodu uygulamanın acaba hızlıve kolay bir yolu yok mu ? Burada biraz geri gidip getchar fonksiyonunu inceledikten sonra ilerki paragraflara bakmadan güzel bir çözüm bulabilirsiniz. Sonrada buraya dönüp ve bizim çözümümüzü inceleyebilirsiniz.

Bir karakteri kuyruğa geri göndermenin sırrıbu karakteri nasıl elde ettiğimize saklıdır.

EBX yazmacındaki değeri test ederek ön belleğin boşolup olmağınıkontrol ediyoruz. Eğer 0 ise read prosedürünü çalıştırıyoruz.

Eğer geçerli bir karakterimiz varsa

loadsb
        
prosedürünü kullanarak EBX de bulunan değeri bir azaltıyoruz.
loadsb 
prosedürü aşağıdaki işlemle eşdeğerdir.

mov al, [esi] inc esi

Yeni bir read çağrısıolmadığısürece okumuşolduğumuz karakter önbellekte durmaya devam edecektir.Bunun ne zaman gerçekleşeceğini bilmiyoruz ama şunu biliyoruz ki getchar fonksiyonunu çağırmadıkça gerçekleşmeyecek. Bundan dolayıkarakteri geri kuyruğa dönmek için tek yapmamız gereken ESI yazmacının değerini bir azaltmak EBX yazmacını bir arttırmak.

ungetc: dec esi inc ebx ret

Fakat dikkatli olun eğer bir karakteri geri dönmek istiyorsak bu yöntem mükemmel çalışıyor. Fakat birden fazla karakter için ungetc fonksiyonumuzu kullandığımızda çoğu zaman çalışacaktır fakat her zaman değil. Neden?

Çünkü getchar fonksiyonu read komutunu her zaman çalıştırmak zorunda olmadığından, önceden okunmuşkarakterler de önbellekte bulunduğundan ungetc fonksiyonumuz sorunsuz çalışabilir. Ama getchar komutu read çağrısıyaptığında bufferin değişmesine neden olacaktır.

ungetc fonksiyonunun her zaman son karakteri geri koyacağına güvenebiliriz.Ama bundan önce okunmuşlar için geçerli değil.Yani önbellek değişmediği sürece geri dönebiliriz

Eğer programınız birden fazla byte okuyor ise iki alternatifiniz var :

Mümkünse sadece bir byte ileriye doğru okuyor hale getirin , ki bu basit olan yöntemdir.

Eğer bu mümkün değilse öncelikle programınızın bir seferde geri koymasıgereken maximum karakter sayısınıbelirleyin. Bu sayıyıyavaş yavaşarttırın, emin olmak için 16 nın katlarınıtercih edebilirsiniz böylece daha düzenli görünecektir.Sonra programınızın .bss kısmını değiştirerek küçük bir yedek önbellek oluşturabilirsiniz.

sect2 .bss resb 16 ;or whatever the value you came up
        with ibuffer resb BUFSIZE obuffer resb BUFSIZE

Ayrıca ungetc fonksiyonunu geri alınacak karaterin değerini AL ile alacak şekilde değiştirmelisiniz.

ungetc: dec esi inc ebx mov [esi], al ret

Bu değişiklik sayesinde ungetc fonksiyonunu 17 kez sorunsuz olarak çağırabilirsiniz.(İlk çağrınormal önbellekte geri kalanlar ise yedek önbellekte olacak şekilde çağrılır ise)