23 Aralık 2020 Çarşamba

DOS'ta Klavye Düzenini Değiştirmek ve CD Sürücüyü Tanıtmak


Merhaba. Önceki dört yazı DOS üzerineydi ve DOS'la ilgili iki yazı daha planlıyorum. Bu yazıyı, yazı dizilerine kısa bir ara vermek için yazıyorum. MBR yazısında Norton disk editörün kullanımını anlatırken, Norton Utilities CD'si veya imaj dosyası olanların, bunu DOS'ta kullanabilmek için CD sürücüyü tanıtmaları gerektiğini yazmıştım.

DOS kurduktan sonra yapılacak işlerden biri de, klavye düzenini değiştirmek. Elbette olmazsa olmaz değil ama nasıl yapılıyor unutmamak için kendime de bir not düşeceğim. Sonuçta her gün yaptığım birşey değil.


DOS'ta Türkçe Klavye Düzeni
Klavyeyi değiştirmek, MSDOS ve FreeDOS'ta aynı mantıkla ama farklı yollarla yapılır. Standard ASCII karakter setinde ş ve ğ gibi karakterler bulunmaz. Bu nedenle önce "codepage"ler (CP) kullanılarak karakter setini değiştirmek gerekir.

ASCII karakter setinin ilk 128 karakteri sabittir. Standart hiçbir CP'de bunun değiştirildiğini görmedim. Teoride, bunların değiştirilmemesi için bir engel yok ama İngilizce alfabedeki 26 karakter ve rakamlar zaten dillerin çoğunda bulunur ve değiştirilmelerine gerek yoktur. CP'ler yardımıyla 128 ile 255 arası karakterler değiştirilir. Eski bazı CP'lerde ilk yarıdaki karakterlerden bazılarının değiştirildiğini okudum. Sanıyorum, örn. 7-bit ASCII destekleyen bir yazıcıda Almanca çıktı almak için az kullanılan karakterler, ü ve ö gibi karakterlerle değiştirilmiş.

Bir veya birden fazla CP, bir dosyada birleştirilip kullanıma sunulmuştur. Bu dosyalar DOS'ta .cpi uzantısına sahiptirler. FreeDOS'ta .cpi dosyalar upx'le sıkıştırılır ve .cpx uzantısını alırlar. Tüm CP'ler bir numarayla kodlanır. IBM PC BIOS'ta bulunan standart CP 437'dir. Bu arada farklı işletim sistemleri farklı numaralandırma kullandığından, çakışan numaralar olabilir. Ben burada DOS CP numaralarına örnek veriyorum. FreeDOS, CP 850'yi kullanıyor. Örn. 737 ve 851 Yunanca, 855 Kiril alfabesi, 857 Türkçe, 865 Danca ve Norveççe CP numaraları. Ayrıntılı liste Wikipedia'daki Code Page maddesinde bulunuyor.

Hangi CP'in hangi dosyada olduğunu bulmanın kolay yolu yok ne yazık ki. CPI dosyaları okumak için geliştirilmiş bir araç var ve içinde dökümantasyonu da içeriyor. Ayrıca başka bir dökümantasyon kullanarak kendi geliştirdiğim bir kod var ama yalnızca FreeDOS'un açılmış cpx dosyalarında denedim. Her CP içerisinde 3 tane 256 karakterlik bitmap var. İlki 8x16 pixel karakterler, ikincisi 8x14 karakterler ve üçüncüsü 8x8 karakterler.

Lafı uzatmadan, Türkçe karakterler için Türkçe CP'nin yüklenmesi gerek. Bunun için önce DISPLAY.SYS kullanılarak bellekte yer ayrılmalı. DOS'ta bu CONFIG.SYS içerisinden DEVICE veya DEVICEHIGH ifadeleriyle yapılıyor. Aşağıda, CON aygıtı olan ekrana CP yüklenebilmesi için 3 sayfa ayrılıyor.

DEVICE=C:\DOS\DISPLAY.SYS CON=(EGA,,3)

Yazıcılar için aynı iş PRINTER.SYS ile yapılıyor olmalı. DISPLAY.SYS tam anlamıyla bir DOS aygıt sürücüsü değil. Sanırım bu nedenle FreeDOS'ta DISPLAY.SYS yerine DISPLAY.EXE aynı işi yapıyor. Elbette bu, çalıştırılabilir dosya olduğundan AUTOEXEC.BAT'da çağırılmalı.

DISPLAY CON=(EGA,,3)

COUNTRY.SYS, klavye için mutlaka gerekli değil. Bu dosyada ülkelerin tarih, saat formatları ve para birimleri gibi bilgiler var. DATE ve TIME komutlarının çıktısını değiştirebiliyor. Bu dosyada da her ülke için üç haneli bir kod var. Örn. ABD 001, İngiltere 044, Almanya 049, Türkiye 090. Görünüşe göre birçoğu milletlerarası telefon kodlarıyla aynı. Daha geniş bir liste i8086.de adresinde var. Bu dosya yüklenirken ülke koduyla birlikte uygun bir CP seçimlik olarak verilebiliyor. Sanırım CP verilmediği durumda, o ülke için tanımlı ilk CP'yi kendisi buluyor. Fakat bildiğim kadarıyla COUNTRY.SYS, bir CP yüklemiyor. Açıkçası bu dosyaya parametre olarak neden CP girildiğini bilmiyorum. Bu dosya hem MSDOS hem FreeDOS'ta CONFIG.SYS'de çağırılmalı (bu arada FreeDOS'ta FDCONFIG.SYS var, CONFIG.SYS bunun içinden çağırılıyor):

COUNTRY=090,857,C:\DOS\COUNTRY.SYS
COUNTRY=090,,C:\DOS\COUNTRY.SYS

Ve elbette FreeDOS'ta bu dosya C:\FDOS\BIN altında.

Sonra yukarıda ayrılan belleğe, MODE komutuyla .cpi/.cpx dosyasından en az bir CP yüklenmelidir (PREP parametresi). Bu bir komut olduğundan AUTOEXEC.BAT'ta olmalıdır ama INSTALL ifadesiyle CONFIG.SYS'den de çağırılabilir. Sonra aynı komutla, bellekteki CP'lerden biri SEL parametresiyle seçilmelidir:

MODE CON CP PREP=((857) C:\DOS\EGA2.CPI)
MODE CON CP SEL=857

Birden fazla CP yüklemek için CP numaları ilk komutta virgülle ayrılarak yazılır. FreeDOS'ta CP 857, C:\FDOS\CPI\EGA.CPX dosyasında bulunur. FreeDOS, MSDOS .cpi dosyalarını kullanabilir. Tersi, FreeDOS'taki dosyalar sıkıştırılmış olduğundan mümkün değil, ancak açılırlarsa mümkün olabilir.

Bir CP seçildikten sonra ekran fontunun çok az değiştiği görülebilir. Artık Türkçe karakterler yüklemiş ve ekrana basılabilirdir. Şimdi tuşlara basıldığında üretilecek ASCII kodları değişmelidir. 'Ğ'ye basınca artık '[' karakteri çıkmamalıdır. Başka bir deyişle 1Ah scan kodlu tuşa basınca, ASCII 5Bh karakteri yerine 0A7h çıkmalıdır ama aynı zamanda ASCII 5Bh da 09 scan kodlu '8' tuşuna AltGr'yle basınca çıkmalıdır. Bunu yapan, KEYB komutuyla birlikte KEYBRD2.SYS'deki yeni eşleştirme tablosudur. Bir dosya verilmediğinde KEYB komutu KEYBOARD.SYS dosyasındaki klavye düzenlerini yükler. Dolayısıyla keyb de, keyb gr gibi komutlar sorunsuz çalışır. Türkçe klavye KEYBRD2.SYS dosyasında tanımlıdır. Bir dilde birden fazla klavye düzeni de olabilir. Türkçe F ve Q klavyeler gibi. Anladığım kadarıyla KEYB komutuna id parametresi verilmediğinde bulduğu ilk (veya varsayılan) düzeni yüklemektedir. FreeDOS'ta Türkçe için varsayılan düzen Q (id: 179) MSDOS'ta F'tir (id: 440). Dolayısıyla Türkçe Q klavye için, id MSDOS'ta zorunlu FreeDOS'ta seçimliktir.

KEYB TR,,C:\DOS\KEYBRD2.SYS /id:179

KEYBRD2.SYS, FreeDOS'ta C:\FDOS\BIN altındadır.

Bu komutların sonucunda Türkçe klavye kullanılabilir. Daha önce belirttiğim gibi COUNTRY.SYS yüklenmeden de klavye sorunsuz çalışır. Eğer DISPLAY eksik olursa MODE komutları çalışamaz. KEYB, hem DISPLAY hem de MODE'dan bağımsızdır ama gerekli CP'ler yüklenmeden çalıştırılırsa, 'Ğ'ye basıldığında ° (derece) karakteri çıkar. ü, ö ve ç karakterleri standart CP'de de bulunduğundan sorunsuz görünür ama 'İ' olmadığından Shift+i, 'ÿ' karakterini verir. Ancak sanıyorum ki, önce KEYB yüklenip ardından CP'ler yüklenebilir.


DOS'ta CD Sürücüyü Tanıtmak
DOS, normalde CD sürücüleri tanımaz. Bu yüzden Win95 CD'si olan bazı kullanıcıların formatlama macerasının sonu, sistem disketi CD'yi tanımadığından bilgisayarcıda bitmiştir. Dahası MSDOS bir CD sürücü dosyası da içermez. Win95 kapatıldıktan sonra da DOS, yanlış hatırlamıyorsam, CD sürücüyü göremez.

Öncelikle bir sürücü dosyası gerekli. Ne yazık ki o zamanki CD-ROM'ların çoğu bir sürücü disketiyle gelmezdi. IDE/ATAPI CD sürücülerin çoğu OAKCDROM.SYS ile basitçe tanıtılabilir. SCSI sürücüler için başka dosyalar olsa da komutlar aynı.

