11.12. Uzmanlaşma prensibi

Budistlerde bir prensip vardır : Bir işyap ama yapabildiğin en iyisini yap.

Bu prensip UNIX'in çalışma prensibine çok yakındır.Evet bir windows programıakla gelebilecek bütün işleri yapmaya çalışırken tipik bir UNIX programısadece bir işyapar ama yaptığıişi iyi yapar.

UNIX kullanıcısıbu tip programlarıbirleştirerek çeşitli kabuk scriptleri ile boru (pipe) kullanarak kendi uygulamalarını üretir.

Bir UNIX programıyazarken problemimizin bazıparçalarınızaten elimizde olan programlar ile çözüp çözemeyeceğinizi gözden geçirmeniz iyi bir fikir olacaktır.Bundan sonra önceden çözülmemişproblemler için kendi programınızıyazmaya devam edebilirisiniz.

11.12.1. CSV

Bu prensibi ara sıra karşılaştığım günlük hayattan bir örnek ile gösterelim:

Bir web sitesinden indirmişolduğum veritabanıdosyasının 11. elamanına ulaşmam gerekiyor. Bu veritabanıdosyasıcsv (virgülle ayrılmışdeğerler : comma seperated values) dosyasıdır.Bu biçim değişik veritabanıprogramlarıkullanan insanlar arasında paylaşımı sağlayan çok genişbir standarttır.

Dosyanın ilk satırıvirgülle ayrılmışalanların listesi şeklindedir.Geri kalan satırlar virgül ile ayrılmışverileri içerir.

Awk programınıvirgülü ayraç olarak kullanacak şekilde

Awk programınıvirgülü ayraç olarak kullanacak şekilde ayarlamaya çalıştım ama bazısatırlar tırnak içinde virgül içerdiğinden bu satırlarda hataya neden oldu.

Bu yüzden CSV dosyasının 11. elamanınıbana getirecek kendi programımıyazmam gerekti. Fakat UNIX'in ruhuna uygun olarak tek yapmam gereken sadece basit bir filtre yazmaktı.Benim filtremin yapmasıgerekenler şunlardır :

Kurallara bakılırsa benim dosyadan ilk satırısilmem için sed kullanmam gerekecekti ama bunu boru(pipeline) miktarınıdüşürmek için kendi programımda yapmayı tercih ettim.

Tüm prosedürler ile birlikte programımıtamamlamam benim yaklaşık 20 dakikamıaldı.Oysa baştan CSV dosyasının 11. elemanına ulaşan bir program yazacak olsaydım bundan çok daha fazla tutacaktı ayrıca başka bir veritabanının başka elemanlarına ulaşmak için kullanamayacaktım.

Burada öğretim yaptığımızdan birkaç küçük özellik daha ekleyelim:

Mesela kullanım mesajı:

Usage: csv [-t<delim>] [-c<comma>] [-p] [-o
        <outfile>] [-i <infile>]

Bütün parametreler opsiyoneldir ve sıraya bağlıolmadan çalışırlar.

-t parametresi virgüllerin ne ile değiştirileceğini belirtir.Varsayılan tab tuşudur.Mesela -t;bütün virgülleri noktalı virgül ile değiştirir.

-c seçeneği şimdilik işimize yaramayacak fakat ileride işimize yarayabileceği için ekledik.Bu virgülden başka bir karakter ile ayırma yapacağımızda kullanılır.Mesela -c@ ile ayraç olarak @ karakterini kullanır ve program içinde bu karakter değiştirilir.Bu mail adreslerinde alan adıile kullanıcıadınıayırmada kullanışlıdır.

-p seçeneği ilk satırıkorumaya yarar bir başka deyişle silmez.Ama başlangıçta varsayılan değer silinmesidir.

-i ve -o değerleri girdi çıktıdosyalarının isimlerini belirtmek için kullanılır.Standart olarak bunlar stdin ve stdout olarak klasik bir UNIX filtresi gibi tanımlıdır.

-i filename ve -i filename biçiminde ikiside kabul edilecektir. Sadece bir girdi ve çıktıdosyasıbelirtilebilir.

Program bittikten sonra 11. elemana ulaşma amacımızı gerçekleştirmek için tek yapmamız gereken :

% csv '-t;' data.csv | awk '-F;' '{print $11}'

Bu kod seçenekleri EDX yazmacına kaydeder (Dosya numaraları hariç) : virgül DH yazmacına , yeni ayraç DL yazmacına , -p seçeneği için olan bayrağıEDX yazmacının en yüksek bitine kaydeder.Bu sayede sadece sayının işaretine (pozitif mi negatif mi) olduğuna bakarak bayrağın değerini anlayabiliriz.

