25. Bölüm
Cenker Özkurt
Bu ayki yazımızın konusu, Amiga C'de Assembler alt rutinlerinin kullanımı. Assembler, bildiğiniz gibi bilgisayarın en temel dili olarak kullanılıyor. Assembler programları araya hiçbir yorumlayıcı girmediğinden çok hızlı çalışabiliyor. Hız her programcının istediği bir özellik. Bu nedenle özellikle oyunlar assembler dili ile yazılmakta. Assembler dilini diğer dillerden ayıran en büyük özellik; sisteme direkt olarak ulaşabilmek. Örneğin Amiga'nın blitter çipine ulaşmak, assembler dili ile oldukça kolay. Bu sayede de sistemi tam performans çalıştırabiliyorsunuz. Hızlı scroll'lar kayan yazılar, vektör hesapları ve çizimleri bu tür bir yazılım gerektiriyor.
Amiga C'de yazılan programların hemen hepsi sistem komutlarını kullanır. Bu komutlar Library dediğimiz kütüphanelerde bulunuyor. Bildiğiniz gİbi kullanılan Library'ler genellikler 'graphics.library', 'intuition.library', 'dos.library' ve diğerleridir. Eğer biz bu library'leri kullanmadan program yazmak istersek, işte o zaman sisteme direk olarak ulaşıp işimizi görmeliyiz. Tabii bu iş için de Amiga sistemini ve yapısını iyi tanımamız gerekmektedir. Örneğin açmış olduğumuz iki ekrandan birindeki resmi diğer ekrana taşımak istiyoruz. Bu işi Amiga C'de yaparken bir blitter komutu olan 'ClipBlit()' ya da 'BltBitMap()' komutlarından birini kullanırız. Bu komutlar Amiga'nın 'graphics.library' kütüphanesinde hazır. Eğer bu komutları kullanmadan aynı işi yapacaksak, direkt olarak blitter adreslerini kullanmalıyız. İşte bu iş için de en iyisi, assembler bir alt rutin oluşturup bu rutini C dilinde çağırmak.
Şimdi Amiga C'de assembler alt rutinlerinin nasıl kullanıldığına bir bakalım. Vermiş olduğum örnekler, basit assembler örnekleri. Daha gelişmiş örnekleri denemek istiyorsanız, dergimizin yazarlarından Cem Gencer'in yazılarını takip etmenizi öneririm.
Amiga C içerisinde bir assembler program parçası kullanırken, bu programın bir assembler program olduğunu compiler'a bildirmemiz gerekmektedir. Aksi takdirde compiler, yazılan programı, C programı olarak derlemek isteyecek ve hata verecektir. Assembler programını aşağıda olduğu gibi ayırmalıyız:
#asm move.l $4,a6 ... #endasm#asm ve #endasm, assembler programının başlangıç ve bitiş bloklarını belirtir. Bu satırlar içerisindeki programı C derleyicisi gözönüne almaz. C derleme işlemi bittikten sonra, Assembler derleme programı işlemine başlar ve programın assembler bölümleri burada derlenir. Aynı zamanda C derleyicisi, programın C ile yazılmış kısımlarını assembler'a çevirmiştir. Bu nedenle assembler derleyicisi tamamı assembler olan bir program üzerinde çalışacaktır. Hatırlayacağınız gibi, geçen sayılarda yazmış olduğumuz programların assembler listesinin nasıl alınacağını göstermiştim. Bu yazımızda bu işlemleri daha derinlemesine inceleyeceğiz.
Assembler rutinlerinin kullanımı ile ilgili birkaç program hazırladım. Şimdi isterseniz bu programlara bir göz atalım.
İlk örneğimiz bilgisayarımızın power ışığını söndürüp mouse tuşuna basılıncaya kadar bekleyen ve tekrar power ışığını yakan bir program. Program içerisinde üç rutin mevcut. Bu rutinler led_on(), wait_mouse() ve led_off(). Rutinlerin içerisi assembler ile yazılmış. Şimdi programımıza bir göz atalım:
/* Aztec-C Assembler */
/* alt rutinlerinin */
/* kullanimi; */
/* Ornek 1. */
/* alt rutinleri çağıran */
/* main bloğu */
main()
{
led_on();
wait_mouse();
led_off();
}
led_on()
{
#asm
bset#1,$bfe001
#endasm
}
wait_mouse()
{
#asm
wait:
btst #6,$bfe001
bne.s wait
#endasm
}
led_off()
{
#asm
bclr#1,$bfe001
#endasm
}
Gördüğünüz gibi, alt rutinleri bir C rutini gibi çağırabiliyoruz, Power ışığını söndürmek için assembler'dan $bfe001 adresinin 1. bitini logic '1' yapmamız yeterli. Daha sonra gene aynı adresin 6. bitinin durumunu kontrol ederek mouse tuşuna basılıncaya kadar bekliyoruz. $bfe001 adresinin 1. bitini tekrar '0' yaptıktan sonra da programdan çıkıyoruz. Programın kullanımı oldukça kolay. Şimdi ikinci örneğimize geçelim.
/* Aztec-C Assembler */
/* alt rutinlerinin */
/* kullanimi; */
/* Ornek 2. */
main()
{
int m;
while(1) {
m=mouse();
switch(m) {
case 0 :printf("mouse off\n");
break;
case 1 :printf("mouse on\n");
break;
}
}
}
mouse()
{
#asm
clr.l d0 ; d0 register'ını sıfırla
btst #6,$bfe001 ;
bne.s 1m ; sol tuşu kontrol et
add.l #1,d0 ; basılı ise d0=1 yapıp çık
1m:
#endasm
}
Bu örnekte, bir assembler altrutinden parametre alarak geri dönmeyi öğreniyoruz, mouse() rutini ile sol mouse'u kontrol ediyoruz. Eğer tuşa basılmış ise rutin '1' değeri ile, basılmamışsa '0' değeri ile geri dönüyor. Bu iş için assembler rutinden çıkmadan önce d0 register'ının değerini değiştirmemiz yeterli oluyor. Bu sistem bütün Amiga programlarında kullanılır. Şimdi programımızın assembler listesine bir göz atalım. Bunu yapmak için programı compile ederken;
CC +L -t prog.colarak parametreleri verin. Buradaki '-t' parametresi prog.asm isimli bir assembler listesinin ortaya çıkmasına neden olacaktır. Bu assembler listesini incelediğimizde, programın assembler diline çevrilirken hangi kademelerden geçtiğini görebiliriz. Şimdi isterseniz bu listeye bir göz atalım:
;:ts=8
;/* Aztec-C Assembler */
;/* alt rutinlerinin */
;/* kulIanimi; */
;/* Ornek 2. */
;
;main()
;{
public_main
_main:
link a5,#.2
movem.l .3,-(sp)
;int m;
;
;while(1) {
.4
; m=mouse();
jsr _mouse
move.ld0,-4(a5)
; printf("left mouse=%d\n",m);
move.l-4(a5),-(sp)
pea .l+0
jsr _printf
add.w#8,sp
;}
bra .4
.5
;}
.6
movem.l (sp)+,.3
unlk a5
rts
.2 equ -4
.3 reg
.1
dc.b 108,101,102,116,32,109,111,117,115,101,61,37,100,10,0
ds 0
;
;mouse()
;{
public_mouse
_mouse:
link a5,#.8
movem.l .9,-(sp)
;#asm
clr.l d0
btst #6,$bfe001
bne.s 1m
add.l #1,d0
lm:
;}
.10
movem.l (sp)+,.9
unlk a5
rts
.8 equ 0
.9 req
public _printf
public .begin
dseg
end
Programın assembler listesi içinde ';' işaretlerinden sonra hangi C satırının assembler diline çevrildiği yazılmış. Bu sayede programı incelemek gerçekten çok kolay olmakta.
Listeye baktığımızda, çeşitli assembler komutları ile karşılaşıyoruz. Buradaki komutların bir kısmı, yalnızca Aztec Assembler için kullanılıyor. Diğerleri ise standart assembler komutları. Bu etapta bizi ilgilendiren birkaç komut var. Bunlardan listenin başında görmüş olduğunuz gibi ana main bloğu,
public_main
_main:
olarak seviyelendirilmiş. Buradaki 'public' komutu '_main' isminin bir rutin ismi olduğunu belirtiyor. Gerçekten de programımızın başlangıç rutini buradan başlıyor. Diğer alt rutinler de aynı şekilde tanımlanmış. Bu sayede assembler listesi içerisinde bir alt rutin ismi tanımlamak mümkün. Bizim kullandığımız mouse() rutini de,
public_mouse
_mouse:
olarak tanımlanmış. '_mouse' başındaki '_' işareti assembler derleyici tarafından rutin ismi olduğunu belirtmek için kullanılıyor. Bu işaret aynı zamanda global olarak tanımlanan değişkenlerin başında da kullanılıyor. Programın en son satırlarında ise,
public _printf
public .begin
komutları mevcut. Buradaki tanımlamalar listemiz içerisinde bulunmayan _printf ve .begin rutinlerinin programımızın içerisine ekleneceğini belirtiyor. Bu ekleme olayı link sırasında gerçekleşiyor. Buradan da anlaşılacağı gibi program aslında ilk olarak .begin alt rutinine ve daha sonra _main rutinine sıçrıyor, '.begin' rutinini inceleyerek neler yaptığını görebilirsiniz. Rutinlerin assembler listesini Aztec disketlerinde bulabilirsiniz.
Şimdi yukarıdaki 'public' deyiminden yararlanarak bir assembler alt rutine daha göz atalım:
/* Aztec-C Assembler */
/* alt rutinlerinin */
/* kullanimi; */
/* Ornek 3. */
main()
{
printf("Sol tusa basiniz!..\n");
while(!mouse());
printf("\n");
}
#asm
public _mouse:
_mouse
clr.l d0
btst #6,$bfe001
bne.s 1m
add.l #1,d0
1m:
rts
#endasm
artık burada rutin tanımlamamızı direkt olarak assembler listesi içerisinden yapıyoruz, 'public' komutunu kullanmadığımız takdirde C listesi içerisinden mouse() rutinini çağıramayız. Sonuç olarak 'public' assembler komutu, yanında verilen ismin diğer programlar tarafından bir rutin olarak tanımlanmasında kullanılıyor.
Assembler listeleri aynı zamanda C'de kullanılan değişkenleri de tanıyabiliyor ve ulaşabiliyor. Bunu bir örnekle inceleyelim:
/* Aztec-C Assembler */
/* alt rutinlerinin */
/* kullanimi; */
/* Ornek 4. */
long a;
main()
{
move();
printf("a=%d\n",a);
}
#asm
public _move
_move:
move.l #10,_a
rts
#endasm
'a' değişkenini global olarak tanımlıyoruz. Bu sayede kendi hazırladığımız '_move' assembler rutini, değişkeni tanıyabiliyor. Burada '_a'ya 10 değerini verdiğimizde, C'den de bu değeri okumamız mümkün. Bu yöntemle ortak olarak assembler ve C dilleri arasında değişken kullanmak mümkün olabiliyor. Programımızın assembler listesini incelediğimizde '_a' değişkeni için 4 byte'lık bir yer ayrıldığını görebiliyoruz. Listemizi bir inceleyelim:
;:ts=8
;/* Aztec-C Assembler */
;/* alt rutinlerinin */
;/* kullanimi; */
;/* Ornek 4. */
;
;int a;
global _a,4
;
;main()
public _main
_main:
link a5,#.2
movem." .3,-(sp)
;move();
jsr _move
;printf("a=%d\n",a);
move.l _a,-(sp)
pea .1+0
jsr _printf
add.w #8,sp
;}
.4
movem.l (sp)+,.3
unlk a5
rts
.2 equ 0
.3 reg
.1
dc.b97,61,37,100,10,0
ds0
;
;#asm
public _move
_move:
move.l #10,_a
rts
public _printf
public _move
public .begin
dseg
end
'global' assembler komutu ile '_a' değişkeni için 4 byte'lık yer ayrılıyor. Bu satırın başında dikkat ettiyseniz '; int a;' mevcut. Hatırrlarsanız 'int' tipindeki bir değişken normalde 2 byte'lık yer harcaması gerekirken, burada 4 byte'lık yer ayrılmış. Bunun sebebi compile ederken;
CC +L -t prog.ciçerisinde +L opsiyonunu kullanmamız. Bu sayede bütün 'int tipindeki değişkenler 'long' tipine çevriliyor ve 4 byte'lık yer harcıyor. Programı bir de;
CC -t prog.colarak compile edin. Bu durumda '_a' değişkeni için 2 byte'lık yer harcandığını görebilirsiniz. Fakat bu durumda programın çalışması tehlikeye biniyor. Çünkü hazırlanan assembler alt rutinler genellikle parametrelerini 'long' türünde istediğinden, bu şekilde değişkenleri yollamak daha sağlıklı.
Bu ayki programlarımız da bu kadar. Bu yazımızda, Amiga C içerisinde assembler rutinlerini kullanmayı öğrendik. Assembler rutinleri yazarken çok dikkatli olun. Bildiğiniz gibi assembler hata affetmez. Gelecek ay yeni örnek programlarla buluşmak üzere hepinize iyi günler...