Hard Disk etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
Hard Disk etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

2 Kasım 2021 Salı

Disk Sistemi Nedir, Ne Değildir? #4.5: FAT32


Merhaba. Kısa bir ara verdiğim dosya sistemlerine FAT32'yle devam ediyorum. Önceki yazıda FAT32'ye değindim ama ayrıntılı incelememiştim. Bu yazıda FreeDOS'u FAT32 diske kurup inceleyeceğim ve MSDOS'la karşılaştıracağım.

FAT32, daha önce bahsettiğim gibi, disk büyüklüklerinin 90'larda FAT16'nın getirdiği 2 GB sınırının üstüne çıkmasıyla geliştirildi ve Ağustos 1996'da Win95 OSR2 (ve birlikte gelen MS-DOS 7.1) ile son kullanıcıya sunuldu. Dolaysıyla tek satılan hiçbir MS-DOS sürümü FAT32'yi desteklemiyor. Ve Microsoft, WinXP'den sonraki işletim sistemlerinde, FAT32 disklere kurulumu desteklemeyeceğini de açıkladı. FAT32, yapısal olarak FAT16'ya benzer. Bu sayede DOS çekirdeğindeki dosya sistemi rutinleri tekrardan yazılmamış ve FAT32 desteği yalnızca 5KB civarında bir kodla çekirdeğe eklenmiştir [1].

FAT32'yi ele almaya en temelde MBR'den başlarsam, burada FAT32'yle ilgili tek fark, bölümleme tablosundaki bölüm türüdür. FAT32 formatlı CHS diskler 0x0B ve LBA destekli diskler 0x0C ile işaretlidir. Bunu serinin ikinci yazısında yazmıştım.

FAT32 boot sektörünün ilk 36 byte'ı FAT16'yla aynı, ama FAT32'de fazladan alanlar var. Buna da serinin üçüncü yazısında değindim. Eklenen aynalama flag'ları, kök dizin kümesi ve FSINFO sektörü göstericisi gibi alanlarla FAT'in yetenekleri geliştirilmiştir. Sektördeki kod ise FAT16'nınkinden biraz farklıdır.

Anlattıklarımın teoride kalmaması için önce sanal bir FreeDOS kurdum. Bu kurulumu bir önceki yazıda anlattığımdan detaylara inmeyeceğim. Yalnız, diskin 2 GB'dan büyük tek bölüm olması iyi olur ve bölümlerken fdisk'in FAT32 desteği mutlaka açık olmalı. Aşağıdaki komutla makinanın bölümleme tablosuna baktım:

hexdump -C FreeDOS.vdi | less

Aşağıdaki çıktıda görüleceği gibi bölüm türü 0x0B, yani FAT32 CHS.

002001c0  01 00 0b fe bf 09 3f 00  00 00 4b f5 7f 00 00 00  |......?...K.....|

Diskin CHS olmasının nedeni 8 GB'den küçük olması. Daha büyük diskli bir makinaya kurduğumda bu değer 0x0C idi.

FAT32'deki asıl farklılık doğal olarak boot sektörde. FAT32 boot sektör veri yapısını (DOS 7.1 EBPB - Extended BIOS Parameter Block) konuyla ilgili yazıda iki tabloya bölerek vermiştim. Şimdi tek parça olarak tekrar gözden geçirirsek:

Sektör OffsetiBüyüklükAçıklama
0x003 byteAsıl koda JMP içerir
0x038 byteOEM Adı
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ı
0x11wordAyrılmışNot1
0x13wordAyrılmışNot2
0x15byteMedya tanımlayıcı
0x16wordAyrılmışNot3
0x18wordSilindirin içerdiği sektör sayısı
0x1AwordKafa sayısı
0x1CdwordGizli sektörlerin sayısı
0x20dwordToplam sektör sayısıNot4
0x24dwordFAT başına sektör sayısı
0x28wordAynalama flag'larıNot5
0x2AwordSürümNot6
0x2CdwordKök dizin kümesi
0x30wordFSINFO sektörü
0x32wordYedek boot sektörü
0x3412 byteAyrılmışNot7
0x40byteFiziksel sürücü numarası
0x41byteAyrılmışNot8
0x42byteGenişletilmiş imza (0x28 veya 0x29)
0x43dwordBölüm seri numarası
0x4711 byteBölüm etiketi
0x528 byteDosya sisteminin türüNot9
Wikipedia'dan derlenmiştir. (madde1, madde2)

Not1: FAT32'den önceki sistemlerde bu alan, kök dizinde yer alabilecek dizin ve dosya girdilerinin üst sınırını tutar. Bu sistemlerde kök dizin normal dizinler gibi genişleyemez. FAT32, bu kısıtı kaldırdığından bu alan sıfırdır.
Not2: Eski FAT sürümlerinde bu alan sektör sayısını tutar. FAT32'de değeri sıfırdır (sektör sayısı buraya sığmayacağından), yerine 0x20 offsetindeki değer kullanılır.
Not3: Eski FAT'larda bu alan, FAT'ın içerdiği sektör sayısını tutar ancak bu değer yine bir word'e sığmayacağından 0x24 offsetindeki dword kullanılır.
Not4: Eğer bu alan sıfırsa işletim sistemi toplam sektör sayısını MBR'deki bölüm kaydından alır.
Not5: Normalde dosya yerleşim tablosu en az iki kopya tutulur ve yapılan her işlem iki kopyaya da işlenir. Bu flag'le yalnız bir tablonun aktif olması sağlanabilir.
Not6: Bu alan tanımlı olsa da kullanılmamaktadır ve değeri hep sıfırdır.
Not7: Bu alanın adı Microsoft dökümantasyonunda "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.
Not8: Bu byte her zaman sıfır olmalıdır. Ancak Windows NT'de 0. ve 1. bitler "dirty bit" olarak kullanılır. FSINFO bölümünde ayrıntılı ele alınacaktır.
Not9: Bazı işletim sistemleri, toplam sektör sayısı dword'u aştığı durumda, burayı sektör sayısını saklamak için kullanır.

Yukarıdaki hexdump komutunun çıktısında, MBR'den sonra boot sektör geliyor. Elbette MBR sıfırıncı sektördeyken, boot sektör 63. sektörde ama hexdump'a -v parametresini vermediğimden, sıfırdan oluşan alanlar '*' karakteriyle gösteriliyor.

00207e00  eb 58 90 46 52 44 4f 53  35 2e 31 00 02 08 20 00  |.X.FRDOS5.1... .|
00207e10  02 00 00 00 00 f8 00 00  3f 00 ff 00 3f 00 00 00  |........?...?...|
00207e20  4b f5 7f 00 ee 1f 00 00  00 00 00 00 02 00 00 00  |K...............|
00207e30  01 00 06 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00207e40  80 00 29 f7 16 04 38 46  52 45 45 44 4f 53 32 30  |..)...8FREEDOS20|
00207e50  31 36 46 41 54 33 32 20  20 20 fc fa 29 c0 8e d8  |16FAT32   ..)...|


OEM Adı: FRDOS5.1
Byte/Sektör: 512 byte
Sektör/Küme: 8
Ayrılmış Sektörler: 32
FAT Sayısı: 2
Medya Tanımlayıcı: 0xF8 (Sabit disk)
Sektör/Silindir: 0x3F = 63
Kafa: 0xFF = 255
Gizli Sektörler: 0x3F = 63
Toplam Sektör Sayısı: 0x7F F54B = 8 385 867
--- Buradan sonrası FAT16'yla ortak olmayan kısım ---
Sektör/FAT: 0x1FEE = 8174
Aynalama Flag'ları: 0
Sürüm: 0
Kök Dizin Kümesi: 2
FSINFO Sektörü: 1*
Yedek Boot Sektör: 6*
Fiziksel Sürücü Numarası: 0x80
Genişletilmiş İmza: 0x29
Bölüm Seri Numarası: 3804-16F7
Bölüm Etiketi: FREEDOS2016
Dosya Sistemi: FAT32

* Bu alanlar yazının devamında ele alınacak.

Elbette bu verileri böyle okumak zor, bunun yerine disk editörde görüntüleyelim:


Maalesef tüm bilgiler ekrana sığmadı. Bu arada bir kurnazlık yapıp, disk editörü açmadan önce aşağıdaki kodla karakterleri 8x16 pikselden 8x14 piksele çevirdim:

mov ax,1111
mov bl,0
int 10

Kaynak stackoverflow. Bu cevapta yukarıdaki kod için 25 satır modu diyor, halbuki 28 satır olmalıydı. Belki VBox BIOS'uyla ilgili bir uyumsuzluktan farklı olabilir. Aynı cevapta AX=1112h değeriyle 43 satır moduna geçip tüm sektörü bir ekrana sığdırabilirdim ancak okumak zorlaştığı için vazgeçtim.