MSDOS'ta önce CONFIG.SYS'de DEVICE veya DEVICEHIGH ifadesiyle sürücü yüklenir. Parametre olarak /D: ile bir aygıt sürücüsü adı verilmelidir (sürücü harfiyle karıştırılmamalı, o sonra).

DEVICEHIGH=C:\CDROM\OAKCDROM.SYS /D:MSCD0001

Bu satırla, CD sürücü bir aygıt olarak bellekte yerini alır ancak henüz bir sürücü harfi atanmamıştır. Yukarıda verilen sürücü adı, AUTOEXEC.BAT'da MSCDEX'e /D: parametresiyle girilir. Bu şekilde CD sürücüye boştaki ilk sürücü harfi atanacağı gibi istenirse /L: parametresiyle başka bir sürücü harfi de verilebilir:

MSCDEX /D:MSCD0001 /L:E

MSCDEX'in açılımı Microsoft CD Extensions'tır. FreeDOS'ta, aygıt sürücüsü DEVLOAD komutuyla AUTOEXEC.BAT'ta yüklenir. /H parametresi HIGH, yani yukarı bellek alanı (DEVICEHIGH ile aynı), /Q parametresi 'Quiet' anlamındadır. Bu arada FreeDOS 1.2'de UDVD2.SYS adında bir sürücü dosyası gelmektedir.

DEVLOAD /H /Q C:\FDOS\BIN\UDVD2.SYS /D:FDCD0001

Ve son olarak FreeDOS'ta MSCDEX'e muadil SHSUCDX bulunur.

SHSUCDX /D:FDCD0001 /L:E

UDVD2.SYS, MSDOS'ta ve OAKCDROM.SYS FreeDOS'ta sorunsuz yüklenebilir. Ancak UDVD2.SYS'nin OAKCDROM.SYS'den küçük olması muhtemelen daha az bellek harcadığı anlamına gelmektedir. Bunun yanında UDVD2.SYS'nin yüklenme süresi de önemli ölçüde kısadır.

10 Aralık 2020 Perşembe

Keyboard Scan Code ve Keyboard Handler


Merhaba. Bu yazının konusu klavyenin nasıl çalıştığı. Bu kez başlık İngilizce çünkü internette "klavye tarama kodları" diye arattığımda yalnız bir kaynak bulunduğu için terimleri kafama göre Türkçeleştirmenin doğru olmadığını düşündüm. Bunun doğruluğu/yanlışlığına dair ayrı bir yazı çıkar, o yüzden bir kenara bırakıyorum.


Keyboard Scan Code
Klavyede bir tuşa basıldığında bilgisayara 'N' tuşuna basıldı '5' tuşuna basıldı gibi bir bilgi iletilmez. Zaten öyle olsaydı Türkçe, İngilizce, Rusça vs. klavye arasında geçiş yapamazdık. Bunun yerine klavye bilgisayara 1. tuşa basıldı, 8. tuşa basıldı gibi bir sıra numarası gönderir. Bu sıra numaralarına tuşların scan kodları denir. Kodlar, yalnızca tuşun (İngilizce) klavyedeki sıra numarasıdır. Örn. ESC: 1, 1'den Backspace'e kadar olan üst sıra 2 .. 14, Tab ile başlayıp Enter ile sonlanan ikinci sıra 15 .. 28 vb. Türkçe klavyedeki " (çift tırnak) karakteri üst sırada olsa da scan kodu 41'dir çünkü standart AT klavyede bu tuş orta sırada Enter'ın yanında bulunur. 

ESC'in AT klavyenin solunda olmasına rağmen scan kodunun 1 olması, aslında öncülü XT klavyede birinci sırada olmasından kaynaklanır. Dikkat edilirse iki klavyede de F11 ve F12 tuşları bulunmuyor. Buna sonradan da değineceğim.


Klavyenin Gelişimi
Bugün kullandığımız klavyenin standartları 1981'de piyasaya sürülen IBM PC'nin klavyesine (Model F) kadar gidiyor. Mart 1983'te piyasaya sürülen IBM PC XT'de de aynı klavye bulunuyor. Bu modellerde özelleşmiş bir klavye denetleyicisi (keyboard controller) bulunmuyor, dolayısıyla klavyeler programlanabilir değil. Programlanabilirlikten kasıt donanımın kendini test etmesi, resetlemesi veya tuş tekrarlama frekansı gibi parametlerin ayarlanması. Bu klavye yalnızca, anakarttaki 8048 entegresine basılan tuşun scan kodunu iletiyor.

1984'te piyasaya sürülen PC AT'de kullanılan klavyeyle (yukarıda görseli olan) birlikte IBM, önceden kullandığı kodları değiştiriyor ve anakarta 8042 klavye denetleyicisini ekliyor. Bu denetleyici yalnız klavyeden sorumlu ve klavyeye yukarıda saydığım programlanabilir özellikler ekleniyor. Ayrıca geriye doğru uyumluluk için klavyeye eklenen bir anahtarla klavye XT protokolünde çalıştırılabiliyor. SysRq tuşu da bu modelde ekleniyor.

Nisan 1986'da standart 101 tuşlu Model M klavye piyasaya sürülüyor. Insert, Home gibi tuşlarla F11, F12 tuşları ekleniyor. 