Kodumuz burada :

;;;;;;; csv.asm ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Convert a comma-separated file to a something-else separated file.
;
; Started:	31-May-2001
; Updated:	 1-Jun-2001
;
; Copyright (c) 2001 G. Adam Stanislav
; All rights reserved.
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
%include	'system.inc'
%define	BUFSIZE	2048
section	.data
fd.in	dd	stdin
fd.out	dd	stdout
usg	db	'Usage: csv [-t<delim>] [-c<comma>] [-p] [-o <outfile>] [-i <infile>]', 0Ah
usglen	equ	$-usg
iemsg	db	"csv: Can't open input file", 0Ah
iemlen	equ	$-iemsg
oemsg	db	"csv: Can't create output file", 0Ah
oemlen	equ	$-oemsg
section .bss
ibuffer	resb	BUFSIZE
obuffer	resb	BUFSIZE
section	.text
align 4
ierr:
	push	dword iemlen
	push	dword iemsg
	push	dword stderr
	sys.write
	push	dword 1		; return failure
	sys.exit
align 4
oerr:
	push	dword oemlen
	push	dword oemsg
	push	dword stderr
	sys.write
	push	dword 2
	sys.exit
align 4
usage:
	push	dword usglen
	push	dword usg
	push	dword stderr
	sys.write
	push	dword 3
	sys.exit
align 4
global	_start
_start:
	add	esp, byte 8	; discard argc and argv[0]
	mov	edx, (',' << 8) | 9
.arg:
	pop	ecx
	or	ecx, ecx
	je	near .init		; no more arguments
	; ECX contains the pointer to an argument
	cmp	byte [ecx], '-'
	jne	usage
	inc	ecx
	mov	ax, [ecx]
.o:
	cmp	al, 'o'
	jne	.i
	; Make sure we are not asked for the output file twice
	cmp	dword [fd.out], stdout
	jne	usage
	; Find the path to output file - it is either at [ECX+1],
	; i.e., -ofile --
	; or in the next argument,
	; i.e., -o file
	inc	ecx
	or	ah, ah
	jne	.openoutput
	pop	ecx
	jecxz	usage
.openoutput:
	push	dword 420	; file mode (644 octal)
	push	dword 0200h | 0400h | 01h
	; O_CREAT | O_TRUNC | O_WRONLY
	push	ecx
	sys.open
	jc	near oerr
	add	esp, byte 12
	mov	[fd.out], eax
	jmp	short .arg
.i:
	cmp	al, 'i'
	jne	.p
	; Make sure we are not asked twice
	cmp	dword [fd.in], stdin
	jne	near usage
	; Find the path to the input file
	inc	ecx
	or	ah, ah
	jne	.openinput
	pop	ecx
	or	ecx, ecx
	je near usage
.openinput:
	push	dword 0		; O_RDONLY
	push	ecx
	sys.open
	jc	near ierr		; open failed
	add	esp, byte 8
	mov	[fd.in], eax
	jmp	.arg
.p:
	cmp	al, 'p'
	jne	.t
	or	ah, ah
	jne	near usage
	or	edx, 1 << 31
	jmp	.arg
.t:
	cmp	al, 't'		; redefine output delimiter
	jne	.c
	or	ah, ah
	je	near usage
	mov	dl, ah
	jmp	.arg
.c:
	cmp	al, 'c'
	jne	near usage
	or	ah, ah
	je	near usage
	mov	dh, ah
	jmp	.arg
align 4
.init:
	sub	eax, eax
	sub	ebx, ebx
	sub	ecx, ecx
	mov	edi, obuffer
	; See if we are to preserve the first line
	or	edx, edx
	js	.loop
.firstline:
	; get rid of the first line
	call	getchar
	cmp	al, 0Ah
	jne	.firstline
.loop:
	; read a byte from stdin
	call	getchar
	; is it a comma (or whatever the user asked for)?
	cmp	al, dh
	jne	.quote
	; Replace the comma with a tab (or whatever the user wants)
	mov	al, dl
.put:
	call	putchar
	jmp	short .loop
.quote:
	cmp	al, '"'
	jne	.put
	; Print everything until you get another quote or EOL. If it
	; is a quote, skip it. If it is EOL, print it.
.qloop:
	call	getchar
	cmp	al, '"'
	je	.loop
	cmp	al, 0Ah
	je	.put
	call	putchar
	jmp	short .qloop
align 4
getchar:
	or	ebx, ebx
	jne	.fetch
	call	read
.fetch:
	lodsb
	dec	ebx
	ret
read:
	jecxz	.read
	call	write