FSINFO Sektörü
FAT32'de boot sektörden başka iki önemli sektör daha var. Bunlardan biri FSINFO sektörü yani dosya sistemi bilgisi. Buna, boot sektör yazısında kısaca değindim. FAT32'ye kadar, diskteki boş alan, FAT'deki boş kümeler sayılarak hesaplanırdı. Teknik ayrıntıları şimdilik ihmal edersek, FAT16'da küme sayısı en fazla 216 = 65 536 iken, FAT32'de bu ≈268M'ye çıktı. Bu durum, önceki boş alan hesabının 4096 kat yavaş çalışması demekti. Bu sorunu çözmek için FAT32'de boş ve dolu sektör sayılarını tutan FSINFO sektörü tanımlanmıştır. Her ne kadar bu sektörün boot sektörde bir göstericisi olsa da, göstericinin değeri çoğunlukla 1 olup, boot sektörden sonraki sektörde bulunur ve formatı aşağıdaki gibidir:

Sektör OffsetiBüyüklükAçıklama
0x004 byteSektör imzası 'RRaA'
0x04480 byteAyrılmış
0x1E44 byteSektör imzası 'rrAa'
0x1E8dwordBoş küme sayısı
0x1ECdword
Dolu küme sayısı
0x1F012 byteAyrılmış
0x1FC4 byteSektör imzası 0x0,0x0,0x55,0xAA
Wikipedia'dan derlenmiştir

Boş ve dolu küme sayısı, disk düzgün unmount edilmediğinde gerçeği yansıtmayabilir. Windows NT'de bir FAT32 disk takıldığında, boot sektörde 0x41. byte'ın sıfırıncı biti set edilir (dirty bit) ve unmount edildiğinde resetlenir. Bir disk takıldığında, bu bit sıfır değilse diskin düzgün kaldırılmadığı anlaşılır. Bu durumda boş ve dolu küme sayıları gerçeği yansıtmıyor olabilir ve kullanıcıdan CHKDSK'i çalıştırması istenir. Benzer şekilde GÇ hatası olursa birinci bit set edilir ve disk tekrar takıldığında kullanıcıdan yüzey taraması yapması istenir. Bu alanlara formatlama sırasında 0xFFFF FFFF değeri yazılır. Bu değer geçersiz olup işletim sisteminin gerçek değerleri hesaplayarak buraya yazması beklenmektedir.

Aşağıda, bu alanlara ait hexdump çıktısı bulunuyor:

00208000  52 52 61 41 00 00 00 00  00 00 00 00 00 00 00 00  |RRaA............|
00208010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
002081e0  00 00 00 00 72 72 41 61  be dd 0f 00 ed 18 00 00  |....rrAa........|
002081f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 55 aa  |..............U.|


Bu çıktıya göre diskte 0x0F DDBE = 1 039 806 tane boş küme var. Boot sektörden, her kümede 512 byte'lık 8 sektör olduğundan yola çıkarak, boş alan 4061.742 MB bulunur:


Ve diskteki veri, 0x18ED = 6381 dolu kümede 24.925 MB'tır. Elbette bu değer dosyaların toplam büyüklüğü değil, diskteki toplam kullanılan alandır. Aradaki fark ve "slack space" kavramına FAT yazısının kümeler başlığında değinmiştim.


Yedek Boot Sektör
FAT32'de boot sektörün bozulmasına karşı altıncı sektörde bir yedeği saklanıyor. Teoride bu, herhangi bir sektörde bulunabilir ama MS resmi dökümantasyonunda 6'dan başka bir sektörde olmasını önermiyor. Yalnızca Win95'in boot sektör kodu burayı okumaya çalışıyor (FreeDOS ve WinXP okumuyor). Bunu tutan gösterici, zaten okunamayan bir sektördeyse tutmanın bir anlamı da yok gibi. Virüslerin boot sektörü sıfırlamasına karşı koruma sağlasa da, yeni nesil virüslerin yedeği de sıfırlamamasına engel olmuyor. Sadece boot sektör bozulduğunda disk kurtarma programlarının işine yarayabilir. Bu arada, yedek boot sektörden sonra yedek FSINFO sektörü de bulunuyor.

FAT32 desteğinin, Win95 OSR2 ile başlayıp WinXP'ye kadar devam ettiğini yukarıda yazdım. İncelemek istediysem de VBox'ta OSR2'yi kurmayı beceremedim (sanırım desteklenmiyor, aygıtları tararken mavi ekran alıyor), ben de vmware'de kurdum. Kurulumu özetlersem (biraz karışık): bootdisk.com adresinden Win95b boot disketini indirdim. İTÜ yazılım sunucusundan aldığım kurulum dosyaları ve disket imajıyla, k3b'de boot edilebilir bir .iso oluşturdum. Vmware'de işletim sistemini Win95 seçip 128 MB RAM, 12 GB diskli ve disket sürücülü bir sanal makina kurdum, bunu oluşturduğum iso ile başlattım. A: sürücüsündeyken FDISK'le diskin tamamında bir bölüm oluşturdum. Makinayı tekrar CD'yle başlatıp diski formatladım. Makina açılırken Esc'e basıp CD'yi seçmek için hızlı davranmak gerek, yoksa makina sabit diskten açılıp "Missing operating system"de kalıyor. Açıldıktan sonra diski formatladım, içinde "setup" adında bir dizin oluşturup, CD'deki dosyaları buraya kopyaladım ve bu dizinden SETUP.EXE'yi çalıştırdım. CD'yi kopyalamayınca, sonradan aygıt sürücüsü ararken, kurulumun yapıldığı dizine ulaşmaya çalışıyor. Geri kalan adımlarsa standart.

Bunu kurmaya değer mi? Açıkçası boot sektörün veri alanında bir fark yok. Disk büyüklüğü dışında FreeDOS ile hemen hemen aynı. Benzer şekilde, 32 ve 64-bit WinXP için de aynısını söyleyebilirim. Boot sektör, Win95'te disk editörle açılabiliyor ama program Windows'u algılayıp salt okunur moda geçiyor ve bunun değiştirilmesine izin vermiyor. WinXP'de sabit disk, HxD ile okunabiliyor ancak WinXP'yle herhangi bir sayfaya bağlanmak, TLS uyumsuzluğundan ötürü olanaksız. Bu nedenle WinXP'de dosya paylaşımını açıp HxD'nin kurulum dosyasını makinaya kopyaladım.