IBM Model M klavye (Kaynak: https://en.wikipedia.org/wiki/File:IBM_Model_M.png)

Ve son olarak Microsoft, Windows 95'le birlikte klavyelere 3 tuş daha ekliyor: Sağ ve sol Windows tuşuyla, menü tuşu.


Basma Kodları, Bırakma Kodları, Farklı Scan Kod Setleri
Klavye, bilgisayarla 60h ve 64h portu üzerinden haberleşir. 60h verinin, 64h denetleyici komutlarının iletildiği porttur. Klavyede bir tuşa basıldığında, anakarttaki denetleyiciye (8042) tuşun scan kodunu gönderir. Bu koda basma kodu (make code) adı verilir. Tuş bırakıldığındaysa bırakma kodu (break code) gönderilir. Ancak üç farklı kod standardı bulunur. 

Not: 60h aynı zamanda PS/2 farenin de bağlandığı porttur.

Set 1 de denen birinci standart, PC XT'de bulunan yukarıda açıkladığım standarttır. Bu, uyumluluk modu olarak bırakılmıştır. PC AT ile ortaya ikinci standart (set 2) çıkar. 8042 denetleyicisi, set 2 kodlarını set 1'e dönüştürür ve işletim sistemine iletir ancak bu davranış değiştirilebilir. Son olarak set 3, Ekim 1983'te IBM PC 3270 ile çıkmış ve set 2 ile kısmen uyumlu kodlardır. Set 3 de set 2 gibi 8042 tarafından birinci kod setine çevrilir. Kısaca, donanımın ilettiği farklı set kodlar işletim sistemi tarafında tek bir set gibi görünür. Klavyenin veri pinine osiloskop bağladığımda gördüğüm sinyalle 60h portundan okuduğum değer aynı değildir (XT klavyem yoksa).

Set 1'deki basma kodlarına yukarıda kısaca değindim. Bırakma kodları, basma kodlarının 80h ile VEYA işleminden geçirilmesiyle elde edilir. Örn. basma kodu 01h olan ESC'in bırakma kodu 81h, basma kodu 1Ch olan Enter'ın bırakma kodu 9Ch'dır. Bu hesapla bir klavyede maksimum 127 (sıfırıncı kod yok) tuş olabileceği düşünülebilir. Fakat bazı tuşlar için klavye birden fazla scan kod üretir. Genişletilmiş scan kod (extended scan code) adı verilen kodlar 0E0h ile başlar. Örn. nümerik klavyedeki Enter ve '/' (bölü) tuşu genişletilmiş scan kod üretir. Bunlara basıldığında sırasıyla 0E0h 1Ch ile 0E0h 35h üretilir. Genişletilmiş scan kodlarda, bırakma kodu ikinci byte'ın 80h ile VEYA işlemine sokulmasıyla elde edilir. Yani 0E0h 0B5h veya 0E0h 9Ch gibi. Bu arada genişletilmiş kodların hemen hemen tamamı 0E0h ile başlasa da bütün bir 0EXh bloğu aslında bunlara ayrılmıştır ve pratikte az sayıda da olsa 0E1h ile başlayan kod dizisi vardır.

Bu kodlar linux'ta showkey -s ile görülebilir. Örn:


Kodları DOS'ta görüntülemek için basit bir kod yazdım. Öncelikle belirtmeliyim ki, VirtualBox genişletilmiş scan kod setini tanımıyor. Kodu vmware'de geliştirdim ve test ettim. Kod, DOSBox'ta da düzgün çalışıyor ama DOSBox'ta TurboC'de kodu yazarken bazı garip hatalar aldım (bu benden de kaynaklanıyor olabilir). Ancak kod DOSBox'ta vmware'den bile stabil çalışıyor.

Yukarıdaki ekran görüntüsünde sırayla Enter, F10, F11 ve nümerik klavyedeki '/' ile Enter'a bastım. Bu çıktıdakiler elbette birinci sete çevrilmiş kodlar. ESC'e basarak programı sonlandırdım. Kodda 64h portuna yazdığım iki denetleyici komutu var. 0ADh klavyeyi durduruyor, 0AEh tekrar aktifleştiriyor. Bu arada klavyenin tarihinden bahsederken XT klavyelerde on fonksiyon tuşu olduğunu, F11 ile F12'nin AT'lerle birlikte eklendiğini söylemiştim. İşte bu nedenle F1 .. F10 arası tuşların scan kodları 3Bh ile 44h arasıyken F11'in scan kodu 57h ve F12'ninki 58h'dır. Bu programın kaynak kodu aşağıda:

#include <stdio.h>

unsigned char readport()    {
    unsigned char r;
    asm    {
        mov     dx, 0x0064   // klavyeyi disable et
        mov     al, 0xAD
        out     dx, al
        push    dx

        mov     dx, 0x0060   // klavye portundan oku
        in      al, dx
        mov     r, al

        pop     dx
        mov     al, 0xAE     // klavyeyi enable et
        out     dx, al
    }
    return r;
}


int  main(int argc, char* argv[])    {
    unsigned char r, r0;

    for( ;; )    {
        asm { cli }     // interruptlari kapat
        r = readport();
        if((r ^ 0xE0) < 0x10)   {
            // eğer porttan extended kod okunuyorsa
            printf("--- Ext. Key: %02X  ", r);
            // bir byte daha oku:
            printf("Escaped Char: %02X  \n", r0 = readport());
        }
        else if (r0 != r)       {
            // önceki basilan tustan farkliysa scan kodunu yaz
            printf("%02X\n", r);
        }

        if(r == 0x01)   {       // ESC ile cik
            asm { sti }
            break;
        }

        r0 = r;
    }

    return 0;
}

Çalıştırılabilir dosya ve kaynak kod buradan indirilebilir. 

Set 2'de, basılan tuş bir byte'lık kodlarla temsil edilir. Bu sette ESC 76h, '1' 16h, '2' 1Eh şeklinde karışık gider. Kodların klavyede nasıl göründüğü aşağıdadır: 

Kaynak: Wikibooks

Bu görseldeki 0F0h ile başlayan kodlar, set 2'deki bırakma kodlarıdır. Anlaşılacağı gibi bırakma kodu, basma kodundan önce gelen 0F0h ile temsil edilir. ESC 76h ise bırakma kodu 0F0h 76h'dır. Genişletilmiş kodlar bu sette de 0E0h ile başlar. Örn. sol windows tuşunun basma kodu 0E0h 1Fh ve bırakma kodu 0E0h 0F0h 1Fh'dir. 0E0h, set 1'deki gibi bırakma kodundan önce gelir. 

Set 3'te ilginç şekilde genişletilmiş tuşlar bulunmaz. Bunların görüntüsüne bu bağlantıdan; bu setlerle ilgili ayrıntılı bilgi ve dönüşüm tablosuna bu bağlantıdan ulaşılabilir. 

8042 klavye denetleyicisi normalde klavyenin türünü bulur ve klavyeden gelen tarama kodlarını dönüştürür. Güncel işletim sistemlerinin hiçbiri artık birinci seti kullanmamaktadır. Örn. linux'ta atkbd.c klavye sürücüsünde atkbd_select_set( ... ) fonksiyonu 2 veya 3 değerini döndürür. Bu fonksiyonun döndürdüğü değer atkdb struct'ı içerisindeki set elemanına yazılır.

8042'nin gelen veriyi hangi sete dönüştüreceği ayarlanabilir. 60h portuna 0F0h, 0 yazılarak mevcut set sorgulanır. Birinci, ikinci ve üçüncü setler için sırayla 43h, 41h ve 3Fh değerleri döner. Eğer 60h portuna 0F0h'ten sonra 1, 2 veya 3 yazılırsa 8042, istenen kod setinde veri göndermeye başlar. Elbette bunu DOS'ta yapınca, klavye sürücüsü başka setle gelen kodları anlamlandırmaya çalışacak ve yeniden başlatılıncaya kadar sistem kullanılmaz duruma gelecektir. Bu arada Linux'ta showkey -s komutu geçici olarak klavyeyi raw moda geçirir.


Klavye ile İletişim
Klavyeler, IBM PC AT'lere kadar beş pinli DIN konektörle bağlanıyordu. Bu klavyelerden bulmak sanıyorum bit pazarı haricinde imkansız. IBM, 1987'de PS/2 konektörünü geliştirdi. Bunu hatırlayanlar olacaktır, altı pinli, klavye için mor, fare için yeşil renkli bir konektördü. Bunlardan hala çöpe atılmamışsa, kilerde veya depoda tek tük bulunabilir. Günümüzdeyse hemen hepsinin USB bağlantısı var. 

PS/2 ve DIN konektörün sinyalleri tamamen aynıdır. İkisinde de Vcc, GND'nin yanısıra CLK ve DATA pinleri vardır. DATA'daki veri, CLK sinyaliyle senkron olarak (çift yönlü seri senkron iletim) gider. Her tuş basımında klavye CLK'yla senkron şekilde scan kodlarını DATA pininden seri olarak sürer. 

Bu bilgi, bilgisayarda birşey yapmak için gerekli olmasa da Arduino veya başka geliştirme kartlarıyla klavyeyi kullanmak için lazım olabilir.


Bir Tuşa Basıldığında Neler Olur?
Klavyede bir tuşa basıldığında scan kodunun denetleyiciye gönderildiğinden yukarıda bahsettim. Peki işlemci seviyesinde neler olur, ona bakalım. 

Öncelikle klavye denetleyicisi, birincil 8259 kesme denetleyicisinin (IRQ Controller) on dokuzuncu pinine (IRQ 1 bacağına) ulaşarak kesme isteğini işlemciye bildirir. İşlemci 8086 modundaysa Int 9'u çağırır, korumalı modda PIC programlanarak başka bir kesme çağırılır. 

Int 9 kodunu normalde BIOS yükler. İşletim sistemi gerekirse buna wrapper yazar veya tamamen değiştirebilir. BIOS'un çalışması bittiğinde (boot öncesi) Int 09 vektörü F000:E987 adresindedir. Bu kesme klavyeyi anlık olarak kapatır ve 60h portundan scan kodunu okur. Bundan sonra Int 15h/4Fh çağırılır. Int 15/4Fh, normalde bir tuşun scan kodunun değiştirilmesi gerekiyorsa kullanılır (örn. Fn tuşu ile Ctrl'nin yerini değiştirmek)*. Sonraki adımlar basılan tuşa göre değişkenlik gösterir. 
 
Shift, CTRL ve Alt tuşları için BIOS veri alanında (BIOS Data Area - BDA) bir flag bulunur. Bu flag 0040h:0017h ve 0018h adreslerindedir. Bu adreste CapsLock, NumLock, Pause vb. tuşlar için de flaglar bulunur.

Kaynaklar: Ralf Brown Int. List ve lowlevel.eu


Benzeri bir flag daha 0040h:0096h ile 0097h adreslerinde bulunur: 
 
Kaynak: Ralf Brown Interrupt List

Eğer basılan tuş yukarıda adı geçen özel tuşlardan biriyse, int 9 gerekli bitleri ayarlar. Örn. sağ shift'e basıldığında 36h scan kodu okunur ve BIOS 0417h'daki 0. biti set eder. Bu tuş bırakıldığında 0B6h scan kodu okunur ve BIOS 0417h'daki 0. biti resetler. 

Özel amacı olmayan bir tuşa basıldığında yalnız bu tuşa mı basılıyor yoksa bir tuş kombinasyonuyla mı basılıyor, önemlidir. Tuş kombinasyonları yukarıdaki flag'larla anlaşılır. Int 9, bir tuşa basıldığında bunun ASCII kodunu hesaplamak için bir tablodan yararlanır. Örn. basılan tuş yalnız 'A' ise, okunan scan kod 1Eh ve buna karşılık gelen ASCII kodu 'a'ya karşılık gelen 61h'dır. Ancak Shift+A basıldığında yine 1Eh scan kodu okunmasına rağmen shift flag aktiftir (öncesinde shift scan kodu gelmektedir ve flag set edilmiştir) ve int 9 bunun ASCII kodunu 41h ('A') olarak yorumlar. Aynı şey, shift flag aktif değilken CapsLock açıksa da gerçekleşir. Eğer Ctrl+A basılıyorsa, ASCII kodu 01h olarak dönüştürülür. Dolayısıyla BIOS'daki tablo 128 scan kod için 128 satırdan oluşur. Tuşun kendi değeri, Shift ile değeri, CTRL ile değeri için ayrı ayrı sütunlar eklenir. 

Scan kod - ASCII dönüşümü, hemen hemen her tuş için yapılır ve basılan tuş klavye ara belleğinde (keyboard buffer) depolanır. Fonksiyon tuşları, Home, PgUp gibi tuşların ASCII kodu olmadığından ara bellekte ASCII kodları 0 olarak depolanır. Ctrl, Alt, NumLock gibi tuşlarsa depolanmaz. Klavye arabelleği BDA'da 1Eh offsetinde bulunur ve 32 byte uzunluktadır. Her bir tuş için bir scan kod ve bir ASCII kod depolandığından 16 tuşa kadar burada saklanır. Bu veri yapısı bir "ring buffer" (Türkçesi olmayan bir terim daha) olarak kurulmuştur. Ring buffer'ın başlangıç adresi 1Ah ve bitiş adresi 1Ch adresinde bulunur. Bu bir ring buffer olduğundan 1Ah'daki adres 1Ch'deki adresten büyük olabilir. Bir tuş buraya alınacağı zaman tuşun scan ve ASCII kodundan oluşan word, 1Ch'nin gösterdiği adrese yazılır ve 1Ch'deki gösterici 2 arttırılır. Bu arttırım sonucu oluşan değer, ring buffer'ın sonu olan 3Eh adresini aşıyorsa bu değerden 20h çıkartılır. O zaman gösterici tekrar 1Eh adresini göstermeye başlar. Sonradan basılan tuş bellekte, önce basılan tuştan daha küçük offsette yer alabilir. Eğer arttırım sonucunda 1Ch'deki değer 1Ah'daki değere eşit olacaksa klavye arabelleği taşmıştır: Kullanıcı 16 tuşa basmış, BIOS bunları sıraya almış ancak işletim sistemi bunları bellekten oku(ya)mamıştır. Bazı BIOS'lar bu durumda anakarttaki minik hoparlörden sesle kullanıcıyı uyarırlar. Her okuma sonucu 1Ah'daki değer iki eksiltilir.

Buraya kadar scan kod porttan okunmuş, ASCII koduna çevrilmiş ve arabelleğe yazılmıştır. Bundan sonra klavye tekrar aktifleştirilir ve kesmeden çıkılarak ana programa geri dönülür.