.read:
	push	dword BUFSIZE
	mov	esi, ibuffer
	push	esi
	push	dword [fd.in]
	sys.read
	add	esp, byte 12
	mov	ebx, eax
	or	eax, eax
	je	.done
	sub	eax, eax
	ret
align 4
.done:
	call	write		; flush output buffer
	; close files
	push	dword [fd.in]
	sys.close
	push	dword [fd.out]
	sys.close
	; return success
	push	dword 0
	sys.exit
align 4
putchar:
	stosb
	inc	ecx
	cmp	ecx, BUFSIZE
	je	write
	ret
align 4
write:
	jecxz	.ret	; nothing to write
	sub	edi, ecx	; start of buffer
	push	ecx
	push	edi
	push	dword [fd.out]
	sys.write
	add	esp, byte 12
	sub	eax, eax
	sub	ecx, ecx	; buffer is empty now
.ret:
	ret

Yukarıda kullandığımız kodların çoğu hex.asm kodumuzdan alınmıştır.Fakat önemli bir farkla : her bir 'linefeed' karakteri için write çağrısıyapmadık.İsterseniz bu kodu interaktif olarak da kullanabilirsiniz.

Bu bölümü yazmadan önce interaktif problem için daha güzel bir çözüm buldum. Sadece gerektiğinde satırların ayrıyazıldığından emin olmak istiyorum.Hepsinden sonra intereaktif olmayan düzende bütün satırlarıyazdırmaya gerek yoktur.

Yeni çözüm ise write sistem çağrısınıönbelleğin boşolduğu her anda çağırmaktır.Bu yolla interaktif modda çalışırken klavyeden bir satır okur onu işler sonra girişönbelleği boşolursa çıktıyıyazdırır bir sonraki satıra geçer.

11.12.1.1. Önbelleklemenin karanlık yüzü

Bu değişiklik çok özel durumda oluşan gizli bir kilitlenmeyi önleyecektir.Bunu önbelleklemenin karanlık yüzü olarak adlandıracağız çünkü hemen ilk bakışta farkedilemeyecek bir biçimde karşımıza çıkmaktadır.

Yukarıda CSV programına benzer programlarda ortaya çıkması muhtemel değildir. Ama gelin başka bir filtre programına göz atalım : Diyelim ki bu sefer bizim girdimiz kırmızı, yeşil,mavi (rgb) renk yoğunluklarınıiçeren bir veri dosyasıve çıktımız ise girdinin negatifi olsun.

Bunun gibi filtreyi yazmak gerçekten kolaydır. Kodun büyük bir kısmıdiğer filtreler gibi olacaktır o yüzden sadece içteki döngüyü gösterelim.

.loop: call getchar not al ;Create a negative call putchar jmp short .loop

Bu filtre veri dosyasıüzerinde çalıştığından interaktif olarak kullanmak mümkün olmayacaktır.

Ama bu filtre bir resim işleme yazılımıtarafından kullanılabilir.write çağrısınıread çağrısından önce yapmadıkça muhtemelen kilitlenecektir.

Mesela neler olabilir.

  1. Resim editörü bizim filtremizi C dilinde popen() fonksiyonunu kullanarak yükler.

  2. Resmin ilk satırınıokur.(Resim bitmap yada pixmap formatında olabilir.)

  3. Filtremizin fd.in girişine bağlanan boru (pipe) ile ilk satırıyazar.

  4. Filtremin girdiden aldığıher bir pixeli negatif yapıp çıktıönbelleğine gönderecek.

  5. Filtremiz sonraki pixele geçmek için getchar çağrısını kullanacak.

  6. getchar girişönbelleğini boşbulacak böylece read prosedürünü çağıracak

  7. read ise SYS_read sistem çağrısınıgerçekleştirecek

  8. İşletim sisteminin çekirdeği filtremizi resim düzenleyici program daha fazla veri gönderene kadar askıya alacak.

  9. Resim düzenleyici program ise filtrenin çıkışından almış olduğu işlenmişolan ilk satırıresme ikinci satırıgöndermeden önce yerleştirecek.

  10. Sistem çekirdeği resim editörünü filtreden bir çıktı alıncaya kadar askıya alacak.

Burada resim editörümüz filtrenin çıktıvermesi için beklerken filtre resim editöründen diğer bir satırıbekliyor.Çıktıise önbellekte öylece duruyor.

Resim editörümüz ve filtremiz bir birini böyle sonsuza kadar bekleyecekler(En azından bu işe birileri son verene kadar) . Böylece yazılımlarımız yarışdurumlarına(Race Conditions ) girmiş olacak.

Eğer filtremiz daha fazla veri beklemeden önce önbellekteki veriyi açığa çıkarsa böyle bir problem oluşmayacaktı.