Not: Güncel Fedora, istemci olarak (XP'nin desteklediği) SMB1 protokolüne izin vermediğinden bağlanabilmek için smb.conf içinde [global] stanzasında "client min protocol = NT1" eklemek gerekiyor*.


Dosya Yerleşim Tablosu
Tablonun yeri FAT16 gibi, gizli sektörlerle ayrılmış sektörler toplanarak bulunuyor. Başka bir deyişle, boot sektörden gizli sektör sayısı kadar ileride.

fat_start = hidden_sectors + reserved_sectors (1)

FAT16'dan farklı olarak, FAT32'de kök dizinin sabit olmadığına değinmiştim. Dolayısıyla root_dir_start'ın hesaplanmasına gerek yok.

data_start = fat_start + number_of_FATs * sectors_per_FAT (2)


Son olarak küme numarasını sektör numarasına dönüştüren fonksiyon aynı:

clus2sect(c) = (c - 2) * sectors_per_cluster + data_start (3)


Dizin ağacını elde edebilmek için, kök dizin kümesine ait gösterici boot sektörden okunur ve (3) formülünde yerine konarak fiziksel yeri bulunur. Dizin ağacına değinmeden önce yerleşim tablosuna göz atalım. Boot sektörü 63. sektörde (gizli sektörler) ve bunun için 32 sektör ayrılmış (ayrılmış sektörler). (1) formülünden, FAT, 63 + 32 = 95. sektörde. .vdi dosya başlığı 0x200000 byte uzunlukta yani 512 * 4096. O halde dosya başlığı 512 byte'lık 4096 blok ve MBR+Boot sektör, 95 blok daha olmak üzere 4191. blokta FAT bulunuyor:

dd if=~/VirtualBox\ VMs/FreeDOS/FreeDOS.vdi \
bs=512 skip=4191 | hexdump -C | less

00000000  f8 ff ff 0f ff ff ff 0f  03 00 00 00 04 00 00 00  |................|
00000010  05 00 00 00 ff ff ff 0f  00 00 00 00 ff ff ff 0f  |................|
[SNIP]


Girdilerin mantığı, FAT'ın önceki sürümleriyle aynı olduğundan tüm blokları incelemeye gerek yok. Burada sadece ilk sekiz girdiyi alıntıladım. Bu arada github'daki fatread koduna FAT32 desteğini de ekledim*:

Küme0: 0xFFF FFF8 (0x0000)
Küme1: 0xFFF FFFF (0x0004)
Küme2: 0x3 (0x0008)
Küme3: 0x4 (0x000C)
Küme4: 0x5 (0x0010)
Küme5: 0xFFF FFFF (0x0014)
Küme6: 0x0 (0x0018)
Küme7: 0xFFF FFFF (0x001C)
...

Sıfırıncı girdi her zamanki gibi medya tanımlayıcı veya FAT ID, ancak dikkat edilirse 32 bitlik değerin ilk 4 biti sıfır. Birinci kümede yine dosya sonu (EOF) imi var. İkinci küme üçü, üçüncü dördü, dördüncü beşi ve beşinci küme EOF'u gösteriyor. Boot sektöründen, kök dizininin ikinci kümede başladığını hatırlayalım. O halde kök dizin bulundu. Altıncı küme boş ve yedinci kümede bir EOF daha var.

FAT32'de girdiler 32 bit olsa da, bunların yalnız düşük anlamlı 28 biti kullanılır. Yüksek anlamlı nibble ayrılmıştır. Dolayısıyla küme sayısının teorik üst sınırı 228 = 268 435 456'dır. 12 değerin özel anlamı olduğu için pratik sınır, teoriğin 12 eksiğidir. Bu değerler, eski FAT sürümlerine benzer şekilde, 0x0FFF FFF8 ile 0x0FFF FFFF arası EOF imi, 0x0FFF FFF7 bozuk küme, 0x0 boş küme ve ayrılmış 0x1 ile 0x0FFF FFF6 değerleridir. Son olarak 0x0FFF FFF0 ile 0x0FFF FFF5 arası değerlerin de uyumluluk nedeniyle kullanılmaması tavsiye edilmektedir.

Bu arada birinci girdinin 27. biti, boot sektörün 0x41. byte'ı gibi dirty bit olarak kullanılabilir. Eğer mount sırasında bu bit sıfırsa, işletim sistemi diski taramaya çalışır veya en azından FSINFO'daki değerlerin güvenilmez olduğunu bilir. Benzer şekilde 26. bit de GÇ hatalarında kullanılır.

*Not: FAT32 tablosu büyük bir alan (örn. 2 * 8174 sektör) kaplar. .vdi diskte de fazla veri yoksa FAT içeriği çoğunlukla sıfır olacaktır. Bu boş alanlar .vdi dosyada saklanmadığı için ("Preallocated" değilse), fatread, boş (sparse) alanları dikkate almaz ve büyük küme numaralarında hatalar oluşur. Örn. FreeDOS diskinin 259840. kümesinde FAT ID görünüyor çünkü program aslında ikinci FAT'ı okuyor:



Dizinler ve Dizin Tablosu
Boot sektörden, kök dizinin ikinci kümede olduğu biliyorum ve yukarıda beşinci kümeye kadar devam ettiğini de buldum. fat_start = 95 ve sectors_per_FAT = 8174'tü. O halde (2) formülünden data_start = 16443 ve (3) formülünden clust2sect(2) = 16443 bulunur. Ancak aşağıda da görüldüğü gibi bir sorun var:

dd if=~/VirtualBox\ VMs/FreeDOS/FreeDOS.vdi bs=512 skip=$((4096+16443)) | hexdump -C | head
00000000  2d 2d 2d 2d 2d 2d 2d 2d  2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
00000010  2d 2d 2d 2d 2d 2d 2d 2d  2d 2d 2d 0d 0a 54 61 64  |-----------..Tad|

Yukarıdaki * imli not aslında sorunu açıklıyor: Boş alanlar .vdi dosyada tutulmadığından, hesap doğru olsa da yanlış bir sektör görülüyor. Öyleyse sanal makinanın içinden DISKEDIT'le bakmam gerek. Aslında C:\'deyken DISKEDIT'i başlatınca kök dizini açıyor ama hesabın doğru olduğunu göstermek için Alt+P'ye basıp 16443 yazınca da aynı veriler görülüyor. (F2: Hexdecimal görünüm)

Kök dizinin içeriği

Kök dizinin başında "FREEDOS2016" girdisinin olduğunu gördüm. hexdump -C ~/VirtualBox\ VMs\FreeDOS\FreeDOS.vdi | less komutuyla dosyayı açıp "/FREEDOS2016" diye aratarak dosyanın 0x407600 ofsetinde girdiyi buldum.

Buradaki girdiler de önceki FAT sürümleriyle benzer. En önemli değişiklik, küme numaraları 32 bit olduğundan, küme numarasının düşük anlamlı word'u 0x1A'da yüksek anlamlı word'u 0x12'de bulunuyor. Ayrıca MS-DOS 7 ve WinNT ile gelen bazı yeni özellikler var. FAT yazısındaki tabloya bu özellikleri ekleyip güncelledim:

OffsetBüyüklükAçıklama
0x008 byteDosya adı
0x083 byteDosya uzantısı
0x0B1 byteDosya özellikleri
0x0C1 byteMSDOS: Ayrılmış
WinNT: Büyük/küçük harf bilgisi
0x0D1 byteOluşturma zamanı (milisaniye)
0x0EwordOluşturma zamanı
0x10word
Oluşturma tarihi
0x12wordErişim tarihi
0x14wordKüme num. (31..16 bitler)
0x16word
Değiştirme zamanı
0x18word
Değiştirme tarihi
0x1AwordKüme num. (15..0 bitler)
0x1CdwordDosya büyüklüğü

0x0C byte'ı MS-DOS ve Win95'te dikkate alınmaz. WinNT'de (ve XP'de) üçüncü bit dosya adının ve dördüncü bit uzantının küçük harf olduğunu gösterir:

0x00: TEST000.TXT
0x08: test000.TXT
0x10: TEST000.txt
0x18: test000.txt

Saat ve tarih formatı FAT16'yla aynıdır. Önceki yazıdan hatırlarsak; Sa: saat, Dk: dakika ve Sn: Saniye olmak üzere:

Offset 0x0F, 0x17
Offset 0x0E, 0x16
Sa4Sa3Sa2Sa1Sa0Dk5Dk4Dk3Dk2Dk1Dk0Sn4Sn3Sn2Sn1Sn0

Aynı şekilde Yi: yıl ve Gü: gün olmak üzere:

Offset 0x11, 0x13, 0x19Offset 0x10, 0x12, 0x18
Yi6Yi5Yi4Yi3Yi2Yi1Yi0Ay3Ay2Ay1Ay0Gü4Gü3Gü2Gü1Gü0

Saat veri yapısında saniye için 5-bit ayrıldığından, zaman çözünürlüğünün iki saniye olduğunu yazmıştım. 0x0D byte'ı 10 milisaniyelik birimleri temsil eder. Bununla çözünürlük 10ms'ye inmiş olur. Elbette bu byte'ın yalnız 0..199 arası değerleri anlamlıdır.

Örnek:
0b706e40  4e 54 4c 44 52 20 20 20  20 20 20 27 08 00 00 10  |NTLDR      '....|
0b706e50  8e 38 50 53 01 00 00 10  8e 38 8f 1c c0 d0 03 00  |.8PS.....8......|

NTLDR'nin dosya özellikleri 0x27 yani; arşiv, gizli, salt-okunur ve sistem dosyası. 0x0C'deki değer 0x08, yani dosya adı küçük harf. Uzantı zaten yok. Dosya oluşturma ve değiştirme zamanı aynı: 0x1000. Saatin ikinci biti 1, yani 02:00. Tarihler de aynı: 0x388E = 0011100 0100 01110 = 1980+28/04/14. Erişim tarihi ben dosyanın özelliklerine baktığım için 0x5350 = 0101001 1010 10000 = 1980+41/10/16. Dosyanın yer aldığı küme 0x011C8F ve boyutu 0x3D0C0 = 250048 byte.

Uzun Dosya Adı (LFN) Desteği
FAT32, VFAT yani uzun dosya adı desteği konusunda FAT16'ya ek herhangi bir değişiklik içermediğinden, ayrıntılı bilgi önceki yazıda bulunabilir.


FAT32 Boot Kodu

a. FreeDOS Boot Kodu
FreeDOS boot kodunu sanal makinamdan alıp github'daki kodlarla karşılaştırdım ve LBA destekli boot32lb.asm'nin çalıştığını buldum. Bunu indirip kendi yorumlarımı, başında "; --" ile ekledim. Dosya buradan indirilebilir. Yazının geri kalanındaki satır numaraları, açıklamalarımı eklediğim dosyaya göre olacak.

Kod standart olarak 0:0x7C00 adresine yüklenir (satır 54) ve buradan real_start (s.117) satırına atlar. 60. ile 116. satırlar arasında boot sektördeki verilerin göstericileri tanımlıdır. FAT16 kodundaki gibi burada da kernel 0x60:0 adresine yükleneceğinden, kod kendini 0x1FE0:0'a kopyalar ve çalışmaya oradan devam eder (s.123-129). 131. satırdaki göstericide çekirdeğin yükleneceği adres var. 141. satırda ekrana "Loading FreeDOS" yazılır ve ardından gelen calc_params'ta fat_start ve data_start, (1) ve (2) formüllerinden hesaplanır (s.155 ve s.163)

FAT16'da reserved_sectors çoğunlukla 1 olup, boot sektörden hemen sonra FAT başlar. FAT32'de arada FSINFO ve yedek boot sektör olduğundan bu değer birden büyüktür. Kök dizin göstericisi boot sektörde bulunduğundan, FAT16'daki gibi hesaplanmasına gerek yoktur.

169-178 satırları arasında ilginç bir kod var. Bir döngüde AX'teki 512 değeri bytes_per_sector ile karşılaştırılıp, eşit değilse AX'in iki katı alınır. Her adımda 278. satırdaki kaydırma işleminin parametresi bir arttırılır (self modifying code) ve bu parametre, küme numaralarının FAT'teki yerini hesaplarken kullanılır. Kısaca kod 512 byte'dan büyük sektörleri destekler gibi görünüyor.