Yukarıda anlattığım adımları VirtualBox'ın kaynak kodundan derledim. VBoxBiosAlternative386.asm VBox BIOS'unun assembly kod dökümü. Dosyanın en sağ sütununda, komutun olduğu bellek adresi ve kaynak kodunun olduğu dosya var. Int 9 kesme adresi olan F000h:E987h, bu dosyanın 18 268. satırından başlıyor ve kaynak kodu orgs.asm dosyasının 974. satırı. Klavyenin kapatılması, int 15h/AH=4Fh çağrısı burada gerçekleşiyor ve genişletilmiş scan kodlar için flag'lar burada ayarlanıyor (1011. satır). Sonra assembly kodundan, keyboard.c dosyasındaki int09_function fonksiyonu (371. satır) çağırılıyor. Bu fonksiyonun assembly kodu VBoxBiosAlternative386.asm dosyasında 7501. satırda. Bu fonksiyon, bir switch/case içinde, eğer basılan tuş, özel görevi olan bir tuşsa gerekli flag'ları ayarlıyor, normal bir tuşsa keyboard.c:90'da tanımlanan scan_to_scanascii yapısından tuşun ASCII ve scan kod karşılıklarını buluyor. Bu tablodaki her satır sırayla, tuşun normal, Shift'le, CTRL ile ve Alt ile basılması durumundaki kodları içeriyor. Son eleman 40h ise tuşun CapsLock'la, 20h ise NumLock'la durumunu değiştirdiğini belirtiyor. 604. satırda enqueue_key fonksiyonu çağırılarak çevrilen tuş klavye arabelleğine atılıyor. Bu fonksiyon da aynı dosyanın 339. satırında bulunuyor (VBoxBiosAlternative386.asm:7454).

Aslında BDA'nın 80h ve 82h offsetinde klavye arabelleği için birer tane daha başlangıç ve bitiş göstericileri var. Bu alan, XT BIOS'un 16 tuşluk kısıtlı ara belleğine alternatif olarak AT BIOS'larda tanımlandı. Ancak AT'ler çıktığında piyasada o kadar çok program eski ara belleği hardcoded olarak kullanıyordu ki, bu yeni göstericiler zamanla işlevsiz kaldı. Öyle ki, VBox bile bu göstericileri dikkate almıyor.

* DOS'ta bir klavye düzeni (örn. Türkçe klavye) yüklendiğinde Int 15h/4Fh, scan kodunu alıp ilgili klavye düzenine (layout) ait scan kod - ASCII kod karşılık tablosunda bulunup bulunmadığına bakar. Eğer kod bu tabloda varsa int 9 yerine kendisi tuşun scan ve ASCII kodlarını klavye arabelleğine sürer ve çıkışta carry flag'ı resetleyerek int 9'u bypass eder.


Klavye Arabelleğinden Okumak
Yukarıda herhangi bir şekilde klavye arabelleğinden okumaktan, basılan tuşu kullanıcı programına veya işletim sistemine iletmekten söz etmedim. Klavye portuna doğrudan erişim bir çok nedenle etkin bir yöntem değildir. Örn. klavye standart olmayan bir dilde olabilir ve tüm Alt, CapsLock vb. görevli tuşları kontrol etmeye çalışmak kodu gereksiz yere karmaşık hale getirir. BIOS, basılan tuşları arabelleğe attığı gibi arabellekten okumak için de fonksiyonlar sunar. Bu fonksiyonlar int 16h altında sunulur. 

Fonksiyonlardan ilki, AH=00 ile çağırılan sıfırıncı fonksiyondur. Görevi, klavye arabelleğinde tuş varsa, oradan bunu silmek ve AX'te döndürmektir. Arabellek boşsa fonksiyon, bir tuşa basılana kadar bekler. AH=01 ile çağırılan birinci fonksiyon, arabellekte tuş varsa bunu AX'te döndürür ancak arabellekten silmez. Tuş yoksa da sıfırıncı fonksiyonun tersine, girdi beklemeden sonlanır. Arabellekte tuş olup olmadığını zero flag aracılığıyla geri döndürür.

Yukarıdaki iki fonksiyon XT fonksiyonları olup XT tuş setiyle uyumludur. Başka bir deyişle AT klavyeyle gelen F11, F12, Home, Insert gibi tuşlar bu fonksiyonlarla okunamaz. AT serisiyle birlikte int 16h'ya 10h ve 11h fonksiyonları eklenmiştir. 10h fonksiyonunun görevi 00 ile aynı olup AT tuş takımındaki tuşları da okur. Aynı şekilde 11h fonksiyonu da 01 fonksiyonuyla uyumludur. 00 fonksiyonu çağırılıp F12'ye basılırsa herhangi birşey olmazken 10h fonksiyonu çağırılırsa F12'nin scan kodunu döndürecektir. 

Benzer biçimde 20h ve 21h fonksiyonları 122 tuş klavyeler için varsa da bu klavyelerin yaygın olmaması nedeniyle bu fonksiyonlar bir çok bilgisayarda bulunmaz.


Küçük Bir Uygulama
Klavye arabelleğiyle ilgili yazdıklarımı açıklamak için aşağıda küçük bir kod yazdım. Program sonsuz bir döngüde BDA'yı ekrana basıyor. Klavye arabelleği burada olduğundan, basılan tuşların arabelleğe girişi gerçek zamanlı görülebiliyor. Q, programı sonlandırıyor ve ESC arabelleği temizliyor. Kaynak kodu aşağıda:

#include<stdio.h>
#include<stdlib.h>

#define MEMLINES 10
#define MEMSIZE (MEMLINES * 16)

// return current page
unsigned char getpage()     {
    unsigned char p;
   
    asm     {
        mov     ah, 0x0F
        int     0x10
        mov     p, bh
    }

    return p;
}

// return current cursor position
void getline(unsigned char *x, unsigned char *y)    {
    unsigned char tx, ty;
    unsigned char p = getpage();

    asm     {
        mov     ah, 0x03
        mov     bh, p
        int     0x10
        mov     tx, dl
        mov     ty, dh
    }

    *x = tx;
    *y = ty;
}

// set given cursor position
void setline(unsigned char x, unsigned char y)      {
    unsigned char p = getpage();

    asm     {
        mov     ah, 0x02
        mov     bh, p
        mov     dl, x
        mov     dh, y
        int     0x10
    }
}

// listen on port 60h and return keypress and key release values
char keypress()     {
    unsigned char r;

    asm     {
        in      al, 0x60
        mov     r, al
    }

    return r;
}

// clear keyboard buffer by copying last key pointer over first
// key pointer i.e. 0040h:001Ch <-- 0040h:001Ah
void clearKBbuffer()    {
    unsigned char far *startKBbuffer = (unsigned char far *)0x0041C;
    unsigned char far *endKBbuffer = (unsigned char far *)0x0041A;

    *startKBbuffer++ = *endKBbuffer++;
    *startKBbuffer   = *endKBbuffer;

}


int main(int argc, char* argv[])        {
    int i, j;
    unsigned char curX, curY;
    // memblock points to the start of BIOS Data Area
    unsigned char far *memblock = (unsigned char far *)0x00400;

    getline(&curX, &curY);

    while(1)    {

        for(i = 0; i < MEMLINES; i++)   {
            // print offsets here:
            printf("%06X  ", memblock + (i << 4));

            // hexadecimal block is printed below:
            for(j = 0; j < 16; j++)     {
                printf("%02X ", memblock[(i << 4) + j]);
                if(j == 7)  printf("- ");
            }

            printf("  ");

            // char block is printed below:
            for(j = 0; j < 16; j++)     {
                    if((memblock[(i << 4) + j] < 32) ||
                       (memblock[(i << 4) + j] > 128))
                        printf(".");
                    else
                        printf("%c", memblock[(i << 4) +j]);
            }

            printf("\n");
        }
       
        setline(curX, curY);

        if (keypress() == 0x10)     // if Q key is pressed
            break;                  // break the infinite loop
        if (keypress() == 1)    {   // if ESC key is pressed
            clearKBbuffer();        // clear keyboard buffer
            // break;
        }
    }
   
    return 0;
}


Programı TurboC'de yazdım. TurboC'yi bulmak zor olabileceği için kaynak kodla çalıştırılabilir dosyası buradan indirilebilir. Program hem sanal makinada hem de DosBox'ta çalıştırılabiliyor (DosBox'ta prompt en alt satırdayken çalıştırınca getline() ve setline() düzgün çalışmıyor. cls komutundan sonra çalıştırınca çıktı düzgün görünüyor).

Programı ilk çalıştırdığımdaki ekran görüntüsü yanda. 1Ah ve 1Ch adreslerinde 38h bulunuyor. Birbirine eşit olmaları arabelleğin boş olduğu anlamına geliyor. Programın adını yazarken kullandığım readmem karakterleri arabellekte duruyor. Hatta 1Eh adresinde 'o' karakteri ve sonra ASCII kodu 9, scan kodu 0Fh olan Tab tuşu görülüyor. Komutu yazarken "project" yerine "pro" yazıp tamamlaması için Tab'a basmışım (FreeDOS). Ardından "readmem" yazıp tekrar Tab'a basmışım ama aynı dizinde hem readmem.exe hem de readmem.obj dosyası olduğundan yalnız nokta karakteri çıkmış. Ben de tekrar 'e' ve tekrar Tab'a basıp ardından 1Ch tuşuna yani Enter'a basarak programı çalıştırmışım. 1Ch, 37h offsetinde, bu nedenle de göstericiler 38h'yı gösteriyor. 

Sırayla 'wertyu' tuşlarına bastım. 'wer' 38h'dan arabelleğin sonu olan 3Eh'ya kadar olan alanda bulunuyor. Ardından ring buffer başa dönüyor, geri kalan 'tyu' tuşları 1Eh ile 24h arasında bulunuyor. Esc'e basarak arabelleği sıfırlayıp (1Ah'daki adresi 1Ch'deki adrese yazarak) sonrasında 'asdf'ye basıyorum (aşağıdaki görsel). Bunu yapmamla 'asd' tuşları önceden bastığım 'wer'in üzerine yazılıyor ve 'f' yine 1Eh adresine geliyor. Son olarak arabelleği temizlemeden 'Q'ya basıp ve programı sonlandırıyorum. Int 16h'yla tuşları okumadığım için program sonlanınca, bastığım asdf ve q tuşları DOS tarafından okunarak prompt'ta gösteriliyor.


Klavye bu kadarla sınırlı değil. Burada anlatılanlar yalnızca bir giriş olabilir. Klavyeyi açma ve kapatma komutlarının yanında daha onlarca denetleyici komutu bulunuyor. Bunlarla örn. klavye resetlenip donanım testi yapılabiliyor. PS/2 fare de belirttiğim gibi 60h portundan bağlı. Farenin tek farkı, klavye denetleyicisi IRQ1 oluştururken farenin IRQ12 oluşturması ancak farenin aktifleştirilmesi veya pasifleştirilmesi yine 64h portuna komut göndererek yapılıyor.


Kaynaklar: 
Metin içinde bağlantısı verilen kaynakların yanısıra:

25 Ekim 2020 Pazar

Disk Sistemi Nedir, Ne Değildir? #3: Boot Sektörü


Merhaba. Serinin önceki iki yazısında disklerin fiziksel yapısını ve nasıl bölümlere ayrıldığını ele aldım. Bu yazıda konu biraz daha genelden özele ilerleyecek. Linux'ta DOS'tan bildiğimiz boot sektörü bulunmuyor. Windows'ta da NTFS kendi başına bir yazı konusu olacak kadar karışık. Bu nedenle bu yazıda DOS'a yoğunlaşacağım. Bu yazıda anlatacaklarım, Wikipedia'da Volume Boot Record (VBR) maddesinde ele alınan yapı olacak. Neden bu yapıyı boot sektörü olarak adlandırmayı tercih ettiğimi yazının sonundaki bölümde açıkladım. Şimdi, nedir bu boot sektörü?

Evet, Nedir Bu Boot Sektörü?
Boot sektörü, FAT ve NTFS (ve öncülü HPFS) dosya sistemlerinde disk bölümünün ilk sektörüdür. Ext ve XFS gibi dosya sistemlerinde bu yapı bulunmaz. Yapısında bir kod bloğu ve diskle ilgili bazı bilgiler bulunur. İşlev olarak MBR'ye benzer, şöyle ki, MBR işletim sisteminin yüklenmesi için boot sektörünü yükleyip işleyişi ona devrediyorsa; boot sektörü de boot sürecini devam ettirip DOS çekirdeği olan IO.SYS veya NT ön-çekirdeği olan NTLDR ve sonrasında NTOSKRNL.EXE dosyasını yüklemelidir. Bu arada Windows Vista'dan itibaren NTLDR yerine artık /boot/bcd dosyası bulunmaktadır [1].

Disketler ve bazı USB diskler bölümlenemez. Bunun nedeni bir MBR'lerinin olmamasıdır. Buna karşın sabit diskler MBR'leri sayesinde bölümlenebilir. BIOS ve MBR kodunun ne yaptığını (önceki yazıdan) biliyoruz. Ayrıca üzerine basa basa, BIOS bakış açısından MBR ile boot sektörü arasında fark olmadığını yazmıştım (Alıntı: Code in the MBR and VBR is in essence loaded the same way. [2]). Sistem sabit diskten açılırken:
  1. BIOS, ilk sektörden MBR'yi okur ve çalıştırır.
  2. MBR kodu, boot edilebilir disk bölümün ilk sektörünü okur ve çalıştırır.
  3. Boot sektör kodu, IO.SYS'yi bulup yükler ve süreç devam eder.
DOS, disketten açılırken ikinci adım çalışmaz çünkü disketin ilk sektörü boot sektörüdür. Ve boot sektör kodu da MBR kodu gibi 0:7C00h adresine yüklenir. 

Hatırlatma: UEFI'yle işletim sisteminin yüklenmesi için başka bir algoritma sunar [3]. Yukarıdaki algoritma UEFI'nin olmadığı ortamlarda doğrudur.

[1]: Windows NT 6 startup process
[2]: https://en.wikipedia.org/wiki/Volume_boot_record
[3]: https://en.wikipedia.org/wiki/Boot_sector#UEFI


Boot Sektöründe Neler Var?
Boot sektöründe genel olarak bulunduğu diskin yapısı ve dosya sistemine ait veriler bulunur. Bunlarla yükleme işlemi için gerekli dosyaların diskteki yeri hesaplanır. Dosya sisteminin mantıksal birimi olan kümelere (cluster), CHS/LBA'ya ait bilgiler, DOS'ta dir çıktısındaki disk etiketi, dosya yerleşim tablosu (FAT) sürümü bu bilgilerden başlıcalarıdır. Bu yazıda önce elimdeki sistemlerin boot sektörlerini dosyalara kopyalayacağım. Ardından boot sektör formatı üzerinden bu dosyaları inceleyeceğim.

Yazının sonunda (Boot sektörüyle VBR farkı başlığında) da açıkladığım üzere, kavramlar için kullandığım terimler Wikipedia'dakinden biraz farklı. Yukarıda bahsettiğim veriler Wikipedia'da Volume boot record maddesinden ayrı olarak BIOS parameter block (BPB) ve Design of the FAT file system maddelerinde açıklanıyor. Kişisel görüşüm BIOS parameter block ifadesinin BIOS Data Area (BDA) ile karıştırılabilir olduğu yönünde.


Boot Sektörü Okumak
Boot sektörü Windows'ta okumak için yine HxD'yi kullandım. Önceki yazıda yaptığım gibi HxD'yi yönetici olarak çalıştırıp MBR'yi açtım*. Dizüstü bilgisayarımın diskinde üç bölüm vardı, bunların Windows'un kurulu olduğu sonuncusuyla ilgileniyorum (önceki yazıdaki ekran görüntüsünde görülebilir). Bu bölümün hangi sektörde başladığını önceki yazıdaki bilgileri kullanarak bulabilirim. Üçüncü bölümün başlangıç adresi olan 1E6h adresindeki UInt32 değeri üstteki Sector bölümüne yazıp boot sektörünü görüntüledim. Bunu yalnızca NTFS boot sektörünün FAT'a ne kadar benzediğini göstermek için yapıyorum.

*GPT disklerde bölümleri bulmak için GUID girdisini okumak gerekir. GPT formatını ileride ayrı bir yazıda ele alacağım.


Kırmızı bölge kod, mavi bölge veri ve yeşil boot sektör imzası.

DOS622 makinasında iki tane birincil disk bölümü oluşturup formatlamıştım. Bu makinayı Damn Small Linux (DSL) ile başlatıp komut satırını açıyorum. Linux'ta boot sektörü okumak için herhangi bir program kurmak gerekmiyor. sudo fdisk -l ile diskleri kontrol ettiğimde /dev/hda1 ve /dev/hda2 olmak üzere iki disk görünüyor. Diski dd ile okuyup çıktıyı hexdump'a yönlendirebilir veya hexdump ile diski okuyup head ile ilk 32 satırına (512 byte) bakabilirim:

sudo hexdump -C -v /dev/hda1 | head -n 32

veya

sudo dd if=/dev/hda1 count=1 bs=512 | hexdump -C -v

Aynı komutlar /dev/hda2 için de tekrarlanabilir. Bu verileri kendi makinama kopyalamaya ihtiyacım yok ama gerekirse scp ile bir önceki yazıda anlatıldığı üzere kolayca kopyalayabilirim.

FreeDOS makinasındaysa bir tane birincil, bir tane uzatılmış disk bölümü oluşturup uzatılmış bölüm içinde de iki sanal bölüm daha oluşturmuş ama formatlamamıştım. DSL ile açıp sudo fdisk -l ile disk bölümlerine baktığımda, birincil bölüm /dev/hda1 ve uzatılmış bölüm /dev/hda2'yi görüyorum.

Disk /dev/hda: 1073 MB, 1073741824 bytes
64 heads, 63 sectors/track, 520 cylinders
Units = cylinders of 4032 * 512 = 2064384 bytes

   Device Boot    Start       End    Blocks   Id  System
/dev/hda1   *         1        173     348736+   6  FAT16
/dev/hda2           174        520     699552    5  Extended
/dev/hda5           174        346     348736+   6  FAT16
/dev/hda6           347        520     350752+   6  FAT16


Başlangıç ve bitiş sektörleri gözönünde bulundurulduğunda /dev/hda5 ve /dev/hda6 uzatılmış bölümdeki sanal bölümler. Yukarıdaki komutları /dev/hda2 için çalıştırdığımda önceki yazıda diskeditor'le de gördüğüm, kodu olmayan ama bölümleme tablosu bulunan sektörü görüyorum. /dev/hda5 ve /dev/hda6'da 0F6h byte'ları olan sektörler görünüyor çünkü bu diskleri formatlamamıştım. Bu da demek oluyor ki, disk bölümü formatlandığında boot sektörü formatlandığı dosya sistemine göre yazılıyor.

Peki neden 0F6h? Bu değer, Atari disketleri formatlanırken boş alanlara yazılması için düşünülmüş. Sonra IBM bu şekilde PC standardına eklemiş ve o günden bu yana değiştirilmemiş. Bildiğim kadarıyla özel bir nedeni yok.

Son olarak bir de bir disketin boot sektörüne bakmak istiyorum. Bunun için kendi linux makinamda (host) dd ile 1.44 MB'lik disket büyüklüğünde bir dosya oluşturacağım:

dd if=/dev/zero of=floppy.ima bs=512 count=2880

2880, 80 silindir, 18 sektör ve 2 kafanın çarpımından, bir disketteki toplam sektör sayısı. Windows'ta bu büyüklükte bir txt dosya oluşturulup sonradan adı değiştirilebilir. Önemli olan dosyanın 1 474 560 byte olması.