Satır 189'da kök dizin kümesinin sektörü bulunur ve bu readDisk fonksiyonuyla okunur (s.194). 201'den 212. satıra kadar, kök dizin girdilerinde KERNEL.SYS aranır. Arama sırasında DI'nin değeri arttırılıp, bu değer bytes_per_sector'u geçince sonraki sektör okunur (s.216) ve aynı anda DX'teki sectors_per_cluster değeri azaltılır. Eğer kümedeki tüm sektörler okunduysa, 216. satırda DX=0 olacağından next_cluster fonksiyonu EAX'teki kümenin ardışığını bulur ve 188. satırdan itibaren tüm adımlar KERNEL.SYS bulunana kadar tekrar eder. Dosya bulunursa küme numarası EAX'e yüklenir (ff_done satırı). Bu, convert_cluster ile (s.232) sektör numarasına dönüştürülüp, kümenin tamamı readDisk ile (s.236) sektör sektör okunur. Dosyanın ardışık kümesi, EoF imi içeriyorsa 232. satırdaki fonksiyon çağrısı carry döndüreceğinden, dosyanın sonuna kadar okunduğu anlaşılır ve boot_success satırında programın çalışması çekirdeğe devredilir.

Tek tek fonksiyonların ayrıntılarına burada değinmeyeceğim. Kodun yorumlarında bunları açıklamaya çalıştım.


b. Windows Boot Kodu
Windows boot koduyla ilgili araştırma yaparken UNI Magdeburg'dan Jens Elkner'in kişisel alanında boot sektör koduyla ilgili kaynaklar buldum. Kaynak kod kapalı olduğu için kısaca buradaki Win95'e ait kodun üzerinden geçeceğim. Bu bölümde satır numaraları yerine offset adreslerine referans verdim.

Windows boot koduyla ilgili en önemli ayrıntı, iki parçadan oluşması. Boot sektör, Win95'te üçüncü, WinXP'lerde onikinci sektörde bulunan başka bir kodu yüklemekle görevli.

Win95'e bakıldığında, DOS'tan kalan kodu devralmış görünüyor. Kodun başında, kullanılıp kullanılmadığı bile belirsiz bir disket parametre tablosundaki değerler değiştiriliyor (0x7C6E, 0x7C81 vb.). Ardından boot ortamı disketse (0x7C8E), parametreler işlenmek üzere 0x7CB5 adresine dallanılıyor. Eğer ortam diskse (MBR varsa) MBR okunuyor, o bölüme ait bölüm kaydı bulunuyor (boot sektördeki gizli sektörler = bölüm kaydındaki başlangıç LBA adresi) ve boot eden bölümün türü 2 ile OR'lanarak (0x7CAA) 0x7C02'ye (NOP komutunun üzerine) yazılıyor. Bu değer, 0x7D40 adresinde 0xE ile karşılaştırılacak. Çünkü, LBA destekleniyorsa bölüm türü 0xC veya 0xE'dir (0xC OR 2 = 0xE) ve o halde kodda int 0x13'ün 0x42 fonksiyonu kullanılır. MS'in LBA desteğini kodla test etmemesi son derece garip ama 95'te çok eski olmayan her bilgisayarın LBA desteklediğini düşünüyorum.

0x7CC4'te CX=3 oluyor. Bu değer, 0x7D31'deki read_disk fonksiyonuna 2 olarak girecek. Böylece boot sektörden sonraki iki sektör, yani FSINFO ve ikinci parça kod belleğe okunuyor. Burada okuma başarısız olursa (0x7CD2), kod yedek boot sektörü (0x7CD9) okumaya çalışıyor. İkinci parça, 0x8000 adresine sorunsuz yüklenebildiyse oraya atlıyor. Bu arada, 0x7CD4'teki komutun hiçbir işlevi yok ama 0xF8'den (medya tanımlayıcı) dolayı belki verinin özel bir anlamı vardır. Örn. bir yerde değişken olarak kullanılıyor olabilir.

0x7D03 ile 0x7D30 arasında, hata mesajlarını gösteren ve bilgisayarı yeniden başlatan kısımlar var. Hata mesajlarına kadar olan alanda, LBA ve CHS disk okuma fonksiyonları bulunuyor. Bu arada hata mesajlarının hemen gerisinde 4 tane garip gösterici var, kendisinden sonraki göreli adresin bir eksiğini tutuyor. Yine MS gariplikleri.

İkinci fazda bir sürü gereksiz CLI/STI bloğu var. 0x8016'ya kadar data_start hesaplanıyor ve [BP-04] adresinde depolanıyor (0x801B). [BP-08]'e ileride kullanılmak üzere -1 yazılıyor (0x801F). 0x803E'de, SHLD komutuyla EDX, 16-bit sola kaydırılıp, EAX'in yüksek anlamlı word'ü DX'e yazılıyor. Yani EAX'teki değer DX:AX'e yazılıyor. Kodda birden fazla yerde bu komut var çünkü hesaplamalarda bazı yerde EAX kullanılırken bazı yerde DX:AX kullanılıyor ve read_disk, sektörü DX:AX'ten alıyor. Onun yerine read_disk'i optimize etseydiniz?! Bu arada 0x8047'deki iki kaydırma işlemi, DX:AX'i EAX'e yazıyor (SHLD'nin tersi). Yeri gelmişken, read_disk'te bir gariplik de, LBA desteği kontrol edilmeden önce DAP paketi oluşturuluyor. E, LBA desteği yoksa bu kullanılmayacak ama?!

0x8028'de okunan kök dizin kümesi, 0x8050 ile 0x8067 arasında sektör numarasına dönüştürülüyor ve kök dizin tablosu 0:0x700 adresine okunuyor (0x8068 ile 0x8073). Tabloda IO.SYS dosyası aratılıyor (0x8081) ve bulunması durumunda 0x8084'ten 0x809F'teki dosyayı belleğe yükleyen bölüme dallanılıyor. Bu bölümde, 0x80A2'de IO.SYS'nin küme numarası alınıyor ve DX:AX'e aktarılıp (0x80CF), bu kümeden sadece dört sektör 0:0x700 adresine okunuyor (0x80D9 ve 0x80DC). 0x80E4 ve 0x80EA adreslerinde sırayla IO.SYS'nin başındaki 'MZ' imzası ve kodun ilk iki karakteri doğrulanıyor. Eğer bunlar tutarlıysa, dosya 0x70:0x200 adresinde çalıştırılıyor, aksi halde "Invalid system disk" hatası çıkıyor (ama neden?). IO.SYS, geri kalan sektörlerini kendisi okumak zorunda.

0x80FD ile 0x811F arasında, DX:AX'te verilen kümenin ardışığı hesaplanıyor. Bu fonksiyon 0x8120'de, verilen kümenin, hangi FAT sektöründe olduğunu bulan alt-fonksiyonu çağırıyor. Bulunan sektör numarası 0x801F adresindeki değişkene yazılıyor. Böylece, örn. IO.SYS'nin ilk kümesi 3 ise, buna ait girdi FAT'in ilk sektöründedir ve büyük olasılıkla dosyanın ardışık kümesi 4 olup, buna ait girdi de aynı yerdedir. Dolayısıyla aynı sektörün tekrar okunması gerekmez. Eğer EAX'teki değer (0x8138), son okunan FAT sektörüyle ([BP-08]'deki) aynıysa 0x813C'deki JE komutuyla fonksiyonun sonuna atlanır.

WinXP'deyse disket parametre tablosundan vazgeçilmiş. Kod, ikinci fazı onikinci sektörden 0x8000'e yükleyip oraya atlıyor. LBA desteği kodla kontrol ediliyor. Koddan gereksiz parçalar atılınca, boot sektörde epey boşluk kalmış. İkinci faz, Win95'le neredeyse aynı ama gereksiz CLI/STI'ler kaldırılmış ve yukarıda yazdığım gibi sektör numarası sürekli olarak EAX'te tutulunca kaydırma işlemlerine gerek kalmamış. İki kod arasında toplam 63 byte fark var. Bu arada WinXP, IO.SYS yerine NTLDR dosyasını 0x2000:0 adresine yüklüyor. Kısacası, Win95'teki gariplikler WinXP'de bulunmuyor ve bu kod daha optimize yazılmış.


[1]: https://en.wikipedia.org/wiki/File_Allocation_Table#FAT32

12 Ocak 2021 Salı

Disk Sistemi Nedir, Ne Değildir? #4: FAT


Merhaba. Serinin bu yazısında dosya yerleşim tablosu (file allocation table), yani FAT'tan bahsedeceğim. Bu, ilk başta asıl amacımdı ama ilk yazıda söz ettiğim gibi, boot sektör olmadan FAT, MBR olmadan da boot sektör havada kalırdı.

Temel Kavramlar
FAT, diskte boot sektöründen sonra gelen ve kümelerin (cluster) boş olup olmadığını ve ardışığı kümeyi gösteren yapı olup, bir çeşit bağlı listedir. Kümeleri bir sonraki bölümde açıklayacağım.

Dizinler, kullanıcı tarafından doğrudan okunamayan ve dizin içindeki dosyaların listesinden (directory table) oluşan özel dosyalardır. Bu liste, dosya ve dizin girdilerinden (directory entry) oluşur.