Dosyayı oluşturduktan sonra DOS6.22'yi açıp Settings -> Storage'dan, Floppy Controller'a oluşturduğum sanal disketi takıyorum. A: sürücüsüne bu haliyle geçersem General failure reading drive A hatasını alırım. (Not: FreeDOS'ta formatlarken format komutunun bir bug'ına denk geldim. Bu yüzden FreeDOS'ta disketin iki kere formatlanması gerekiyor. İlk deneme hata alıyor.)

format A: /S

/S parametresiyle disketi, sistem disketi olarak formatladım yani formatlandıktan sonra içine IO.SYS ve COMMAND.COM gibi dosyalar aktarıldı. Disketin içine bakmak için floppy.ima'yı hexdump'la açmak yeterli. Windows'da yine HxD'yi kullanmak gerekiyor.


Boot Sektörü Veri Yapıları
BIOS parameter block maddesinde ele alınan yapılardan, günümüzde neredeyse yalnız NTFS için olan kullanımda. Oluşturduğumuz sanal makina disklerinde ve diskette DOS 4.0 EBPB var. Önce DOS 4.0 EBPB'ye bakalım:

Sektör OffsetiBüyüklükAçıklama
0x003 byteAsıl koda JMP içerir
0x038 byteOEM AdıNot1
0x0BwordSektörün içerdiği byte
0x0DbyteKümenin içerdiği sektör sayısı
0x0EwordAyrılmış (reserved) sektör sayısı
0x10byteFAT (dosya yerleşim tablosu) sayısı
0x11wordKök dizindeki en fazla girdi sayısıNot2
0x13wordToplam sektör sayısıNot3
0x15byteMedya tanımlayıcıNot4
0x16wordFAT başına sektör sayısı
0x18wordSilindirin içerdiği sektör sayısı
0x1AwordKafa sayısı
0x1CdwordGizli sektörlerin sayısı
0x20dwordToplam sektör sayısıNot5
0x24byteFiziksel sürücü numarasıNot6
0x25byteAyrılmışNot7
0x26byteGenişletilmiş imza (0x28 veya 0x29)
0x27dwordBölüm seri numarası
0x2B11 byteBölüm etiketi
0x368 byteDosya sisteminin türü
Wikipedia'dan derlenmiştir. (madde1, madde2)

Not1: OEM adı alanı diski formatlayan sisteme ait bir imzadır. Normalde özel bir değeri yoktur. Ancak keyfi değerler bazı DOS sürümlerinin disketi okumamasına neden olabilir.
Not2: (FAT32 öncesi) FAT dosya sisteminin yapısı gereği kök dizin "\" altında belli bir sayıdan fazla girdi (dosya+dizin) oluşturulamaz. Tasarım gereği bu dizin özeldir, her zaman ikinci kümede olmalıdır ve büyüklüğü sabittir. Elbette bu istisna alt dizinleri kapsamaz.
Not3: Bu alan 16-bit olduğundan, bölüm sektör sayısı 65 535'den büyük olması durumunda yerini 0x20'deki alana bırakır. Çoğunlukla değeri sıfırdır.
Not4: (Media descriptor) Bu byte diskin türünü belirlemek için kullanılır. Sabit diskler için değeri 0xF0 ve 1.44M disketler için 0xF8'dir. Diğer değerlerle uygulamada karşılaşılacağını düşünmüyorum.
Not5: 0x13'teki alan kullanımdan kaldırılınca (Not3'e bakınız) yerini bu alan almıştır. Eğer 0x13'ün yanısıra bu alan da sıfırsa işletim sistemi toplam sektör sayısını MBR'den okur.
Not6: Disket sürücüler için sırayla 0x00, 0x01... Sabit diskler için 0x80, 0x81 v.b.
Not7: Her ne kadar bu alan ayrılmış (reserved) olarak belirtilse de, WinNT Chkdsk komutu burayı diskteki hatalar için bir flag biti olarak kullanır.

Bu bilgilerle disketin boot sektörünü inceleyelim:

00000000  eb 3c 90 4d 53 44 4f 53  35 2e 30 00 02 01 01 00  |.<.MSDOS5.0.....|
00000010  02 e0 00 40 0b f0 09 00  12 00 02 00 00 00 00 00  |...@............|
00000020  00 00 00 00 00 00 29 05  14 76 1b 4e 4f 20 4e 41  |......)..v.NO NA|
00000030  4d 45 20 20 20 20 46 41  54 31 32 20 20 20 fa 33  |ME    FAT12   .3|


İlk üç byte JMP +3Ch; NOP komutunu içeriyor. JMP'ın kendisi iki byte olduğuna göre kodun başlangıcı 2+3Ch=3Eh offsetinde. 0FAh, 033h, 0C0h byte'ları CLI, XOR AX,AX komutlarını içeriyor (0C0h yukarıda görünmüyor). Diğer değerler sırasıyla;

OEM Adı: MSDOS5.0
Byte/Sektör: 0200h = 512 byte
Sektör/Küme: 1
Ayrılmış Sektörler: 1 (boot sektörün kendisi)
FAT Sayısı: 2
Kök Dizindeki Max. Girdi Sayısı: 0E0h = 224
Toplam Sektör Sayısı1: 0B40h = 2880
Medya Tanımlayıcı: 0F0h (1.44M Disket)
Sektör/FAT: 9
Sektör/Silindir: 12h = 18
Kafa: 2
Gizli Sektörler: 0
Toplam Sektör Sayısı2: 0
Fiziksel Sürücü Num.: 0
Ayrılmış: 0
Genişletilmiş imza: 29h
Bölüm seri numarası: 05h, 14h, 76h, 1Bh = 1B76-1405 (Yukarıdaki ekran görüntüsündeki değerle aynı)
Bölüm etiketi: NO NAME
Dosya Sistemi: FAT12

Aynı şekilde DOS6.22'nin sabit diskine bakalım:

00000000  eb 3c 90 4d 53 44 4f 53  35 2e 30 00 02 04 01 00  |.<.MSDOS5.0.....|
00000010  02 00 02 00 00 f8 fa 00  3f 00 10 00 3f 00 00 00  |........?...?...|
00000020  e1 e7 03 00 80 00 29 12  b6 0b 4f 4d 53 2d 44 4f  |......)...OMS-DO|
00000030  53 5f 36 20 20 20 46 41  54 31 36 20 20 20 fa 33  |S_6   FAT16   .3|


OEM Adı: MSDOS5.0
Byte/Sektör: 0200h = 512 byte
Sektör/Küme: 4 yani her küme 2048 byte.
Ayrılmış Sektörler: 1 (boot sektörün kendisi)
FAT Sayısı: 2
Kök Dizindeki Max. Girdi Sayısı: 0200h =512
Toplam Sektör Sayısı1: 0
Medya Tanımlayıcı: 0F8h (Sabit disk)
Sektör/FAT: 250
Sektör/Silindir: 3Fh = 63
Kafa:10h = 16
Gizli Sektörler: 3Fh = 63
Toplam Sektör Sayısı2: 03 E7E1h = 255 969
Fiziksel Sürücü Num.:80h
Ayrılmış: 0
Genişletilmiş imza: 29h
Bölüm seri numarası: 12h, 0B6h, 0Bh, 4Fh = 4F0B-B612
Bölüm etiketi: MS-DOS_6
Dosya Sistemi: FAT16


Gelelim FAT32'nin yapısına. Wikipedia'da bu DOS 7.1 EBPB olarak geçiyor ve yapının ilk 36 byte'ı aynı.

Sektör OffsetiBüyüklükAçıklama
0x0036 byte(FAT16 yapısına bakınız)
0x24dwordFAT başına sektör sayısı
0x28wordAynalama flag'larıNot8
0x2AwordSürüm
0x2CdwordKök dizin kümesiNot9
0x30wordFSINFO sektörüNot10
0x32wordYedek boot sektörüNot11
0x3412 byteAyrılmışNot12
0x40byteFiziksel sürücü numarasıNot6
0x41byteAyrılmışNot7
0x42byteGenişletilmiş imza (0x28 veya 0x29)
0x43dwordBölüm seri numarası
0x4711 byteBölüm etiketi
0x528 byteDosya sisteminin türü
Wikipedia'dan derlenmiştir. (madde1, madde2)

Not8: Normalde dosya yerleşim tablosu en az iki kopya tutulur ve her dosya okuma yazma işlemi bu iki kopyaya da işlenir. Bu flag ile istenirse yalnız bir tablonun aktif olması sağlanabilir.
Not9: Not2'de belirtilen tasarım kısıtlarını kaldırmak için yeni tasarıma böyle bir alan eklenmiştir.
Not10: FAT32'yle birlikte küme sayısının teorik üst sınırı 65526'dan 228=268 435 456'ya çıktı. 232 değil 228, çünkü 4 bit ayrılmıştır. Bu durumda bile dosya sistemindeki boş alanı hesaplamak veya boş bir küme bulmak için incelenmesi gereken çok fazla küme vardır ve bu performans sorunlarına neden olur. Bu nedenle bu bilgileri tutmak için FSINFO adında ayrı bir veri yapısı tanımlanmıştır. Bu yapı genelde birinci sektörde yani boot sektörün hemen sonrasında tutulur ve RRaA imzasıyla anlaşılır.
Not11: FAT32'yle birlikte, bölümün ilk sektörünün bozulmasına karşı yedek boot sektörü tutulur. Bu her zaman altıncı sektördedir. MBR kodu eğer sıfırıncı sektörü okuyamazsa hardcoded olarak altıncı sektöre bakar. Bu değer kodda olmasına rağmen boot sektörde de bir değer ayrılmıştır ama altıdan başka bir değer olması tavsiye edilmez.
Not12: Bu alanın adı dökümantasyonda "boot file name" olarak görünüyor. Normalde boot sektörün yükleyeceği dosya adı boot kodunda yer alır. Bu veriyi sabit bir alanda tutmak için ayrılmış olabileceği akla geliyor.

Bu arada yukarıdaki notların bir kısmını Microsoft'un FAT32 File System Specification belgesinden derledim. Maalesef Microsoft'un sitesinde bu belge artık yok ama buradan indirilebilir. 