Bilindiği gibi standart FAT'in, FAT12, FAT16 ve FAT32 olmak üzere türleri vardır. Bunun dışında bir çok üretici dosya girdilerindeki ayrılmış alanlarda farklı verileri (örn. dosya sahibi) tutarak FAT'i özelleştirmişlerdir. Standart olmadıklarından bunlara değinmeyeceğim. FAT sürüm numaraları, bir kümenin yerleşim tablosunda temsil edildiği bit sayısıdır ve ilginç şekilde, en karmaşık olanı FAT12'dir. Bunun dışında, exFAT, MS tarafından flash diskler için özelleştirilmiş bir FAT sürümüdür.

VFAT, özel bir sürüm olmayıp 8+3'ten uzun dosya adlarını (long file names - LFN) tutmak için mevcut sürümler üzerine bir genişlemedir. FAT normalde CP/M'den kalan mirasla sekiz karakter dosya adı ve üç karakter uzantı olmak üzere 8+3 dosya adlarını destekler. Microsoft bunu değiştirmek için bazı sihirler yapmıştır. 


Kümeler (Clusters)
Kümeler, FAT dosya sisteminin en küçük yapı taşıdır. Disk sisteminde sektör neyse dosya sisteminde de küme o'dur. Bir küme, bir veya birden fazla sektörden oluşur ve içinde dosyalardaki veriler bulunur veya elbette boş olabilir. Bir dosya da, bir veya birden fazla kümeden oluşur.

FAT ilk ortaya çıktığında (aslında FAT8 olarak ortaya çıktı) henüz küme kavramı yoktu. Sektörlerin FAT tarafından doğrudan adreslenmesi mevcut disklerde sorun olmuyordu. Kapasiteler zamanla artsa da, disk bütün olarak kullanılamadığında -kullanışsız da olsa- diski bölümleme seçeneği vardı. 

FAT12, 12 bitle, 4096 sektöre kadar kullanabiliyordu, yani en fazla 2MB (pratikte sınır daha düşük). 1984'te MSDOS 3.0, FAT16 desteğiyle geldi, öyle ki 20MB'lik sabit diskleriyle IBM PC AT'ler yeni çıkmıştı ve MS bu bilgisayarları kaybetmek istemiyordu. İlk FAT16, 16 bitle, 65 536 sektöre yani 32MB'a kadar destekleyebiliyordu. Bugün kullanılan FAT16'ysa, 1987'de Compaq DOS v3.31'le çıktı. Birim sayısı üst sınırı aynı kalmak üzere, belli sayıda sektör, küme adı altında gruplandırılacak, böylece birimler büyüyecek ve algoritmaya yalnız bir çarpan eklenerek adreslenebilir disk alanı arttırılacaktı: FAT12 disk bölümleri 16MB'a ve FAT16 disk bölümleri 2GB'a kadar. Adı geçen çarpansa önceki yazıda değindiğim "küme başına sektör" (sectors per cluster) değeridir [1].

90'ların ortasında disk kapasiteleri 2GB sınırına dayanmıştı. FAT16 da yeterli olmadığından, FAT32 geliştirildi ve adreslenebilir en büyük disk 2TB oldu. FAT32'yle birlikte küme sayısı çok arttığı için, diskteki boş alanı hesaplamak veya ilk boş kümeyi bulmak problem haline geldi, bu nedenle başka FAT dosya sistemleri muhtemelen olmayacak.

Küme sayısının fazla olması performans sorunlarına yol açar, ancak az olması da, aynı diskin daha büyük kümelerden oluşması demektir. Küme, dosya sistemindeki temel birim olduğundan, bundan küçük veya eşit her dosya bir küme kaplar. Yani 1 KB'lık bir dosya 4KB'lik kümelerin olduğu diskte 4KB yer kaplar. Benzer şekilde 5KB'lık bir dosya, diskte iki kümesiyle 8KB yer kaplayacaktır. Bu harcanan alana "slack space" adı verilir. Küçük dosyaların çok olması bu harcanan alanı arttırır.

Sözü edilen kümelere karşılık, dosya yerleşim tablosunda bir sayı bulunur. Bu sayı kümenin boş veya dolu olduğu ve doluysa dosyanın devamının nerede olduğunu tutar. Eğer bir küme, dosyanın son kümesiyse FAT'da dosya sonu imi (end of file (EOF) veya end of chain mark (EOC)) bulunur. İşletim sistemi diski okurken GÇ hatalarıyla karşılaşırsa, o sektöre ait kümeyi FAT'da bozuk küme olarak işaretler.


Dosya Yerleşim Tablosu
FAT'in ayrıntısına girmeden önce, diskteki yapıların yerleşimine göz atalım (yandaki görsel). Bildiğimiz gibi disketler ve diğer bölümlenmeyen ortamlar dışında, diskin en başında MBR bulunuyor. MBR'deki bölüm kaydı boot sektörleri işaret ediyor. FAT görüldüğü gibi hemen hemen hep iki kopya olarak tutuluyor ve başlangıcı (fat_start), önceki yazıda belirttiğim gibi, boot sektöründeki gizli ve ayrılmış sektörlerin toplamına eşit. FAT sayısı ve tabloların sektör cinsinden uzunluğu yine boot sektöründe var. Bunlar çarpılıp fat_start'a eklenince kök dizinin (mor alan) başladığı sektör bulunuyor (root_dir_start). Kök dizinin büyüklüğü, içinde kaç girdi için yer ayrıldığına bağlı. Bu değer, root_dir_start'a eklenince, 2. kümenin başlangıcı yani veri alanı (data_start) bulunuyor. Sıfırıncı ve birinci kümelerin özel anlamları var ve bunlar disk üzerinde yer almıyor. 

Bunlar önceki yazıdan bilinen şeyler. data_start bulunduktan sonra hangi kümenin hangi sektörde başladığını bulmak için, küme numarasının iki eksiği kümenin içerdiği sektör sayısıyla çarpılıyor:

clus2sect(c) = (c - 2) * sectors_per_cluster + data_start [2]

Bu arada çizim, basit olması için tek disk bölümü içeriyor ve tabii ki ölçekli değil.


FAT12
FAT12, disketlerde bugün hala kullanılmakta (disketlerin ne kadar kullandığı ayrı bir konu). Normalde 12 bitten, 4096 kümeye kadar destekliyor ancak ayrılmış küme numaraları çıkarılınca bu sınır 4078 oluyor.

FAT12'yi incelemek için, önceki yazıda oluşturduğum FreeDOS sistem disketini kullanacağım. (Hatırlatma: dd ile 1 474 560 byte'lık freedos.ima dosyasını oluşturdum. FreeDOS sanal makinasına takıp FORMAT A: /S ile formatladım). Bunu, içeriğindeki veri MSDOS disketine göre küçük olmasından ötürü seçtim. Diskette KERNEL.SYS ve COMMAND.COM olmak üzere iki dosya var. Dosya büyüklüklüklerini 512'ye (sektör başına byte*küme başına sektör değerinden bir kümenin byte cinsinden büyüklüğü) bölüp yukarı yuvarladığımda ilki 92 ve ikincisi 131 küme uzunlukta. Aşağıdaki komutla disket kalıp dosyasına bakıyorum:

hexdump -C -v freedos.ima | less

Önceki yazıda, disket için fat_start değerinin 1 olduğunu hesaplamıştım. Dolayısıyla FAT 512. byte'dan (0200h) başlıyor. FAT içeriğinin bir parçası aşağıda:

00000200  f0 ff ff 03 40 00 05 60  00 07 80 00 09 a0 00 0b  |....@..`........|
[ SNIP ]
00000280  05 57 80 05 59 a0 05 5b  c0 05 5d f0 ff 5f 00 06  |.W..Y..[..].._..|
[ SNIP ]
00000340  0d d7 80 0d d9 a0 0d db  c0 0d dd e0 0d df 00 0e  |................|
00000350  ff 0f 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
[ SNIP ]

Daha önce belirttiğim gibi her girdi 12 bit uzunlukta, yani 1.5 byte. Daha zor olamazmış gibi her girdi little endian olarak tutuluyor. n. girdiyi bulmak için n'i 1.5 ile çarpıp ondalık varsa aşağı yuvarlamak gerekiyor. Bunun kolay yolu girdiyi n + (n >> 1) işlemine sokmak:

Girdi No.0123456...n
Yeri0134679...n + (n >> 1)

Sıfırıncı girdinin düşük anlamlı sekiz biti sıfırıncı byte'da ve kalan yüksek anlamlı 4 bit, birinci byte'ın düşük anlamlı yarısında, çünkü little endian. Başka bir deyişle ilk word'un son on iki biti. Bütün çift sayılı girdiler için böyle.

Birinci girdinin düşük anlamlı 4 biti, birinci byte'ın bir önceki girdiye ait olmayan yarısında. Yani yüksek anlamlı yarısında ve kalanı bir sonraki byte'da. Bu da tüm tek sayılı girdiler için genellenebilir. Bunun kodunu vermek açıklamaktan kolay:

for(int k = 0; k < clusters; k++)    {
  cur_cluster = (k >> 1) + k;     // 1.5 ile carpma
  if(k & 1)        // tek sayili kume icin 4 kaydir
    fat_entry = *((unsigned short *) &FAT[cur_cluster]) >> 4;
  else        // cift sayili kume icin son 12 biti al
    fat_entry = *((unsigned short *) &FAT[cur_cluster]) & 0x0FFF;
  printf("%d -> %d [%X] \n", k, fat_entry, fat_entry);
}

Bu kodun bütünü github'ımda var. 

Örnek FAT bloğu için bu kodun ürettiği çıktıyla aşağıdaki listeyi hazırladım (parantez içindeki değerler offset): 

Küme 0: 0xFF0 (0x0000)
Küme 1: 0xFFF (0x0001)
Küme 2: 0x003 (0x0003)
Küme 3: 0x004 (0x0004)
Küme 4: 0x005 (0x0006)
...
Küme90: 0x05B (0x0287)
Küme91: 0x05C (0x0288)
Küme92: 0x05D (0x028A)
Küme93: 0xFFF (0x028B)
Küme94: 0x05F (0x028D)
...
Küme222: 0x0DF (0x034D)
Küme223: 0x0E0 (0x034E)
Küme224: 0xFFF (0x0350)
Küme225: 0x0 (0x0351)
Küme226: 0x0 (0x0353)
...

İlk iki girdinin özel anlamı var buna sonra geleceğim. Zaten veri ikinci kümede başlıyordu. İkinci girdinin değeri 3, yani ikinci kümedeki verinin devamı 3. kümede. Üçüncü girdinin değeri 4, yani bunun devamı dördüncü kümede... Bu zincir 93. kümeye kadar sürüyor. 93. kümede 0xFFF işareti var. Bunun anlamı, dosya veya dizi/zincir sonu, dosyanın o kümede bittiğini belirtiyor. İngilizcesi end of file (EOF) veya end of chain (EOC). Bu im yalnız dosyaların değil, dizinlerin de sonunu gösterdiğinden EOC daha doğru denilebilir ama dizinlerin de aslında dosyaları barındıran özel bir dosya olduğu gözönüne alınırsa EOF da mantıklı. Dizinleri ele alırken buna değineceğim. 0xFF8 ile 0xFFF arasındaki tüm değerler dosya sonu imi. 225. kümeden itibaren FAT sonuna kadar sıfırlar dolu. Sıfır o kümenin boş olduğu anlamına geliyor. Elbette bütün kümelere bakmak gerekiyor ancak yukarıdaki örnekte 226. kümeye kadar iki dosya olduğunu söyleyebiliriz. 

İşletim sistemi bir kümeyi okurken kümede bozuk sektör bulursa, o kümeyi 0xFF7 ile bozuk olarak işaretler ve o küme kullanılmaz. 0x001 değeri ayrılmıştır çünkü birinci küme özel olduğundan diğer kümeler onu gösteremez. 0xFF0 ile 0xFF6 arası değerler de ayrılmıştır. Geri kalan değerlerin de kümenin ardışığını göstermekte kullanıldığını gördük. İkinci kümede hangi dosyanın olduğunu, dizin tablosuna baktığımızda bulacağız.

Sıfırıncı kümede FAT ID adı verilen değer bulunur. FAT ID aslında boot sektöründeki medya tanımlayıcı byte'ın, ilgili FAT girdisi uzunluktaki halidir. Medya tanımlayıcının çoğunlukla 0xF0 (disket) veya 0xF8 (disk) olduğunu söylemiştim. O halde FAT12 disketlerde 0xFF0, disklerde 0xFF8 ve FAT16 disklerde 0xFFF8'dir. Birinci kümede her zaman dosya sonu imi bulunur.


FAT16
FAT16'da girdilerin 16 bit olması dışında hiç bir fark yoktur. Küme sayısının teorik limiti 65 536 olsa da, ayrılmış değerleri çıkarınca 65 524 kümeyi destekler.

FAT16 örneği olarak, FreeDOS sanal makinasının diskini inceleyeceğim. Bu makinada bir çok dosya olduğundan, FAT yukarıdaki örnekten daha çok EOF içeriyor. VirtualBox sanal makinalarında disk .vdi formatında. Bu formatın tanımlarına girmek bu yazının amacını aşıyor. Tek bilinmesi gereken, .vdi dosyalarda disk verisi 0x200000 offsetinde başlıyor (aslında disk verisinin göstericisi 0x158 offsetinde ancak 0x200000 değeri kodda gömülü yani tüm .vdi dosyalarda aynı). Dolayısıyla .vdi dosyayı hexdump ile açıp başlangıç offsetine kadar PageDown ile inmem gerek:

hexdump -C ~/VirtualBox\ VMs/FreeDOS/FreeDOS.vdi | less

Alternatif olarak:

dd if=~/VirtualBox\ VMs/FreeDOS/FreeDOS.vdi \
bs=512 skip=4096 | hexdump -C | less

Yukarıdaki skip değeri MBR için 4096, boot sektörü için 4159 ve FAT için 4160 olmalı. Ayrılmış sektörler 1 olduğundan, boot sektöründen hemen sonra FAT geliyor. Dikkat edilirse .vdi dosyada içi sıfır olan çok blok olduğundan, hexdump'la -v parametresini kullanmadım.

00208000  f8 ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
00208010  ff ff 0a 00 ff ff 0c 00  0d 00 ff ff 95 0a ff ff  |................|
[ SNIP ]
00208030  ff ff ff ff ff ff 1c 00  ff ff ff ff 2a 00 ff ff  |............*...|
[ SNIP ]
0020b220  ff ff 00 00 ff ff ff ff  00 00 00 00 00 00 00 00  |................|
0020b230  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
0020b240  00 00 00 00 00 00 00 00  00 00 00 00 00 00 28 19  |..............(.|
0020b250  ff ff 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
[ SNIP ]

Girdilerin hepsi 16 bit olduğundan okumak daha kolay. Kodu da oldukça basit:

for(int k = 0; k < clusters; k++)    {
    fat_entry = *(unsigned short *)&FAT[k << 1];
    printf("%d -> %d [%X] \n", k, fat_entry, fat_entry);
}

Bu kodu yazarken .vdi dosyaları da açabilecek şekilde yazdım. Böylece bu kodla FreeDOS.vdi dosyasını da okuyabiliyorum. Yukarıdaki bloğu incelediğimde;

Küme0: 0xFFF8 (0x0000)
Küme1: 0xFFFF (0x0002)
Küme2: 0xFFFF (0x0004)
...
Küme9: 0x000A (0x0012)
Küme10: 0xFFFF (0x0014)
Küme11: 0x000C (0x0016)
Küme12: 0x000D (0x0018)
Küme13: 0xFFFF (0x001A)
Küme14: 0x0A95 (0x001C)
Küme15: 0xFFFF (0x001E)
...
Küme30: 0x002A (0x003C)
...
Küme2709: 0xFFFF (0x152A)
...
Küme6416: 0xFFFF (0x3220)
Küme6417: 0x0000 (0x3222)
Küme6418: 0xFFFF (0x3224)
...
Küme6439: 0x1928 (0x324E)
Küme6440: 0xFFFF (0x3250)
...

Sıfırıncı girdi, FAT12'deki gibi FAT ID veya medya tanımlayıcı byte, elbette 16-bit olarak. Bu bir disk olduğundan değeri 0xFFF8. Birinci kümede de yine bir dosya sonu imi var. Dosya sonu imi 0xFFF8 ile 0xFFFF arası değerler, bozuk kümeler 0xFFF7 ile işaretleniyor ve ayrılmış değerler 0xFFF0 ile 0xFFF6 arası. FAT12'deki değerlerin başında 0b1111 olan 16-bitlik hali.

Burada, küme11, 12'yi, küme12, 13'ü ve küme13 EOF'u gösterirken, ilginç şekilde küme 14, 2709'u gösteriyor. Yani bir parçası ondördüncü kümede olan dosyanın sonraki parçası 2709'da. Küme2709'a baktığımda burada EOF var. Benzer şekilde küme30, 42. kümeyi gösteriyor. Yukarıdaki örnekte görülmüyor ama, 30. kümeden sonra dizi şu şekilde ileriyor: 30->42->51->74->210->245->EOF. Küme6416'da bir dosya sonlanıyor, bir sonraki küme boş olmasına rağmen, ondan sonraki kümede yine EOF var. Sonrasındaki on dokuz küme daha boş ama 6439 ve 6440'da bir dosyanın iki kümesi bulunuyor. Peki bu düzensizliğin nedeni nedir?

Fragmentasyon
Fragmentasyonun kelime anlamı parçalanma. Yukarıdaki çıktıda iki tür fragmentasyon örneği var. İlki, bir dosyaya ait kümelerin ard arda gelmemesi sonucu olan fragmentasyon. Küme14'te ve daha belirgin örneği küme30'da görülüyor. Kümeler ard arda olmadıklarından, bu dosyaları okumak için diskin farklı silindirlerdeki farklı sektörlerin karışık olarak okunması gerek. Bu da özellikle dönen disklerde erişim süresinin uzamasına neden olur. Buna neden olan en önemli iki bileşen, disk kafalarının doğru silindir üzerine getirilmesi için gereken zaman (seek time) ve okuma kafasının, veri içeren sektörün üzerine gelmesi için plakaların dönmesi gereken süredir (rotational latency). 

İkinci tür fragmentasyon, boş kümelerin ard arda gelmemesinden kaynaklanıyor. Örneğin 6417. ve 6419. kümeler boş ama öncesinde ve sonrasında veri var. Bu da önceden yazılmış görece küçük (veya büyük ama fragmente) dosyaların silinmesiyle oluşuyor. DOS, bir dosya oluşturulacağı zaman ilk boş kümeye değil, en sondaki boş bloğun ilk boş kümesinden itibaren yazıyor (birinci türden fragmentasyonu önlemek için).


Dizinler ve Dizin Tablosu
Buraya kadar, kümelerin diskte nasıl takip edildiğini anlattım. Ancak bir dosyanın hangi kümede başladığını bulmadan veya zincirin hangi dosyaya ait olduğunu bilmeden takip etmenin anlamı yok. Bu noktada yardıma dizin tabloları yetişiyor. Önceki yazıda FreeDOS boot ederken 3 farklı değer hesaplanmıştı: fat_start, root_dir_start ve data_start. Sonra kod, KERNEL.SYS'nin nerede başladığını (ilk kümesini), root_dir_start alanındaki kayıttan bularak belleğe yüklemişti. Bu alan kök dizinin başlangıcıdır.
 
FreeDOS boot sektörü

Yukarıdaki ekran görüntüsü yardımıyla kök dizinin başlangıcını bulalım. Bir ayrılmış sektör olduğuna göre (görselde 1.) bir sonraki sektör FAT başlangıcı. İki tane FAT var (2.) ve her biri 171 sektör (3.) uzunlukta, yani FAT toplam 342 sektör. Şu anda 63. sektörde (4.) olduğuma göre kök dizin 406. sektörde başlıyor. Diskeditörle Alt+P'ye basıp 406. sektöre ilerlerim:

FreeDOS Kök Dizin Tablosu

Veya yukarıda boot sektör için dd ile baştaki 4159 bloğu atlamam gerektiğini bulmuştum, o halde hiç hesap bile yapmadan linuxta aşağıdaki komutu kullanırım:

dd if=~/VirtualBox\ VMs/FreeDOS/FreeDOS.vdi bs=512 \
skip=$((4159+1+171*2)) | hexdump -C | less

İşte bu tablo, bir (kök) dizin tablosu ve ufak farklar dikkate alınmazsa kök dizindeki dosyaların listesini içeriyor. Her girdi 32 byte ve formatı şöyle:

OffsetBüyüklükAçıklama
0x008 byteDosya adı
0x083 byteDosya uzantısı
0x0B1 byteDosya özellikleri
0x0C2 byteAyrılmış
0x0EwordMSDOS: Ayrılmış
FreeDOS: Oluşturma zamanı
0x10word
MSDOS: Ayrılmış
FreeDOS: Oluşturma tarihi
0x122 byteAyrılmış
0x142 byteAyrılmış*
0x16word
Değiştirme zamanı
0x18word
Değiştirme tarihi
0x1AwordKüme numarası
0x1CdwordDosya büyüklüğü

Dosya adı, hepsi büyük harf olmak üzere 8 karakter. Eğer dosyanın adı 8 karakterden kısaysa bu alan boşluk (0x20) karakteriyle doldurulur. Uzantı da aynı şekilde. Dosya özellikleri aşağıdaki flag bitlerinden oluşuyor:

Bit    Açıklama
0Salt okunur
1Gizli
2Sistem dosyası
3Bölüm etiketi
4Dizin
5Arşiv
6Aygıt sürücüsü
7Ayrılmış










Salt okunur, adından da anlaşılabilir. Gizli dosyalar, dir komutuyla listelenmeyen dosyalar. Sistem dosyalarıysa, işletim sistemi çalışırken disk araçlarının dokunmaması gereken dosyalar. Bölüm etiketi, boot sektördeki etiketi ezen özel bir dosya. Dizin, adından anlaşılabilir. Arşiv biti, herhangi bir dosya değiştiğinde, değiştiren program tarafından set ediliyor ve yedekleme programları bu biti aktif olan dosyaları yedekleyip biti sıfırlıyor. Aygıt sürücü dosyaları diskte bulunmuyor ama bu bit, sürücülerin bellekteki girdilerinde bulunuyor.

0x0E ve 0x10 offsetlerindeki alanlar, MS-DOS 6 ve öncesine kadar kullanılmıyor. FreeDOS ve DOS7 (Win95), dosyanın oluşturulma zamanı ve tarihini burada tutuyor. DOS6'da bu alanlar sıfır ancak örn. DOS6 sanal makinasını DSL'le açıp bir dosya oluşturursam, DSL'deki FAT sürücüsü bu alana yazdığı için sıfır olmayabilir ama DOS6, bu alanı görmezden geliyor. 0x14 offsetinde benzeri bir durum var. DSL'de oluşturduğum dosyalarda bu alan sıfırdan farklı ancak ne olduğuna dair bir dökümantasyon bulunmuyor. Dosya erişim tarihinin kopyası olabilir.

0x16 offsetindeki değiştirme zamanı bütün FAT'lerde ortak ve bu değer little endian olarak saklanıyor. Formatı şu şekilde (Sa: Saat, Dk: Dakika, Sn: Saniye olup, saniyelere 5 bit ayrılmasından dolayı çözünürlük 2 saniyedir): 

Offset 0x17
Offset 0x16
Sa4Sa3Sa2Sa1Sa0Dk5Dk4Dk3Dk2Dk1Dk0Sn4Sn3Sn2Sn1Sn0

Aynı şekilde 0x18'deki değiştirme tarihi de tüm FAT'lerde ortak ve little endian biçimindedir. Yıl değeri 1980 ile başlar. (Yi: Yıl, Gü: Gün): 

Offset 0x19Offset 0x18
Yi6Yi5Yi4Yi3Yi2Yi1Yi0Ay3Ay2Ay1Ay0Gü4Gü3Gü2Gü1Gü0

0x1A offsetindeki küme numarası, dosyanın ilk kümesinin yerini gösterir. Dosyanın ilk kümesi bulunduktan sonra devamının nasıl bulunacağını biliyoruz. Son olarak dosya büyüklüğü, dosyanın byte cinsinden büyüklüğüdür.

Dosya adının ilk karakteri 00'sa bu, dizin listesinin sonunu gösterir, yani işletim sistemi ilk karakteri 00 olan bir girdi bulursa, listeyi okumayı durdurur. Bir dosya silinirse DOS, dosya adının ilk karakterini 0x5 veya 0xE5 ile değiştirip FAT'taki küme girdilerini sıfırlar. Başka bir deyişle 0x5 veya 0xE5'le başlayan girdiler silinmiş dosyalara aittir. Eğer silinmiş dosyaya ait kümelerin üzerine başka dosya yazılmamışsa ve dosya fragmente olmamışsa, dosyaya ait girdinin ilk karakteri düzeltilerek geri alınabilir. Silinmiş dosyalara, sonraki bölümde örnek vereceğim.

Şimdi yukarıdaki ekran görüntüsünde bulunan dizin tablosunu inceleyelim:

00000000  46 52 45 45 44 4f 53 32  30 31 36 08 00 00 00 00  |FREEDOS2016.....|
00000010  00 00 00 00 00 00 e2 b8  0b 4f 00 00 00 00 00 00  |.........O......|
[ SNIP ]
00000040  46 44 4f 53 20 20 20 20  20 20 20 10 00 00 e8 b8  |FDOS       .....|
00000050  0b 4f 00 00 00 00 e8 b8  0b 4f 03 00 00 00 00 00  |.O.......O......|
00000060  4b 45 52 4e 45 4c 20 20  53 59 53 20 00 00 f3 b8  |KERNEL  SYS ....|
00000070  0b 4f 00 00 00 00 40 ad  ab 48 1e 00 5d b6 00 00  |.O....@..H..]...|
[ SNIP ]
000000e0  41 64 00 69 00 73 00 6b  00 31 00 0f 00 22 00 00  |Ad.i.s.k.1..."..|
000000f0  ff ff ff ff ff ff ff ff  ff ff 00 00 ff ff ff ff  |................|
00000100  44 49 53 4b 31 20 20 20  20 20 20 10 00 00 73 1a  |DISK1      ...s.|
00000110  0c 4f 0c 4f 00 00 73 1a  0c 4f 02 00 00 00 00 00  |.O.O..s..O......|

İlk girdi FREEDOS2.016 ve özelliklerine bakıldığında, bunun bir bölüm etiketi olduğu görülüyor. Dolayısıyla bu, özel bir girdi. Kümesi ve büyüklüğü sıfır.

İkinci girdi olan "FDOS"un, kalan 4 karakteri ve uzantısı boşluk karakteriyle doldurulmuş ve özelliklerinden dizin olduğu görülüyor. Oluşturma ve değiştirme tarihi ve saati aynı. Saat 0xB8E8 yani: 10111 000111 01000 = 23:07:32 (saniye alanı 16 ama 2 sn çözünürlükteydi o nedenle ikiyle çarpılacak). Ve tarih: 0x4F0B yani: 0100111 1000 01011 = 1980+39/8/11. Bu dizin 3. kümeden başlıyor ve uzunluğu sıfır. Şimdi tarih ve saati bir kontrol edelim:


FreeDOS boot sektörü görselinden görüleceği üzere (görselde 5.) kök dizine 512 girdi ayrılmış. Girdilerin her biri 32 byte ve bir sektör de 512 byte ise kök dizin 32 sektör uzunluktadır. Daha önce kök dizinin 406. sektörde olduğunu hesaplamıştım, o halde bu diskte data_start 438. sektördedir. Formül [2]'den ikinci küme 438. ve üçüncü küme 438+16=454. sektörden başlar. Linux'ta;

dd if=~/VirtualBox\ VMs/FreeDOS/FreeDOS.vdi bs=512 \
skip=$((4160+171*2+32+16)) | hexdump -C | less

Diskeditörle 454. sektöre gittiğimde (Alt+P ile), sektörün görünümü değişiyor. Diskeditör burada bir dizin olduğunu anlayıp dizin görünümünde gösteriyor (yanda) ve bu görünümde, yukarıda yaptığım hesaplamaları kendiliğinden yapıyor. F2 ile Hex görünümüne geçersem, dizinin ilk iki girdisinin "." (dizinin kendisi) ve ".." olduğu görülüyor. Dizinin kendisi 3. kümede ve üst dizin kök dizin olduğundan küme sıfır. Genellersem, dizinin kendisini temsil eden "." girdisi, dizinin bulunduğu kümeyi; üst dizini temsil eden ".." girdisi, üst dizinin bulunduğu kümeyi gösterir. Üst dizin kök dizinse, gösterdiği küme sıfırdır. FAT16'yı incelerken 3. kümede EOF görmüştüm, yani bu dizinin içindeki dosya ve altdizinlerin listesi bu küme içinde listeleniyor. Dizin tablosu daha çok dosya içerdiğinde, büyüyüp birden fazla kümeye yayılabilir ama ne kadar büyük olursa olsun, kendi girdisindeki dosya büyüklüğü sıfırdır. Wikipedia'da FAT'ın tasarımının anlatıldığı maddede önemli bir cümle var: "Dizin tablosu dizini temsil eden özel bir dosya türüdür". Dizinler de dosyalar gibi kümelerde saklanır ve FAT'ta girdileri vardır ancak kök dizin istisnadır. FAT12 ve FAT16'da kök dizinin FAT girdisi olmaz. Dosya sistemi hiyerarşisinden bahsedebilmek için önce kök dizin bulunmalıdır. Bu nedenle, fragmente olmayacağı, taşınmayacağı ve diskin başında olacağı, tasarımla garantilenerek kök dizinin bulunması kolaylaştırılmıştır.

Bir sonraki KERNEL.SYS girdisini incelersek, oluşturma tarihi FDOS'la aynı, yalnız saniye alanı 3 farklı yani FDOS dizininden 5-6 sn sonra oluşturulmuş. Düzenleme tarihi olan 11.05.2016, kurulum CD'sindeki dosyanın tarihi olmalı. Dosya 0x1E kümesinde (sektör 886) başlıyor ve 0xB65D=46 685 byte uzunlukta.

Bu girdinin altında, adında sıfırlar ve küçük harfler olup kümesi sıfır, büyüklüğü 4Gb olan, gizli bir bölüm etkiketi var. Adında disk1 okunuyor ve kendisinden sonra gelen geçerli girdi de aynı ada sahip, ama yukarıdaki standarda uymuyor...


Uzun Dosya Adı (LFN) Desteği
Bilindiği gibi DOS'ta dosya adları 8+3 karakterle (büyük harfler, sayılar ve bazı noktalama işaretleri) kısıtlı ancak Win95'ten itibaren uzun dosya adları FAT'le kullanılabiliyordu. Bu, VFAT olarak adlandırılan bir genişlemedir ve dosya sistemi sürücüsü destekliyorsa tüm FAT sürümlerinde kullanılabilir.

Üst bölüm yeni sürümden, alt eski
VFAT'ın hilesi, asıl girdiden önce yer alan ve geçersiz görünen girdiler içinde uzun dosya adını tutmaktır. Yukarıdaki örnekte "DISK1" uzun olmasa da, küçük harflerinden dolayı bir VFAT girdisine sahiptir. Eski sürüm diskedit bu girdileri tanımazken, yeni sürüm bunları tanıyıp dosya türünü LFN olarak belirtmektedir (yandaki görsel).

Bu iyi bir örnek olmadığından, sanal makinayı DSL ile açıp FreeDOS disketini taktım ve terminalde aşağıdaki komutları uyguladım:


Dikkat: Disket, -t vfat parametresi verilmediğinde uzun dosya adı desteği olmadan mount edilir. vi ile dosyanın içine bir kaç karakter yazıp :wq ile kaydettim ve çıktım. Aynı dosya şöyle de oluşturulabilirdi:

echo test \
> "long file name (LFN) support on FAT file system.txt"

Dosya vi ile kaydedilirken, vi geçici bir .swp dosya oluşturup bunu siliyor. Bu noktada hexdump -C freedos.ima | less komutuyla kalıp dosyasına bakınca 0x2640 ile 0x2700 offsetleri arasında herbirinin başında 0xE5 karakterleri olan (silinmiş) dosya girdileri var. Demek ki uzun adlı dosyalar silinirken bütün girdilerinin başına 0xE5 karakteri geliyor. Benim ilgilendiğim asıl dosya 0x2780 offsetindeki LONGFI~1.TXT. Hatırlayanlar vardır, Win95'teki bir çok dosya DOS altında böyle görünürdü, çünkü uzun dosya adı girdileri DOS tarafından görmezden gelinirdi. Girdilerin daha kolay okunabilmesi için sıralamasını tersine çevireceğim (başındaki offset numaralarından gerçek sıra anlaşılabilir):

00002780  4c 4f 4e 47 46 49 7e 31  54 58 54 20 00 00 25 87  |LONGFI~1TXT ..%.|
00002790  26 52 26 52 00 00 25 87  26 52 f9 00 29 00 00 00  |&R&R..%.&R..)...|
00002760  01 6c 00 6f 00 6e 00 67  00 20 00 0f 00 d4 66 00  |.l.o.n.g. ....f.|
00002770  69 00 6c 00 65 00 20 00  6e 00 00 00 61 00 6d 00  |i.l.e. .n...a.m.|
00002740  02 65 00 20 00 28 00 4c  00 46 00 0f 00 d4 4e 00  |.e. .(.L.F....N.|
00002750  29 00 20 00 73 00 75 00  70 00 00 00 70 00 6f 00  |). .s.u.p...p.o.|
00002720  03 72 00 74 00 20 00 6f  00 6e 00 0f 00 d4 20 00  |.r.t. .o.n.... .|
00002730  46 00 41 00 54 00 20 00  66 00 00 00 69 00 6c 00  |F.A.T. .f...i.l.|
00002700  44 65 00 20 00 73 00 79  00 73 00 0f 00 d4 74 00  |De. .s.y.s....t.|
00002710  65 00 6d 00 2e 00 74 00  78 00 00 00 74 00 00 00  |e.m...t.x...t...|

Geçerli olan tek kayıt, asıl kayıt (0x2780). Dosya büyüklüğü ve küme numarası gibi değerler bu kayıtta. Uzun dosya adı girdilerinin ilk karakteri bir sıra numarası ve offset küçüldükçe artıyor. Girdi dizisinin sonuncusunda, ilk karakterin 6. bitinin bir olması gerek (örn. 0x2700'deki 0x44, dördüncü ve son girdi). Sıra numarasından sonra 16-bitlik 5 karakter var. Bu karakterler unicode'a çok benzeyen UCS karakter setinde. Böylelikle dosya adlarında Çince veya Kiril karakterler kullanmak mümkün. Normal bir girdide dosya özelliklerine karşılık gelen 11. byte her zaman 0x0F, yani gizli+sistem+salt-okunur+disk etiketi. Diskte birden fazla disk etiketinin olması hatalı, üstüne diğer özelliklerin de bulunması bu kaydı geçersiz kılarak işletim sistemi tarafından görmezden gelinmesine neden oluyor.

Uzun dosya adı girdilerinin 12. byte'ı her zaman sıfır ve 13. byte'da bir checksum bulunuyor. Bunun algoritması wikipedia'da VFAT long file names başlığında var. 14. byte'dan 25. byte'a kadar olan 12 byte, 6 karakter daha içeriyor. 26. byte'da bulunan küme numarası, bu girdilerde her zaman 0 ve kalan son 4 byte'da 2 karakter olmak üzere her girdi 13 karakter içeriyor. Dosya adları sıfır sonlandırmalı string'ler ve sonuncu girdideki karakterler 13 byte'dan azsa, kalan alanlar 0xFF ile dolduruluyor. Yukarıda dosya alanı tam dolduğundan görülmüyor ama DISK1 örneğinde 14. byte'da sıfır karakteri ve sonrasında 0xFF'ler açıkça görülebilir.

Bu yazıyla birlikte disk sistemi serisinin ana konularını tamamlamış oldum. FAT32 bu yazıya sığmadı. FAT32'de kök dizin mantığı tümüyle değiştiği için onu ayrı bir yazıda ele almak daha doğru. Bunun dışında GPT, NTFS gibi konularda da ileride ek yazılar yayınlayacağım.

[1]: https://en.wikipedia.org/wiki/File_Allocation_Table

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.