Son olarak NTFS disk bölümlerinin de buna çok benzer bir yapısı var. FAT32 gibi bu yapının da ilk 36 byte'ı FAT16 ile hemen hemen aynı.

Sektör OffsetiBüyüklükAçıklama
0x0036 byte(FAT16 yapısına bakınız)
0x24byteFiziksel sürücü numarasıNot6
0x25byteAyrılmış / Kullanılmıyor
0x26byteGenişletilmiş imza (0x80)
0x27byteAyrılmış
0x28qwordBölümdeki sektör sayısı
0x30qwordAna dosya tablosunun (MFT)
bulunduğu küme numarası
0x38qwordDosya tablosunun ikinci kopyasının
bulunduğu küme numarası
0x40dwordFile Record Segment (FRS)'de
bulunan küme sayısıNot13
0x44dwordIndex block'taki küme sayısıNot13
0x48qwordBölüm seri numarası
0x50dwordChecksum
Wikipedia ve microsoft.com'dan derlenmiştir. (madde1, madde2, madde3)

Not13: 0x40 ve 0x44 offsetlerindeki iki alan aslında dword değil 1 byte + 3 byte ayrılmış olarak tanımlıdır. 3 byte'lık ayrılmış alanın FAT32'de farklı bir kullanımı olduğundan uyumluluk için kasıtlı bırakılmış olabilir. Daha doğrusu bir FAT32 disk programı yanlışlıkla NTFS disk bölümünü bozamasın diye.

Yazının başında da belirttiğim gibi NTFS boot sektörü öncülü FAT'a benzemekle birlikte daha karışık. Bu nedenle yazıyı uzatmamak için NTFS'i ve boot sektöründeki kodu ayrı bir yazıda ele alacağım.

MBR yazısında diskin büyüklüğü hakkında iki farklı terimden bahsetmiştim. Bunlardan biri ham (raw) alan diğeri biçimlendirilebilir (formatlanabilir) alandı. Tabloda ayrılmış sektörler ve gizli sektörler olmak üzere iki alan var, bunların pratikte bazı hesaplamalarda kullanılıyor ve teoride kök dizinle boot sektör arasına yer açmak için de kullanılabilir. Bunlardan başka, bölümdeki dosyaların yerlerini tutan iki tane FAT var. Bu yapılar diskte olmadığında dosya sisteminden söz edilemez, başka bir deyişle dosyalar yazılamaz veya yazılsa bile başı nerede sonu nerede bilinemez. Diskin ham alanı MBR'deki sektör sayısı 512 byte'la (sektördeki byte sayısı) çarpıldığında çıkan alanken, formatlanabilir alan yukarıda sayılan yapıların format sonrasında ham alandan çıkarılmasıyla geri kalan alandır. 

Örn. bir disket için 1 474 560 byte'lık (2880 sektör) bir dosya oluşturmuştum. Bu onun ham alanıdır. Başka bir deyişle bir diskete herhangi bir dosya yapısı olmadan bu kadar byte yazabilirim. Aşağıdaki ekran görüntüsünde DOS formatlanmış disketle ilgili ne diyor?

2880 sektörden 2847 tanesi kullanılabilir. 2x16 sektör FAT ve 1 sektör de boot sektör olmak üzere toplamda 33 sektör dosya sisteminin kullanımına. Dolayısıyla bu diskette 1 457 664 byte'dan daha büyük bir dosya oluşturamam. 

Bu iddia, DSL ile boot edip aşağıdaki komutlarla denenebilir:

sudo mount /dev/fd0 /mnt
sudo rm /mnt/*
sudo dd if=/dev/zero of=/mnt/test
sudo ls -la /mnt

Hatırlayalım; önceki yazıda oluşturduğum FreeDOS makinasında, 3 disk bölümü var. İkisi genişletilmiş (extended) bir tanesi birincil. DOS622 makinasında 2 birincil disk bölümü var ve diskin yarısında bölüm yok. Şimdi yukarıda yazdıklarım için bir kolaylık göstereceğim ancak maalesef sadece Norton Disk Editörü olanlara.

DOS622 makinasında diskedit.exe'yi (v2000) çalıştırdım ve Alt+A ile diskin bölümleme tablosunu görüntüledim (sağ üst görsel). Hemen ardından bir daha Enter'a bastım (kursör birinci bölüm üzerindeyken) ve boot sektör tüm bilgileri okunmuş şekilde açıldı (sağ alt). Üstelik sağdaki sütunda DOS'un raporladığı değerler de gösteriliyor. Bu ne işe yarar? Bozuk bir diski veya disketi (elbette fiziksel olarak bozuk olmayan) DOS çalışan sağlam bilgisayarıma takıp açtığımı varsayalım. Disk editörle kontrol ettiğimde boot sektöründe "Sectors per cluster" değeri işletim sisteminin raporladığından farklı olsun. Bu değeri olması gereken değerle değiştirerek -küçük bir ihtimal de olsa- diski okunabilir duruma getirebilirim. Bu ekrandaki bölüm etiketi ve bölüm seri numarasını not edip, disk editörden çıktıktan sonra dir komutuyla doğrulayabilirim.

Disk editörde tekrar Alt+A tuş kombinasyonuyla MBR'yi görüntüleyip (sağ alttaki "Absolute Sector 0" ifadesine dikkat) diğer bölümü de (veya FreeDOS'ta) aynı şekilde kontrol etmek mümkün. Aralarında büyük bir fark olmadığından ayrıca incelemeyeceğim.


Boot Kodu Nasıl Çalışır?
 
a. FreeDOS Boot Kodu
Bu bölümde hem FreeDOS'un hem de DOS'un boot kodlarını inceleyeceğim. Öncelikle belirtmeliyim ki, FreeDOS boot kodu disketlerde (FAT12) ve disklerde (çoğunlukla FAT16) biraz farklı. Aradaki farkla yeri geldiğinde değineceğim.

FreeDOS'ta bir disket, /S parametresi olmadan formatlandığında boot kodu, basit bir hata mesajıyla boot işlemini sonlandırıyor. Başka bir deyişle boot edebilen bir kod yazılmıyor. Bunun nedeni, FreeDOS boot kodu DOS'a göre daha karmaşık olduğundan "Missing IO.SYS" vb. hata mesajlarına yer kalmaması olabilir. Bu kod çok basit olduğu için üzerinde durmayacağım.
FreeDOS'u incelemenin kolay yanı açık kaynak kodlu olması. FreeDOS'un boot kodunu github'dan indirdim. Bazı yerlere kendi açıklamalarımı da ekledim. Bunlar "; --" şeklinde başlıyor. Dosyanın orjinali buradan, benim açıklamalarımı içeren dosya buradan indirilebilir. Yaptığı iş çok karışık değil. Önce bir jmp komutu (satır** 69) asıl kod parçasına dallanıyor. Bundan hemen sonra sektörün veri yapılarına ait tanımlar var (s73-90). Kodun 0:7C00h adresinde çalıştığını söylemiştim. Kod, 0060h:0 adresine yükleceği kernelin, kendi üzerine yazmasını engellemek için kendini 1FE0h:0 adresine kopyalıyor (s162-168) ve bir far jmp ile kopyalanan kodda kaldığı yerden çalışmasını sürdürüyor. Kernel yüklendiğinde, bu kodun çalışması tamamlanmış olsa bile, kernel.sys'nin boot sektöründeki bazı verilere erişmesi gerekebilir.

**: Satır numaralarını açıklamalarımı eklediğim dosyaya göre verdim.

Boot kodu açısından üç tane önemli veri var. 
1. FAT başlangıcı (fat_start): Dosya yerleşim tablosunun başladığı sektör, gizli sektörlerle ayrılmış sektörlerin toplamı.

fat_start = hidden_sectors + reserved_sectors

Ayrılmış sektörlerin boot sektörüyle FAT arasında yer açmak için kullanılabileceğini belirtmiştim. Bu alan, FAT'in boot sektöründen kaç sektör sonra bulunduğunu bildirir. FAT12 ve FAT16 için bu alan hemen hemen her zaman 1'dir, yani FAT boot sektöründen hemen sonra başlar. Gizli sektörler terimi biraz yanıltıcı olup aslında o boot sektörün kaçıncı sektör olduğunu tutar. Başka bir deyişle o boot sektörün LBA adresidir. Bu alan yalnız boot ederken hesaplamaya katılır. Boot edilmeyen bir diskte sıfır olması hiç bir sorun oluşturmaz.
 
2. Kök dizin tablosunun başlangıcı (root_dir_start): FAT başlangıcına, FAT sayısıyla FAT başına sektör sayısının çarpımının eklenmesiyle bulunan sektör.

root_dir_start = fat_start + number_of_FATs * sectors_per_FAT

3. Diskte dosya verisinin bulunduğu ilk sektör (data_start): Bu da kök dizindeki girdi sayısının, bir sektördeki byte sayısının 32'de birine bölünüp, kök dizin tablosunun başlangıcı (yukarıdaki) değerine eklenmesiyle bulunur. Çünkü bir dosya girdisi 32 bytedır. Sektördeki byte sayısının 32'ye bölünmesiyle bir sektörün içerebileceği girdi sayısı bulunur. Toplam girdi sayısı buna bölündüğünde kök dizin tablosunun sektör cinsinden uzunluğu bulunur.

data_start = root_dir_start + root_dir_entries / (bytes_per_sector >> 5)

Not: Dosya yerleşim tablosu ve dizin tablolarının girdileri, ayrıntılı olarak bir sonraki yazının konusu olacak.

192 ile 228. satırlar arasında bu değerler hesaplanıp ilgili değişkenlere atanıyor. Bellekteki adresleri sırayla 7BD2h, 7BD6h ve 7BDAh. Sonrasında kök dizin tablosunun tamamı belleğe okunuyor (s238-243). 240. satırda pop edilen DI'nin değeri 221. satırda push edilen AX'in değerinden geliyor. Bu da data_start değeri hesaplanırken bulunan, kök dizinin sektör cinsinden uzunluğu. 249 ile 263. satırlar arasında kök dizin tablosunda kernel.sys dosyası aranıyor. Dosya bulunursa bu dosyanın bulunduğu küme (cluster) stack'e atılıp (s263) dosya yerleşim tablosunun tamamı (sectors per FAT tane sektör) diskten okunuyor (s277-290). 

FreeDOS'ta disk (FAT16) ve disket (FAT12) boot kodunun farklı olduğu yer bu nokta. Muhtemelen bu kod format.com içinde hem FAT12 hem FAT16 için derlenmiş halde bulunuyor. Tablonun yapısını ve nasıl çözümlendiğini gelecek yazıda açıklayacağım. Basitçe tablo linked list gibi, o kümeden sonraki kümenin hangisi olduğunu veya dosyanın orada bitip bitmediğini tutuyor. Bu kümelerin listesi 0060h:2000h'da bir listeye word word (satır 301 ve 327) yazılıyor. Bu liste bir döngüyle (s. 349-355) okunup ilgili kümelere karşılık gelen sektörler (yani kernel.sys) 0060h:0 adresine yazılıyor ve 353. satırdaki far jmp ile kernel çalıştırılıyor. 

Dikkat edilirse, kod kompleks (ve dolayısıyla uzun) olduğundan hata mesajı olarak yalnıca bir "Error!." (s. 379) var. readDisk fonksiyonunda her okunan sektör için ekrana bir nokta karakteri basılıyor. Bu, muhtemelen bir sektör okunamadığında hangisinde sorun olduğunu saptamak için gerekli. Fonksiyonda LBA desteği test ediliyor ama hem disket için LBA desteklenmiyor hem de zaten disketten boot ediliyorsa bu test çalıştırılmıyor (s. 416). LBA destekleniyorsa disk okuması daha önceden oluşturulan DAP paketiyle yapılıyor aksi halde read_normal_BIOS (s. 442) bölümü çalıştırılıyor. Bu bölümde (s. 445-486) LBA_SECTOR_0 ve LBA_SECTOR_16 adreslerinde tutulan dword değer alınıp CHS adrese çevriliyor. Bu fonksiyon her sektörü ES:63A0h'ya okuyup sonra fonksiyona girişte ES:BX'te tutulan hedefe kopyalıyor (s. 493-500).

FreeDOS aynı zamanda FAT32 diskleri de destekliyor. Ben önceki yazıda sanal makinayı kurarken FAT32'yi bilerek devre dışı bırakmıştım. Bu nedenle mevcut disk boot sektörü yukarıda bahsettiğim FAT16 kod bloğu dışında tamamen disket boot sektörüyle aynı. Ancak FAT32 diskler için oluşturulmuş boot32.asm ve boot32lb.asm adında iki ayrı boot sektör kodu daha var. Bunları incelersem bile ayrı bir yazıda inceleyeceğim.


b. DOS Boot Kodu
DOS boot kodu kaynak kodu olmamasına rağmen görece basitliği nedeniyle incelemek daha kolay. Yukarıda oluşturduğum floppy.ima dosyasını kullandım. Bu disketi önceden formatlamıştım. Dolayısıyla boot sektörü var. Bunun ilk 512 byte'ını linux'ta dd komutuyla ayırdım. 

dd if=floppy.ima of=floppy.bin count=1 bs=512 

Sonra MBR için yaptığım gibi objdump ile disassemble ettim:

objdump -M intel -D -b binary -m i8086 --adjust-vma=0x7C00 floppy.bin > floppy.asm

floppy.asm dosyasındaki veri yapılarını ve metinleri elle ayıklayıp değişkenlere dönüştürdüm ve elbette yorum ekledim. Dosya buradan indirilebilir.

Burada da benzer şekilde en başta 7C00h adresinde jmp komutu var. Kodun başında stack ayarlanıyor ve hemen sonra disket parametre tablosu değiştiriliyor (7C3Eh - 7C70h arası). Bu tablo disket sürücü denetleyicisiyle ilgili parametreleri tutan ve int 1Eh vektörünün gösterdiği bir tablo (dolayısıyla çağırılabilir değil). Bu tabloya sürücünün desteklemediği anlamsız değerler girmek makinanın kilitlenmesine yol açıyor. Muhtemelen burada kafa konumlandırma süreleriyle oynayarak biraz daha hızlı boot edilmesi amaçlanmış. Fakat işin garibi bu kod parçası disklerde de bulunuyor.

Bu tablonun bir kopyası oluşturulup (7C59h), bazı değerler değiştirildikten sonra bu kopyanın göstericisi int 1Eh vektörüne yazılıyor (7C68h) ve int 13h bir kere daha çağırılarak disket sürücü denetleyicisi yeni parametrelerle resetleniyor. Ardından FreeDOS'taki benzer hesaplamalar burada da yapılıyor. Önce FAT sektörlerinin sayısı bulunuyor (7C87h) sonra bu değere gizli ve ayrılmış sektörlerin sayısı ekleniyor. Bütün bu değerler 32-bit olarak DX:AX'te işlem görüyor. Dikkat edilirse burada değişkenler daha önceden çalışmış kodun üzerine yazılıyor. data_start bellekte 7C49h ve root_dir_start 7C50h (7C9Ah) adreslerinde tutuluyor. Kök dizin listesinin sektör sayısı farklı bir yolla hesaplansa da (7CA8h-7CB6h) aynı sonucu veriyor. 7C50h'deki root_dir_start sektörü 7CCBh'da CHS adresine dönüştürülüp 7CD2h'de diskten okunuyor. 

Yukarıda bütün kök dizin listesi değil yalnızca ilk sektörü okunuyor. Ardından listenin başında IO.SYS ve sonraki kayıtta MSDOS.SYS olup olmadığına bakılıyor ve içlerinden herhangi biri olması gerektiği yerde yoksa kod hata verip bilgisayarı yeniden başlatıyor1.

7D05h adresinde başlayan loadkernel fonksiyonu IO.SYS'nin başlangıç kümesini alıyor, FreeDOS gibi 2 çıkarıyor ve bu dosyanın ilk sektörünü bulup readloop içerisinde yalnızca 3 (7D1Dh) ardışık sektör okuyor2 3. Döngü bittiğinde kod IO.SYS'ye 3 tane değer geçiriyor (7D3Eh): Dosyaların bulunduğu ilk sektör AX:BX'te, CH'de media descriptor byte ve DL'de boot edilen fiziksel sürücü numarası. Ve 7D4Dh'da 0070h:0 adresine yüklenen kernele dallanıyor. 

DOS boot sektörü aşağıdaki üç nedenden (veya kısıttan) dolayı daha basit ancak diğer yandan da birşeylerin ters gitmesine çok açık:

1: DOS kernel dosyalarının kaydının birinci ve ikinci sırada olmasını şart koşuyor. Dosyalar diskte olsalar bile kayıtları farklı bir yerdeyse boot edemiyor. FreeDOS'ta bir disketin boot kodu boot edilmesine olanak veriyorsa, kernel.sys istenirse sonradan elle kopyalansın, yine de boot edilebilir. DOS'ta edilemez.
2: Eğer IO.SYS'de bir defragmantasyon söz konusuysa belleğe okunan dosyanın IO.SYS olduğunun bir garantisi yok. 
3: Dosyanın boyutundan bağımsız üç sektör okunuyor. IO.SYS kendisinin geri kalanını belleğe okumaktan kendi sorumlu.


Linux'ta Bu Yapılar Nasıl?
Kısaca söylemem gerekirse bu yapıların hiçbiri yok. Yani ne disk bölümünün ilk sektöründe sektör sayısı, kafa numarası, ayrılmış sektörler var, ne de herhangi çalıştırılabilir bir kod. BIOS ile kernel arasındaki bağlantıyı sağlamak için boot loader'lar var. Örn. GRUB'u ele alırsam, (1) GRUB Stage1 kodu MBR'de yazılı olabilir. Bu durumda GRUB MBR kodu core.img'ı çalıştırır, grub.conf dosyasını bulup istenen işletim sistemini yükler. (2) Veya Stage1 kodu tercihen /boot disk bölümünün ilk sektöründedir. Bölümleme tablosunda bu bölüm boot edilebilirdir ve standart MBR kodu bu bölümün ilk sektöründeki Stage1 kodunu yükler, ardından core.img yüklenerek birinci adımdaki işlemler devam eder. 
 
LILO ve syslinux'ta da benzeri bir süreç söz konusu. Bu arada UEFI de aslında bir boot loader olduğundan, UEFI destekli makinalarda linux çekirdeği herhangi bir boot loader'a gerek kalmadan BIOS'tan UEFI aracılığıyla çağırılabilir.


Ek: Boot Sektörü ile VBR Farkı

Wikipedia'da "Boot sector" maddesi daha önce MBR olarak ele aldığım yapıyı kapsıyor (daha genel). MBR maddesindeki açıklama şöyle diyor: "Bu madde, bölümlenebilen ortamlarda PC'lere özgü boot sektörü hakkındadır. Bölümlenemeyen ortamlardaki ilk sektör için sürücü boot kaydı maddesine bakınız (volume boot record)." Boot sektörü maddesindeki açıklama da şunu diyor: "Bu madde, genel olarak boot sektörü konsepti hakkındadır. PC'lerdeki sürücü boot kaydı VBR ve ana boot kaydı için MBR maddelerine bakın."

Neden böyle bir adlandırma karmaşası oldu? Bu konuları öğrenirken VBR adıyla hiç karşılaşmadım. İki yapıyı birbirinden ayırt etmek için hep MBR ve boot sektörü ifadelerini kullandım çünkü öğrendiğim kaynaklarda da böyleydi. Wikipedia'da VBR maddesindeki referanslarda da VBR ifadesine rastlamadım. Özetle VBR ifadesi görece yeni bir terim olmalı. Michael Tischer'in PC Intern 3.0: Sistem Programlama (1. basım, 1992) kitabından da kontrol ettim: s.557, MBR için Partition Sector deniyor. Boot sektörüyle anlatılan (s.919) benim bu yazıda değindiğim konu.