> Dosya Sistemleri ve Disk İşlemleri: Kursumuzun bu bölümünde dosya sistemlerini belli bir derinlikte inceleyeceğiz. Dosya sistemlerini ele almadan önce bilgisayar sistemlerindeki disk sistemleri hakkında bazı temel bilgilerin edinilmesi gerekmektedir. Eskiden diskler yerine teyp bantları kullanılıyordu. Teyp bantları sıralı erişim sağlıyordu. Sonra manyetik diskler kullanılmaya başlandı. Kişisel bilgisayarlardaki manyetik disklere "hard disk" de deniyordu. Bugünlerde artık hard diskler de teknoloji dışı kalmaya başlamıştır. Bugün disk sistemleri için artık flash bellekler (EEPROM bellekler) kullanılmaktadır. Yani SSD (Solid State Disk) diye isimlendirilen bu disk sistemlerinin artık mekanik bir parçası yoktur. Bunlar tamamen yarı iletken teknolojisiyle üretilmiş entegre devrelerdir. Disk sisteminin türü olursa olsun bu disk sistemini yöneten ondan sorumlu bir denetleyici birim bulunmaktadır. Buna "disk denetleyicisi (disk controller)" denilmektedir. Kernel mod ayghıt sürücüler bu disk denetleyicisini programlayarak transferleri gerçekleştirmektedir. Bugünkü disk sistemlerini şekilsel olarak aşağıdaki gibi düşünebiliriz: DISK BIRIMI <======> DİSK DENETLEYİCİSİ <=======> Aygıt Sürücü <=======> User Mod | | | Disk Cache Örneğin biz uder modda bir diskten bir sektör okumak istediğimizde bu işlemi yapan blok aygıt sürücüsünden istekte bulunuruz. >> Sektör: Bir diskten transfer edilecek en küçük birime sektör denilmektedir. Bir sektör genel olarak 512 byte uzunluğundadır. Örneğin biz bir diskteki 1 byte'ı değiştirmek istesek önce onun içinde bulunduğu sektörü belleğe okuyup değişikliği bellekte yapıp o sektörü yeniden diske yazarız. Disklere byte düzeyinde değil sektör düzeyinde erişilmektedir. Diskteki her sektöre ilk sektör 0 olmak üzere bir numara verilmiştir. Disk denetleyicileri bu numarayla çalışmaktadır. Eskiden disklerdeki koordinat sistemi daha farklıydı. Daha sonra teknoloji geliştikçe sektörünü yerini belirlemek için tek bir sayı kullanılmaya başlandı. Bu geçiş sırasında kullanılan bu sisteme LBA (Logical Block Addressing) deniliyordu. Artık ister hard diskler olsun isterse SSD'ler olsun tıpkı bellekte her byte'ın bir numarası olduğu gibi her sektörün de bir sektör numarası vardır. Transflerler bu sektör numarasayla yapılmaktadır. Aslında "dosya (file)" kavramı mantıksal bir kavramdır. Diskteki fiziksel birimler sektör denilen birimlerdir. Yani diskler dosyalardan oluşmaz sektörlerden oluşur. Dosya bir isim altında bir grup sektörü organize etmek için uydurulmuş bir kavramdır. Aslında dosyanın içindekiler diskte ardışıl sektörlerde olmak zorunda -değildir. Kullanıcı için dosya sanki ardışıl byte'lardan oluşan bir topluluk gibidir. Ancak bu bir aldatmacadır. Dosyadaki byte'lar herhangi bir biçimde ardışıl olmak zorunda değildir. Örneğin elimizde 100K'lık bir dosya olsun. Aslında bu 100K'lık dosya diskte 200 sektör içerisindedir. Peki bu dosyanın parçaları hangi 200 sektör içerisindedir? İşte bir biçimde bu bilgiler de disk üzerinde tutulmaktadır. Blok aygıt sürücüleri disk denetleyicilerini programlar, disk denetleyicileri disk birimine erişir ve transfer gerçekleşir. Disk tarnsferleri CPU aracalığıyla değil "DMA (Direct Memory Access)" denilen özel denetleyicilerle sağlanmaktadır. Yani aygıt sürücü disk denetleyicisini ve DMA'yı programlar transfer yapılana kadar bekler. Bu surada işletim sistemi zaman alacak bu işlemi meşgul bir döngüde beklemez. O anda istekte bulunan thread'i bekleme kuyruğuna yerleştirerek sıradaki thread'i çizelgeler. İşletim sistemlerinde diskten transfer işlemi yapan blok aygıt sürücüleri ismine "disk cache" ya da "buffer cache" ya da "page cache" denilen bir cache sistemi kullanmaktadır. Tabii cache sistemi aslında çekirdek tarafından organize edilmiştir. Blok aygıt sürücüsünden bir sektörlük bilgi okunmak istediğinde aygıt sürücü önce bu cache sistemine bakar. Eğer istenen sektör bu cache sisteminde varsa hiç bekleme yapmadan oradan alıp talep eden thread'e verir. Eğer sektör cache'te yoksa blok aygıt sürücüsü disk denetleyicisini ve DMA denetleyicisini programlayarak sektörü önce cache'e tarnsfer eder. Oradan talep eden thread'e verir. Bu amaçla kullanılan cache'lerde cache algoritması (cache replacement algorithm) genel olarak LRU algoritmasıdır. Yani son zamanalrda erişilen yerler mümkün olduğunca cache'te tutulmaktadır. İşlerim sistemlerinin dosya sistemleri arka planda bu blok aygıt sürücülerini kullanmaktadır. Dolayısıyla tüm dosya işlemleri aslında bu cache sistemi ile gerçekleşmektedir. Yani örneğin bugünkü modern işletim sistemlerinde ne zaman bir dosya işlemi yapılsa o dosyanın okunan ya da yazılan kısmı disk cache içerisine çekilemektedir. Aynı dosya üzerinde bir işlem yapıldığında zaten o dosyanın diskteki blokları cache'te olduğu için gerçek anlamda bir disk işlemi yapılmayacaktır. Pekiyi aygıt sürücü bir sektörü yazmak isterse ne olmaktadır? İşte yazma işlemleri de doğrudan değil cache yoluyla yapılmaktadır. Yani sektör önce disk cache'e yazılır. Sonra çizelgelenir ve işletim sisteminin bir kernel thread'i yoluyla belli periyotlarda diske transfer edilir. User moddan çeşitli thread'lerin diskten okumalar yaptığını düşünelim. Önce bu talepler işletim sistemi tarafından kuyruklanır, çizelgelenir sonra etkin bir biçimde transfer gerçekleştirilir. İşletim sistemlerinin bu kısmına "IO çizelgeleyeicisi (IO scheduler)" denilmektedir. Disk sistemleriyle ilgili programcıların ilk bilmesi gereken işlemler bir sektörün okunmasının ve yazılmasının nasıl yapılacağıdır. Yukarıda bu işlemleri yapan yazılımsal birimin blok aygıt sürücüleri olduğunu belirtmiştik. Aygıt sürücülerin de birer dosya gibi açılıp kullanıldığını biliyoruz. O halde sektör transferi için bizim hangi aygıt sürücüyü kullanacağımızı bilmemiz gerekir. UNIX/Linux sistemlerinde bilindiği gibi tüm temel aygıt sürücülere ilişkin aygıt dosyaları "/dev" dizini içerisindedir. Bir Linux sisteminde lsblk" komutu ile disklere ilişkin blok aygıt sürücülerinin listesini elde edebilirsiniz. Örneğin: $ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS sda 8:0 0 60G 0 disk ├─sda1 8:1 0 1M 0 part ├─sda2 8:2 0 513M 0 part /boot/efi └─sda3 8:3 0 59,5G 0 part / sr0 11:0 1 1024M 0 rom Linux sistemlerinde disklere ilişkin blok aygıt sürücüleri diskin türüne göre farklı biçimlerde isimlendirilmektedir. Örneğin hard diskler ve SSD diskler tipik olarak "sda", "sdb", "sdc" biçiminde isimlendirilmektedir. Micro SD kartlar ise genellikle "mmcblk0", "mmcblk1" ve "mmcblk2" gibi isimlendirilmektedir. Örneğin burada "sda" ismi har diski bir bütün olarak ele alan aygıt dosyasının ismidir. Disk disk bölümlerinden oluşmaktadır. Bu disk bölümlerini sanki ayrı disklermiş gibi ele alan aygıt dosyaları da "sda1", "sda2", "sda3" biçimindedir. Burada "disk bölümü (disk partition)" terimini biraz açmak istiyoruz. >> "Disk Partition" : Bir diskin bağımsız birden fazla diskmiş gibi kullanılabilmesi için disk mantıksal bölümlere ayrılmaktadır. Bu bölümlere "disk bölümleri (disp partitions)" denilmektedir. Bir disk bölümü diskin belli bir sektöründen başlar belli bir sektör uzunluğu kadar devam eder. Disk bölümlerinin hangi sektörden başladığı ve hangi uzunlukta olduğu diskin başındaki bir tabloda tutulmaktadır. Bu tabloya "disk bölümleme tablosu (disk partition table)" denilmektedir. Disk bölümleme tablosu eskiden diskin ilk sektöründe tutuluyordu. Sonra UEFI BIOS'larla birlikte eski sistemle uyumlu olacak biçimde yeni disk bölümleme tablo formatı geliştirildi. Bunlara "GUID Disk Bölümleme Tablosu (GUID Partition Table)" denilmektedir. Örneğin 3 disk bölümüne sahip bir diskin mantıksal organizasyonu şöykedir: Disk Bölümleme Tablosu Birnci Disk Bölümü İkinci Disk Bölümü Üçüncü Disk Bölümü İşte "lsblk" yaptığımız Linux sisteminde biz "/dev/sda" aygıt dosyaısnıürüsünü açarsak tüm diski tek parça olarak ele alırız. Eğer "/dev/sda" aygıt dosyasını açarsak sanki Birinci Disk Bölümü ayrı diskmiş gibi yalnızca o bölümü ele alabiliriz. Örneğin "/dev/sda" aygıt dosyasından okuyacağımız 0 numaralı sektör aslında İkinci Disk Bölümünün ilk sektörürüdr. Tabii bu sektör "/dev/sda" aygıt dosyasındaki 0 numaralı sektör değildir. Linux sistemlerinde bir diskten bir sektör okumak için yapılacak tek şey ilgili aygıt sürücüyü open fonksiyonuyla açmak dosya göstericisini konumlandırıp read fonksiyonu ile okuma yapmaktır. Biz yukarıda bir diskten okunup yazılabilen en küçük birimin bir sektör olduğunu (512 byte) söylemiştik. Her ne kadar donanımsal olarak bir diskten okunabilecek ya da diske yazılabilecek en küçük birim bir sektör olsa da aslında işletim sistemleri transferleri sektör olarak değil blok blok yapmaktadır. Bir blok ardışıl n tane sektöre denilmektedir. Örneğin Linux işletim sisteminin disk cache sistemi aslında 4K büyüklüğünde bloklara sahiptir. 4K'nın aynı zamanda sayfa büyüklüğü olduğunu anımsayınız. Dolayısıyla biz Linux'ta aslında disk ile bellek arasında en az 4K'lık transferler yapmaktayız. O halde işletim sisteminin dosya sistemi ve diske doğrudan erişen sistem programcıları Linux sistemlerinde diskten birer sektör okuyup yazmak yerine 4K'lık blokları okuyup yazarsa sistemle daha uyumlu çalışmış olur. Pekiyi biz ilgili disk aygıt sürücüsünü açıp read fonksiyonu ile yalnızca 10 byte okumak istersek ne olur? İşte bu durumda blok aygıt sürücüsü gerçek anlamda o 10 byte'ın içinde bulunduğu 4K'lık bir kısmı diskten okur onu cache'e yerleştirir ve bize onun yalnızca 10 byte'ını verir. Aynı byte'ları ikinci kez okumak istersek gerçek anlamda bir disk okuması yapılmayacak RAM'de saklanmış olan cache'in içerisindeki bilgiler bize verilecektir. Aşağıda diski bir bütün olarak gören "/dev/sda" aygıt sürücüsü açılıp onun ilk sektörü okunmuş ve içeriği HEX olarak ekrana (stdout dosyasına) yazıdırılmıştır. Burada bir noktaya dikkatinizi çekmek istiyoruz. Bu aygıt sürücüyü temsil eden aygıt dosyasına ancak root kullanıcısı erişebilmektedir. Bu dosyaların erişim haklarına dikkat ediniz: $ ls /dev/sda* -l brw-rw---- 1 root disk 8, 0 Ağu 29 14:56 /dev/sda brw-rw---- 1 root disk 8, 1 Ağu 29 14:56 /dev/sda1 brw-rw---- 1 root disk 8, 2 Ağu 29 14:56 /dev/sda2 brw-rw---- 1 root disk 8, 3 Ağu 29 14:56 /dev/sda3 Bu durumda programınızı sudo ile çalıştırmalısınız. * Örnek 1, #include #include #include #include void exit_sys(const char *msg); int main(void) { int fd; unsigned char buf[512]; if ((fd = open("/dev/sda", O_RDONLY)) == -1) exit_sys("open"); if (read(fd, buf, 512) == -1) exit_sys("read"); for (int i = 0; i < 512; ++i) printf("%02x%c", buf[i], i % 16 == 15 ? '\n' : ' '); close(fd); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } Dosya sistemi (file system) denildiğinde ne anlaşılmaktadır? Bir dosya sisteminin iki yönü vardır: Bellek ve disk. Dosya sisteminin bellek tarafı işletim sisteminin açık dosyalar için kernel alanında yaptığı organizasyonla (dosya betimelyici tablosu, dosya nesnesi vs) ilgilidir. Disk tarafı ise diskteki organizasyonla ilgilidir. Biz kursumuzda bellek tarafındaki organizasyonun temellerini gördük. Şimdi bu bu bölümde disk üzerindeki organizasyonu ele alacağız. Dosya kavramını diskte oluşturmak için farklı dosya sistemleri tarafından farklı disk organizasyonları kullanılmaktadır. Bugün kullanılan çok sayıda dosya sistemi vardır. Bu sistemlerin her birinin disk organizasyonu diğerinden az çok farklıdır. Ancak bazı dosya sistemlerinin birbirlerine organizasyonları birbirine çok benzemektedir. Bunlar adreta bir aile oluşturmaktadır. Örneğin Microsoft'un FAT dosya sistemleri, Linux'un ext dosya sistemleri kendi aralarında birbirine oldukça benzemektedir. Microsft'un dünyanın kişisel bilgisayarlarında kullandığı dosya sistemlerine aile olarak FAT (File Allocation Table) denilmektedir. Bu FAT dosya sistemlerinin kendi içerisinde FAT12, FAT16 ve FAT32 biçiminde varyasyonları vardır. Microsoft daha sonra yine FAT tabanlı ancak çok daha gelişmiş NTFS denilen bir dosya sistemi gerçekleştirmiştir. Bugün Windows sistemlerinde genel olarak NTFS (New Technology File SYstems) dosya sistemleri kullanılmaktadır. Ancak Microsoft hala FAT tabanlı dosya sistemlerini de desteklemektedir. Linux sistemlerinde "EXT (Extended File System)" ismi verilen "i-node tabanlı" dosya sistemleri kullanılmaktadır. Bu EXT dosya sistemlerinin EXT2, EXT3, EXT4 biçiminde varyasyonları vardır. Bugünkü Linux sistemlerinde en çok EXT4 dosya sistemi kullanılmaktadır. Apple firması yine i-node tabanlı olan, HFS (Hierarchical File System), HFS+ (Hierarchical File System Plus), APFS (Apple File System) isimli dosya sistemlerini kullanmaktadır. Bunlar da aile olarak birbirlerine çok benzemektedir. Bugünkü macOS sistemlerinde genellikle HFS+ ya da APFS dosya sistemleri kullanılmaktadır. Bilindiği gibi UNIX/Linux sistemlerinde Windows sistemlerinin aksine "sürücü (drive)" kavramı yoktur. Dosya sisteminde tek bir "kök dizin (root directory)" vardır. Eğer biz bir dosya sistemine erişmek istiyorsak önce onu belli bir dizinin üzerine "mount" ederiz. Artık o dosya sisteminin kökü mount ettiğimiz dizin üzerinde bulunur. Örneğin bir flash belleği USB yuvasına taktığımızda Windows'ta o flash bellek bir sürücü olarak gözükmektedir. Ancak Linux sistemlerinde o flash bellek belli bir dizinin altında gözükür. Yani o dizine mount işlemi yapılmaktadır. Bir dosya sistemi bir dizine mount edildiğinde artık o dizin ve onun altındaki dizin ağacı görünmez olur. Onun yerine mount ettiğimiz blok aygıtındaki dosya sisteminin kökü görünür. Mount işlemi Linux sistemlerinde aslında bir sistem fonksiyonuyla yapılmaktadır. Bu sistem fonksiyonu "libc" kütüphanesinde "mount" ismiyle bulunmaktadır. >> "mount" : Fonksiyonun prototipi şöyledir: #include int mount(const char *source, const char *target, const char *filesystemtype, unsigned long mountflags, const void *data); Fonksiyonun, -> Birinci parametresi blok aygıt dosyasının yol ifadesini, -> İkinci parametre mount dizinini (mount point) belirtmektedir. -> Üçüncü parametre dosya sisteminin türünü almaktadır. -> Dördüncü parametre mount bayraklarını belirtmektedir. Bu parametre 0 geçilebilir. -> Son parametre ise dosya sistemi için gerekebilecek ekstra verileri belirtmektedir. Bu parametre de NULL adres geçilebilir. Fonksiyon başarı durumunda 0 değerine, başarısızlık durumunda -1 değerine geri dönmektedir. Dosya sisteminin türünün otomatik tespit eden bazı özel fonksiyonlar bulunmaktadır. Örneğin, "libmount" kütüphanesi içerisindeki statfs fonksiyonuyla ya da "libblkid" kütüphanesi içerisindeki fonksiyonlarla bunu sağlayabilirsiniz. Tabii bu fonksiyonu çağırabilmek için prosesimizin etkin kullanıcı id'sinin 0 olması ya da prosesimizin uygun önceliğe (appropriate privilege) sahip olması gerekir. mount fonksiyonu POSIX standartlarında yoktur. Çünkü işletim sisteminin gerçekleştirimine oldukça bağlı bir fonksiyondur. Tabii kullanıcılar mount işlemini bu sistem fonksiyonu yoluyla değil, "mount" isimli kabuk komutuyla yapmaktadır. mount işlemi için elimizde bir blok aygıt sürücüsüne ilişkin aygıt dosyasının bulunuyor olması gerekir. Ancak blok aygıt sürücüleri mount edilebilmektedir. Tabii ilgili blok aygıt sürücüsünün sektörleri içerisinde bir dosya sisteminin bulunuyor olması gerekir. mount isimli kabuk komutunun tipik kullanımı şöyledir: sudo mount Mount edilecek dizine genel olarak İngilizce "mount point" de denilmektedir. Örneğin bilgisayarımıza bir SD kart okuyucu bağlamış olalım. lsblk yaptığımızda şöyle bir görüntüyle karşılaştığımızı varsayalım: $ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS sda 8:0 0 60G 0 disk ├─sda1 8:1 0 1M 0 part ├─sda2 8:2 0 513M 0 part /boot/efi └─sda3 8:3 0 59,5G 0 part / sdb 8:16 1 0B 0 disk sdc 8:32 1 14,8G 0 disk ├─sdc1 8:33 1 60M 0 part /media/kaan/72FA-ACF3 └─sdc2 8:34 1 14,8G 0 part /media/kaan/fa57bb30-99ca-4966-8249-6b0c6c4f4d8d sdd 8:48 1 0B 0 disk sr0 11:0 1 1024M 0 rom Burada taktığımız SD kart "sdc" ismiyle gözükmektedir. "/dev/sdc" aygıt dosyası SD kartı bir bütün olarak görmektedir. Bu SD kartın içerisinde iki farklı disk bölümünün oluşturulduğu görülmektedir. Bu disk bölümlerine ilişkin aygıt dosyaları da "/dev/sdc1" ve "/dev/sdc2" dosyalarıdır. Biz "/dev/sdc" aygıtını mount edemeyiz. Çünkü bu aygıt, diski bir bütün olarak görmektedir. Oysa "/dev/sdc1" ve "/dev/sdc2" aygıtlarının içerisinde daha önceden oluşturulmuş olan dosya sistemleri vardır. Biz bu aygıtları mount edebiliriz. Mount işlemi için sistem yöneticisinin bir dizin oluşturması gerekir. Mount işlemleri için Linux sistemlerinde kök dizinin altında bir "mnt" dizini oluşturulmuş durumdadır. Yani mount edilecek dizini bu dizinin altında yaratabilirsiniz. Tabii böyle bir zorunluluk yoktur. Biz bulunduğumuz dizinde boş bir dizin yaratıp bu dizini mount point olarak kullanabiliriz. Örneğin: $ sudo mount /dev/sdc1 mydisk mount komutu ilgili blok aygıtındaki dosya sistemini otomatik olarak tespit etmeye çalışır. Genellikle bu tespit otomatik yapılabilmektedir. Ancak bazı özel aygıtlar ve dosya sistemleri için bu belirlemenin açıkça yapılması gerekebilir. Bunun için mount komutunda "-t " seçeneği kullanılır. Örneğin: $ sudo mount -t vfat /dev/sdc1 mydisk Burada -t seçeneğine argümanı olarak aşağıdaki gibi dosya sistemleri kullanılabilir: ext2 ext3 ext4 ntfs vfat tmpfs xfs ... Dosya sisteminin otomatik belirlenmesi mount sistem fonksiyonu tarafından yapılmaktadır. mount komutu birtakım işlemlerle bunu sağlamaktadır. Mount edilmiş olan bir blok aygıtının mount işlemi umount isimli sistem fonksiyonuyla kaldırılabilir. >> "umount" : Fonksiyonun prototipi şöyledir: #include int umount(const char *target); Fonksiyon mount dizinini parametre olarak almaktadır. Başarı durumunda 0 değerine, başarısızlık durumunda -1 değerine geri döner. Artık umount yapıldıktan sonra mount point dizinin içeriğine yeniden erişilebilmektedir. Unmount işlemi de yine komut satırından "umount" komutuyla yapılabilmektedir. Komutun genel biçimi şöyledir: $ sudo umount Örneğin: $ sudo umount mydisk Pek çok UNIX türevi sistemde olduğu gibi Linux sistemlerinde de "otomatik mount" mekanizması bulunmaktadır. Sistem boot edildiğinde konfigürasyon dosyalarından hareketle otomatik mount işlemleri yapılabilmektedir. USB aygıtları genel olarak zaten otomatik mount işlemi oluşturmaktadır. "systemd" init sisteminde "mount unit" dosyaları ile otomatik mount işlemleri yönetilebilmektedir. Klasik "system5" init sistemlerinde çekirdek yüklendikten sonra "/etc/fstab" dosyasında otomatik mount edilecek blok aygıtları belirtilebilmektedir. "/etc/fstab" dosyasına "systemd" tarafından da açılış sırasında bakılmaktadır. Aşağıda mount sistem fonksiyonu çağrılarak mount işlemi yapan bir örnek verilmiştir. Programı, $ sudo ./mymount /dev/sdc1 mydisk vfat benzer biçimde çalıştırabilirsiniz: * Örnek 1, /* mymount.c */ #include #include #include void exit_sys(const char *msg); /* mymount */ int main(int argc, char *argv[]) { if (argc != 4) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } if (mount(argv[1], argv[2], argv[3], 0, NULL) == -1) exit_sys("mount"); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } Linux sistemlerinde bir dosyayı sanki blok aygıtı gibi gösteren hazır aygıt sürücüler bulunmaktadır. Bunlara "loop" aygıt sürücüleri denilmektedir. Bu aygıt sürücülere ilişkin aygıt dosyaları "/dev" dizini içerisinde "loopN" ismiyle (burada N bir sayı belirtiyor) bulunmaktadır. Örneğin: $ ls -l /dev/loop* brw-rw---- 1 root disk 7, 0 Haz 4 22:31 /dev/loop0 brw-rw---- 1 root disk 7, 1 Haz 4 22:31 /dev/loop1 brw-rw---- 1 root disk 7, 2 Haz 4 22:31 /dev/loop2 brw-rw---- 1 root disk 7, 3 Haz 4 22:31 /dev/loop3 brw-rw---- 1 root disk 7, 4 Haz 4 22:31 /dev/loop4 brw-rw---- 1 root disk 7, 5 Haz 4 22:31 /dev/loop5 brw-rw---- 1 root disk 7, 6 Haz 4 22:31 /dev/loop6 brw-rw---- 1 root disk 7, 7 Haz 4 22:31 /dev/loop7 crw-rw---- 1 root disk 10, 237 Haz 4 22:31 /dev/loop-control Bir dosyayı blok aygıt sürücüsü biçiminde kullanabilmek için önce "losetup" programı ile bir hazırlık işleminin yapılması gerekir. Hazırlık işleminde "loop" aygıt sürücüsüne ilişkin aygıt dosyası ve blok aygıt sürücüsü olarak gösterilecek dosya belirtilir. Bu işlemin sudo ile yapılması gerekmektedir. Örneğin: $ sudo losetup /dev/loop0 mydisk.dat Tabii bizim burada "mydisk.dat" isimli bir dosyaya sahip olmamız gerekir. İçi 0'larla dolu 100 MB'lik böyle bir dosyayı dd komutuyla aşağıdaki gibi oluşturabiliriz: $ dd if=/dev/zero of=mydisk.dat bs=512 count=100000 Burada artık "/dev/loop0" aygıt dosyası adeta bir disk gibi kullanılabilir hale gelmiştir. Biz bu "/dev/loop0" dosyasını kullandığımızda bu işlemlerden aslında "mydisk.dat" dosyası etkilenecektir. Sıfırdan bir diske ya da bir disk bölümüne bir dosya sistemi yerleştirebilmek için onun formatlanması gerekir. UNIX/Linux sistemlerinde formatlama için "mkfs.xxx" isimli programlar bulundurulmuştur. Örneğin aygıtta FAT dosya sistemi oluşturmak için "mkfs.fat" programı, ext4 dosya sistemi oluşturmak için "mkfs.ext4" programı kullanılmaktadır. Örneğin biz yukarıda oluşturmuş olduğumuz "/dev/loop" aygıtını ext2 dosya sistemi ile aşağıdaki gibi formatlayabiliriz: $ sudo mkfs.ext2 /dev/loop0 Burada işlemden aslında "mydisk.dat" dosyası etkilenmektedir. Artık formatladığımız aygıta ilişkin dosya sistemini aşağıdaki gibi mount edebiliriz: $ mkdir mydisk $ sudo mount /dev/loop0 mydisk Loop aygıtının dosya ile bağlantısını kesmek için "losetup" programı "-d" seçeneği ile çalıştırılır. Tabii önce aygıtın kullanımdan düşürülmesi gerekir: $ sudo umount mydisk $ sudo losetup -d /dev/loop0 Eğer loop aygıt sürücüsünün bir dosyayı onun belli bir offset'inden itibaren kullanmasını istiyorsak losetup programında "-o (ya da "--offset") seçeneğini kullanmalıyız. Örneğin bir disk imajının içerisindeki Linux dosya sisteminin disk imajının 8192'inci sektöründen başladığını varsayalım. "dev/loop0" aygıt sürücüsünün bu imaj dosyasını bu offset'ten itibaren kullanmasını şöyle sağlayabiliriz: $ sudo losetup -o 4194304 /dev/loop0 am335x-debian-11.7-iot-armhf-2023-09-02-4gb.img Buradaki "512 * 8192 = 4194304" olduğuna dikkat ediniz. Şimdi de yukarıda değindiğimiz FAT, ext dosya sistemlerini sırasıyla inceleyelim. Biz kursumuzda önce FAT dosya sisteminden bahsedeceğiz sonra UNIX/Linux sistemlerindeki i-node tabanlı EXT dosya sistemleri üzerinde duracağız. >> FAT Dosya Sistemi: FAT dosya sistemi Microsof tarafından DOS işletim sistemi için geliştirilmiştir. Ancak bu dosya sistemi hala kullanılmaktadır. FAT dosya sistemi o zamanlar teknolojisiyle tasarlanmıştır. Dolayısıyla modern dosya sistemlerinde bulunan bazı özellikler bu dosya sisteminde bulunmamaktadır. FAT dosya sistemi kendi aralarında FAT12, FAT16 ve FAT32 olmak üzere üç gruba ayrılmaktadır. Bu sistemlerin arasındaki en önemli fark dosya sistemi içerisindeki FAT (File Allocation Table) denilen tablodaki elemanların uzunluklarıdır; -> FAT12'de FAT elemanları ise 12 bit, -> FAT16'da 16 bit, -> FAT32'de 32 bittir. Microsoft, Windows sistemlerine geçtiğinde bu FAT sistemini biraz revize etmiştir. Buna da VFAT denilmektedir. Bir disk ya da disk bölümü (Disk Partition) FAT dosya sistemiyle formatlandığında disk bölümünde dört mantıksal bölüm oluşturulmaktadır: -> Boot Sektör -> FAT Bölümü -> Root Dir Bölümü -> Data Bölümü Bir dosya sisteminin içi boş bir biçimde kullanıma hazır hale getirilmesi sürecine formatlama denilmektedir. Formatlama sırasında ilgili disk ya da disk bölümünde ilgili dosya sistemi için meta data alanlar oluşturulmaktadır. Windows'ta ilgili disk ya da disk bölümünü FAT dosya sistemiyle formatlamak için "Bilgisayar Yönetimi / Disk Yönetimi" kısmından ilgili disk bölümü seçilir ve farenin sağ tuşuna basılarak formatlama yapılır. Benzer biçimde formatlama "Bilgisayarım (My Computer)" açılarak orada ilgili disk bölümünün üzerine sağa tıklanarak da yapılabilmektedir. Linux sistemlerinde bir blok aygıt sürücüsü ya da doğrudan bir dosya "mkfs.fat" programıyla formatlanabilir. Biz yukarıda da belirttiğimiz gibi bir dosyayı sanki disk gibi kullanacağız. Örneğin "dd" programıyla 50MB'lik içi sıfırlarla dolu bir dosya oluşturalım: $ dd if=/dev/zero of=mydisk.dat bs=512 count=100000 Burada 512 * 100000 byte'lık (yaklaşık 50 MB) içi sıfırlarla dolu bir dosya oluşturulmuştur. Bu dosyayı "/dev/loop0" blok aygıt sürücüsü biçiminde kullanılabilmesi şöyle sağlanabilir: $ sudo losetup /dev/loop0 mydisk.dat Şimdi artık "mkfs.fat" programı ile formatlamayı yapabiliriz. Yukarıda FAT'in FAT12, FAT16 ve FAT32 olmak üzere üç türünün olduğunu belirtmiştik. FAT türü "mkfs.fat" programında -F12, -F16 ya da -F32 seçenekleriyle belirtilmektedir. Örneğin biz blok aygıtımızı FAT16 biçiminde şöyle formatlayabiliriz: $ sudo mkfs.fat -F16 /dev/loop0 Aslında "mkfs.xxx" programları blok aygıt dosyası yerine normal bir dosya üzerinde de formatlama yapabilmektedir. Tabii biz kursumuzda bir blok aygıtı oluşturup onu mount edeceğiz. Şimdi biz FAT16 olarak formatladığımız "/dev/loop0" blok aygıtını mount edebiliriz. Tabii bunun için önce bir "mount dizininin (mount point)" oluşturulması gerekmektedir: $ mkdir fat16 $ sudo mount /dev/loop0 fat16 Artık fat16 dizini oluşturduğumuz FAT dosya sisteminin kök dizinidir. Ancak bu dosya sisteminin tüm bilgileri "mydisk.dat" dosyasında bulundurulacaktır. FAT dosya sistemiyle formatlanmış olan bir diskin ya da disk bölümünün ilk sektörüne "Boot Sector" denilmektedir. Dolayısıyla boot sektör ilgili diskin ya da disk bölümünün mantıksal 0 numaralı sektöründedir. Boot sektör isminden de anlaşılacağı gibi 512 byte uzunluğundadır. Bu sektörün iç organizasyonu şöyledir: Jmp Kodu | BPB (BIOS Parameter Block) | DOS Yükleyici Programı | 55 AA Boot sektörün hemen başında Intel Mimarisinde BPB bölümünü atlayarak DOS işletim sistemini yükleyen yükleyici program için bir jmp komutu bulunmaktadır. Bugün artık DOS işletim sistemi kullanılmadığı için buradaki jmp kodun ve yükleyici programın bir işlevi kalmamıştır. Ancak BPB alanı eskiden olduğu yerdedir ve dosya sistemi hakkında kritik bilgiler bu bölümde tutulmaktadır. Sektörün başındaki Jmp Code tipik olarak "EB 3C 90" makine komutundan oluşmaktadır. Bazı kaynaklar bu jmp kodu da BPB alanına dahil etmektedir. Eğer dosya sisteminde yüklenecek bir DOS işletim sistemi yoksa buradaki yükleyici program yerine format programı buraya ekrana mesaj çıkartan küçük program kodu yerleştirmektedir. Aşağıda "mkfs.fat" programı ile FAT16 biçiminde formatlanan FAT dosya sisteminin boot sektör içeriği görülmektedir: $ hexdump -C mydisk.dat -n 512 -v 00000000 eb 3c 90 6d 6b 66 73 2e 66 61 74 00 02 04 04 00 |.<.mkfs.fat.....| 00000010 02 00 02 00 00 f8 64 00 20 00 08 00 00 00 00 00 |......d. .......| 00000020 a0 86 01 00 80 01 29 fa 0b 93 c5 4e 4f 20 4e 41 |......)....NO NA| 00000030 4d 45 20 20 20 20 46 41 54 31 36 20 20 20 0e 1f |ME FAT16 ..| 00000040 be 5b 7c ac 22 c0 74 0b 56 b4 0e bb 07 00 cd 10 |.[|.".t.V.......| 00000050 5e eb f0 32 e4 cd 16 cd 19 eb fe 54 68 69 73 20 |^..2.......This | 00000060 69 73 20 6e 6f 74 20 61 20 62 6f 6f 74 61 62 6c |is not a bootabl| 00000070 65 20 64 69 73 6b 2e 20 20 50 6c 65 61 73 65 20 |e disk. Please | 00000080 69 6e 73 65 72 74 20 61 20 62 6f 6f 74 61 62 6c |insert a bootabl| 00000090 65 20 66 6c 6f 70 70 79 20 61 6e 64 0d 0a 70 72 |e floppy and..pr| 000000a0 65 73 73 20 61 6e 79 20 6b 65 79 20 74 6f 20 74 |ess any key to t| 000000b0 72 79 20 61 67 61 69 6e 20 2e 2e 2e 20 0d 0a 00 |ry again ... ...| 000000c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000000d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000000e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000000f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000130 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000140 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000180 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000190 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000001a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000001b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000001c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000001d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000001e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa |..............U.| Burada yükleyici programın DOS olmaması durumunda ekrana yazdırdığı mesaj görülmektedir. Tabii bu mesajın çıkması için bu diskin ya da disk bölümünün aktif disk ya da aktif disk bölümü olması gerekir. Yani bu diskten ya da disk bölümünde boot etme girişimi olmadıktan sonra bu mesaj görülmeyecektir. FAT dosya sisteminin en önemli meta data bilgileri boot sektörün hemen başındaki BPB (Bios Parameter Block) alanında tutulmaktadır. Bu bölümün bozulması durumunda dosya sistemine erişim mümkün olamamaktadır. Başka bir deyişle bu dosya sisteminin bozulmasını sağlamak için tek yapılacak şey bu BPB alanındaki byte'ları sıfırlamaktır. Tabii zamanla FAT dosya sistemindeki diğer bölümleri inceleyerek bozulmuş olan BPB alanını onaran yardımcı araçlar da çeşitli kişiler ve kurumlar tarafından geliştirilmiştir. Boot sektörün sonunda "55 AA" değeri bulunmaktadır. Bu bir sihirli sayı (magic number) olarak bulundurulmaktadır. Bazı programlar ve bazı boot loader'lar kontrolü boot sektöre bırakmadan önce bu sihirli sayıyı kontrol edebilmektedir. Böylece rastgele bozulmalarda bu sihirli sayı da bozulacağı için yetersiz olsa da basit bir kontrol mekanizması oluşturulabilmektedir. FAT dosya sisteminde en önemli kısım şüphesiz "BPB (BIOS Parameter Block)" denilen kısımdır. BPB hemen boot sektörün başındadır ve FAT dosya sisteminin diğer bölümleri hakkında kritik bilgiler içermektedir. Tabii BPB bölümü 1980'lerin anlayışıyla tasarlanmıştır. Bu tasarımda hatalar DOS'un çeşitli versiyonlarında geçmişe doğru uyumu koruyarak giderilmeye çalışılmıştır. Biz burada önce FAT12 ve FAT16 sistemlerinde kullanılan BPB bloğunun içeriğini tek tek ele alacağız. FAT32 ile birlikte BPB bloğuna eklemeler de yapılmıştır. FAT32 BPB formatını daha sonra ele alacağız. >>> FAT12 & FAT16 : Aşağıda FAT12 ve FAT16 sistemlerindeki BPB bloğunun formatı açıklanmaktadır. Tablodaki Offset sütunu Hex olarak ilgili alanın Boot sektörün başından itibaren kaçıncı byte'tan başladığını belirtmektedir. Offset (Hex) Uzunluk Anlamı 00 3 Byte Jmp Kodu 03 8 Byte OEM Yorum Alanı 0B WORD Sektördeki Byte Sayısı 0C BYTE Cluster'daki Sektör Sayısı 0E WORD Ayrılmış Sektörlerin Sayısı 10 BYTE FAT Kopyalarının Sayısı 11 WORD Kök Dizinlerindeki Girişlerin Sayısı 13 WORD Toplam Sektör Sayısı (Eski) 15 BYTE Ortam Belirleyicisi (Media Descriptor) 16 WORD FAT'in Bir Kopyasındaki Sektör Sayısı 18 WORD Bir Yüzdeki Sektör Dilimlerinin Sayısı (Artık Kullanılmıyor) 1A WORD Disk Yüzeylerinin (Kafalarının) Sayısı (Artık Kullanılmıyor) 1C DWORD Saklı Sektörlerin Sayısı 20 DWORD Yeni Toplam Sektör Sayısı 24 3 Byte Reserved 27 DWORD Volüm Seri Numarası 2B 11 Byte Volüm İsmi Bu tabloya göre, -> Jump Kodu: Yukarıda da belirttiğimiz gibi BPB bloğunu geçerek yükleyici programa atlayan makine komutlarından oluşmaktadır. Boot loader programlar akışı buradan boot sektöre devretmektedir. Dolayısıyla BPB alanının atlanması gerekmektedir. Burada bazen Intel short jump bazen de near jump komutları bulunur. Tipik içerik "EB 3C 90" biçimindedir. -> OEM Yorum Alanı: Formatlama programının kendine özgü yazdığı 8 byte'lık küçük yazıdır. Buraya eskiden DOS işletim sisteminin versiyon numarası yazılıyordu. Örneğin Windows bu BPB alanın yeni biçiminin tanındığı en eski sistem olan "MSDOS5.0" yazısını buraya yerleştirmektedir. Ancak buraya yerleştirilen yazı herhangi bir biçimde kullanılmamaktadır. -> Sektördeki Byte Sayısı: Bir sektörde kaç byte olduğu bilgisi burada tutulmaktadır. Tabii bu değer hemen her zaman 512'dir. Yani Little Endian formatta hex olarak burada "00 02" değerlerini görmemiz gerekir. -> Cluster'daki Sektör Sayısı: Dosyaların parçaları disk üzerinde ardışıl bir biçimde konumlandırılmak zorunda değildir. FAT dosya sisteminde bir dosyanın hangi parçasının diskte nerede konumlandırıldığı "FAT (File Allocation Table)" denilen bir bölümde saklanmaktadır. Eğer bir dosya çok fazla parçaya ayrılırsa hem disk üzerinde daha çok yayılmış olur hem de FAT bölümünde bu dosyanın parçalarının yerini tutmak için gereken alan büyür. Bu nedenle dosyaların parçaları sektörlere değil, cluster denilen birimlere bölünmüştür. Bir cluster ardışıl n tane sektörün oluşturduğu topluluktur. Örneğin bir cluster'ın 4 sektör olması demek 4 sektörden oluşması (yani 2K) demektir. Şimdi elimizde 10,000 byte uzunluğunda bir dosya olsun. Bir cluster'ın 1 sektör olduğunu düşünelim. Bu durumda bu 10,000 byte'lık dosya toplamda 10000 / 512 = 19.53125 yani 20 cluster yer kaplayacaktır. FAT bölümünde bu 20 cluster 20 elemanlık yer kaplayacaktır. Şimdi bir cluster'ın 4 sektörden oluştuğunu düşünelim. Bu durumda 10,000 byte'lık dosya 10000 / 2048 = 4.8828125 yani 5 cluster yer kaplayacaktır. Bu dosyanın yerini tutmak için FAT bölümünde 5 eleman yeterli olacaktır. Görüldüğü gibi cluster bir dosyanın bir parçasını tutabilen en düşük tahsisat birimidir. Halbuki sektör diskten transfer edilecek en küçük birimdir. Sektör yerine dosya sisteminin cluster kavramını kullanmasının iki nedeni vardır. Birincisi cluster ardışıl sektörlerden oluştuğu için dosyanın parçaları diskte daha az yayılmış olur. İkincisi de dosyanın parçalarının yerlerini tutmak için daha az alan gerekmektedir. Pekiyi bir cluster kaç sektörden oluşmalıdır? Eğer bir cluster çok fazla sayıda sektörden oluşursa dosyanın son parçasında kullanılmayan alan (buna "içsel bölünme (internal fragmentation)" da denilmektedir) fazlalaşır diskin kullanım kapasitesi azalmaya başlar. Örneğin bir cluster'ın 32 sektörden (16K) oluştuğunu varsayalım. Bu durumda 1 byte'lık bir dosya bile 16K yer kaplayacaktır. Çünkü dosya sisteminin minimum tahsisat birimi 16K'dır. Örneğin bir sistemde 100 tane 1 byte'lık dosyanın diskte kapladığı alanla 1 tane 100 byte'lık dosyanın diskte kapladığı alan kıyaslandığında 100 tane 1 byte'lık dosyanın diskte çok daha fazla yer kapladığı görülecektir. İşte UNIX/Linux sistemlerinde dosyaları tek bir dosyada peşisıra birleştiren ve bunların yerlerini dosyanın başındaki bir başlık kısmında tutan "tar" isimli bir yardımcı program bulunmaktadır. "tar" programının bir sıkıştırma yapmadığına diskteki kaplanan alanı azaltmak için yalnızca dosyaları uç uca eklediğine dikkat ediniz. Tabii genellikle dosyalar tar'landıktan sonra ayrıca sıkıştırılabilir. Bu sistemlerdeki "tar.gz" gibi dosya uzantıları tar'landıktan sonra zip'lenmiş olan dosyaları belirtmektedir. Pekiyi o halde bir cluster'ın kaç sektör olacağına nasıl karar verilmektedir? İşte sezgisel olarak disk hacmi büyüdükçe kaybedilen alanların önemi azalacağı için cluster'ın çok sektörden oluşturulması, disk hacmi azaldıkça az sektörden oluşturulması yoluna gidilmektedir. Format programları bu değerin kullanıcı tarafından belirlenmesine olanak sağlamakla birlikte default değer de önermektedir. Linux'taki "mkfs.fat" programında ise cluster boyutu "-s" seçeneği ile belirlenmektedir. Örneğin: $ sudo mkfs.fat -F16 -s 2 /dev/loop0 Burada bir cluster 2 sektörden oluşturulmuştur. İşte BPB bloğunun "0C" offset'inde bir cluster'ın kaç sektörden oluştuğu bilgisi yer almaktadır. İşletim sistemi dosyaların parçalarına erişirken hep bu bilgiyi kullanmaktadır. (Burada değeri disk editörü ile değiştirsek dosya sistemi tamamen saçmalayacaktır.) Yukarıdaki örnek boot sektörde bir cluster 4 sektörden (yani 4K = 2048 byte'tan) oluşmaktadır. -> Ayrılmış Sektörlerin Sayısı: Burada boot sektörü izleyen FAT bölümünün kaçıncı sektörden başladığı bilgisi yer almaktadır. Tabii buradaki orijin FAT disk bölümünün başıdır. Yani boot sektör 0'ıncı sektörde olmak üzere FAT bölümünün kaçıncı sektörden başladığını belirtmektedir. Pekiyi neden boot sektör ile FAT arasında boşluk bırakmak gerekebilir? İşte hard disklerde işletim sistemi FAT bölümünü ilk silindire hizalamak isteyebilir. Eğer özel uygulamalarda boot sektör yükleyici programı uzunsa yükleyicinin diğer parçaları da burada bulunabilmektedir. Yukarıdaki örnek FAT bölümünün boot sektöründe bu byte'lar "04 00" biçimindedir. Little Endian formatta bu değer 4'tür. O halde bu dosya sisteminde FAT bölümü 4'üncü sektörden başlamaktadır. -> FAT Kopyalarının Sayısı: FAT bölümü izleyen paragraflarda da görüleceği gibi FAT dosya sisteminin önemli bir meta-data alanıdır. Bu nedenle bu bölümün backup amaçlı birden fazla kopyasının bulundurulması uygun görülmüştür. Tipik olarak bu alanda 2 değeri bulunur. Yani FAT bölümünün toplamda iki kopyası vardır. FAT bölümünün kopyaları hemen birbirinin peşi sıra dizilmiştir. Yani bir kopyanın bittiği yerde diğeri başlamaktadır. -> Kök Dizinlerindeki Girişlerin Sayısı: FAT dosya sistemindeki bölümlerin dizilimin şöyle olduğunu belirtmiştik: Boot Sektör FAT ve Kopyaları Root Dir Bölümü Data Bölümü İşletim sisteminin tüm bölümlerin hangi sektörden başladığını ve kaç sektör uzunlukta olduğunu bilmesi gerekir. İşte "Root Dir" bölümü dizin girişlerinden oluşmaktadır. Bir dizin girişi 32 byte uzunluğundadır. Burada toplam kaç giriş olduğu belirtilmektedir. Dolayısıyla "Root Dir" bölümünün sektör uzunluğu buradaki sayının 32'ye bölümü ile hesaplanır. Bizim oluşturduğumuz örnek FAT16 disk bölümünde burada "0x0200" (512) değeri bulunmaktadır. Bu durumda Root Dir bölümünün sektör uzunluğu 512 / 32 = 16'dır. -> Toplam Sektör Sayısı (Eski): Bu alanda disk bölümündeki toplam sektör sayısı bulundurulmaktadır. Ancak BPB formatının tasarlandığı 1980'lerin başında henüz hard diskler çok yeniydi ve teknolojinin bu kadar hızlı gelişeceği düşünülmemişti. Dolayısıyla toplam sektör sayısı için 2 byte'lık yer o zamanlar için yeterli gibiydi. Toplam sektör sayısı için ayrılan 2 byte'lık yerde yazılabilecek maksimum değer 65535'tir. Bu değeri 512 ile çarparsak 33MB'lık bir alan söz konusu olur. Gerçekten de o devirlerde diskler 33MB'den daha yukarıda formatlanamıyordu. DOS 4.01'e kadar 33MB bir üst sınırdı. Ancak DOS 4.01 ile birlikte bu toplam sektör sayısı geçmişe doğru uyum korunarak 4 byte yükseltildi. Dolayısıyla DOS 4.01 ve sonrasında artık disk bölümünün toplam kapasitesi 2^32 * 2^9 = 2TB'ye yükselmiş oldu. 4 byte'tan oluşan yeni toplam sektör sayısı alanı boot sektörün "0x20" offset'inde bulunmaktadır. Dosya sistemleri toplam sektör sayısı için önce "0x13" offset'inde bulunan bu alana başvurmaktadır. Eğer bu alanda 0 yoksa bu alandaki bilgiyi, eğer bu alanda 0 varsa "0x20" offset'inden çekilen DWORD bilgiyi dikkate almaktadır. -> Ortam Belirleyicisi (Media Descriptor): Bu alanda dosya sisteminin konuşlandığı medyanın türünün ne olduğu bilgisi bulunmaktadır. Aslında artık böyle bir bilgi işletim sistemleri tarafından kullanılmamaktadır. Buradaki 1 byte'ın yaygın değerleri şunlardır: 0xF0: 1.44 Floppy Disk 0xF8: Hard disk Bu alanda artık hep F8 byte'ı bulunmaktadır. -> FAT'in Bir Kopyasındaki Sektör Sayısı: Bu alanda FAT'in bir kopyasının kaç sektör uzunluğunda olduğu bilgisi bulunmaktadır. FAT'in default olarak 2 kopyasının olduğunu anımsayınız. -> Bir Yüzdeki Sektör Dilimlerinin Sayısı (Artık Kullanılmıyor): Bu alanda diskin bir yüzeyinde kaç sektör dilimi olduğu bilgisi yer almaktadır. Eskiden sektörlerin koordinatları "yüzey numarası, track numarası ve sektör dilimi numarası" ile belirtiliyordu. Uzunca bir süredir artık bu sistem terk edilmiştir. Dolayısıyla bu alana başvurulmamaktadır. -> Disk Yüzeylerinin (Kafalarının) Sayısı (Artık Kullanılmıyor): Burada diskte toplam kaç yüzey (kafa) olduğu bilgisi yer alıyordu. Ancak yine koordinat sistemi uzunca bir süre önce değiştirildiği için bu alan artık kullanılmamaktadır. -> Saklı Sektörlerin Sayısı: Bu alanda FAT dosya sisteminin diskin toplamda kaçıncı sektöründen başladığı bilgisi yer almaktadır. Bu bilgi aynı zamanda Disk Bölümleme Tablosu (Disk Partition Table) içerisinde de yer almaktadır. İşletim sistemleri bu iki değeri karşılaştırıp BPB bloğunun bozuk olup olmadığı konusunda bir karar da verebilmektedir. -> Yeni Toplam Sektör Sayısı: "0x13" offset'indeki WORD olarak bulundurulan eski "toplam sektör sayısı" bilgisinin DWORD olarak yenilenmiş biçimi bu alanda tutulmaktadır. -> Volüm Seri Numarası: Bir disk bölümü FAT dosya sistemi ile formatlandığında oraya rastgele üretilmiş olan bir "volüm seri numarası" atanmaktadır. Bu volüm seri numarası eskiden floppy disket zamanlarında disketin değişip değişmediğini anlamak için kullanılıyordu. Bugünlerde artık bu alan herhangi bir amaçla kullanılmamaktadır. Ancak sistem programcısı bu seri numarasından başka amaçlar için faydalanabilir. -> Volüm İsmi: Her volüm formatlanırken ona bir isim verilmektedir. Bu isim o zamanki dosya isimlendirme kuralı gereği 8 + 3 = 11 karakterden oluşmaktadır. FAT dosya sistemine ilişkin bir uygulama yazabilmek için yapılacak ilk şey boot sektörü okuyup buradaki BPB bilgilerini bir yapı nesnesinin içerisine yerleştirmektir. Bu bilgilerden hareketle bizim FAT dosya sistemine ilişkin meta data alanlarının ilgili disk bölümünün kaçıncı sektöründen başlayıp kaç sektör uzunluğunda olduğunu elde etmemiz gerekir. Çünkü dosya sistemi ile ilgili işlemlerin hepsinde bu bilgilere gereksinim duyulacaktır. Bu bilgilerin yerleştirileceği yapı şöyle olabilir: typedef struct tagBPB { uint16_t fatlen; /* Number of sectors in FAT (A) */ uint16_t rootlen; /* Number of sectors in ROOT (NA) */ uint16_t nfats; /* Number of copies of FAT (A) */ uint32_t tsects; /* Total sector (A) */ uint16_t bps; /* Byte per sector(A) */ uint16_t spc; /* Sector per cluster(A) */ uint16_t rsects; /* Reserved sectors(A) */ uint8_t mdes; /* Media descriptor byte(A) */ uint16_t spt; /* Sector per track(A) */ uint16_t rootents; /* Root entry (A) */ uint16_t nheads; /* Number of heads (A) */ uint16_t hsects; /* Number of hidden sector( A) */ uint16_t tph; /* Track per head (NA) */ uint16_t fatloc; /* FAT directory location (NA) */ uint16_t rootloc; /* Root directory location (NA) */ uint16_t dataloc; /* First data sector location (NA) */ uint32_t datalen; /* Number of sectors in Data (NA) */ uint32_t serial; /* Volume Serial Number (A) */ char vname[12]; /* Volume Name (A) */ } BPB; Burada (A) ile belirtilen elemanlar zaten BPB içerisinde olan (available) elemanlardır. NA (not available) ile belirtilen elemanlar BPB içerisinde yoktur. Dört işlemle hesaplanarak değeri oluşturulacaktır. Linux'ta boot sektör'ü okuyarak oradaki BPB bilgilerini yukarıdaki gibi bir yapıya yerleştiren örnek bir program aşağıda verilmiştir. Derlemeyi şöyle yapabilirsiniz: $ gcc -o app fatsys.c app.c Programı FAT dosya sistemine ilişkin blok aygıt dosyasının yol ifadesini vererek sudo ile çalıştırabilirsiniz. Örneğin: $ sudo ./app /dev/loop0 Aşağıdakine benzer bir çıktı elde edilecektir: Byte per sector: 512 Sector per cluster: 4 Number of reserved sectors: 4 Number of FAT copies: 100 Number of sectors in Root Dir: 32 Number of FAT copies: 2 Number of sectors in volume: 100000 Media Descriptor: F8 Number of Root Dir entries: 200 Number of hidden sectors: 0 FAT location: 4 Root Dir location: 204 Data location: 236 Volume Serial Number: BC7B-4578 Volume Name: FAT16 Aşağıda bu konuya ilişkin bir örnek verilmiştir: * Örnek 1, Biz aşağıdaki örnekte FAT dosya sistemine ilişkin tüm önemli alanların yerlerine ve uzunluklarına ilişkin bilgileri elde ederek bir yapıya yerleştirdik. Artık şu bilgilere sahibiz: -> FAT bölümünün yeri ve uzunluğu (yapının fatloc ve fatlen elemanları) -> Root DIR bölümünün yeri ve uzunluğu (yapının rootloc ve rootlen elemanları) -> Data bölümünün yeri ve uzunluğu (yapının dataloc ve datalen elemanları) Programın kodları: /* fatsys.h */ #ifndef FATSYS_H_ #define FATSYS_H_ #include #define FILE_INFO_LENGTH 32 /* Type Declarations */ typedef struct tagBPB { uint16_t fatlen; /* Number of sectors in FAT (A) */ uint16_t rootlen; /* Number of sectors in ROOT (NA) */ uint16_t nfats; /* Number of copies of FAT (A) */ uint32_t tsects; /* Total sector (A) */ uint16_t bps; /* Byte per sector(A) */ uint16_t spc; /* Sector per cluster(A) */ uint16_t rsects; /* Reserved sectors(A) */ uint8_t mdes; /* Media descriptor byte(A) */ uint16_t spt; /* Sector per track(A) */ uint16_t rootents; /* Root entry (A) */ uint16_t nheads; /* Number of heads (A) */ uint16_t hsects; /* Number of hidden sector( A) */ uint16_t tph; /* Track per head (NA) */ uint16_t fatloc; /* FAT directory location (NA) */ uint16_t rootloc; /* Root directory location (NA) */ uint16_t dataloc; /* First data sector location (NA) */ uint32_t datalen; /* Number of sectors in Data (NA) */ uint32_t serial; /* Volume Serial Number (A) */ char vname[12]; /* Volume Name (A) */ } BPB; /* Function prototypes */ int read_bpb(int fd, BPB *bpb); #endif /* fatsys.c */ #include #include #include #include #include #include "fatsys.h" int read_bpb(int fd, BPB *bpb) { uint8_t bsec[512]; if (read(fd, bsec, 512) == -1) return -1; bpb->bps = *(uint16_t *)(bsec + 0x0B); bpb->spc = *(uint8_t *)(bsec + 0x0D); bpb->rsects = *(uint16_t *)(bsec + 0x0E); bpb->fatlen = *(uint16_t *)(bsec + 0x16); bpb->rootlen = *(uint16_t *)(bsec + 0x11) * FILE_INFO_LENGTH / bpb->bps; bpb->nfats = *(uint8_t *)(bsec + 0x10); if (*(uint16_t *)(bsec + 0x13)) bpb->tsects = *(uint16_t *)(bsec + 0x13); else bpb->tsects = *(uint32_t *)(bsec + 0x20); bpb->mdes = *(bsec + 0x15); bpb->spt = *(uint16_t *)(bsec + 0x18); bpb->rootents = *(uint16_t *)(bsec + 0x11); bpb->nheads = *(uint16_t *)(bsec + 0x1A); bpb->hsects = *(uint16_t *)(bsec + 0x1C); bpb->tph = (uint16_t)(bpb->tsects / bpb->spt / bpb->nheads); bpb->fatloc = bpb->rsects; bpb->rootloc = bpb->rsects + bpb->fatlen *bpb->nfats; bpb->dataloc = bpb->rootloc + bpb->rootlen; bpb->datalen = bpb->tsects - bpb->dataloc; bpb->serial = *(uint32_t *)(bsec + 0x27); memcpy(bpb->vname, bsec + 0x2B, 11); bpb->vname[11] = '\0'; return 0; } /* app.c */ #include #include #include #include #include "fatsys.h" void exit_sys(const char *msg); int main(int argc, char *argv[]) { BPB bpb; int fd; if (argc != 2) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } if ((fd = open(argv[1], O_RDWR)) == -1) return -1; if (read_bpb(fd, &bpb) == -1) exit_sys("read_bpb"); printf("Byte per sector: %d\n", bpb.bps); printf("Sector per cluster: %d\n", bpb.spc); printf("Number of reserved sectors: %d\n", bpb.rsects); printf("Number of FAT copies: %d\n", bpb.fatlen); printf("Number of sectors in Root Dir: %d\n", bpb.rootlen); printf("Number of FAT copies: %d\n", bpb.nfats); printf("Number of sectors in volume: %u\n", bpb.tsects); printf("Media Descriptor: %02X\n", bpb.mdes); printf("Number of Root Dir entries: %02X\n", bpb.rootents); printf("Number of hidden sectors: %d\n", bpb.hsects); printf("FAT location: %d\n", bpb.fatloc); printf("Root Dir location: %d\n", bpb.rootloc); printf("Data location: %d\n", bpb.dataloc); printf("Number of sectors in Data: %d\n", bpb.datalen); printf("Volume Serial Number: %04X-%04X\n", bpb.serial >> 16, 0xFFFF & bpb.serial); printf("Volume Name: %s\n", bpb.vname); close(fd); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } Şimdi yukarıdaki yapıp biraz daha geliştirelim. Bunun için dosya sistemini temsil eden aşağıdaki gibi bir yapı oluşturabiliriz: typedef struct tagFATSYS { int fd; /* Volume file descriptor */ BPB bpb; /* BPB info */ uint32_t fatoff; /* Offset of FAT */ uint32_t rootoff; /* Offset of root directory */ uint32_t dataoff; /* Offset of DATA */ uint32_t clulen; /* Cluster length as bytes */ /* ... */ } FATSYS; Dosya işlemi yaparken dosya sisteminin belirli bölümlerine konumlandırma yapacağımız için onların offset'lerini de FATSYS yapısının içerisine yerleştireceğiz. Dosya sistemini açan ve kapatan aşağıdaki fonksiyonlar oluşturabiliriz: FATSYS *open_fatsys(const char *path) { FATSYS *fatsys; int fd; if ((fd = open(path, O_RDWR)) == -1) return NULL; if ((fatsys = (FATSYS *)malloc(sizeof(FATSYS))) == NULL) return NULL; if (read_bpb(fd, &fatsys->bpb) == -1) { free(fatsys); return NULL; } fatsys->fd = fd; fatsys->fatoff = fatsys->bpb.fatloc * fatsys->bpb.bps; fatsys->rootoff = fatsys->bpb.rootloc * fatsys->bpb.bps; fatsys->dataoff = fatsys->bpb.dataloc * fatsys->bpb.bps; fatsys->clulen = fatsys->bpb.bps * fatsys->bpb.spc; return fatsys; } int close_fatsys(FATSYS *fatsys) { if (close(fatsys->fd) == -1) return -1; free(fatsys); return 0; } Kullanım şöyle olabilir: FATSYS *fatsys; if ((fatsys = open_fatsys("/dev/loop0")) == NULL) exit_sys("open_fatsys"); close_fatsys(fatsys); Aşağıda bu değişliklerin yapıldığı kodlar verilmiştir. * Örnek 1, /* fatsys.h */ #ifndef FATSYS_H_ #define FATSYS_H_ #include #define FILE_INFO_LENGTH 32 /* Type Declarations */ typedef struct tagBPB { uint16_t fatlen; /* Number of sectors in FAT (A) */ uint16_t rootlen; /* Number of sectors in ROOT (NA) */ uint16_t nfats; /* Number of copies of FAT (A) */ uint32_t tsects; /* Total sector (A) */ uint16_t bps; /* Byte per sector(A) */ uint16_t spc; /* Sector per cluster(A) */ uint16_t rsects; /* Reserved sectors(A) */ uint8_t mdes; /* Media descriptor byte(A) */ uint16_t spt; /* Sector per track(A) */ uint16_t rootents; /* Root entry (A) */ uint16_t nheads; /* Number of heads (A) */ uint16_t hsects; /* Number of hidden sector( A) */ uint16_t tph; /* Track per head (NA) */ uint16_t fatloc; /* FAT directory location (NA) */ uint16_t rootloc; /* Root directory location (NA) */ uint16_t dataloc; /* First data sector location (NA) */ uint32_t datalen; /* Number of sectors in Data (NA) */ uint32_t serial; /* Volume Serial Number (A) */ char vname[12]; /* Volume Name (A) */ } BPB; typedef struct tagFATSYS { int fd; /* Volume file descriptor */ BPB bpb; /* BPB info */ uint32_t fatoff; /* Offset of FAT */ uint32_t rootoff; /* Offset of root directory */ uint32_t dataoff; /* Offset of DATA */ uint32_t clulen; /* Cluster length as bytes */ /* ... */ } FATSYS; /* Function prototypes */ int read_bpb(int fd, BPB *bpb); FATSYS *open_fatsys(const char *path); int close_fatsys(FATSYS *fatsys); int read_cluster(FATSYS *fatsys, uint32_t clu, void *buf); int wite_cluster(FATSYS *fatsys, uint32_t clu, const void *buf); #endif /* fatsys.c */ FATSYS *open_fatsys(const char *path) { FATSYS *fatsys; int fd; if ((fd = open(path, O_RDWR)) == -1) return NULL; if ((fatsys = (FATSYS *)malloc(sizeof(FATSYS))) == NULL) return NULL; if (read_bpb(fd, &fatsys->bpb) == -1) { free(fatsys); return NULL; } fatsys->fd = fd; fatsys->fatoff = fatsys->bpb.fatloc * fatsys->bpb.bps; fatsys->rootoff = fatsys->bpb.rootloc * fatsys->bpb.bps; fatsys->dataoff = fatsys->bpb.dataloc * fatsys->bpb.bps; fatsys->clulen = fatsys->bpb.bps * fatsys->bpb.spc; return fatsys; } int close_fatsys(FATSYS *fatsys) { if (close(fatsys->fd) == -1) return -1; free(fatsys); return 0; } /* app.c */ #include #include #include #include #include "fatsys.h" void exit_sys(const char *msg); int main(int argc, char *argv[]) { FATSYS *fatsys; if ((fatsys = open_fatsys("/dev/loop0")) == NULL) exit_sys("open_fatsys"); close_fatsys(fatsys); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } FAT dosya sisteminde dosya sistemindeki "Data Bölümü" dosya içeriklerinin tutulduğu bölümdür. İşletim sistemi bu bölümün sektörlerden değil cluster'lardan oluştuğunu varsaymaktadır. Anımsanacağı gibi "cluster" bir dosyanın parçası olabilecek en küçük tahsisat birimidir ve ardışıl n sektörden olulmaktadır. Buradaki n değeri 2'nin bir kuvvetidir (yani 1, 2, 4, 8, ... biçiminde). İşte volümün Data bölümündeki her cluster'a 2'den başlanarak ki 0 ve 1 reserved bırakılmıştır, bir cluster numarası karşı getirilmiştir. Örneğin bir cluster'ın 4 sektörden oluştuğunu düşünelim. Bu durumda Data bölümünün ilk 4 sektörü 2 numaralı cluster, sonraki 4 sektörü 3 numaralı cluster, sonraki 4 sektörü 4 numaralı cluster biçiminde numaralanmaktadır. Bizim FAT dosya sistemi üzerinde ilk yapmaya çalışacağımız alt seviye işlemlerden biri belli bir numaralı cluster'ı okuyup yazan fonksiyonları gerçekleştirmektir. Bu fonksiyonların prototipleri şöyle olabilir: int read_cluster(FATSYS *fatsys, uint32_t clu, void *buf); int wite_cluster(FATSYS *fatsys, uint32_t clu, const void *buf); Data bölümünün ilk cluster'ının 2 numaralı cluster olduğunu 0, 1 cluster'larının kullanılmadığını anımsayınız. Bu fonksiyonlar basit biçimde şöyle yazılabilir: int read_cluster(FATSYS *fatsys, uint32_t clu, void *buf) { if (lseek(fatsys->fd, fatsys->dataoff + (clu - 2) * fatsys->clulen, SEEK_SET) == -1) return -1; return read(fatsys->fd, buf, fatsys->clulen); } int write_cluster(FATSYS *fatsys, uint32_t clu, const void *buf) { if (lseek(fatsys->fd, fatsys->dataoff + (clu - 2) * fatsys->clulen, SEEK_SET) == -1) return -1; return write(fatsys->fd, buf, fatsys->clulen); } Aşağıda fonksiyonun kullanımına ilişkin bir örnek verilmiştir. * Örnek 1, /* fstsys.h */ #ifndef FATSYS_H_ #define FATSYS_H_ #include #define FILE_INFO_LENGTH 32 /* Type Declarations */ typedef struct tagBPB { uint16_t fatlen; /* Number of sectors in FAT (A) */ uint16_t rootlen; /* Number of sectors in ROOT (NA) */ uint16_t nfats; /* Number of copies of FAT (A) */ uint32_t tsects; /* Total sector (A) */ uint16_t bps; /* Byte per sector(A) */ uint16_t spc; /* Sector per cluster(A) */ uint16_t rsects; /* Reserved sectors(A) */ uint8_t mdes; /* Media descriptor byte(A) */ uint16_t spt; /* Sector per track(A) */ uint16_t rootents; /* Root entry (A) */ uint16_t nheads; /* Number of heads (A) */ uint16_t hsects; /* Number of hidden sector( A) */ uint16_t tph; /* Track per head (NA) */ uint16_t fatloc; /* FAT directory location (NA) */ uint16_t rootloc; /* Root directory location (NA) */ uint16_t dataloc; /* First data sector location (NA) */ uint32_t datalen; /* Number of sectors in Data (NA) */ uint32_t serial; /* Volume Serial Number (A) */ char vname[12]; /* Volume Name (A) */ } BPB; typedef struct tagFATSYS { int fd; /* Volume file descriptor */ BPB bpb; /* BPB info */ uint32_t fatoff; /* Offset of FAT */ uint32_t rootoff; /* Offset of root directory */ uint32_t dataoff; /* Offset of DATA */ uint32_t clulen; /* Cluster length as bytes */ /* ... */ } FATSYS; /* Function prototypes */ int read_bpb(int fd, BPB *bpb); FATSYS *open_fatsys(const char *path); int close_fatsys(FATSYS *fatsys); int read_cluster(FATSYS *fatsys, uint32_t clu, void *buf); int wite_cluster(FATSYS *fatsys, uint32_t clu, const void *buf); #endif /* fatsys.c */ #include #include #include #include #include #include "fatsys.h" int read_bpb(int fd, BPB *bpb) { uint8_t bsec[512]; if (read(fd, bsec, 512) == -1) return -1; bpb->bps = *(uint16_t *)(bsec + 0x0B); bpb->spc = *(uint8_t *)(bsec + 0x0D); bpb->rsects = *(uint16_t *)(bsec + 0x0E); bpb->fatlen = *(uint16_t *)(bsec + 0x16); bpb->rootlen = *(uint16_t *)(bsec + 0x11) * FILE_INFO_LENGTH / bpb->bps; bpb->nfats = *(uint8_t *)(bsec + 0x10); if (*(uint16_t *)(bsec + 0x13)) bpb->tsects = *(uint16_t *)(bsec + 0x13); else bpb->tsects = *(uint32_t *)(bsec + 0x20); bpb->mdes = *(bsec + 0x15); bpb->spt = *(uint16_t *)(bsec + 0x18); bpb->rootents = *(uint16_t *)(bsec + 0x11); bpb->nheads = *(uint16_t *)(bsec + 0x1A); bpb->hsects = *(uint16_t *)(bsec + 0x1C); bpb->tph = (uint16_t)(bpb->tsects / bpb->spt / bpb->nheads); bpb->fatloc = bpb->rsects; bpb->rootloc = bpb->rsects + bpb->fatlen *bpb->nfats; bpb->dataloc = bpb->rootloc + bpb->rootlen; bpb->datalen = bpb->tsects - bpb->dataloc; bpb->serial = *(uint32_t *)(bsec + 0x27); memcpy(bpb->vname, bsec + 0x2B, 11); bpb->vname[11] = '\0'; return 0; } FATSYS *open_fatsys(const char *path) { FATSYS *fatsys; int fd; if ((fatsys = (FATSYS *)malloc(sizeof(FATSYS))) == NULL) return NULL; if ((fd = open(path, O_RDWR)) == -1) return NULL; if (read_bpb(fd, &fatsys->bpb) == -1) { close(fd); free(fatsys); return NULL; } fatsys->fd = fd; fatsys->fatoff = fatsys->bpb.fatloc * fatsys->bpb.bps; fatsys->rootoff = fatsys->bpb.rootloc * fatsys->bpb.bps; fatsys->dataoff = fatsys->bpb.dataloc * fatsys->bpb.bps; fatsys->clulen = fatsys->bpb.bps * fatsys->bpb.spc; return fatsys; } int close_fatsys(FATSYS *fatsys) { if (close(fatsys->fd) == -1) return -1; free(fatsys); return 0; } int read_cluster(FATSYS *fatsys, uint32_t clu, void *buf) { if (lseek(fatsys->fd, fatsys->dataoff + (clu - 2) * fatsys->clulen, SEEK_SET) == -1) return -1; return read(fatsys->fd, buf, fatsys->clulen); } int write_cluster(FATSYS *fatsys, uint32_t clu, const void *buf) { if (lseek(fatsys->fd, fatsys->dataoff + (clu - 2) * fatsys->clulen, SEEK_SET) == -1) return -1; return write(fatsys->fd, buf, fatsys->clulen); } /* app.c */ #include #include #include #include #include "fatsys.h" void exit_sys(const char *msg); int main(int argc, char *argv[]) { FATSYS *fatsys; unsigned char buf[8192]; if (argc != 2) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } if ((fatsys = open_fatsys(argv[1])) == NULL) exit_sys("open_fatsys"); if (read_cluster(fatsys, 2, buf) == -1) exit_sys("read_cluster"); for (int i = 0; i < fatsys->clulen; ++i) printf("%02X%c", buf[i], i % 16 == 15 ? '\n' : ' '); close_fatsys(fatsys); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } FAT dosya sisteminde her dosya cluster'lara bölünerek Data bölümündeki cluster'larda tutulmaktadır. Dosyanın parçaları ardışıl cluster'larda olmak zorunda değildir. Örneğin bir cluster'ın 4 sektör olduğu bir volümde 10000 byte uzunluğunda bir dosya söz konusu olsun. Bir cluster'ın bıyutu 4 * 512 = 2048 byte'tır. O halde bu dosya 5 cluster yer kaplayacaktır. Ancak son cluster'da kullanılmayan bir miktar boş alan da kalacaktır. İşte örneğin bu dosyanın cluster numaraları aşağıdaki gibi olabilir: 2 8 14 15 21 Görüldüğü gibi dosyanın parçaları ardışıl cluster'larda olmak zorunda değildir. Tabi işletim sistemi genellikle dosyanın parçalarını mümkün olduğu kadar ardışıl cluster'larda saklama çalışır. Ancak bu durum mümkün olmayabilir. Belli bir süre sonra artık dosyaların parçalarını birbirinden uzaklaşmaya başlayabilir. İşte FAT dosya sisteminde hangi dosyanın hangi parçalarının Data bölümünün hangi cluster'larında olduğunun saklandığı meta data alana "FAT (File Allocation Table)" denilmektedir. FAT bölümü FAT elemanlarından oluşur. FAT'lar 12 bit, 16 bit, ve 32 bit olmak üzere üçe ayrılmaktadır. 12 bit FAT'lerde FAT elemanları 12 bit, 16 bit FAT'lerde FAT elemanları 16 bit ve 32 bit FAT'lerde FAT elemanları 32 bit uzunluğundadır. İlk iki cluster kullanılmadığı için FAT'in ilk elemanı da kullanılmaktadır. FAT bağlı listelerden oluşan bir meta data alanıdır. Her dosyanın ilk cluster'ının nerede olduğu dizin girişinde tutulmaktadır. Sonra her FAT elemanı dosyanın ğparçasının hangi cluster'da olduğu bilgisini tıtar. Volümde toplan N tane cluster varsa FAT bölümünde de toplam N tane FAT elemanı vardır. FAT bölümünde her bir dosya için ayrı bir bağlı liste bulunmaktadır. Bir dosyanın ilk cluster'ı biliniyorsa sonraki tüm cluster'ları bu bağlı liste izlenerek elde edilebilmektedir. Bağlı listenin organizasyonu şu biçimdedir: Dosyanın ilk cluster'ının yerinin 8 olduğunu varsayalım. Şimdi FAT'in 8'inci elemanına gidildiğinde orada 14 yazıyor olsun. 14 numaralı elemanına gittiğimizde orada 18 yazdığını düşünelim. 18 elemana gittiğimizde orada 22 yadığını düşünelim. Nihayet 22 numaralı elemana gittiğimizde orada FFFF biçiminde özel bir değerin yazdığını varsayalım. Bu durumu şekilsel olarak şöyle gösterebiliriz: 8 ----> 14 ----> 18 ----> 22 (FFFF) Bu durumda bu dosyanın cluster'ları sırasıyla 8 14 18 22 numaralı cluster'lardır. Burada FFFF değeri EOF anlamına özel bir cluster numarasıdır. Yani FAT'teki her FAT elemanı dosyanın sonraki parçasının hangi cluster'da olduğunu belirtmektedir. Böylece işletim sistemi dosyanın ilk cluster numarasını biliyorsa bu zinciri takip ederek onun bütün cluster'larını elde edebilir. Örneğin 1 cluster'ın 4 sektör olduğu bir FAT16 sisteminde 19459 byte'lık bir dosya toplam 10 cluster yer kaplamaktadır. Biz bu dosyanın ilk cluster numarasının 4 olduğunu biliyoruz. Aşağıdaki örnek FAT bölümünde bu dosyanın tüm cluster'larının numaraları bağlı liste izlenerek elde edilebilecektir: 00000800 f8 ff ff ff 00 00 ff ff 05 00 06 00 07 00 08 00 |................| 00000810 09 00 0a 00 0b 00 0c 00 0d 00 ff ff 00 00 00 00 |................| 00000820 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000830 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000840 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000850 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| ... Bu byte'lar bir FAT16 sisteminin FAT bölümüne ilişkin olduğuna göre her bir FAT elemanı 2 byte yer kaplayacaktır. Burada FAT elemanlarının hex karşılıkları şöyledir (Little Endian notasyon kullanıldığına dikkat ediniz): 0 1 2 3 4 5 6 7 8 9 10 11 12 13 <0000> <0005> <0006> <0007> <0008> <0009> <000A> <000B> <000C> <000D> Burada FAT elemanlarının numaralarını desimal sistemde elemanların yukarısına yazdık. Söz konusu dosyanın ilk cluster numarasının 4 olduğunu bildiğimizi varsayıyoruz. -> 4 numaralı FAT elemanında 5 (0005) yazmaktadır. O halde dosyanın sonraki cluster numarası 5'tir. -> 5 numaralı FAT elemanında 6 (0006) yazmaktadır. -> 6 numaralı FAT elemanında 7 (0007), -> 7 numaralı FAT elemanında 8 (0008), -> 8 numaralı FAT elemanında 9 (0009), -> 9 numaralı FAT elemanında 10 (000A), -> 10 numaralı FAT elemanında 11 (000B), -> 11 numaralı FAT elemanında 12 (0000D), -> 12 numaralı FAT elemanında 13 (000D), -> 13 FAT elemanında da özel değer olan 65535 (FFFF) bulunmaktadır. Bu özel değer zinicirin sonuna gelindiğini belirtmektedir. Bu durumda bu dosyanın tüm parçaları sırasıyla şu cluster'lardadır: 4 5 6 7 8 9 10 11 12 13 Burada işletim sisteminin dosyanın parçalarını diskte ardışışıl cluster'lara yerleştirdiğini görüyorsunuz. Ancak bu durum her zaman böyle olma zorunda değildir. 16 bir FAT'te bir FAT elemanında bulunacak değerler şunlar olabilmektedir (değerler little Endian olarak WORD'e dönüştürülmüştür): 0000 Boş cluster 0001 Kullanılmıyor 0002 - FFEF Geçerli, sonraki cluster FFF0H - FFF6 Reserved cluster FFF7 Bozuk cluster, işletim sistemi bu cluster'a dosya parçası yerleştirmez FFF8 - FFFF Son cluster Pekiyi işletim sistemleri FAT bölümünü nasıl ele alıp işlemektedir? Aslında FAT bölümündeki sektörler zaten çok kullanıldığı için işletim sisteminin aşağı seviyeli disk cache sisteminde bulunuyor durumda olurlar. Ancak işletim sistemleri genellikle FAT elemanları temelinde de bir cache sistemi de oluşturmaktadır. Böylece bir cluster değeri verildiğinde eğer daha önce o cluster ile işlemler yapılmışsa o cluster'ın sonraki cluster'ı hızlı bir biçimde elde edilebilmektedir. Biz burada volümü açtığımızda tüm FAT bölümünü okuyup FATSYS yapısının içerisine yerleştireceğiz. Sonra da ilk cluster numarası bilinen dosyaların cluster zincirini elde eden bir fonksiyon yazacağız. openfat_sys fonksiyonunun yeni versiyonu aşağıdaki gibi olabilir: FATSYS *open_fatsys(const char *path) { FATSYS *fatsys; int fd; if ((fatsys = (FATSYS *)malloc(sizeof(FATSYS))) == NULL) return NULL; if ((fd = open(path, O_RDWR)) == -1) goto EXIT1; if (read_bpb(fd, &fatsys->bpb) == -1) goto EXIT2; fatsys->fd = fd; fatsys->fatoff = fatsys->bpb.fatloc * fatsys->bpb.bps; fatsys->rootoff = fatsys->bpb.rootloc * fatsys->bpb.bps; fatsys->dataoff = fatsys->bpb.dataloc * fatsys->bpb.bps; fatsys->clulen = fatsys->bpb.bps * fatsys->bpb.spc; if ((fatsys->fat = (uint8_t *)malloc(fatsys->bpb.fatlen * fatsys->bpb.bps)) == NULL) goto EXIT2; if (lseek(fatsys->fd, fatsys->fatoff, SEEK_SET) == -1) goto EXIT3; if (read(fd, fatsys->fat, fatsys->bpb.fatlen * fatsys->bpb.bps) == -1) goto EXIT3; return fatsys; EXIT3: free(fatsys->fat); EXIT2: close(fd); EXIT1: free(fatsys); return NULL; } İlk cluster numarası bilinen dosyanın cluster zincirini elde eden fonksiyon da aşağıdaki gibi yazılabilir: uint16_t *getclu_chain16(FATSYS *fatsys, uint32_t firstclu, uint16_t *count) { uint16_t clu, n; uint16_t *chain, *temp; uint32_t capacity; clu = firstclu; capacity = CHAIN_DEF_CAPACITY; n = 0; if ((chain = (uint16_t *)malloc(sizeof(uint16_t) * CHAIN_DEF_CAPACITY)) == NULL) return NULL; do { chain[n++] = clu; if (n == capacity) { capacity *= 2; if ((temp = realloc(chain, sizeof(uint16_t) * capacity )) == NULL) { free(chain); return NULL; } chain = temp; } clu = *(uint16_t *)(fatsys->fat + clu * 2); } while (clu < 0xFFF8); *count = n; return chain; } Bu fonksiyonda dosyanın cluster zinciri için uint16_t türünden dinamik büyütülen bir dizi oluşturulmuştur. Dizi eski uzunluğunun iki katı olacak biçimde büyütülmektedir. Fonksiyon bize cluster zincirini vermekte hem de bu zincirin uzunluğunu vermektedir. Aşağıda tüm kodlar bütün olarak verilmiştir. * Örnek 1, /* fatsys.h */ #ifndef FATSYS_H_ #define FATSYS_H_ #include #define FILE_INFO_LENGTH 32 #define CHAIN_DEF_CAPACITY 8 /* Type Declarations */ typedef struct tagBPB { uint16_t fatlen; /* Number of sectors in FAT (A) */ uint16_t rootlen; /* Number of sectors in ROOT (NA) */ uint16_t nfats; /* Number of copies of FAT (A) */ uint32_t tsects; /* Total sector (A) */ uint16_t bps; /* Byte per sector(A) */ uint16_t spc; /* Sector per cluster(A) */ uint16_t rsects; /* Reserved sectors(A) */ uint8_t mdes; /* Media descriptor byte(A) */ uint16_t spt; /* Sector per track(A) */ uint16_t rootents; /* Root entry (A) */ uint16_t nheads; /* Number of heads (A) */ uint16_t hsects; /* Number of hidden sector( A) */ uint16_t tph; /* Track per head (NA) */ uint16_t fatloc; /* FAT directory location (NA) */ uint16_t rootloc; /* Root directory location (NA) */ uint16_t dataloc; /* First data sector location (NA) */ uint32_t datalen; /* Number of sectors in Data (NA) */ uint32_t serial; /* Volume Serial Number (A) */ char vname[12]; /* Volume Name (A) */ } BPB; typedef struct tagFATSYS { int fd; /* Volume file descriptor */ BPB bpb; /* BPB info */ uint32_t fatoff; /* Offset of FAT */ uint32_t rootoff; /* Offset of root directory */ uint32_t dataoff; /* Offset of DATA */ uint32_t clulen; /* Cluster length as bytes */ uint8_t *fat; /* FAT sectors */ /* ... */ } FATSYS; /* Function prototypes */ int read_bpb(int fd, BPB *bpb); FATSYS *open_fatsys(const char *path); int close_fatsys(FATSYS *fatsys); int read_cluster(FATSYS *fatsys, uint32_t clu, void *buf); int wite_cluster(FATSYS *fatsys, uint32_t clu, const void *buf); uint16_t *getclu_chain16(FATSYS *fatsys, uint32_t firstclu, uint16_t *count); void freeclu_chain(uint16_t *chain); #endif /* fatsys.c */ #include #include #include #include #include #include "fatsys.h" int read_bpb(int fd, BPB *bpb) { uint8_t bsec[512]; if (read(fd, bsec, 512) == -1) return -1; bpb->bps = *(uint16_t *)(bsec + 0x0B); bpb->spc = *(uint8_t *)(bsec + 0x0D); bpb->rsects = *(uint16_t *)(bsec + 0x0E); bpb->fatlen = *(uint16_t *)(bsec + 0x16); bpb->rootlen = *(uint16_t *)(bsec + 0x11) * FILE_INFO_LENGTH / bpb->bps; bpb->nfats = *(uint8_t *)(bsec + 0x10); if (*(uint16_t *)(bsec + 0x13)) bpb->tsects = *(uint16_t *)(bsec + 0x13); else bpb->tsects = *(uint32_t *)(bsec + 0x20); bpb->mdes = *(bsec + 0x15); bpb->spt = *(uint16_t *)(bsec + 0x18); bpb->rootents = *(uint16_t *)(bsec + 0x11); bpb->nheads = *(uint16_t *)(bsec + 0x1A); bpb->hsects = *(uint16_t *)(bsec + 0x1C); bpb->tph = (uint16_t)(bpb->tsects / bpb->spt / bpb->nheads); bpb->fatloc = bpb->rsects; bpb->rootloc = bpb->rsects + bpb->fatlen *bpb->nfats; bpb->dataloc = bpb->rootloc + bpb->rootlen; bpb->datalen = bpb->tsects - bpb->dataloc; bpb->serial = *(uint32_t *)(bsec + 0x27); memcpy(bpb->vname, bsec + 0x2B, 11); bpb->vname[11] = '\0'; return 0; } FATSYS *open_fatsys(const char *path) { FATSYS *fatsys; int fd; if ((fatsys = (FATSYS *)malloc(sizeof(FATSYS))) == NULL) return NULL; if ((fd = open(path, O_RDWR)) == -1) goto EXIT1; if (read_bpb(fd, &fatsys->bpb) == -1) goto EXIT2; fatsys->fd = fd; fatsys->fatoff = fatsys->bpb.fatloc * fatsys->bpb.bps; fatsys->rootoff = fatsys->bpb.rootloc * fatsys->bpb.bps; fatsys->dataoff = fatsys->bpb.dataloc * fatsys->bpb.bps; fatsys->clulen = fatsys->bpb.bps * fatsys->bpb.spc; if ((fatsys->fat = (uint8_t *)malloc(fatsys->bpb.fatlen * fatsys->bpb.bps)) == NULL) goto EXIT2; if (lseek(fatsys->fd, fatsys->fatoff, SEEK_SET) == -1) goto EXIT3; if (read(fd, fatsys->fat, fatsys->bpb.fatlen * fatsys->bpb.bps) == -1) goto EXIT3; return fatsys; EXIT3: free(fatsys->fat); EXIT2: close(fd); EXIT1: free(fatsys); return NULL; } int close_fatsys(FATSYS *fatsys) { free(fatsys->fat); if (close(fatsys->fd) == -1) return -1; free(fatsys); return 0; } int read_cluster(FATSYS *fatsys, uint32_t clu, void *buf) { if (lseek(fatsys->fd, fatsys->dataoff + (clu - 2) * fatsys->clulen, SEEK_SET) == -1) return -1; return read(fatsys->fd, buf, fatsys->clulen); } int write_cluster(FATSYS *fatsys, uint32_t clu, const void *buf) { if (lseek(fatsys->fd, fatsys->dataoff + (clu - 2) * fatsys->clulen, SEEK_SET) == -1) return -1; return write(fatsys->fd, buf, fatsys->clulen); } uint16_t *getclu_chain16(FATSYS *fatsys, uint32_t firstclu, uint16_t *count) { uint16_t clu, n; uint16_t *chain, *temp; uint32_t capacity; clu = firstclu; capacity = CHAIN_DEF_CAPACITY; n = 0; if ((chain = (uint16_t *)malloc(sizeof(uint16_t) * CHAIN_DEF_CAPACITY)) == NULL) return NULL; do { chain[n++] = clu; if (n == capacity) { capacity *= 2; if ((temp = realloc(chain, sizeof(uint16_t) * capacity )) == NULL) { free(chain); return NULL; } chain = temp; } clu = *(uint16_t *)(fatsys->fat + clu * 2); } while (clu < 0xFFF8); *count = n; return chain; } void freeclu_chain(uint16_t *chain) { free(chain); } /* app.c */ #include #include #include #include #include "fatsys.h" void exit_sys(const char *msg); int main(int argc, char *argv[]) { FATSYS *fatsys; uint16_t count; uint16_t *chain; if (argc != 2) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } if ((fatsys = open_fatsys(argv[1])) == NULL) exit_sys("open_fatsys"); if ((chain = getclu_chain16(fatsys, 4, &count)) == NULL) { fprintf(stderr, "cannot get cluster chain!...\n"); exit(EXIT_FAILURE); } printf("Number of clusters in file: %u\n", count); for (uint16_t i = 0; i < count; ++i) printf("%u ", chain[i]); printf("\n"); freeclu_chain(chain); close_fatsys(fatsys); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } Biz şimdi ilk cluster'ını bildiğimiz bir text dosyanın içeriğini yazdırmak isteyelim. Bunun için önce getclu_chain16 fonksiyonunu çağırırız. Sonra read_cluster fonksiyonu ile cluster'ları okuyup içini yazdırabiliriz. Ancak burada şöyle bir sorun vardır: Dosyanın son cluster'ı tıka basa dolu değildir. Orada dosyaya dahil olmayan bye'lar da vardır. İşletim sistemi dosyanın uzunluğunu elde edip son cluster'daki dosyaya dahil olmayan kısmı belirleyebilmektedir. Aşağıda ilk cluster'ı bilinen bir text dosyanın yazdırılmasına yönelik bir örnek verilmiştir. Burada dosyanın son cluster'ındaki dosyaya ait olmayan kısım da yazdırılmaktadır. * Örnek 1, /* app.c */ #include #include #include #include #include "fatsys.h" void exit_sys(const char *msg); int main(int argc, char *argv[]) { FATSYS *fatsys; unsigned char buf[8192]; uint16_t count; uint16_t *chain; if (argc != 2) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } if ((fatsys = open_fatsys(argv[1])) == NULL) exit_sys("open_fatsys"); if ((chain = getclu_chain16(fatsys, 4, &count)) == NULL) { fprintf(stderr, "cannot get cluster chain!...\n"); exit(EXIT_FAILURE); } for (uint16_t i = 0; i < count; ++i) { if (read_cluster(fatsys, chain[i], buf) == -1) exit_sys("read_cluster"); for (int i = 0; i < fatsys->clulen; ++i) putchar(buf[i]); } freeclu_chain(chain); close_fatsys(fatsys); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } İşletim sisteminin dosya sistemi bize aslında cluster'larda olan dosya parçalarını "dosya" adı altında ardışıl byte topluluğu gibi göstermektedir. Biz işletim sisteminin sistem fonksiyonu ile dosyayı açarız ve read fonksiyonu ile okumayı yaparız. Bütün diğer işlemler işletim sisteminin çekirdek kodları tarafından yapılmaktadır. Biz yukarıda 16 bit FAT için işlemler yaptık. Pekiyi 12 bit ve 32 bit FAT bölüm nasıldır? 32 bit FAT önemli bir farklılığa sahip değildir. Her FAT elemanı 32 bit yani 4 byte uzunluktadır. Dolayısıyla daha büyük bir volüm için kullanılabilir. 16 bit FAT'te toplam 65536 FAT elemanı elemanı olabilir (Bazılarının kullanılmadığını da anımsayınız.) Bir cluster en fazla 64 sektör uzunluğunda olabilmektedir. Bu durumda FAT16 sistremlerinde volümün maksimum uzunluğu 2^16 * 2^6 * 2^9 = 2GB. 12 Bit FAT'ler biraz daha karmaşık görünümdedir. 12 bit 8'in katı değildir ve 3 hex digitle temsil edilmektedir. Bu nedenle 12 Bit FAT'te FAT zinciri izlenirken dikkat edilmelidir. Eğer volüm küçükse (eskiden floppy diskler vardı ve onlar çok küçüktü) FAt12 sistemi FAT tablosunun daha az yer kaplamasını sağlamaktadır. FAT12 sisteminde bir FAT elemanı 12 bit olduğu için FAT bölümünde en fazla 2^12 = 4096 FAT elemanı olabilir. Microsoft kendi format programında FAT12 volümlerinde bir cluster'ı maksimum 8 sektör olarak almaktadır. Bu durumda FAT12 volümü maksimum 2^12 * 2^3 * 2^9 = 2^24 = 16MB olabilmektedir. Başka bir deyişle Microsoft 16MB'nin yukarısındaki volümleri FAT12 olarak formatlamamaktadır. Aşağıda 12 bit FAT tablosunun baş kısmı görülmektedir: 00000200 f8 ff ff 00 40 00 05 60 00 07 80 00 09 a0 00 0b |....@..`........| 00000210 c0 00 ff 0f 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000220 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000230 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000240 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000250 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000260 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000270 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 12 bit'in 3 hex digit yani 1.5 olduğuna dikkat ediniz. Buradaki 12 bit şöyle yapılmaktadır. Cluster numarası önce 1.5 ile çarpılır ve noktalı kısım atılır. (Bu işlem 3 ile çarpılıp 2'ye bölünme biçiminde yapılabilir.) Elde edilen offset'ten WORD bilgi çekilir. Eğer cluster numarası çifte yüksek anlamlı 4 bit atılır, eğer cluster numarası tek ise düşük anlamlı 4 bit atılır. Yüksek anlamlı 4 bit'in atılması 0x0FFF ile "bit and" işlemi uygulanarak, düşük anlamlı 4 bit'in elde edilmesi sayının 4 kez sağa ötelenerek yapılabilir. Örneğin yukarıdaki FAT bölümünde biz 4 numaralı cluster'ın değerini elde edecek olalım. 4 * 1.5 = 6'dır. 6'ıncı offset'ten WORD çekilirse 0x6005 değeri elde edilir. Yüksek anlamı 4 bit atıldığında ise 0x005 değeri elde edilecektir. Şimdi 5 numaralı cluster'ın değerini elde etmek isteyelim. Bu durumda 5 * 1.5 = 7.5 olur. Noktadan sonraki kısım atılırsa 7 elde edilir. 7'inci offset'ten WORD öçekildiğinde 0x0060 değeri elde edilecektir. Bu değerin de düşük anlamlı 4 biti atıldığında 0x006 değeri elde edilir. 12 Bit FAT sisteminde bir FAT elemanın alabileceği değerler de şöyledir: 000 Boş cluster 001 Kullanılmıyor 002 - FEF Geçerli, sonraki cluster FF0H - FF6 Reserved cluster FF7 Bozuk cluster, işletim sistemi bu cluster'a dosya parçası yerleştirmez FF8 - FFF Son cluster 12 bit FAT tablosunda ilk cluster değeri bilinen dosyanın cluster zincirlerini elde etmek için aşağıdaki gibi bir fonksiyon yazılabilir. uint16_t *getclu_chain12(FATSYS *fatsys, uint32_t firstclu, uint16_t *count) { uint16_t clu, word, n; uint16_t *chain, *temp; uint32_t capacity; clu = firstclu; capacity = CHAIN_DEF_CAPACITY; n = 0; if ((chain = (uint16_t *)malloc(sizeof(uint16_t) * CHAIN_DEF_CAPACITY)) == NULL) return NULL; do { chain[n++] = clu; if (n == capacity) { capacity *= 2; if ((temp = realloc(chain, sizeof(uint16_t) * capacity )) == NULL) { free(chain); return NULL; } chain = temp; } word = *(uint16_t *)(fatsys->fat + clu * 3 / 2); clu = clu % 2 == 0 ? word & 0x0FFF : word >> 4; } while (clu < 0xFF8); *count = n; return chain; } Fonksiyonda 12 bit FAT değerinin elde edilmesi şöyle yapılmıştır: clu = clu % 2 == 0 ? word & 0x0FFF : word >> 4; >>> FAT32: FAT32 sisteminde her FAT elemanı 32 bittir. Ancak bu sistemde boot sektördeki BPB alanında da faklılıklar vardır. Bu nedenle 32 bit FAT sistemi FAT12 ve FAT16 ile tam uyumlu değildir. FAT32 için bazı fonksiyonların yeniden yazılması gerekir. Biz FAT dosya sisteminin boot sektörünü, FAT ve Data bölümlerini ele aldık. Ele almadığımız tek bölüm "Root Dir" bölümüdür. Şimdi "Root Dir" bölümü ve dosya bilgilerinin nasıl saklandığı konusu üzerinde duracağız. Microsoft'un FAT dosya sisteminde ve UNIX/Linux sistemlerinde kullanılan i-node tabanlı dosya sistemlerinde dizinler de tamamen bir dosya gibi organize edilmektedir. Yani dizinler de aslında birer dosyadır. Bir dosyanın içerisinde o dosyanın bilgileri bulunurken bir dizin dosyasının içerisinde o dizindeki dosyalara ilişkin bilgiler bulunmaktadır. Yani dizinler aslında "o dizindeki dosyaların bilgilerini içeren dosyalar" gibidir. Bir dizin dosyası "dizin girişlerinden (directory entry)" oluşmaktadır. FAt12 ve FAT16 dosya sistemlerinde bir dizin dosyasındaki dizin girişleri 32 byte uzunluğundaydı. Yani dizin ddosyaları 32 byte'lık kayıtların peşi sıra gelmesiyle oluşuyordu. O zamanalarda DOS sistemlerinde bir dosyanın ismi için en fazla 8 karakter, uzantısı için de en fazla 3 karakter kullanılabiliyordu. Dolayısıyla 32 byte'lık dizin girişlerinin 1 byte'ı dosyanın ismi için ayrılmıştı. Sonra Microsoft dosya isimlerini 8+3 formatından çıkartarak onların 255'e kadar uzatılmasını sağladı. Ancak bu yaparken de geçmişe doğur uyumu korumak için birden fazla 32 byte'lık dizin girişleri kullandı. Biz önce burada klasik 8+3'lük dizin girişlerinin formatını göreceğiz. 32'lik klasik dizin girişi formatı şöyledir: Offset (Hex) Uzunluk Anlamı 00 8 Byte Dosya ismi (File Name) 08 3 Byte Dosya Uzantısı (Extension) 0B 1 Byte Dosya Özelliği (Attribute) 0C 1 Byte Kullanılmıyor (Reserved) 0D BYTE Yaratılma Zamanının Milisaniyesi 0E WORD Dosyanın Yaratılma Zamanı (Creation Time) 10 WORD Dosyanın Yaratılma Tarihi (Creation Date) 12 WORD Son Okunma Zamanı (Last Access Time) 14 WORD Kullanılmıyor (Reserved) 16 WORD Son Yazma Zamanı (Last Write Time) 18 WORD Son Yazma Tarihi (Last Write Date) 1A WORD İlk Cluster Numarası (First Cluster) 1C DWORD Dosyanın Uzunluğu (File Length) Aşağıda "x.txt" dosyanın ve "mydir" dizinin 32 byte'lık dizin girişleri görülmektedir. 58 20 20 20 20 20 20 20 54 58 54 20 00 0b 5d 92 |X TXT ..].| 59 59 59 59 00 00 5d 92 59 59 0e 00 0f 00 00 00 |YYYY..].YY......| 4d 59 44 49 52 20 20 20 20 20 20 10 00 7a f0 96 |MYDIR ..z..| 59 59 59 59 00 00 f0 96 59 59 10 00 00 00 00 00 |YYYY....YY......| Bu formattaki, -> Dosya İsmi: 32'lik dizin girişlerinin ilk 8'byte'ı dosya isminden oluşmaktadır. Eğer dosya ismi 8 karakterden kısa ise SPACE karakterleriyle (0x20) padding yapılmaktadır. Klasik FAT16 ve FAT12 sistemlerinde dosya isimlerinin ve uzantılarının büyük harf-küçük harf duyarlılığı yoktur. Tüm dosyalar bu sistemlerde "büyük harfe dönüştürülerek" dizin girişlerinde tutulmaktadır. -> Dosya Uzantısı: Dosya uzantısı en fazla 3 karakterden oluşmaktadır. Eğer 3 karakterden kısa ise SPACE (0x20) karakterleriyle padding yapılmaktadır. -> Dosya Özelliği: Bu alanda dosyanın özleliklerine ilişkin bit bit alanı bulundurulmaktadır. Buradaki her bit'in bir anlamı vardır. Özellik byte'ı aşağıdaki bitlerden oluşmaktadır: 7 6 5 4 3 2 1 0 Reserved Reserved Archive Dir VLabel System Hidden ReadOnly Eğer ReadOnly biti 1 ise dosya "read-only" biçimdedir. Böyle dosyalara işletim sistemi yazma yapmaz. Hidden biti 1 ise dosya "dir" komutu uygulandığında görüntülenmez. DOS işletim sisteminin kendi dosyalarını System özelliği ile vurgulamaktadır. Yani eğer bir dosya işletim sistemine ilişkin bir dosya ise System biti 1 olur. Volüm isimleri boot sektörün yanı sıra kök dizinde bir dosya ismi gibi de tutulmaktadır. Böyle girişlerin VLabel biti 1 olur. Eğer bir dizin söz konusu ise Dir biti 1 olmaktadır. FAT dosya sisteminde normal dosyalara "Archive" dosyaları denilmektedir. Bu nedenle bu bit hemen her zaman 1 olarak görülür. Aşağıda "x.txt" ve "mydir" dizin girişlerine ilişkin özellik byte'ınn bitleri görülmektedir: x.txt (0x20) 0 0 1 0 0 0 0 0 mydir (0x10) 0 0 0 1 0 0 0 0 "x.txt" dosyasının özellik bitlerinden yalnızca Archive biti set edilmiştir. "mydir" dizinin de yalnızca "Dir" biti set edilmiştir. İşletim sistemi bir dizin girişinin normal bir dosyaya mı yoksa bir dizin dosyasına mı ilikin olduğunu özellik byte'ının 4 numaralı bitine bakarak tespit etmektedir. -> Tarih ve Zaman Bilgileri FAT12 ve FAT16 dosya sistemlerinde 2 byte ile kodlanmaktadır. Eskiden DOS sistemlerinde dosyanın yaratılma tarihi ve zamanı ve son okunma tarihi tutulmazdı. Bu alanlar "reserved" durumdaydı. Sonra Microsoft bu alanları bu amaçla kullanmaya başladı. Tarih bilgisi byte içerisinde bitsel düzeyde tutulmaktadır. Tarih bilgisinin tutuluş formatı şöyledir: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 y y y y y y y m m m m d d d d d Burada WORD değerin düşük anlamlı 5 biti gün için, sonraki 4 biti ay için ve geri kalan 7 biti yıl için bulundurulmuştur. Tarih bilgisinin yıl alanı için 7 bit ayrıldığına göre buraya nasıl 2024 gibi bir tarih yerleştirilebilmektedir. İşte DOS işletim sisteminin ilk versiyonu 1980 yılında oluşturulduğu için buradaki tarih bilgisi her zmana 1980 yılından itibaren bir offset belirtmektedir. Yani örneğin 2024 yılı için buradaki yıl bitlerine 44 kodlanmaktadır. Örneğin bir dosyanın 32'lik dizin girişi şöyledir: 58 20 20 20 20 20 20 20 54 58 54 20 18 AB 03 B3 59 59 59 59 00 00 09 B3 59 59 06 00 1B 00 00 00 Buaradk 0x18'inci offset'ten little endian formatta WORD çekersek 0x5959 değerini elde ederiz. Şimdi bu WORD değeri 2'lik sistemde ifade edelim: 5 9 5 9 0101 1001 0101 1001 Şimdi de yukarıda belirttiğimiz gibi sayıyı bit'lerine ayrıştıralım: 0101100 => 44 1010 => 10 11001 => 25 yıl ay gün O halde buradaki tarih 25/10/2024'tür. 16 bitle (WORD ile) zaman bilgisi de şöyle kodlanmıştır: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 h h h h h m m m m m m s s s s s Burada bir noktayaya dikkat ediniz: 0 ile 24 arasındaki saatler 5 bit ile tutulabilir. 0 ile 60 arasındaki dakikalar ise ancak 6 bit ile tutulabilir. Burada geriye 5 bit almıştır. Dolayısıyla saniyeleri tutmak için bu 5 bit yeterli değildir. İşte FAT dosya sistemini tasarlayanlar saniyedeki duyarlılığı azaltarak saniye değerinin yarısını buraya yazılması yoluna gitmişlerdir. Yani zaman bilgisinin saniye kısmı eski FAT12 ve FAT16 sistemlerinde tam duyarlılıkla tutulamamaktadır. Örneğin yukarıdaki dizin girişinde dosyanın son değiştirilme zamanı için 0x16'ıncı offset'ten WORD çektiğimizde 0xB309 değerini elde ederiz. Şimdi bu değeri 2'lik sisteme dönüştürelim: B 3 0 9 1011 0011 0000 1001 Şimdi de bit alanlarını ayrıştıralım: 10110 => 22 011000 => 24 01001 => 9 saat dakika saniye Burada işletim sistemi saniye alanına mevcut saniyenin yarısını yazdığına göre bu dosyanın değiştirilme zamanı 22:24:18 olacaktır. Burada küçük bir noktaya dikkatinizi çekmek istiyoruz: Eskiden dizin girişinin 0x0D numaralı BYTE'ı da "reserved" durumdaydı sonra bu byte'a dosyanın yaratılma zamanına ilişkin milisaniye değeri yerleştirildi. Dolayısıyla artık dosyanın yaratılma zamanı saniye duyarlılığında ifade edilebilmektedir. Bu durumda yaratılma zamanındaki saniye 2 ile çarpılıp buradaki milisaniye ile toplanmaktadır. Tabii genel olarak Microsoft'un arayüzü dosyaların zaman gilfgilerinin saniyelerini default durumda zaten gmstermemektedir. Örneğin: D:\>dir Volume in drive D is YENI BIRIM Volume Serial Number is 2C68-EBFD Directory of D:\ 25.10.2024 22:24 27 x.txt 25.10.2024 22:26 8 con 25.10.2024 22:26 59 y.txt 25.10.2024 22:28 mydir 3 File(s) 94 bytes 1 Dir(s) 52.194.304 bytes free -> İlk Cluster Numarası: Biz daha önce FAT bölümünü incelerken bir dosyanın cluster zincirini elde edebilmek için onun ilk cluster numarasının bilinmesi gerektiğini belirtmiştik. (Bir bağlı listeyi dolaşabilmek için onun ilk düğümünün yerinin bilinmesi gerektiğini anımsayınız.) İşte bir dosyanın ilk cluster numarası dizin girişinde saklanmaktadır. Yani işletim sistemi önce dosyanın dizin girişini bulmakta sonra FAT'ten onun cluster zincirini elde etmektedir. Yukarıdaki dosyasının dizin girişini yeniden veriyoruz: 58 20 20 20 20 20 20 20 54 58 54 20 18 AB 03 B3 59 59 59 59 00 00 09 B3 59 59 06 00 1B 00 00 00 Burada söz konusu dosyanın ilk cluster numarası dizin girişinin 0x1A ofsfetinden başlayan WORD bilgidir. Bu bilgiyi örnek dizin girişinden çektiğimizde 0x006 değerini elde ederiz. Bu durumda bu dosyanın ilk cluster numarası 6'dır. -> Dosyanın Uzunluğu: Dosyanın uzunluğu dizin girişindeki son 4 byte'lık (DWORD alan) alanda tutulmaktadır. İşletim sistemi dosyanın son cluster'ındaki geçerli byte sayısını bu uzunluktan yararlanarak elde etmektedir. Örneğin yukarıdaki dizin girişine ilişkin dosya uzunluğu 0x0000001B = 27'dir. Dizin dosyalarına ilişkin uzunluklar için işletim sistemi hep 0 değerini yazmaktadır. Dizinler de bir dosya gibi ele alınmaktadır. Dolayısıyla dizinlerin de bir cluster zinciri vardır. Ancak FAT12 ve FAT16 sistemlerinde kök dizinin yeri ve uzunluğu baştan bellidir. Kök dizin için bir cluster zinciri yoktur. FAT32 dosya sisteminde kök dizin de normal bir dizin gibi büyüyebilmektedir. Yani kök dizinin de bir cluster zinciri vardır. 32'lik bir dizin girişini aşağıdaki gibi bir yapıyla temsil edebiliriz: #pragma pack(1) typedef struct tagDIR_ENTRY { unsigned char name[8]; unsigned char ext[3]; uint8_t attr; char reserved1[1]; uint8_t crtime_ms; uint16_t crdate; uint16_t crtime; uint16_t rdtime; char reserved2[2]; uint16_t wrtime; uint16_t wrdate; uint16_t fclu; uint32_t size; } DIR_ENTRY; #pragma pack() Pekiyi bir dosya silindiğinde ne olur? İşletim sistemi FAT dosya sisteminde bir dosya silindiğinde iki işlem yapar: -> Dosyanın silindiğinin anlaşılması için dizin girişindeki dosya isminin ilk karakterini 0xE5 olarak değiştirir. Böylece dizin girişlerini tararken 32'lik girişin ilk karajkter 0xE5 ise o dizin girişini silindiği gerekçesiyle atlamaktadır. Ancak işletim sistemi bu 32'lik dizin girişinin diğer byte'larına dokunmamaktadır. -> İşletim sistemi dosyanın cluster zincirini de sıfırlamaktadır. Böylece bu dosyanın FAT'te kapladığı alan artık "boş" gözükecektir. Ancak işletim sistemi dosyanın Data bölümündeki cluster'ları üzerinde herhangi bir işlem yapmaz. Pekiyi FAT dosya sisteminde "undelete" yapan programlar nasıl çalışmaktadır? İşte bu programlar dosyanın dizin girişine bakıp onun ilk cluster'ının numarasını elde edip FAT bölümünde yersine bir algoritmayla onun cluster zincirini yeniden oluşturmaya çalışmaktadır. Ancak böyle bir kurtarmanın garantisi yoktur. Çünkü işletim sistemi boşaltılmış cluster'ları başka bir dosya için tahsis etmiş olabilir. Ya da FAT'teki cluster zincirini tahmin eden programlar bu konuda yanılabilmektedir. Ancak ne olursa olsun dosyanın ilk karakteri silindiği için bu karakter kurtarma sırasında kullanıcıya sorulmaktadır. Öte yandan FAT12 ve FAT16 sistemlerinin orijinali yalnızca 8+3'lük dosya isimlerini destekliyordu. Yani bir dosyanın ismi en fazla 8 karakterden uzantısı da en fazla 3 karakterden oluşabiliyordu. 90'lı yılların ortalarına doğru Microsoft FAT dosya sisteminde uzun dosya isimlerinin de kullanılmasına olanak sağlamıştır. Microsoft bunu yaparken geçmişe doğru uyumu mümkün olduğunca korumaya da çalışmıştır. Microsoft'un bu yeni düzenlemesinde 8+3'ten daha uzun dosya izimleri birden fazla 32'lik girişle temsil edilmektedir. Ancak Microsoft geçmişe doğru uyumumu korumak için her uzun dosya isminin bir de 8+3'lük kısa ismini oluşturmak istemiştir. Bu durumda uzun dosya isimlerinin kullanıldığı FAT sistemlerinde 8+3'lük alan sığmayan dosya isimleri aşağıdaki formata göre dizin girişlerinde bulundurulmaktadır: <32'lik giriş> <32'lik giiriş> ... <32'lik giriş> Tabii eğer istenirse (örneğin Linux böyle yapmaktadır) 8+3'lük sınıfı aşmayana dosyalar da sanki uzun isimli dosyalarmış gibi saklanabilmektedir. Burada dosyanın 8+3'lük kısa isminin dışındaki uzun ismi de 32'lik girişlerde ASCII olarak değil UNICODE olarak tutulmaktadır. Uzun dosya isimlerine ilişkin girişlerin sonunda 8+3'lük kısa bir girişin de bulundurulduğunu belirtmiştik. Peki bu uzun dosya isminden kısa giriş nasıl elde edilmektedir? Uzun dosya isimlerinin tutulduğu 32'lik girişlerin ilk byte'ında önemli bilgiler vardır. Bu byte'a "sıra numarası (sequence number)" denilmektedir. Bu byte bit bit anlamlandırılmaktadır. Byte'ın bitlerinin anlamları şöyledir: D L X X X X X X Burada en yükske anlamlı bit olan D biti 32'lik girişin silinip silinmeidğini anlatmaktadır. Eğer bu giriş silinmişse bu bit 1, silinmemişse 0 olacaktır. 7 numaralı bit (L biti) 32'lik girişlerin aşağıdan yukarıya doğru son giriş olup olmadığını belirtmektedir. Ger kalan 6 bit 32'lik girişlerin sıra numarasını belirtir. Yani her 32'lik girişin bir sıra numarası vardır. Her 32'lik giriş uzun dosya isminin 13 UNICODE karakterini tutmaktadır. Pekiyi biz 32'lik bir girişin eski kısa ilişkin 32'lik bir giriş mi yoksa uzun ismin 32'lik girişlerinden biri mi olduğunu nasıl anlayabiliriz? İşte bunun için 32'lik girişin 0x0B offset'inde bulunan özellik byte'ının düşük anlamlı 4 bitine bakmak gerekir. Eğer bu 4 bitin hepsi 1 ise bu 32'lik giriş uzun dosya isminin 32'lik girişlerinden biridir. Aşağıda uzun dosya isimlerine ilişkin 32'lik girişlerin genel formatı verilmiştir. Ayrıntılı format için Microsoft'un "FAT File System Specification" dokümanına başvurabilirsiniz. Field name Offset Size Description LDIR_Ord 0 1 Sequence number (1-20) to identify where this entry is in the sequence of LFN entries to compose an LFN. One indicates the top part of the LFN and any value with LAST_LONG_ENTRY flag (0x40) indicates the last part of the LFN. LDIR_Name1 1 10 Part of LFN from 1st character to 5th character. LDIR_Attr 11 1 LFN attribute. Always ATTR_LONG_NAME and it indicates this is an LFN entry. LDIR_Type 12 1 Must be zero. LDIR_Chksum 13 1 Checksum of the SFN entry associated with this entry. LDIR_Name2 14 12 Part of LFN from 6th character to 11th character. LDIR_FstClusLO 26 2 Must be zero to avoid any wrong repair by old disk utility. LDIR_Name3 28 4 Part of LFN from 12th character to 13th character. Biz sonraki örneklerde uzun dosya isimlerini dikkate almayacağız. Onları geçeceğiz. Aşağıda kök uzun dosya isimlerinin ve silinmiş dosya isimlerinin geçilerek kök dosya sistemindeki dosyaların listesini elde eden bir fonksiyon verilmiştir. * Örnek 1, /* fatsys.h */ #ifndef FATSYS_H_ #define FATSYS_H_ #include #define FILE_INFO_LENGTH 32 #define CHAIN_DEF_CAPACITY 8 #define ROOT_DEF_CAPACITY 8 #define DIR_ENTRY_SIZE 32 /* Type Declarations */ typedef struct tagBPB { uint16_t fatlen; /* Number of sectors in FAT (A) */ uint16_t rootlen; /* Number of sectors in ROOT (NA) */ uint16_t nfats; /* Number of copies of FAT (A) */ uint32_t tsects; /* Total sector (A) */ uint16_t bps; /* Byte per sector(A) */ uint16_t spc; /* Sector per cluster(A) */ uint16_t rsects; /* Reserved sectors(A) */ uint8_t mdes; /* Media descriptor byte(A) */ uint16_t spt; /* Sector per track(A) */ uint16_t rootents; /* Root entry (A) */ uint16_t nheads; /* Number of heads (A) */ uint16_t hsects; /* Number of hidden sector( A) */ uint16_t tph; /* Track per head (NA) */ uint16_t fatloc; /* FAT directory location (NA) */ uint16_t rootloc; /* Root directory location (NA) */ uint16_t dataloc; /* First data sector location (NA) */ uint32_t datalen; /* Number of sectors in Data (NA) */ uint32_t serial; /* Volume Serial Number (A) */ char vname[12]; /* Volume Name (A) */ } BPB; typedef struct tagFATSYS { int fd; /* Volume file descriptor */ BPB bpb; /* BPB info */ uint32_t fatoff; /* Offset of FAT */ uint32_t rootoff; /* Offset of root directory */ uint32_t dataoff; /* Offset of DATA */ uint32_t clulen; /* Cluster length as bytes */ uint8_t *fat; /* FAT sectors */ uint8_t *rootdir; /* Root sectors */ /* ... */ } FATSYS; #pragma pack(1) typedef struct tagDIR_ENTRY { unsigned char name[8]; unsigned char ext[3]; uint8_t attr; char reserved1[1]; uint8_t crtime_ms; uint16_t crdate; uint16_t crtime; uint16_t rdtime; char reserved2[2]; uint16_t wrtime; uint16_t wrdate; uint16_t fclu; uint32_t size; } DIR_ENTRY; #pragma pack() /* Function prototypes */ int read_bpb(int fd, BPB *bpb); FATSYS *open_fatsys(const char *path); int close_fatsys(FATSYS *fatsys); int read_cluster(FATSYS *fatsys, uint32_t clu, void *buf); int wite_cluster(FATSYS *fatsys, uint32_t clu, const void *buf); uint16_t *getclu_chain16(FATSYS *fatsys, uint32_t firstclu, uint16_t *count); uint16_t *getclu_chain12(FATSYS *fatsys, uint32_t firstclu, uint16_t *count); void freeclu_chain(uint16_t *chain); DIR_ENTRY *get_rootents(FATSYS *fatsys, uint16_t *count); #endif /* fatsys.c */ #include #include #include #include #include #include "fatsys.h" int read_bpb(int fd, BPB *bpb) { uint8_t bsec[512]; if (read(fd, bsec, 512) == -1) return -1; bpb->bps = *(uint16_t *)(bsec + 0x0B); bpb->spc = *(uint8_t *)(bsec + 0x0D); bpb->rsects = *(uint16_t *)(bsec + 0x0E); bpb->fatlen = *(uint16_t *)(bsec + 0x16); bpb->rootlen = *(uint16_t *)(bsec + 0x11) * FILE_INFO_LENGTH / bpb->bps; bpb->nfats = *(uint8_t *)(bsec + 0x10); if (*(uint16_t *)(bsec + 0x13)) bpb->tsects = *(uint16_t *)(bsec + 0x13); else bpb->tsects = *(uint32_t *)(bsec + 0x20); bpb->mdes = *(bsec + 0x15); bpb->spt = *(uint16_t *)(bsec + 0x18); bpb->rootents = *(uint16_t *)(bsec + 0x11); bpb->nheads = *(uint16_t *)(bsec + 0x1A); bpb->hsects = *(uint16_t *)(bsec + 0x1C); bpb->tph = (uint16_t)(bpb->tsects / bpb->spt / bpb->nheads); bpb->fatloc = bpb->rsects; bpb->rootloc = bpb->rsects + bpb->fatlen *bpb->nfats; bpb->dataloc = bpb->rootloc + bpb->rootlen; bpb->datalen = bpb->tsects - bpb->dataloc; bpb->serial = *(uint32_t *)(bsec + 0x27); memcpy(bpb->vname, bsec + 0x2B, 11); bpb->vname[11] = '\0'; return 0; } FATSYS *open_fatsys(const char *path) { FATSYS *fatsys; int fd; if ((fatsys = (FATSYS *)malloc(sizeof(FATSYS))) == NULL) return NULL; if ((fd = open(path, O_RDWR)) == -1) goto EXIT1; if (read_bpb(fd, &fatsys->bpb) == -1) goto EXIT2; fatsys->fd = fd; fatsys->fatoff = fatsys->bpb.fatloc * fatsys->bpb.bps; fatsys->rootoff = fatsys->bpb.rootloc * fatsys->bpb.bps; fatsys->dataoff = fatsys->bpb.dataloc * fatsys->bpb.bps; fatsys->clulen = fatsys->bpb.bps * fatsys->bpb.spc; if ((fatsys->fat = (uint8_t *)malloc(fatsys->bpb.fatlen * fatsys->bpb.bps)) == NULL) goto EXIT2; if ((fatsys->rootdir = (uint8_t *)malloc(fatsys->bpb.rootlen * fatsys->bpb.bps)) == NULL) goto EXIT3; if (lseek(fatsys->fd, fatsys->fatoff, SEEK_SET) == -1) goto EXIT4; if (read(fd, fatsys->fat, fatsys->bpb.fatlen * fatsys->bpb.bps) == -1) goto EXIT4; if (lseek(fatsys->fd, fatsys->rootoff, SEEK_SET) == -1) goto EXIT4; if (read(fd, fatsys->rootdir, fatsys->bpb.rootlen * fatsys->bpb.bps) == -1) goto EXIT4; return fatsys; EXIT4: free(fatsys->rootdir); EXIT3: free(fatsys->fat); EXIT2: close(fd); EXIT1: free(fatsys); return NULL; } int close_fatsys(FATSYS *fatsys) { free(fatsys->fat); if (close(fatsys->fd) == -1) return -1; free(fatsys); return 0; } int read_cluster(FATSYS *fatsys, uint32_t clu, void *buf) { if (lseek(fatsys->fd, fatsys->dataoff + (clu - 2) * fatsys->clulen, SEEK_SET) == -1) return -1; return read(fatsys->fd, buf, fatsys->clulen); } int write_cluster(FATSYS *fatsys, uint32_t clu, const void *buf) { if (lseek(fatsys->fd, fatsys->dataoff + (clu - 2) * fatsys->clulen, SEEK_SET) == -1) return -1; return write(fatsys->fd, buf, fatsys->clulen); } uint16_t *getclu_chain16(FATSYS *fatsys, uint32_t firstclu, uint16_t *count) { uint16_t clu, n; uint16_t *chain, *temp; uint32_t capacity; clu = firstclu; capacity = CHAIN_DEF_CAPACITY; n = 0; if ((chain = (uint16_t *)malloc(sizeof(uint16_t) * CHAIN_DEF_CAPACITY)) == NULL) return NULL; do { chain[n++] = clu; if (n == capacity) { capacity *= 2; if ((temp = realloc(chain, sizeof(uint16_t) * capacity )) == NULL) { free(chain); return NULL; } chain = temp; } clu = *(uint16_t *)(fatsys->fat + clu * 2); } while (clu < 0xFFF8); *count = n; return chain; } uint16_t *getclu_chain12(FATSYS *fatsys, uint32_t firstclu, uint16_t *count) { uint16_t clu, word, n; uint16_t *chain, *temp; uint32_t capacity; clu = firstclu; capacity = CHAIN_DEF_CAPACITY; n = 0; if ((chain = (uint16_t *)malloc(sizeof(uint16_t) * CHAIN_DEF_CAPACITY)) == NULL) return NULL; do { chain[n++] = clu; if (n == capacity) { capacity *= 2; if ((temp = realloc(chain, sizeof(uint16_t) * capacity )) == NULL) { free(chain); return NULL; } chain = temp; } word = *(uint16_t *)(fatsys->fat + clu * 3 / 2); clu = clu % 2 == 0 ? word & 0x0FFF : word >> 4; } while (clu < 0xFF8); *count = n; return chain; } void freeclu_chain(uint16_t *chain) { free(chain); } DIR_ENTRY *get_rootents(FATSYS *fatsys, uint16_t *count) { DIR_ENTRY *dent, *temp; DIR_ENTRY *dents; uint32_t capacity; uint16_t n; if ((dents = (DIR_ENTRY *)malloc(DIR_ENTRY_SIZE * ROOT_DEF_CAPACITY)) == NULL) return NULL; n = 0; capacity = ROOT_DEF_CAPACITY; dent = (DIR_ENTRY *)fatsys->rootdir; for (uint16_t i = 0; i < fatsys->bpb.rootents; ++i) { if (dent[i].name[0] == 0) break; if (dent[i].name[0] == 0xE5 || (dent[i].attr & 0XF) == 0x0F) continue; if (n == capacity) { capacity *= 2; if ((temp = realloc(dents, DIR_ENTRY_SIZE * capacity )) == NULL) { free(dents); return NULL; } dents = temp; } dents[n++] = dent[i]; } *count = n; return dents; } /* app.c */ #include #include #include #include #include #include "fatsys.h" void exit_sys(const char *msg); int main(int argc, char *argv[]) { FATSYS *fatsys; unsigned char buf[8192]; uint16_t count; uint16_t *chain; DIR_ENTRY *dents; if (argc != 2) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } if ((fatsys = open_fatsys(argv[1])) == NULL) exit_sys("open_fatsys"); if ((dents = get_rootents(fatsys, &count)) == NULL) { fprintf(stderr, "cannot get root entries!...\n"); exit(EXIT_FAILURE); } for (int i = 0; i < count; ++i) { for (int k = 0; k < 8; ++k) if (dents[i].name[k] != ' ') putchar(dents[i].name[k]); if (dents[i].ext[0] != ' ') putchar('.'); for (int k = 0; k < 3; ++k) if (dents[i].ext[k] != ' ') putchar(dents[i].ext[k]); putchar('\n'); } close_fatsys(fatsys); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } İşletim sistemi bir dizin dosyası içerisindeki 32'lik girişlerini gözden geçirirken dosya isminin ilk karakterini '\0' karakter olarak gördüğünde (yani sayısal 0 değeri) işlemini sonlandırmaktadır. Yani dizin dosyası içerisindeki bütün girişlerin gözden geçirilmesine gerek yoktur. Yukarıda da belirttiğimiz gibi dosya isminin ilk karakteri 0xE5 ise işletim sistemi bu 32'lik girişi de silinmiş dosya olduğu gerekçesiyle geçmektedir. İşletim sistemlerinde bir yol ifadesi verildiğinde o yol ifadesinin hedefindeki dosya ya da dizine ilişkin dizin girişinin elde edilmesine "yol ifadelerinin çözümlenmesi (pathname resolution)" denilmektedir. Yol ifadelerinin çözümlenmesi eğer yol ifadesi mutlaksa kök dizinden itibaren, göreli ise prosesin çalışma dizininden itibaren yapılmaktadır. Örneğin FAT dosya sistemine ilişkin "\a\b\c\d.dat" biçiminde bir yol ifadesi verilmiş olsun. Burada hedeflenen "d.dat" dosyasına ilişkin dizin girişi bilgileridir. Ancak bunun için önce kök dizinde "a" girişi, sonra "a" dizininde "b" girişi, sonra "b" dizininde "c" girişi sonra da "c" girişinde "d.dat" girişi bulunmalıdır. Tabii biz burada Windows'taki bir yol ifadesini temel aldık. UNIX/Linux sistemlerinde dosya sistemleri mount edildiği için bu yol ifadesi aslında mount noktasına görelidir. İşletim sistemleri bir yol ifadesini çözümlerken yol ifadesindeki tüm yol bileşenlerine ilişkin dizin giriş bilgilerini de bir cache sisteminde saklamaktadır. İşletim sistemlerinin oluşturduğu bu cache sistemine "directory entry cache" ya da kısaca "dentry cache" denilmektedir. Örneğin prgramcı aşağıdaki gibi bir yol ifadesi kullanmış olsun: "\a\b\c\d.dat" İşletim sistemi buradaki "a", "b", "c" ve "d.dat" dosyalarına ilişkin dizin giriş bilgilerini bir cache sisteminde saklamaktadır. Böylece benzer yol ifadeleri için hiç disk okuması yapılmadan bu cache sisteminden bu bilgiler elde edilebilmektedir. Pekiyi FAT dosya sistemi için yol ifadelerini çözen basit yalın bir kodu nasıl yazabiliriz? Bizim bir yol ifadesi verildiğinde o yol ifadesini parse edip oradaki yol bileşenlerini elde edebilmemiz gerekir. Sonra dizinler bir dosya olduğuna göre dizinlere ilişkin cluster zincirinde diğer bileşenin aranması gerekir. İşlemler böyle devam ettirilir. FAT dosya sistemi için yol ifadesini çözümleyen bir fonksiyonun parametrik yapısı şöyle olabilir: int resolve_path(FATSYS *fatsys, const char *path, DIRECTORY_ENTRY *de); Biz mutlak yol ifadelerini çözümleyecek olalım. Her dizinin bir cluster zinciri vardır. Ancak FAT12 ve FAT16 sistemlerinde kök dizinin bir cluster zinciri yoktur. Kök dizinin yeri ve uzunluğu baştan bellidir. >> "ext" Dosya Sistemi : UNIX/Linux sistemlerinde i-node tabanlı dosya sistemleri kullanılmaktadır. i-node tabanlı dosya sistemlerinin temel organizasyonu FAT dosya sistemlerinden oldukça farklıdır. i-node tabanlı dosya sistemlerinin çeşitli varyasyonu vardır. Linux sistemleri ve BSD sistemleri ağırlıklı olarak ext (extended file system) denilen dosya sistemini kullanmaktadır. ext dosya sistemi ilk kez 1992 yılında tasarlanmıtır. Sonra zaman içerisinde bu dosya sisteminin ext2, ext3 ve ext4 biçiminde çeşitli varyasyonları oluşturulmuştur. Bugün artık genellikle bu ailenin son üyesi olan ext4 dosya sistemi kullanılmaktadır. Ancak yukarıda da belirttiğimiz gibi i-node tabanlı dosya sistemleri bir aile belirtmektedir. Bu ailenin FAT sistemlerinde olduğu gibi genel tasarımı birbirine benzerdir. Biz burada ext dosya sisteminin en uzun süre kullanılan versiyonu olan ext konusunda temel bilgiler vereceğiz. ext2 dosya sisteminin resmi dokümantasyonuna aşağıdaki bağlantıdan erişebilirsiniz: https://cscie28.dce.harvard.edu/lectures/lect04/6_Extras/ext2-struct.html ext2 dosya sistemi üzerinde incelemeler ve denemeler yapmak için yine loop aygıtlarından faydalanabilrisiniz. Bunun için yine önce içi sıfırlarla dolu bir dosya oluşturulur: $ dd if=/dev/zero of=ext2.dat bs=512 count=400000 Sonra loop aygıt sürücüsü bu dosya için hazırlanır: $ sudo losetup /dev/loop0 ext2.dat Artık aygıt formatlanabilir: $ mkfs.ext2 /dev/loop0 Mount işlemi aşağıdaki gibi yapılabilir: $ mkdir ext2 $ sudo /dev/loop0 ext2 i-node tabanlı dosya sistemlerinde volüm kabaca aşağıdaki bölümlere ayrılmaktadır: -> -> -> -> Boot blok (boot block) işletim sistemini boot eden kodların bulunduğu bloktur. Süper blok (super block) FAT dosya sistemlerindeki boot sektör BPB alanına benzemektedir. Yani burada dosya sistemine ilişkin meta data bilgiler bulunmaktadır. i-node blok i-node elemanlarından oluşmaktadır. Data block FAT dosya sistemindeki Data bölümü gibidir. FAT dosya sistemindeki "cluster" yerine i-node tabanlı dosya sistemlerinde "blok (block)" terimi kullanılmaktadır. Bir blok bir dosyanın parçası olabilecek en küçük birimdir. Süper blok hemen volümün 1024 byte offset'inde bulunmaktadır. (yani volümün başında boot sektör programları için 1024 byte yer ayrılmıştır. Süper blok süper bloktta belirtilen blok uzunluğu kadar uzunluğa sahiptir. Ayrıca izleyen paragraflarda da görüleceği gibi ext2 dosya sisteminde her blok grupta süper bloğun bir kopyası da bulunmaktadır.) Yukarıda da belirttiğimiz gibi burada volüm hakkında meta data bilgileri bulunmaktadır. Buradaki alanlar ext2 dokümantasyonunda ayrıntılarıyla açıklanmıştır. Süper blok içerisindeki alanlar aşağıdaki gibidir: | Alan Boyut (Byte) Açıklama |-----------------------|----|----------------------------------------------------------------------------------------- | s_inodes_count | 4 | Dosya sistemindeki toplam inode sayısı. | s_blocks_count | 4 | Dosya sistemindeki toplam blok sayısı. | s_r_blocks_count | 4 | Rezerve edilmiş blok sayısı. | s_free_blocks_count | 4 | Boş blok sayısı. | s_free_inodes_count | 4 | Boş inode sayısı. | s_first_data_block | 4 | İlk veri bloğunun numarası (bu, kök dizinin bulunduğu blok). | s_log_block_size | 4 | Blok boyutunun logaritmasının değeri (örneğin, 1 KB için 10, 4 KB için 12, vs.). | s_log_frag_size | 4 | Parçacık boyutunun logaritması. | s_blocks_per_group | 4 | Her blok grubundaki blok sayısı. | s_frags_per_group | 4 | Her blok grubundaki fragman sayısı. | s_inodes_per_group | 4 | Her blok grubundaki inode sayısı. | s_mtime | 4 | Dosya sisteminin son değiştirilme zamanı (Unix zaman damgası). | s_wtime | 4 | Dosya sisteminin son yazılma zamanı (Unix zaman damgası). | s_mnt_count | 2 | Dosya sisteminin kaç kez bağlandığı (mount) sayısı. | s_max_mnt_count | 2 | Dosya sisteminin kaç kez daha bağlanabileceği (yani, montaj sayısı aşımı). | s_magic | 2 | Süper blok sihirli sayısı (bu, EXT2 dosya sistemini tanımlar ve genellikle `0xEF53`'tür). | s_state | 2 | Dosya sisteminin durumu (örneğin, temiz mi, hata mı). | s_errors | 2 | Hata durumunda yapılacak işlem (örneğin, “ignore”, “panic”, vb.). | s_minor_rev_level | 2 | Küçük revizyon seviyesi (EXT2’yi güncelleyen küçük değişiklikler için). | s_lastcheck | 4 | Dosya sisteminin son kontrol tarihi (Unix zaman damgası). | s_checkinterval | 4 | Dosya sisteminin kontrol edilmesi gereken süre (saniye cinsinden). | s_creator_os | 4 | Dosya sistemini oluşturan işletim sistemi türü (örneğin, Linux, Solaris, vb.). | s_rev_level | 4 | EXT2 dosya sistemi revizyon seviyesi. | s_def_resuid | 2 | Varsayılan rezerv kullanıcı ID'si (uid). | s_def_resgid | 2 | Varsayılan rezerv grup ID'si (gid). | s_first_ino | 4 | İlk inode numarası (genellikle kök dizini için). | s_inode_size | 2 | Inode boyutu (genellikle 128 veya 256 byte). | s_block_group_nr | 2 | Bu süper blok ile ilişkili blok grubu numarası. | s_feature_compat | 4 | Uyumluluk özelliklerinin bit maskesi. | s_feature_incompat | 4 | Uyumsuz özelliklerin bit maskesi. | s_feature_ro_compat | 4 | Okuma-yazma uyumsuz özelliklerinin bit maskesi. | s_uuid | 16 | Dosya sisteminin benzersiz tanımlayıcısı (UUID). | s_volume_name | 16 | Dosya sistemi adının (etiketinin) olduğu alan. | s_last_mounted | 64 | Dosya sisteminin son bağlandığı dizin yolu. | s_algorithm_usage_bmp | 4 | Bloklar ve inode'lar için kullanılan algoritmaların bit maskesi. | s_prealloc_blocks | 1 | Önceden tahsis edilecek blok sayısı. | s_prealloc_dir_blocks | 1 | Önceden tahsis edilecek dizin blokları sayısı. | s_padding | 118| Alanın sonundaki boşluk (süper bloğun uzunluğunu tamamlar). Buradaki önemli alanlar hakkında kısa bazı açıklamalar yapmak istiyoruz: -> s_inodes_count: Bu alanda dosya sistemindeki toplam i-node elemanlarının sayısı bulunmaktadır. Bir ext2 disk bölümünde en fazla buradaki i-node elemanlarının sayısı kadar farklı dosya bulunabilir. -> s_blocks_count: Burada Data bölümündeki toplam blokların sayısı bulunmaktadır. -> s_r_blocks_count: Burada ayrılmış (reserve edielmiş) blokların sayısı bulunmaktadır. -> s_free_blocks_count: Burada Data bölümünde kullanımayan boş blokların sayısı tutulmaktadır. -> s_log_block_size: Burada 1024 değerinin 2 üzeri kaçla çarpılacağını belirten değer tutulmaktadır. Yani blok uzunluğu 1024 << s_log_block_siz biçiminde hesaplanmaktadır. Örneğin burada 2 değeri yazılıyorsa blok uzunluğu, 2^2 * 1024 = 4096 byte'tır. -> s_inode_size: Burada bir i-ndeo elemanının kaç byte olduğu bilgisi yer almaktadır. Örnek dosya sistemimizde i-node elemanları 256 byte uzunluğundadır. ext2 dosya sisteminin super block bilgisi ve bazı önemli alanlarına ilişkin bilgiler "dumpe2fs" isimli utility programla elde edilebilir. Örneğin: $ dumpe2fs /dev/loop0 Aşağıda bir ext2 süper bloğunun örnek bir içeriği verilmektedir: 00004000 60 c3 00 00 50 c3 00 00 c4 09 00 00 f4 b6 00 00 |`...P...........| 00000410 55 c3 00 00 00 00 00 00 02 00 00 00 02 00 00 00 |U...............| 00000420 00 80 00 00 00 80 00 00 b0 61 00 00 51 5c 2e 67 |.........a..Q\.g| 00000430 51 5c 2e 67 01 00 ff ff 53 ef 00 00 01 00 00 00 |Q\.g....S.......| 00000440 42 5c 2e 67 00 00 00 00 00 00 00 00 01 00 00 00 |B\.g............| 00000450 00 00 00 00 0b 00 00 00 00 01 00 00 38 00 00 00 |............8...| 00000460 02 00 00 00 03 00 00 00 ec 89 02 3e a8 11 4c 01 |...........>..L.| 00000470 b0 b5 f5 48 1e 30 79 d6 00 00 00 00 00 00 00 00 |...H.0y.........| 00000480 00 00 00 00 00 00 00 00 2f 68 6f 6d 65 2f 6b 61 |......../home/ka| 00000490 61 6e 2f 53 74 75 64 79 2f 55 6e 69 78 4c 69 6e |an/Study/UnixLin| 000004a0 75 78 2d 53 79 73 50 72 6f 67 2f 44 69 73 6b 49 |ux-SysProg/DiskI| 000004b0 4f 2d 46 69 6c 65 53 79 73 74 65 6d 73 2f 65 78 |O-FileSystems/ex| 000004c0 74 32 00 00 00 00 00 00 00 00 00 00 00 00 0c 00 |t2..............| 000004d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000004e0 00 00 00 00 00 00 00 00 00 00 00 00 6e 47 e8 bc |............nG..| 000004f0 81 50 45 f3 bb 96 6c 7c 51 bc e3 8a 01 00 00 00 |.PE...l|Q.......| 00000500 0c 00 00 00 00 00 00 00 42 5c 2e 67 00 00 00 00 |........B\.g....| Burada toplam i-node elemanlarının sayısı 0xC360 (50016) tanedir. Disk bölümünün i-node bloğu i-node elemanlarından oluşmaktadır. Her i-node elemanının ilki 0 olmak üzere bir indeks numarası vardır. Örneğin: 0 i-node elemanı 1 i-node elemanı 2 i-node elemanı 3 i-node elemanı 4 i-node elemanı ... 300 i-node elemanı 301 i-node elemanı 302 i-node elemanı 303 i-node elemanı 304 i-node elemanı ... Bir dosyanın ismi haricindeki bütün bilgileri dosyaya ilişkin i-node elemanında tutulmaktadır. Zaten stat fonksiyonları da aslında bilgileri bu i-node elemanından almaktadır. Her dosyanın diğerlerinden farklı bir i-node numarası olduğuna dikkat ediniz. Dolayısıyla dosyanın i-node numarası o dosyayı karakterize etmektedir. ("ls" komutunda dosyanın i-node numaralarının -i seçeneği ile elde edildiğini anımsayınız.) Burada hatırlatma yapmak amacıyla stat yapısını yeniden vermek istiyoruz: struct stat { dev_t st_dev; /* ID of device containing file */ ino_t st_ino; /* inode number */ mode_t st_mode; /* protection */ nlink_t st_nlink; /* number of hard links */ uid_t st_uid; /* user ID of owner */ gid_t st_gid; /* group ID of owner */ dev_t st_rdev; /* device ID (if special file) */ off_t st_size; /* total size, in bytes */ blksize_t st_blksize; /* blocksize for file system I/O */ blkcnt_t st_blocks; /* number of 512B blocks allocated */ time_t st_atime; /* time of last access */ time_t st_mtime; /* time of last modification */ time_t st_ctime; /* time of last status change */ }; ext2 dosya sisteminde bir i-node elemanın alanları aşağıdaki gibidir: | Alan Boyut (Byte) Açıklama |-------------------------------------------------------------------------------------------------------------------- |i_mode | 2 | Dosya türü ve izinler (örneğin, `S_IFREG` (normal dosya), `S_IFDIR` (dizin), vs.). |i_uid | 2 | Dosya sahibinin kullanıcı kimliği (UID). |i_size_lo | 4 | Dosyanın boyutunun alt 32 biti (byte cinsinden). |i_atime | 4 | Son erişim zamanı (Unix zaman damgası). |i_ctime | 4 | Son inode değişiklik zamanı (Unix zaman damgası). |i_mtime | 4 | Son değişiklik (modifikasyon) zamanı (Unix zaman damgası). |i_dtime | 4 | Dosyanın silinme zamanı (Unix zaman damgası), eğer geçerliyse. |i_gid | 2 | Dosya sahibinin grup kimliği (GID). |i_links_count| 2 | Dosyaya bağlı olan hard link (bağlantı) sayısı. |i_blocks | 4 | Dosyanın disk üzerinde kullandığı blok sayısı (block, 512 byte'lık bloklar). |i_flags | 4 | Dosya bayrakları (örneğin, `i_dirty`, `i_reserved` gibi). |i_osd1 | 4 | Linux spesifik alan (genellikle genişletilmiş özellikler için kullanılır). |i_block[15] | 4 × 15 = 60| Dosyanın bloklarına işaretçi |i_generation | 4 | Dosyanın versiyon numarası (özellikle NFS gibi ağ dosya sistemlerinde kullanılır). |i_file_acl | 4 | Dosya için ACL (Access Control List) blok numarası. |i_dir_acl | 4 | Dizin için ACL blok numarası. |i_faddr | 4 | Dosyanın "fragman adresi" (bu, çoğu zaman sıfırdır ve eski EXT2 uygulamalarında kullanılır). Biz bu alanların büyük çoğunluğunu aslında stat fonksiyonunda görmüştük. Ancak stat yapısında olmayan bazı elemanlar da burada bulunmaktadır. Biz stat yapısında olmayan bazı önemli elemanlar üzerinde durmak istiyoruz: -> i_dtime: Bu alanda eğer dosya silinmişse dosyanın ne zaman silindiğine yönelik tarih zaman bilgisi tutulmaktadır. Buradaki değer 01/01/1970'ten geçen saniye sayısı cinsindedir. -> i_block ve i_blocks: Bu elemanlar izleyen paragraflarda adaha ayrıntılı bir biçimde ele alınacaktır. -> i_flags: Bu alanda ilgili dosyaya ilişkin bazı bayraklar tutulmaktadır. -> i_file_acl: Dosyaya ilişkin "erişim kontrol listesi (access control list)" ile ilgili bilgiler tutulmaktadır. i-node elemanında dosyanın isminin tutulmadığına dikkat ediniz. ext2 dosya sisteminde bir i-node elemanının uzunluğu süper bloğun s_inode_size elemaında yazmaktadır. Örnek sistemimizde i-node elemanları 256 byte uzunluktadır. Pekiyi dosyanın ismi nerededir ve dosyanın i-node numarası nereden elde edilmektedir? Bunu izleyen paragraflarda göreceğiz. Biz yukarıda i-node tabanlı bir disk bölümünün kaba organizasyonunun aşağıdaki gibi olduğunu belirtmiştik: (1024 byte) Burada sanki süper bloktan hemen sonra i-node blok geliyormuş gibi biz organizasyon resmedilmiştir. Halbuki süper bloktan hemen sonra i-node blok gelmemektedir. ext2 dosya sisteminin gerçek yerleşimi aşağıdaki gibidir: (1024 byte) Bir disk bölümü aslında blok gruplarından (block groups) oluşmaktadır. Her block grubu disk bölümüne ilişkin bir bölümü belirtir. Bir block grubu aşağıdaki gibi bir yapıya sahiptir: İşte "blok grup betimleyici tablosu (block group descriptor table)" block group'ları hakkında bilgi veren bir bölümdür. Bir kaç blok bilginin yer aldığı super block'un "s_blocks_per_group" elemanında saklanmaktadır. Her blok grupta belli sayıda i-node elemanı vardır. Bir blok gruptaki i-node elemanlarının sayısı super block'taki s_inodes_per_group elemanıyla belirtilmektedir. Yani aslında ext2 dosya sisteminde aşağıdaki gibi bir oragnizasyon söz konusudur: (1024 byte) (1024 byte) (1024 byte) (1024 byte) (1024 byte) (1024 byte) (1024 byte) (1024 byte) (1024 byte) ... Görüldüğü gibi ext2 dosya sisteminde super bloğun tek bir kopyası yoktur. Her block grupta super blok yeniden yer almaktadır. Bir blok grupta ayrı bir i-node tablosunun ve data bölümünün olduğuna dikkat ediniz. Pekiyi neden ext2 dosya sisteminde disk bölümü birden fazla blok gruplara ayrılmıştır? İşte bunun nedenlerinden biri güvenliktir. Yani i-node bloklardan biri bozulduğunda diğeri bozulmamış biçimde kalabilir. Blok gruplarındaki "blok grup betimelyici tablosu (block group descriptor table)" blok grupları hakkında bazı meta data bilgileri tutmaktadır. Ancak blok grup betimleyicilerinde yalnızca o blok grubuna ilişkin bilgiler değil tüm blok gruplarına ilişkin bilgiler tutulmaktadır. Yani her blok grubunda yeniden tüm blok gruplarına ilişkin bilgiler tutulmaktadır. Block grup betimleyici tablosu blok grup betimleyicilerindne oluşan bir dizi gibidir: Block Grup Betimleyici Tablosu ... Bir blok grup betimleyicisinin alanları şöyledir: +----------------------------+---------------------------+--------------------------------------+ | Yapı Elemanı | Boyut (Byte) | Açıklama | +----------------------------+---------------------------+--------------------------------------+ | bg_block_bitmap | 4 | Blok haritasının başlangıç adresi | | bg_inode_bitmap | 4 | İnode haritasının başlangıç adresi | | bg_inode_table | 4 | İnode tablosunun başlangıç adresi | | bg_free_blocks_count | 2 | Blok grubunda serbest blok sayısı | | bg_free_inodes_count | 2 | Blok grubunda serbest inode sayısı | | bg_used_dirs_count | 2 | Blok grubundaki kullanılan dizin sayısı | | bg_flags | 2 | Blok grubunun bayrakları (flags) | | bg_reserved | 12 | Rezerv alan (genellikle sıfırdır) | +----------------------------+---------------------------+--------------------------------------+ Blok grup betimleyicisi toplamda 32 byte yer kaplamaktadır. Her blok grubunda blok bitmap'in, i-node bitmap'in ve i-node tablosunun yerinin blok numarası tutulmaktadır. Buradaki blok uzunlukları disk bölümünün başından itibaren yer belirtir. Disk bölümü içerisindeki blokların numaralandırılması ile ilgili ince bir nokta vardır. Eğer disk bölümündeki blok uzunluğu 1K yani 1024 byte ise boot block 0'ıncı bloktadır. Dolayısıyla ilk blok grubundaki süper blok 1'inci bloktadır. Ancak blok büyüklüğü 1024'ten (yani 1K'dan) fazla ise bu durumda boot blok ile süper blok tek blok kabul edilmektedir. Boot blok ile süper bloğun bulunduğu ilk bloğun numarası 0'dır. İlk blok grup betimleyici tablosunun yeri de blok grubundaki super block'tan hemen sonradır. Örneğin dosya sistemindeki blok uzunluğu 4K (4096 byte) ise ilk blok betimleyici tablosunun yeri 4096'ıncı = 0x1000 offset'indedir. (Bu durumda boot blok ile süper bloğun 0 numaralı blok biçiminde tek blok olarak ele alındığını anımsayınız.) Bir blok grubunun toplam kapladığı blok sayısı süper block içerisindeki s_blocks_per_group elemanında tutulmaktadır. Örneğin biz k numaralı blok grubun blok numarasını "k * s_blocks_per_group" işlemiyle elde edebiliriz. Aşağıda örnek bir blok grup betimleyici tablosu verilmiştir: 00001000 0e 00 00 00 0f 00 00 00 10 00 00 00 c7 79 a4 61 |.............y.a| 00001010 02 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00001020 0e 80 00 00 0f 80 00 00 10 80 00 00 25 3d b0 61 |............%=.a| 00001030 00 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00001040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| .... Bir blok grup betimleyicisi 32 byte uzunluktadır. Burada toplam iki blok grup betimleyicisi yani disk bölümünde toplam iki blok grubu bulunmaktadır.Bu iki blok grup betimleyicisini ayrı ayrı aşağıda veriyoruz: 00001000 0e 00 00 00 0f 00 00 00 10 00 00 00 c7 79 a4 61 |.............y.a| 00001010 02 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00001020 0e 80 00 00 0f 80 00 00 10 80 00 00 25 3d b0 61 |............%=.a| 00001030 00 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| Disk bölümünde toplam kaç blok grubu olduğu süper bloktaki s_blocks_count elemanında yazmaktadır. Bir blok grubundaki blok grup betimleyici tablosunun uzunluğu 1 blok kadardır. Blok bitmap'in ve I-node bitmap'in uzunlukları doğrudan süper blokta yazmamaktadır. Bu uzunluklar dolaylı bir biçimde hesaplanmaktadır. Dolayısıyla bir blok gruptaki data alanın başlangıç bloğu da dolaylı bir biçimde hesaplanmaktadır. Pekiyi bir dosyanın i-node numarası biliniyorsa onun disk bölümündeki yerini nasıl hesaplarız? Burada bizim bu i-node elemanının hangi blok grubunda ve o blok grubunun i-node tablosunda nerede olduğunu belirlememiz gerekir. i-node tablosundaki i-node elemanları 1'den başlatılmıştır. Yani bizim elimizde i-node numarası n olan bir dosya varsa aslında bu dosya i-node tablosunun n - 1'inci i-node elemanındadır. Çünkü i-node tablosunun ilk i-node elemanının numrası 0 değil 1'dir. Her blok grupta eşit sayıda i-node elemanı bulunmaktadır. Bir blok gruptaki i-node elemanlarının sayısı doğrudan süper bloktaki s_inodes_per_group elemanında belirtilmektedir. Bu durumda ilgili i-node numarasına ilişkin i-node elemanı i-node numarası n olmak üzere (n - 1) / s_inodes_per_group işlemiyle elde edilebilir. Tabii bu durumda (n - 1) % s_inodes_per_group ifadesi de i-node elemanının o blok gruptaki i-node tablosunun kaçıncı elemanında olduğunu verecektir. Anımsanacağı gibi her blok grubunun i-node tablosunun yeri blok grup betimleyicisinin bg_inode_table elemanında belirtiliyordu. Bir blok grubunun toplam kaç tane bloktan oluştuğu süper bloktaki s_blocks_per_group elemanında tutulmaktadır. Dolayısıyla k'ıncı blok grubunun yeri k * s_blocks_per_group değeri ile tespit edilir. Blok numaralarına boot block dahil değildir. Yani ilk blok grubunun süper bloğunun blok numarası 0'dır. Bu durumda manuel olarak n numaralı i-node numarasına sahip bir dosyanın i-node elemanına şöyle erişilebilir: -> Önce n / s_inodes_per_group ile ilgili i-node elemanının hangi blok grubununda olduğu tespit edilir. Bu değer k olsun. -> Bu blok grubunun yeri k * s_blocks_per_group değeri ile elde edilir ve bu bloğa gidilir. Her bloğun başında 1 blokluk süper blok vardır. Süper bloğu blok grup betimleyici tablosu izler. Blok grup betimleyici tablosu blok grup betimleyicilerinden oluşmaktadır. Her blok grup betimleyicisi 32 byte yer kaplamaktadır. Dolayısıyla biz k numaralı blok grubuna ilişkin blok betimleyicisinin yerini k * 32 ile tespit edebiliriz. (Aslında tüm blok gruplarındaki blok grup betimleyici tablolarının birbirinin aynısı olduğunu anımsayınız.) -> İlgili blok grubunun i-node tablosunun yeri blok grup betimleyicisinin bg_inode_table elemanında belirtilmektedir. Artık biz i-node elemanını burada belirtilen bloktan itibaren n % s_inodes_per_group kadar ilerideki i-node elemanı olarak elde edebiliriz. Bir i-node elemanının uzunluğunun 256 byte olduğunu belirtmiştik. Şimdi 12'inci i-node elemanın yerini bu adımlardan geçerek bulmaya çalışalım. Elimizdeki disk bölümünde bir blok grupta toplam 25008 tane i-node elemanı vardır. O halde 12 numaralı i-node elemanı 0'ıncı blok grubunun 11'inci i-node elemanındadır. 0'ınci blok grubu eğer blok uzunluğu 1K'dan fazla ise diskin 0'ıncı bloğundan başlamaktadır. (Tabii 0'sıncı bloğın hemen başında boot blok, ondan 1024 byte sonra da 0'ın blok grubunun süper bloğu bulunmaktadır.) O halde elimizdeki disk bölümünün 0'ıncı blok grubunun blok betimleyici tablosu 1'inci bloktadır. Bunun yeri de bir blok 4096 byte olduğuna göre 0x1000 offset'indedir. Buradan elde edilen blok grup betimleyici tablosu şöyledir: 00001000 0e 00 00 00 0f 00 00 00 10 00 00 00 c7 79 a4 61 |.............y.a| 00001010 02 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00001020 0e 80 00 00 0f 80 00 00 10 80 00 00 25 3d b0 61 |............%=.a| 00001030 00 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00001040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00001050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| ... Her blok grup betimleyicisinin 32 byte olduğunu anımsayınız. Bu duurmda 0'ıncı blok grup betimleyicisi şöyledir: 00001000 0e 00 00 00 0f 00 00 00 10 00 00 00 c7 79 a4 61 |.............y.a| 00001010 02 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| Bu blok grubundaki i-node tablosunun blok numarası blok grup betimleyicisinin 8'inci offset'inde bulunan bg_inode_table elemanındadır. Bu elemandaki değer 0x00000010 (16)'dır. O halde bizim 16 numaralı bloğa gitmemiz gerekir. 16 numaralı blok disk bölümünün 16 * 4096 = 65536 (0x10000) offset'indedir. Artık bu offset'te ilgili blok grubundaki i-node elemanları bulunmaktadır. Bir i-node elemanı 128 byte olduğuna göre 11'inci elemanının yeri 11 * 256 = 2816 (0xB00) byte ileridedir. O halde bu tablonun disk bölümünün başından itibarenki yeri 65536 + 2816 = 68352 (0x10B00) offset'indedir. Aşağıda ilgili i-node elemanının 256 byte'lık içeriği görülmektedir: 00010b00 a4 81 00 00 c8 79 00 00 87 5c 2e 67 87 5c 2e 67 |.....y...\.g.\.g| 00010b10 87 5c 2e 67 00 00 00 00 00 00 01 00 40 00 00 00 |.\.g........@...| 00010b20 00 00 00 00 01 00 00 00 00 08 00 00 01 08 00 00 |................| 00010b30 02 08 00 00 03 08 00 00 04 08 00 00 05 08 00 00 |................| 00010b40 06 08 00 00 07 08 00 00 00 00 00 00 00 00 00 00 |................| 00010b50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00010b60 00 00 00 00 2e db 09 7c 00 00 00 00 00 00 00 00 |.......|........| 00010b70 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00010b80 20 00 00 00 c8 76 3d ba c8 76 3d ba c8 76 3d ba | ....v=..v=..v=.| 00010b90 87 5c 2e 67 c8 76 3d ba 00 00 00 00 00 00 00 00 |.\.g.v=.........| 00010ba0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00010bb0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00010bc0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00010bd0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00010be0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00010bf0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| Buradaki ilk WORD bilgi (0x81A4) dosyanın erişim haklarını sonraki WORD bilgi (0x0000) kullanıcı id'sini belirtmektedir. Sonraki DWORD bilgi (0x000079C8) de dosyanın uzunluğunu belirtmektedir. Diğer elemanların anlamlarına i-node yapısından erişebilirsiniz. i-node tablosundaki ilk n tane i-node elemanı reserved biçimde tutulmaktadır. Bunaların sayısı süper bloktaki s_first_ino elemanında belirtilmektedir. Üzerinde çalıştığımız dosya sisteminde s_first_ino değeri 11'dir. Yani ilk 10 i-node elemanı reserve edilmiştir. İlk i-node elemanının numarası 11'dir. Örnek dosya sistemimizdeki durum şöyledir: 0. Blok Grubunun i-node Tablosu <1 numaralı i-node elemanı> <2 numaralı i-node elemanı> <3 numaralı i-node elemanı> ... <10 numaralı i-node elemanı> <11 numaralı i-node elemanı (ilk reserved olmaayan eleman)> ... Reserve edilmiş ilk i-node elemanlarının anlamları şöyledir: XT2_BAD_INO 1 bad blocks inode EXT2_ROOT_INO 2 root directory inode EXT2_ACL_IDX_INO 3 ACL index inode (deprecated?) EXT2_ACL_DATA_INO 4 ACL data inode (deprecated?) EXT2_BOOT_LOADER_INO 5 boot loader inode EXT2_UNDEL_DIR_INO 6 undelete directory inode Peekiyi ext2 dosya sisteminde bir dosyanın parçalarının (yani bloklarının) nerelerde olduğu bilgisi nerede tutulmaktadır? Anımsanacağı gibi FAT dosya sisteminde dosyanın parçalarının (orada block yerine cluster terminin kullanıldığını anımsayınız) diskin hangi cluster'larında olduğu FAT bölümünde saklanıyordu. İşte yalnızca ext2 dosya sisteminde değil i-node tabanlı dosya sistemlerinde bir dosyanın diskte hangi bloklarda bulunduğu i-node elemanın içerisinde tutulmaktadır. i-node elemanlarının genel olarak 1228 byte ya da 256 byte uzunlukta olduğunu anımsayınız. Büyük bir dosyanın blok numaralarının bu kadar alana sığmayacağı açıktır. PPekiyi o zaman dosyanın blok numaraları i-node elemanında nasıl tutulmaktadır? İşte i-node elemanında dosyanın hangi bloklerda olduğu "doğrudan (direct)", "dolaylı (indriect)", "çift dolaylı (double indirect)", "üç dolaylı (triple indirect)" bloklarda tutulmaktadır. i-node elemanının demimal 40'ncı offset'inde (i-node elemanın 0x28 offset'indek, "i_blocks" isimli elemanında) 15 elemanlık her biri DWORD değerlerden oluşan bir dizi vardır. Bu diziyi şöyle gösterebiliriz: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 çift dolaylı blok numarası> 14 <üç dolaylı blok numarası> Bu durumda eğer dosya 12 blok ya da ondan daha küçükse zaten dosyanın parçalarının blok numaraları bu diznini ilk 12 elemanından doğrudan elde edilmektedir. Eğer dosya 12 bloktan büyükse bu durumda bu dizinin 12'indeksindeki elemanda yazan blok numarası dosyanın diğer bloklarının blok numaralarını tutan bloğun numarasıdır. Yani dizinin 12'inci elemanında blierilen bloğa gidildiğinde bu bloğun içerisinde blok numaraları vardır. Bu blok numaraları da dosyanın 12'inci bloğundan itibaren bloklarının numaralarını belirtmektedir. Örneğin bir blok 4096 byte olsun. Bu durumda bir blokta 1024 tane blok numarası olabilir. 12 blok numarası doğrudan olduğuna göre dolaylı blokla toplam dosyanın 1024 + 12 = 1036 tane bloğunun yeri tutulmuş olacaktır. Pekiyi ya bu sistemde dosya 1026 bloktan daha büyükse? İşte bu durumda çift dolaylı blok numarasına başvurulmaktadır. Çift dolaylı blok numarasına ilişkin bloğa gidildiğinde oradaki blok numaraları dosyanın blok numaraları değil dosyanın blok numaralarının turulduğu blok numaralarıdır. Eğer dosya çift dolaylı bloklara sığmıyorsa üç dolaylı bloğa başvurulmaktadır. Üç dolaylı blokta belirtilen blok numarasında çift dolaylı blokların numaraları vardır. Çift dolaylı blokların içerisinde dolaylı blokların numaraları vardır. Nihayet dolaylı blokların içerisinde de asıl blokların numaraları vardır. Pekiyi her bloğun 4K uzunluğunda olduğu bir sistemde bir dosyanın i-node elemanında belirtilen maksimum uzunluğu ne olabilir? İşte bu uzunluk aşağıdaki değerlerin toplamıyla elde edilebilir: 12 tane doğrudan blok = 12 * 4096 1 tane dolaylı blok = 1024 * 4096 1 tane çift dolaylı blok = 1024 * 1024 * 4096 1 tane üç dolaylı blok = 1024 * 1024 * 1024 * 4096 Toplam = 12 * 4096 + 1024 * 4096 + 1024 * 12024 * 4096 + 1024 * 1024 * 1024 * 4096 = 4448483065856 = 4 TB civarı. Şimdi aşağıdaki i-node elemanına bakıp dosya bloklarının yerlerini tespit edelim: 00010b00 a4 81 00 00 c8 79 00 00 87 5c 2e 67 87 5c 2e 67 |.....y...\.g.\.g| 00010b10 87 5c 2e 67 00 00 00 00 00 00 01 00 40 00 00 00 |.\.g........@...| 00010b20 00 00 00 00 01 00 00 00 00 08 00 00 01 08 00 00 |................| 00010b30 02 08 00 00 03 08 00 00 04 08 00 00 05 08 00 00 |................| 00010b40 06 08 00 00 07 08 00 00 00 00 00 00 00 00 00 00 |................| 00010b50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00010b60 00 00 00 00 2e db 09 7c 00 00 00 00 00 00 00 00 |.......|........| 00010b70 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00010b80 20 00 00 00 c8 76 3d ba c8 76 3d ba c8 76 3d ba | ....v=..v=..v=.| 00010b90 87 5c 2e 67 c8 76 3d ba 00 00 00 00 00 00 00 00 |.\.g.v=.........| 00010ba0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00010bb0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00010bc0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00010bd0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00010be0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00010bf0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| Burada dosyanın tüm blokları 0x28'inci offset'teki doğrudan bloklarda belirtilmektedir: 00 08 00 00 => 0x800 01 08 00 00 => 0x801 02 08 00 00 => 0x802 03 08 00 00 => 0x803 04 08 00 00 => 0x804 05 08 00 00 => 0x805 06 08 00 00 => 0x806 07 08 00 00 => 0x807 Dosya 0x4 offset'inde belirtilen 0x79C8 = 31176 byte uzunluğundadır. Bu sistemde bir blok 4K olduğuna göre toplam dosyanın parçalarının 8 blok olması gerekmektedir. İşte burada söz konusu dosyanın blokları disk bölümünün başından itibaren 0x800, 0x801, 0x802, 0x803, 0x804, 0x805, 0x806 ve 0x807'inci bloklardadır. Söz konusu sistemde bir blok 4096 byte olduğuna göre dosyanın ilk bloğunun offset numarası "0x800 * 0x1000 = 0x800000" biçimindedir. Şimdi de ext2 dosya sisteminde dizin organizasyonu üzerinde duralım. Tıpkı FAT dosya sistemlerinde olduğu gibi ext2 dosya sisteminde de dizinler birer dosya gibi organize edilmiştir. (Anımsanacağı gibi dizin dosyalarından biz opendir, readdir, closedir fonksiyonlarıyla okuma yapabiliyorduk.) Yani dizinler aslında birer dosya gibidir. Dizin dosyaları "dizin girişleri (directory entries)" denilen girişlerden oluşmaktadır. ... Bir dizin girişin format şöyledir: +----------------+--------------+-------------------------+ | Offset (bytes) | Size (bytes) | Açıklama | +----------------+--------------+-------------------------+ | 0 | DWORD | i-node numarası | | 4 | WORD | Girişin toplam uzunluğu | | 6 | BYTE | Dosya isminin uzunluğu | | 7 | BYTE | Dosyanın türü | | 8 | 0-255 Bytes | Dosya ismi | +----------------+--------------+-------------------------+ Aslında buradaki bilgiler Linux'taki readdir POSIX fonksiyonu ile de alınabilmektedir. readdir fonksiyonu POSIX standartlarına göre en az iki elemana sahip olmak zorundadır. Bunlar d_ino ve d_name elemanlarıdır. Ancak Linux'taki read bize daha fazla bilgi vermektedir. Linux'taki dirent yapısı şöyledir: struct dirent { ino_t d_ino; /* Inode number */ off_t d_off; /* Not an offset; see below */ unsigned short d_reclen; /* Length of this record */ unsigned char d_type; /* Type of file; not supported by all filesystem types */ char d_name[256]; /* Null-terminated filename */ }; Dizin girişleri FAT dosya sistemindeki gibi eşit uzunlukta girişlerden oluşmamaktadır. Bunun nedeni dosya isimlerinin 0 ile 255 karakter arasında değişebilmesidir. Dizin girişlerinin hemen başında DWORD bir alanda dosyanın i-node numarası belirtilmektedir. Dizinler değişken uzunlukta olduğu için ilgili girişin toplam kaç byte uzunlukta olduğu sonraki WORD elemanda tutulmaktadır. Girişteki dosya isminin uzunluğu ise sonraki BYTE elemanında tutulmaktadır. Dosyanın türü hiç i-node elemanına erişmeden elde edilebilsin diye dizin girişlerinde de tutulmaktadır. Dosya türlerini belirten değerler şöyledir: +------------------+-------+-------------------+ | İsim | Değer | Anlamı | +------------------+-------+-------------------+ | EXT2_FT_UNKNOWN | 0 | Unknown File Type | | EXT2_FT_REG_FILE | 1 | Regular File | | EXT2_FT_DIR | 2 | Directory File | | EXT2_FT_CHRDEV | 3 | Character Device | | EXT2_FT_BLKDEV | 4 | Block Device | | EXT2_FT_FIFO | 5 | Buffer File | | EXT2_FT_SOCK | 6 | Socket File | | EXT2_FT_SYMLINK | 7 | Symbolic Link | +------------------+-------+-------------------+ Pekiyi ext2 dosya sisteminde işletim sistemi bir yol ifadesini nasıl çözümlemektedir? Örneğin "/a/b/c.txt" gibi bir yol ifadesinde "c.txt" dosyasının i-node elemanına nasıl erişmektedir? İşte kök dizin dosyasının bilgileri 2 numaralı i-node elemanındadır. İşletim sistemi önce kök dizinin i-node elemanını elde eder. Oradan kök dizinin bloklarına erişir. O bloklar içerisinde ilgili girişi arar. İşlemlerini bu biçimde devam ettirir. Örneğin "/a/b/c.txt" dosyasının i-node elemanına erişmek için önce kök dizinde "a" girişini arar. Sonra "a" girişinin dizin olduğunu doğrular. Sonra "a" dizininde "b" girişini arar. "b" girişinin de dosya olduğunu doğrular. Sonra "b" girişinin içerisinde "c.txt" arar ve hedef dosyanın i-node bilgilerine erişir. Şimdi adım adım elimizdeki disk bölümünde "/a/b/c.txt" dosyasının yerini bulmaya çalışalım. Tabii buradaki kök dizin aslında mount edilmiş dosya sisteminin köküdür. Biz bu dosya sistemini kursumuzda aşağıdaki noktaya mount ettik: "/home/kaan/Study/UnixLinux-SysProg/DiskIO-FileSystems" Kök dizinin i-node elemanı (2 numaralı i-node elemanı) aşağıda verilmiştir: 00010100 ed 41 00 00 00 10 00 00 a8 69 4c 67 a7 69 4c 67 |.A.......iLg.iLg| 00010110 a7 69 4c 67 00 00 00 00 00 00 04 00 08 00 00 00 |.iLg............| 00010120 00 00 00 00 04 00 00 00 2b 06 00 00 00 00 00 00 |........+.......| 00010130 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00010140 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00010150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00010160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00010170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00010180 20 00 00 00 a0 15 ed 2d a0 15 ed 2d 1c 7e f4 e6 | ......-...-.~..| 00010190 42 5c 2e 67 00 00 00 00 00 00 00 00 00 00 00 00 |B\.g............| 000101a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000101b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000101c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000101d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000101e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000101f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| Burada 0x28'inci offset'teki i_blocks elemanının yalnızca ilkinin dolu olduğunu görüyoruz. Demek ki kök dizin tek bir bloktan oluşmaktadır. Kök dizinin blok numarası 0x62B'dir. Şimdi 0x62b bloğunun offset'ini hesaplayalım. Bunun için bu değeri 0x1000 (4096) ile çarpmamız gerekir: 0x62B * 0x1000 = 0x62B000 (6467584) Diskin bu offset'indeki değerler şöyledir: 0062b000 02 00 00 00 0c 00 01 02 2e 00 00 00 02 00 00 00 |................| 0062b010 0c 00 02 02 2e 2e 00 00 0b 00 00 00 14 00 0a 02 |................| 0062b020 6c 6f 73 74 2b 66 6f 75 6e 64 00 00 0c 00 00 00 |lost+found......| 0062b030 10 00 07 01 73 74 64 69 6f 2e 68 00 b2 61 00 00 |....stdio.h..a..| 0062b040 c4 0f 01 02 61 00 00 00 00 00 00 00 00 00 00 00 |....a...........| 0062b050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0062b060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0062b070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0062b080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0062b090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0062b0a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0062b0b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| Buradaki dizin girişlerini çözelim. Dizin giriş formatını aşağıda yeniden veriyoruz: +----------------+--------------+-------------------------+ | Offset (bytes) | Size (bytes) | Açıklama | +----------------+--------------+-------------------------+ | 0 | DWORD | i-node numarası | | 4 | WORD | Girişin toplam uzunluğu | | 6 | BYTE | Dosya isminin uzunluğu | | 7 | BYTE | Dosyanın türü | | 8 | 0-255 Bytes | Dosya ismi | +----------------+--------------+-------------------------+ İlk dizin girişinin i-node numarası 2'dir. Bu girişin uzunluğu 0x0C = 12'dir. O halde bu dizin girişi şöyledir: 0062b000 02 00 00 00 0c 00 01 02 2e 00 00 00 02 Burada dosya ismi 1 karakter uzunluktadır. Dosya ismi yalnızca 0x2E karakterinde oluşmaktadır. Bu karakter de "." karakteridir. Sonraki dizin girişinin i-node numarası yine 2'dir. Girişin uzunluğu yine 0xC = 12'dir. O halde giriş şöyledir: 0062b000 02 00 00 00 |................| 0062b010 0c 00 02 02 2e 2e 00 00 |................| Buradaki dosya uzunluğunun 2 olduğu görülmektedir. Dosya ismi de 0x2E 0x2E karakterinden oluşmaktadır. Bu da ".." ismidir. Her dizinin ilk iki elemanının bu biçimde olduğunu anımsayınız. Sonraki giriş ise şöyledir: 0062b010 0b 00 00 00 14 00 0a 02 |................| 0062b020 6c 6f 73 74 2b 66 6f 75 6e 64 00 00 |lost+found......| Burada dosya i-node numarası 0x0b = 11'dir. Dizin girişinin uzunluğu 0x14 = 20'dir. Dosya isminin uzunluğu 0xA = 10'dur. Dosya ismi "lost+found" biçimindedir. Sonraki giriş ise şöyledir: 0062b020 0c 00 00 00 |lost+found......| 0062b030 10 00 07 01 73 74 64 69 6f 2e 68 00 |....stdio.h..a..| Buaraki girişin i-node numarası 0xC = 12'dir. Girişin toplam uzunluğu 0x10 = 16'dır. Dosyanın isminin uzunluğu 7'dir. Dosya ismi "stdio.h" biçimindedir. Sonraki giriş ise şöyledir: 0062b030 b2 61 00 00 |....stdio.h..a..| 0062b040 c4 0f 01 02 61 00 00 00 00 00 00 00 00 00 00 00 |....a...........| Burada dosyanın i-node numarası 0x61B2 = 25010'dur. Girişin uzunluğu 0xC4 = 196'dır. (Bu değerin çok uzun olması önemli değildir. Çünkü bu dizindeki son dosyadır.) Dosya isminin uzunluğu 1'dir. Dosya türü 0x02'dir. Yani bu giriş bir dizin belirtmektedir. Dosya ismi "a" biçimindedir. İşte işletim sistemi 0x61B2 = 25010'ıncı i-node elemanında bu dizinin bilgilerinin olduğunu tespit eder ve o i-node elemanını okur. Bu i-node elemanı aşağıdaki gibidir: 08010100 ed 41 00 00 00 10 00 00 f8 2b 53 67 a7 69 4c 67 |.A.......+Sg.iLg| 08010110 a7 69 4c 67 00 00 00 00 00 00 03 00 08 00 00 00 |.iLg............| 08010120 00 00 00 00 02 00 00 00 2b 86 00 00 00 00 00 00 |........+.......| 08010130 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 08010140 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 08010150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 08010160 00 00 00 00 f9 65 fb 3c 00 00 00 00 00 00 00 00 |.....e.<........| 08010170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 08010180 20 00 00 00 a0 15 ed 2d a0 15 ed 2d 04 7d e1 7b | ......-...-.}.{| 08010190 a7 69 4c 67 a0 15 ed 2d 00 00 00 00 00 00 00 00 |.iLg...-........| 080101a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 080101b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 080101c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 080101d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 080101e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 080101f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| "a" dizinine ilişkin i-node elemanının 0x28'inci offset'teki blokları bir tanedir ve blok numarası 0x862B = 34547'dir. Bu bloğun offset'i de 34547 * 4096 = 140685312'dir. Dizine ilişkin dizin bloğunun içeriği şöyledir: 0862b000 b2 61 00 00 0c 00 01 02 2e 00 00 00 02 00 00 00 |.a..............| 0862b010 0c 00 02 02 2e 2e 00 00 b3 61 00 00 e8 0f 01 02 |.........a......| 0862b020 62 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |b...............| 0862b030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0862b040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0862b050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0862b060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0862b070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0862b080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0862b090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0862b0a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| Bu dizin girişlerine bakıldığında "b" isimli girişin bulunduğu görülmektedir. İşte yol ifadesi bu aşamalardan geçilerek çözümlenmektedir. Pekiyi bir dosya oluşturulurken boş bloklar nasıl tespit edilmektedir? İşte her blok grup betimleyicisi kendi blok grubundaki boş blokları "block bitmap" denilen tabloda bit düzeyinde tutmaktadır. Blok bitmap tablosu her biti bir bloğun boş mu dolu mu olduğunu tutmaktadır. Blok grup betimleyicisinde yalnızca blok grubunun yeri tutulur. Bunun blok uzunluğu ilgili blok gruplarındaki blok sayısına bakılarak tespit edilmelidir. Her blok grubunda eşit sayıda blok bulunur. Bu sayı süper blok içerisindeki s_blocks_per_group elemanında saklanmaktadır. Aşağıda bir grup betimleyicisinin blok bitmap tablosunun bir bölümünü görüyorsunuz: 0000e000 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0000e010 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0000e020 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0000e030 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0000e040 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0000e050 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0000e060 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0000e070 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0000e080 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0000e090 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0000e0a0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0000e0b0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 0000e0c0 ff ff ff ff ff ff 01 00 00 00 00 00 00 00 00 00 |................| 0000e0d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0000e0e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0000e0f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0000e100 ff 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| Buradaki FF byte'larına dikkat ediniz. FF byte'ı aslında ikilik sistemde 1111 1111 bitlerine karşılık gelmektedir. Yani bu bloklar tamamen tahsis edilmiştir. 00 olan byte'lara ilişkin bloklar tahsis edilmemiş durumdadır. i-node elemanlarının tahsis edilip edilmediğine yönelik de benzer bir tablo tutulmaktadır. Buna "i-node bitmap" tablosu denilmektedir. Her blok grubunda bir i-node bitmap tablosu bulunur. Bu tablo da bitlerden oluşmaktadır. Her bit ilgili i-node elemanının boş mu dolu mu olduğunu belirtir. i-node bitmap tablosunun yeri de yine blok grup betimleyicisinde tutulmaktadır. Bu tablonun uzunluğu da yine süper bloktaki "bir grup bloğundaki i-node elemanlarının sayısı" dikkate alınarak tespit edilmektedir. Aşağıda örnek bir i-node bitmap tablosunun bir kısmını görüyorsunuz: 0000f000 ff 0f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0000f010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0000f020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0000f030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0000f040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0000f050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0000f060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0000f070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0000f080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| Bu blok grubunda toplam 12 i-node elemanı tahsis edilmiş durumdadır. Biz yukarıdaki örneklerde dosya sistemini tanıyabilmek için manuel işlemler yaptık. Pekiyi bu işlemleri programlama yoluyla nasıl yapabiliriz? Yukarıda açıkladığımız dosya sistemi alanlarına ilişkin yapılar çeşitli kütüphanelerin içerisinde hazır bir biçimde bulunmaktadır. Örneğin "libext2fs" kütüphanesi kurulduğunda dosyasında tüm yapı bildirimleri bulunacaktır. >>> "libext2fs" kütüphanesi: Kütüphanenin kurulumunu şöyle yapabilirsiniz: $ sudo apt-get install libext2fs-dev Aslında bu kütüphane ve başlık dosyası yalnızca ext2 dosya sistemine ilişkin değil, ext2 ve ext4 dosya sistemine ilişkin de yapıları ve fonksiyonları bulundurmaktadır. * Örnek 1, Örneğin başlık dosyası içerisindeki süper blok yapısı aşağıdaki gibi bildirilmiştir: struct ext2_super_block { /*000*/ __u32 s_inodes_count; /* Inodes count */ __u32 s_blocks_count; /* Blocks count */ __u32 s_r_blocks_count; /* Reserved blocks count */ __u32 s_free_blocks_count; /* Free blocks count */ /*010*/ __u32 s_free_inodes_count; /* Free inodes count */ __u32 s_first_data_block; /* First Data Block */ __u32 s_log_block_size; /* Block size */ __u32 s_log_cluster_size; /* Allocation cluster size */ /*020*/ __u32 s_blocks_per_group; /* # Blocks per group */ __u32 s_clusters_per_group; /* # Fragments per group */ __u32 s_inodes_per_group; /* # Inodes per group */ __u32 s_mtime; /* Mount time */ /*030*/ __u32 s_wtime; /* Write time */ __u16 s_mnt_count; /* Mount count */ __s16 s_max_mnt_count; /* Maximal mount count */ __u16 s_magic; /* Magic signature */ __u16 s_state; /* File system state */ __u16 s_errors; /* Behaviour when detecting errors */ __u16 s_minor_rev_level; /* minor revision level */ /*040*/ __u32 s_lastcheck; /* time of last check */ __u32 s_checkinterval; /* max. time between checks */ __u32 s_creator_os; /* OS */ __u32 s_rev_level; /* Revision level */ /*050*/ __u16 s_def_resuid; /* Default uid for reserved blocks */ __u16 s_def_resgid; /* Default gid for reserved blocks */ /* * These fields are for EXT2_DYNAMIC_REV superblocks only. * * Note: the difference between the compatible feature set and * the incompatible feature set is that if there is a bit set * in the incompatible feature set that the kernel doesn't * know about, it should refuse to mount the filesystem. * * e2fsck's requirements are more strict; if it doesn't know * about a feature in either the compatible or incompatible * feature set, it must abort and not try to meddle with * things it doesn't understand... */ __u32 s_first_ino; /* First non-reserved inode */ __u16 s_inode_size; /* size of inode structure */ __u16 s_block_group_nr; /* block group # of this superblock */ __u32 s_feature_compat; /* compatible feature set */ /*060*/ __u32 s_feature_incompat; /* incompatible feature set */ __u32 s_feature_ro_compat; /* readonly-compatible feature set */ /*068*/ __u8 s_uuid[16] __nonstring; /* 128-bit uuid for volume */ /*078*/ __u8 s_volume_name[EXT2_LABEL_LEN] __nonstring; /* volume name, no NUL? */ /*088*/ __u8 s_last_mounted[64] __nonstring; /* directory last mounted on, no NUL? */ /*0c8*/ __u32 s_algorithm_usage_bitmap; /* For compression */ /* * Performance hints. Directory preallocation should only * happen if the EXT2_FEATURE_COMPAT_DIR_PREALLOC flag is on. */ __u8 s_prealloc_blocks; /* Nr of blocks to try to preallocate*/ __u8 s_prealloc_dir_blocks; /* Nr to preallocate for dirs */ __u16 s_reserved_gdt_blocks; /* Per group table for online growth */ /* * Journaling support valid if EXT2_FEATURE_COMPAT_HAS_JOURNAL set. */ /*0d0*/ __u8 s_journal_uuid[16] __nonstring; /* uuid of journal superblock */ /*0e0*/ __u32 s_journal_inum; /* inode number of journal file */ __u32 s_journal_dev; /* device number of journal file */ __u32 s_last_orphan; /* start of list of inodes to delete */ /*0ec*/ __u32 s_hash_seed[4]; /* HTREE hash seed */ /*0fc*/ __u8 s_def_hash_version; /* Default hash version to use */ __u8 s_jnl_backup_type; /* Default type of journal backup */ __u16 s_desc_size; /* Group desc. size: INCOMPAT_64BIT */ /*100*/ __u32 s_default_mount_opts; /* default EXT2_MOUNT_* flags used */ __u32 s_first_meta_bg; /* First metablock group */ __u32 s_mkfs_time; /* When the filesystem was created */ /*10c*/ __u32 s_jnl_blocks[17]; /* Backup of the journal inode */ /*150*/ __u32 s_blocks_count_hi; /* Blocks count high 32bits */ __u32 s_r_blocks_count_hi; /* Reserved blocks count high 32 bits*/ __u32 s_free_blocks_hi; /* Free blocks count */ __u16 s_min_extra_isize; /* All inodes have at least # bytes */ __u16 s_want_extra_isize; /* New inodes should reserve # bytes */ /*160*/ __u32 s_flags; /* Miscellaneous flags */ __u16 s_raid_stride; /* RAID stride in blocks */ __u16 s_mmp_update_interval; /* # seconds to wait in MMP checking */ __u64 s_mmp_block; /* Block for multi-mount protection */ /*170*/ __u32 s_raid_stripe_width; /* blocks on all data disks (N*stride)*/ __u8 s_log_groups_per_flex; /* FLEX_BG group size */ __u8 s_checksum_type; /* metadata checksum algorithm */ __u8 s_encryption_level; /* versioning level for encryption */ __u8 s_reserved_pad; /* Padding to next 32bits */ __u64 s_kbytes_written; /* nr of lifetime kilobytes written */ /*180*/ __u32 s_snapshot_inum; /* Inode number of active snapshot */ __u32 s_snapshot_id; /* sequential ID of active snapshot */ __u64 s_snapshot_r_blocks_count; /* active snapshot reserved blocks */ /*190*/ __u32 s_snapshot_list; /* inode number of disk snapshot list */ #define EXT4_S_ERR_START ext4_offsetof(struct ext2_super_block, s_error_count) __u32 s_error_count; /* number of fs errors */ __u32 s_first_error_time; /* first time an error happened */ __u32 s_first_error_ino; /* inode involved in first error */ /*1a0*/ __u64 s_first_error_block; /* block involved in first error */ __u8 s_first_error_func[32] __nonstring; /* function where error hit, no NUL? */ /*1c8*/ __u32 s_first_error_line; /* line number where error happened */ __u32 s_last_error_time; /* most recent time of an error */ /*1d0*/ __u32 s_last_error_ino; /* inode involved in last error */ __u32 s_last_error_line; /* line number where error happened */ __u64 s_last_error_block; /* block involved of last error */ /*1e0*/ __u8 s_last_error_func[32] __nonstring; /* function where error hit, no NUL? */ #define EXT4_S_ERR_END ext4_offsetof(struct ext2_super_block, s_mount_opts) /*200*/ __u8 s_mount_opts[64] __nonstring; /* default mount options, no NUL? */ /*240*/ __u32 s_usr_quota_inum; /* inode number of user quota file */ __u32 s_grp_quota_inum; /* inode number of group quota file */ __u32 s_overhead_clusters; /* overhead blocks/clusters in fs */ /*24c*/ __u32 s_backup_bgs[2]; /* If sparse_super2 enabled */ /*254*/ __u8 s_encrypt_algos[4]; /* Encryption algorithms in use */ /*258*/ __u8 s_encrypt_pw_salt[16]; /* Salt used for string2key algorithm */ /*268*/ __le32 s_lpf_ino; /* Location of the lost+found inode */ __le32 s_prj_quota_inum; /* inode for tracking project quota */ /*270*/ __le32 s_checksum_seed; /* crc32c(orig_uuid) if csum_seed set */ /*274*/ __u8 s_wtime_hi; __u8 s_mtime_hi; __u8 s_mkfs_time_hi; __u8 s_lastcheck_hi; __u8 s_first_error_time_hi; __u8 s_last_error_time_hi; __u8 s_first_error_errcode; __u8 s_last_error_errcode; /*27c*/ __le16 s_encoding; /* Filename charset encoding */ __le16 s_encoding_flags; /* Filename charset encoding flags */ __le32 s_reserved[95]; /* Padding to the end of the block */ /*3fc*/ __u32 s_checksum; /* crc32c(superblock) */ }; Kütüphane aşağıdaki bağlantıdan indirebileceğiniz pdf dosyasında dokümante edilmiştir: https://www.dubeyko.com/development/FileSystems/ext2fs/libext2fs.pdf Şimdi "libext2fs" kütüphanesinin basit bir kullanımı üzerinde duracağız. Kütüphanenin temel başlık dosyası "" isimli dosyadır. Bu dosyanın include edilmesi gerekir. #include Kütüphaneyi kullanan programlar derlenirken link aşamasında "-lext2fs" seçeneğinin bulundurulması gerekir. Örneğin: $ gcc -o sample sample.c -lext2fs Kütüphane içerisindeki fonksiyonların çoğunun geri dönüş değerleri errcode_t türündendir. errcode_t türü long biçimde typedef edilmiştir. Fonksiyonlar başarı durumunda 0 değerine, başarısızlık durumunda hata ile ilgili bir değere geri dönmektedir. Hatayı yazdırmak için error_message isimli fonksiyon bulunmaktadır. Bu fonksiyona errcode_t değeri parametre olarak verilir, fonksiyon da hata yazısına ilişkin static bir dizinin başlangıç adresine geri döner. * Örnek 1, ext2_filsys fs; errcode_t err; if ((err = ext2fs_open("/dev/loop0", 0 /* EXT2_FLAG_RW */, 0, 0, unix_io_manager, &fs)) != 0) { fprintf(stderr, "%s\n", error_message(err)); exit(EXIT_FAILURE); } Bu kütüphane kullanılarak yazılmış programlar genel olarak aygıtlara eriştiği için "sudo" ile çalıştırılması gerekir. Aksi takdirde "Permission denied (EACCESS)" hatası ortaya çıkacaktır. Kütüphanenin kullanılması için, -> ilk yapılacak işlem dosya sisteminin ext2fs_open fonksiyonu ile açılmasıdır. Fonksiyonun prototipi şöyledir: #include errcode_t ext2fs open (const char *name, int args, int superblock, int block size, io_manager manager, ext2_filsys *ret fs); Fonksiyonun birinci parametresi dosya sisteminin bulunduğu dosyanın ya da aygıt dosyasının yol ifadesini almaktadır. Parametrelerin çoğu default 0 geçilebilir. Ancak io_manager parametresi için unix_io_manager argümanının girilmesi gerekmektedir. Fonksiyon ext2fil_sys türünden handle belirten bir nesne vermektedir. Bu tür şöyle typedef edilmiştir: typedef struct struct_ext2_filsys *ext2_filsys; Dosya sistemine ilişkin tüm bilgiler bu struct_ext2_filsys yapısının içerisindedir. Bu yapı şöyle bildirilmiştir: struct struct_ext2_filsys { errcode_t magic; io_channel io; int flags; char * device_name; struct ext2_super_block * super; unsigned int blocksize; int fragsize; dgrp_t group_desc_count; unsigned long desc_blocks; struct opaque_ext2_group_desc * group_desc; unsigned int inode_blocks_per_group; ext2fs_inode_bitmap inode_map; ext2fs_block_bitmap block_map; ... }; Örneğin bu yapının super elemanı süper blok bilgilerinin bulunduğu ext2_super_block isimli yapı nesnesinin adresini vermektedir. * Örnek 1, ext2_filsys fs; errcode_t err; if ((err = ext2fs_open("/dev/loop0", 0 /* EXT2_FLAG_RW */, 0, 0, unix_io_manager, &fs)) != 0) { fprintf(stderr, "cannot open file system!..\n"); exit(EXIT_FAILURE); } -> Açılan dosya sisteminin işlem bitince ext2fs_close fonksiyonu ile kapatılması gerekir. Fonksiyonun prototipi, #include errcode_t ext2fs close (ext2_filsys fs); biçimindedir. * Örnek 1, ext2_filsys fs; errcode_t err; if ((err = ext2fs_open("/dev/loop0", 0 /* EXT2_FLAG_RW */, 0, 0, unix_io_manager, &fs)) != 0) { fprintf(stderr, "cannot open file system!..\n"); exit(EXIT_FAILURE); } /* ... */ ext2fs_close(fs); -> Belli bir numaraya sahip i-node elemanını elde edebilmek için ext2fs_read_inode fonksiyonu kullanılmaktadır. Fonksiyonun prototipi şöyledir: #include errcode_t ext2fs_read_inode(ext2_filsys fs, ext2_ino_t ino, struct ext2_inode *inode); Fonksiyonun birinci parametresi dosya sistemini temsil eden handle değeridir. İkinci parametre bilgileri elde edilecek i-node elemanın numarasını belirtir. Üçüncü parametre de i-node bilgilerinin yerleştirileceği yapının adresini almaktadır. * Örnek 1, if ((err = ext2fs_read_inode(fs, 13, &inode)) != 0) { fprintf(stderr, "cannot read inode!..\n"); exit(EXIT_FAILURE); } Aşağıda genel kullanıma ilişkin örnekler verilmiştir: * Örnek 1, #include #include int main(void) { ext2_filsys fs; errcode_t err; if ((err = ext2fs_open("/dev/loop0", 0 /* EXT2_FLAG_RW */, 0, 0, unix_io_manager, &fs)) != 0) { fprintf(stderr, "cannot open file system!..\n"); exit(EXIT_FAILURE); } printf("Number of i-node: %lu\n", (unsigned long)fs->super->s_inodes_count); printf("Total Block: %lu\n", (unsigned long)fs->super->s_blocks_count); /* ... */ ext2fs_close(fs); return 0; } Öte yandan "e2fsprogs" isimli pakette de ext2, ext3 ve ext4 dosya sistemlerine ilişkin pek çok yardımcı program bulunmaktadır. Örneğin bizim daha önce kullandığımız "dumpe2fs" programı bu paketin bir parçasıdır. Buradaki programlar "libext2fs" kütüphanesi kulanılarak yazılmıştır. Paket pek çok Linux dağıtımında default biçimde kurulu durumdadır. Eğer dağıtımınızda kurulu değilse bu paketi aşağıdaki gibi kurabilirsiniz: $ sudo apt-get install e2fsprogs Bir diske birden fazla bağımsız dosya sisteminin (ve belki de işletim sisteminin) yüklenebilmesi için diskin mantıksal bakımdan parçalara ayrılması gerekmektedir. Diskin mantıksal bakımdan parçalara ayrılmasına ise "disk bölümlemesi" denilmektedir. Disk bölümlemesi aslında disk bölümlerinin hangi sektörden başlayıp kaç sektör uzunluğunda olduğunun belirlenmesi anlamına gelmektedir. Böylece her dosya sistemi başkasının alanına müdahale etmeden yalnızca o disk bölümünü kullanmaktadır. Diskteki disk bölümleri hakkında bilgileri barındıran tabloya "disk bölümleme tablosu (disk partition table)" denilmektedir. Bugün için iki disk bölümleme tablo formatı kullanılmaktadır: -> Klasik (legacy) MBR Disk Bölümleme Tablo Formatı -> Modern UEFI BIOS Sistemlerinin Kullandığı GPT (Guid Partition Table) Formatı UEFI BIOS'lar GPT disk bölümleme tablosu kullanırken eski sistemler ve gömülü sistemler genel olarak klasik MBR disk bölümleme tablosunu kullanmaktadır. Gömülü sistemler için oluşturduğumuz SD kartlar'daki disk bölümleme tablosu klasik (legacy) disk bölümleme tablosudur. Ancak bugünkü büyük çaplı UEFI BIOS'lar önce GPT disk bölümleme tablosuna bakmakta eğer onu bulamazsa klasik disk bölümleme tablosunu aramaktadır. Yani geçmişe doğru uyum korunmaya çalışılmıştır. Daha önceden de belirttiğimiz gibi disk sistemlerine okuma ve yazma işlemleri blok aygıt sürücüleri tarafından yapılmaktadır. Dolayısıyla bir UNIX türevi sistemde her disk için "/dev" dizininin altında bir blok aygıt dosyası bulunmaktadır. Aynı zamanda her disk bölümü için de bir blok aygıt dosyası bulunur. Böylece bir diskin bütünü üzerinde de yalnızca onun belli bir bölümü üzerinde de çalışılabilir. Daha önceden de kullanmış olduğumuz "lsblk" komutu bize sistemimizdeki diskler ve onların bölümleri hakkında bilgiler vermektedir. Örneğin çalıştığımız sistemde "lsblk" komutunu uyguladığımızda şöyle bir çıktı ile karşılaşmaktayız: $ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS loop0 7:0 0 195,3M 0 loop /home/kaan/Study/UnixLinux-SysProg/DiskIO-FileSystems/ext2 sda 8:0 0 60G 0 disk ├─sda1 │ 8:1 0 1M 0 part ├─sda2 │ 8:2 0 513M 0 part /boot/efi └─sda3 8:3 0 59,5G 0 part / sr0 11:0 1 1024M 0 rom Buradaki "sda" aygıtı bir diski bütün olarak ele almak için kullanılmaktadır. Bu diskin 3 tane disk bölüme vardır. Bu disk bölümleri de "sda1", "sda2" ve "sda3" aygıtlarıdır. Bu aygıtlara ilişkin "/dev" dizini altında aygıt dosyaları bulunmaktadır. Örneğin: $ ls -l /dev/sda* brw-rw---- 1 root disk 8, 0 Ara 6 15:08 /dev/sda brw-rw---- 1 root disk 8, 1 Ara 6 15:08 /dev/sda1 brw-rw---- 1 root disk 8, 2 Ara 6 15:08 /dev/sda2 brw-rw---- 1 root disk 8, 3 Ara 6 15:08 /dev/sda3 Diskleri temsil eden isimler o diskin türüne göre değişebilmektedir. -> Normal hard diskler için isimlendirme "sda", "sdb", "sdc" biçiminde yapılmaktadır. Bunların bölümleri de "sda1", "sda2", "sdb1, "sdb2" biçiminde yapılır. -> MicroSD kartlar "mmcblk0", "mmcblk1", "mmcblk2" biçiminde isimlendirilmektedir. Bunların bölümleri de "mmcblk0p1", "mmcblk0p2", "mmcblk1p1", "mmcblk1p2" biçiminde yapılmaktadır. -> Eğer disk bir loop aygıtı biçiminde oluşturulmuşsa disk bölümleri "loop0p1", "loop0p2", "loop1p1", "loop1p2" biçiminde isimlendirilmektedir. Şimdi de disk bölümleme formatlarını inceleyelim: >> "Klasik (legacy) MBR Disk Bölümleme Tablo Formatı" : Klasik MBR (legacy) disk bölümlendirmesinde diskin ilk sektörüne (0 numaralı sektörüne) MBR (Master Boot Record) sektörü denilmektedir. MBR sektörünün sonundaki 2 byte MBR'nin bilinçli olarak oluşturulduğunu belirten sihirli bir sayıdan (magic number) oluşmaktadır. Bu sihirli sayı hex olarak "55 AA" biçimindedir. Aşağıda "loop0" aygıtı üzerinde oluşturulmuş bir MBR sektörü görülmektedir. * Örnek 1, $ sudo hexdump /dev/loop0 -C -v -n 512 00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000000a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000000b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000000c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000000d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000000e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000000f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000130 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000140 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000180 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000190 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000001a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000001b0 00 00 00 00 00 00 00 00 03 74 de d6 00 00 80 01 |.........t......| 000001c0 01 00 83 20 0d 13 3f 00 00 00 01 b0 04 00 00 20 |... ..?........ | 000001d0 0e 13 83 3e 18 26 40 b0 04 00 c0 af 04 00 00 00 |...>.&@.........| 000001e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa |..............U.| Sektörün sonunun 55 AA ile bittiğine dikkat ediniz. Klasik MBR Disk Bölümlemesi'nde MBR sektörünün sonundaki 64 byte'a "Disk Bölümleme Tablosu (Disk Partition Table)" denilmektedir. Tabii sektörün sonunda hex olarak 55 AA bulunduğu için disk bölümleme tablosu da bu 55 AA byte'larının hemen gerisindeki 64 byte'tadır. O halde MBR sektörünün sonu aşağıdaki gibidir: ... <64 byte (Disk Bölümleme Tablosu)> 55 AA Yukarıdaki MBR sektörünün son 64 byte'ı ve 55 AA değerleri aşağıda verilmiştir: 80 01 |.........t......| 000001c0 01 00 83 20 0d 13 3f 00 00 00 01 b0 04 00 00 20 |... ..?........ | 000001d0 0e 13 83 3e 18 26 40 b0 04 00 c0 af 04 00 00 00 |...>.&@.........| 000001e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa |..............U.| Başka bir deyişle Disk Bölümleme Tablosu MBR sektörünün 0x1BE (446) offset'inden başlayıp 64 byte sürmektedir. Disk Bölümleme Tablosu'ndaki her disk bölümü 16 byte ile betimlenmektedir. Dolayısıyla klasik Disk Bölümleme Tablosu 4 disk bölümünü barındırmaktadır. Pekiyi bu durumda 4'ten fazla disk bölümü oluşturulamaz mı? İşte "Genişletilmiş Disk Bölümü (Extended Disk Partition)" kavramı ile bu durum mümkün hale getirilmiştir. Yukarıdaki Disk Bölümleme Tablosu'nun 16 byte'lık disk bölümleri aşağıda verilmiştir: 80 01 01 00 83 20 0d 13 3f 00 00 00 01 b0 04 00 00 20 0e 13 83 3e 18 26 40 b0 04 00 c0 af 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Disk Bölümleme Tablosu'ndaki 16 byte'lık disk bölümünün içeriği şöyledir: +--------------+----------------+---------------------------------------------------------------------+ | Offset (Hex) | Uzunluk | Anlamı | +--------------+----------------+---------------------------------------------------------------------+ | 0 | 1 BYTE | Disk Bölümünün Aktif Olup Olmadığı Bilgisi | | 1 | 3 BYTE | Disk Bölümünün Eski Sistemdeki (CHS Sistemindeki) Başlangıç Sektörü | | 4 | 1 BYTE | Sistem ID Değeri | | 5 | 3 BYTE | Disk Bölümünün Eski Sistemdeki (CHS Sistemindeki) Bitiş Sektörü | | 8 | 4 BYTE (DWORD) | Disk Bölümünün LBA Sistemindeki Başlangıç Sektörü | | C | 4 BYTE (DWORD) | Disk Bölümündeki Sektör Sayısı (Disk Bölümünün Uzunluğu) | +--------------+----------------+---------------------------------------------------------------------+ -> 4 disk bölümünden yalnızca bir tanesi aktif olabilmektedir. Sistem aktif disk bölümünden boot edilmektedir. Aktif disk bölümü için 0x80 değeri, aktif olmayan disk bölümü için 0x00 değeri kullanılmaktadır. -> Eskiden diskteki bir sektörün yeri "hangi yüzde (her yüzü bir kafa okuduğu için, hangi kafada), hangi track'te (track'e silindir (cylinder) de denilmektedir) ve hangi sektör diliminde olduğu bilgisiyle ifade ediliyordu. Bu koordinat sistemine CHS (Cylinder-Head-Sector) koordinat sistemi deniyordu. Sonra bu koordinat sisteminden vazgeçildi. Sektörün yeri ilk sektör 0 olmak üzere tek bir sayıyla temsil edilmeye başlandı. -> Her disk bölümünde farklı bir işletim sisteminin kullandığı dosya sistemi bulunuyor olabilir. "Sistem ID Değeri" o disk bölümünde hangi işletim sistemine ilişkin bir dosya sisteminin bulunduğunu belirtmektedir. Böylece Disk Bölümleme Tablosu'nu inceleyen kişiler disk bölümlerinin hangi işletim sistemi için oluşturulduğunu anlayabilmektedir. Tüm Sistem ID Değerleri için bunların listelendiği dokümanlara başvurabilirsiniz. Biz burada birkaç System ID değerini verelim: 0C: Windows FAT32 Sistemi 0E: Windows FAT Sistemi 0F: Genişletilmiş Disk Bölümü 83: Linux Dosya Sistemlerinden Birisi 82: Linux İçin Swap Alanı Olarak Kullanılacak Disk Bölümü -> Bir disk bölümü için en önemli iki bilgi onun diskin hangi sektöründen başlayıp kaç sektör uzunlukta olduğudur. Yani disk bölümünün başlangıç sektör numarası ve toplam sektör sayısıdır. İşletim sistemleri böylece kendileri için belirlenmiş olan disk bölümlerinin dışına erişmezler. Yani disk bölümleri adeta disk içerisindeki disklerin yerlerini belirtmektedir. -> 90'lı yıllarla birlikte diskteki sektörlerin adreslenmesi için CHS sistemi yavaş yavaş bırakılmaya başlanmış "LBA (Logical Block Address)" denilen sisteme geçilmiştir. Bu sistemde diskin ilk sektörü 0 olmak üzere her sektöre artan sırada bir tamsayı karşılık düşürülmüştür. İşte bu koordinat sistemine LBA denilmektedir. Artık MBR Disk Bölümleri'nde disk bölümünün başlangıç sektörü LBA sistemine göre belirtilmektedir. -> LBA sisteminde bir disk bölümünde en fazla 2^32 tane sektör bulunabilir. Bir sektör 2^9 (512) byte olduğuna göre MBR Disk Bölümleme Tablosu en fazla 2^41 = 2TB diskleri destekleyebilmektedir. Gömülü sistemlerde henüz bu büyüklükte diskler kullanılmadığı için klasik MBR Disk Bölümleme Tablosu iş görmektedir. Ancak masaüstü sistemlerde artık bu sınır aşılmaktadır. İşte UEFI BIOS'lar tarafından kullanılan "GUID Disk Bölümlemesi (GPT)" bu sınırı çok daha ötelere taşımaktadır. Disk Bölümleme Tablosu manuel bir biçimde oluşturulabilir. Ancak Disk Bölümleme Tablosu üzerinde işlem yapan çeşitli araçlar da vardır. Linux sistemlerinde en yaygın kullanılan iki araç "fdisk" ve "gparted" isimli araçlardır. fdisk komut satırından kullanılabilen basit bir programdır. "gparted" ise GUI arayüzü ile görsel bir biçimde aynı işlemleri yapmaktadır. "fdisk" temel bir araçtır. Dolayısıyla Linux sistemleri kurulduğunda büyük olasılıkla zaten kurulmuş olarak sisteminizde bulunuyor olacaktır. Ancak "gparted" programını aşağıdaki gibi siz kurmalısınız: $ sudo apt-get install gparted Bu noktada Linux sistemlerindeki fdisk programının kullanımı üzerinde duracağız. Bu tür denemeleri loop aygıtları üzerinde yapmalısınız. fdisk kullanımını maddeler halinde açıklayalım. -> Hangi disk üzerinde işlem yapılacaksa o diske ilişkin aygıt dosyası fdisk programına komut satırı argümanı olarak verilmelidir. Tabii disk aygıt dosyaları "root" kullanıcısına ilişkin olduğu için fdisk programı da genellikle "sudo" ile çalıştırılır. Örneğin önce blok aygıt sürücülerimize bakalım: ****************************************************** $ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 80G 0 disk ├─sda1 8:1 0 512M 0 part /boot/efi ├─sda2 8:2 0 1K 0 part └─sda5 8:5 0 79,5G 0 part / sr0 11:0 1 1024M 0 rom ****************************************************** Burada "sda" diski bir bütün olarak gösteren aygıt sürücüsüdür. Bu aygıt sürücüye ilişkin aygıt dosyası "/dev/sda" biçimindedir. "sda1", "sda2" ve "sda5" disk üzerindeki disk bölümleridir. Bizim bölümlendirme için diski bir bütün olarak ele almamız gerekir. Bu nedenle "sda" diski için fdisk programı şöyle çalıştırılmalıdır: $ sudo fdisk /dev/sda Tabi biz örneğimizde loop aygıtı kullanacağız. Bu durumda loop aygıtını şöyle kullanıma hazır hale getirebiliriz: ****************************************************** $ dd if=/dev/zero of=disk.img bs=300M count=1 1+0 kayıt girdi 1+0 kayıt çıktı 314572800 bytes (315 MB, 300 MiB) copied, 1,23241 s, 255 MB/s ****************************************************** Buradan diski temsil eden içi sıfırlarla dolu 300MB'lik bir dosya oluşturduk. Şimdi bu dosyayı "/dev/loop0" aygıt dosyası ile bir blok aygıtı gibi gösterelim: $ sudo losetup /dev/loop0 disk.img Artık "/dev/loop0" aygıt dosyası sanki bir disk gibi kullanılabilecektir. Bu aygıt üzerinde işlem yaptığımızda işlemden "disk.img" dosyası etkilenecektir. Artık blok aygıt sürücülerine baktığımızda "loop0" aygıtını göreceğiz: ****************************************************** $ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT loop0 7:0 0 300M 0 loop sda 8:0 0 80G 0 disk ├─sda1 8:1 0 512M 0 part /boot/efi ├─sda2 8:2 0 1K 0 part └─sda5 8:5 0 79,5G 0 part / sr0 11:0 1 1024M 0 rom ***************************************************** Artık fdisk programını bu aygıt üzerinde kullanabiliriz: $ sudo fdisk /dev/loop0 -> Artık interaktif bir biçimde disk bölümlendirme işlemleri yapılabilir. Burada tek harfli çeşitli komutlar girildiğinde interaktif bir biçimde işlemler yapılmaktadır. Bu komutlardan önemli olanlarını açıklamak istiyoruz: -> "n" (new) komutu yeni bir disk bölümü oluşturmak için kullanılmaktadır. Bu komut verildiğinde yaratılacak disk bölümünün "primary" bölüm mü "extended" bölüm mü olduğu sorulmaktadır. Primary disk bölümü ana 4'lük girişteki bölümlerdir. Dolayısıyla burada genellikle "p" komutu ile "primary" disk bölümü oluşturulur. Sonra bize 4 girişten hangisinin disk bölümü olarak oluşturulacağı sorulmaktadır. Bu durumda sıradaki numarayı vermek (disk tamamen ilk kez bölümlendiriliyorsa 1) uygun olur. Sonra da bize ilgili disk bölümünün hangi sektörden başlatılacağı ve ne uzunlukta olacağı sorumaktadır. Aşağıda bir örnek görüyorsunuz: ************************************************************************************** Komut (yardım için m): n Disk bölümü tipi p birincil (0 birincil, 0 genişletilmiş, 4 boş) e genişletilmiş (mantıksal disk bölümleri için konteyner) Seç (varsayılan p): p Disk bölümü numarası (1-4, varsayılan 1): 1 İlk sektör (2048-614399, varsayılan 2048): Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-614399, varsayılan 614399): +50M Yeni bir disk bölümü 1, 'Linux' tipinde ve 50 MiB boyutunda oluşturuldu. *************************************************************************************** -> "p" (print) komutu oluşturulmuş olan disk bölümlerini görüntülemektedir. Örneğin: ************************************************************************************** Komut (yardım için m): p Disk /dev/loop0: 300 MiB, 314572800 bayt, 614400 sektör Birimler: sektör'i 1 * 512 = 512 baytın Sektör boyutu (montıksal/fiziksel): 512 bayt / 512 bayt G/Ç boyutu (en düşük/en uygun): 512 bayt / 512 bayt Disketikeri tipi: dos Disk belirleyicisi: 0x267a62e0 Aygıt Açılış Başlangıç Son Sektör Boyut ld Türü /dev/loop0p1 2048 104447 102400 50M 83 Linux ************************************************************************************** -> fdisk yaratılan disk bölümlerinin ID'sini default olarak 0x83 (Linux) yapmaktadır. Eğer disk bölümüne FAT dosya sistemi yerleştirilecekse "t" (type) komutu ile bölüm ID'si değitirilmelidir. Örneğin: ************************************************************************************** Komut (yardım için m): t Seçilen disk bölümü 1 Hex kod (bütün kodlar için L tuşlayın): c 'Linux' disk bölümünün tipini 'W95 FAT32 (LBA)' olarak değiştirin. Komut (yardım için m): p Disk /dev/loop0: 300 MiB, 314572800 bayt, 614400 sektör Birimler: sektör'i 1 * 512 = 512 baytın Sektör boyutu (montıksal/fiziksel): 512 bayt / 512 bayt G/Ç boyutu (en düşük/en uygun): 512 bayt / 512 bayt Disketikeri tipi: dos Disk belirleyicisi: 0x267a62e0 Aygıt Açılış Başlangıç Son Sektör Boyut ld Türü /dev/loop0p1 2048 104447 102400 50M c W95 FAT32 (LBA) ************************************************************************************** Şu anda biz bir FAT disk bölümü yaratmış olduk. Şimdi ikinci Linux dosya sistemleri için ikinci bölümünü de yaratalım: ************************************************************************************** Komut (yardım için m): n Disk bölümü tipi p birincil (1 birincil, 0 genişletilmiş, 3 boş) e genişletilmiş (mantıksal disk bölümleri için konteyner) Seç (varsayılan p): p Disk bölümü numarası (2-4, varsayılan 2): İlk sektör (104448-614399, varsayılan 104448): Last sector, +/-sectors or +/-size{K,M,G,T,P} (104448-614399, varsayılan 614399): Yeni bir disk bölümü 2, 'Linux' tipinde ve 249 MiB boyutunda oluşturuldu. Komut (yardım için m): p Disk /dev/loop0: 300 MiB, 314572800 bayt, 614400 sektör Birimler: sektör'i 1 * 512 = 512 baytın Sektör boyutu (montıksal/fiziksel): 512 bayt / 512 bayt G/Ç boyutu (en düşük/en uygun): 512 bayt / 512 bayt Disketikeri tipi: dos Disk belirleyicisi: 0x267a62e0 Aygıt Açılış Başlangıç Son Sektör Boyut ld Türü /dev/loop0p1 2048 104447 102400 50M c W95 FAT32 (LBA) /dev/loop0p2 104448 614399 509952 249M 83 Linux ************************************************************************************** Artık diskimizde iki disk bölümü vardır. -> Bir disk bölümünü aktive etmek için "a" komutu (activate) kullanılmaktadır. Örneğin biz FAT32 disk bölümünü aktif disk bölümü haline getirelim: ************************************************************************************** Komut (yardım için m): a Disk bölümü numarası (1,2, varsayılan 2): 1 Disk bölümü 1'de önyüklenebilir bayrağı artık etkin. Komut (yardım için m): p Disk /dev/loop0: 300 MiB, 314572800 bayt, 614400 sektör Birimler: sektör'i 1 * 512 = 512 baytın Sektör boyutu (montıksal/fiziksel): 512 bayt / 512 bayt G/Ç boyutu (en düşük/en uygun): 512 bayt / 512 bayt Disketikeri tipi: dos Disk belirleyicisi: 0x267a62e0 Aygıt Açılış Başlangıç Son Sektör Boyut ld Türü /dev/loop0p1 * 2048 104447 102400 50M c W95 FAT32 (LBA) /dev/loop0p2 104448 614399 509952 249M 83 Linux ************************************************************************************** -> fdisk önce yazılacakları kendi içerisinde biriktirmekte sonra bunları diske yazmaktadır. Biriktirilenlerin diske yazılması için "w" (write) komutu kullanılmaktadır. Örneğin: ************************************************************************************** Komut (yardım için m): w Disk bölümleme tablosu değiştirildi. Disk bölüm tablosunu yeniden okumak için ioctl() çağrılıyor. Disk bölümü tablosu yeniden okunamadı.: Geçersiz bağımsız değişken Çekirdek hala eski tabloyu kullanıyor. Yeni tablo bir sonraki yeniden başlatma işleminden sonra ya da partprobe(8) veya kpartx(8)'i çalıştırdığınızda kullanılacak. ************************************************************************************** -> Bir disk bölümünü silmek için "d" komutu kullanılmaktadır. Disk bölümlerini silerken dikkat ediniz. -> Disk bölümlerini oluşturduktan sonra çekirdeğin onları o anda görmesi için "partprobe" komutu kullanılmalıdır. Örneğin: ************************************************************************************** $ sudo partprobe /dev/loop0 [sudo] kaan için parola: $ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT loop0 7:0 0 300M 0 loop ├─loop0p1 259:0 0 50M 0 part └─loop0p2 259:1 0 249M 0 part sda 8:0 0 80G 0 disk ├─sda1 8:1 0 512M 0 part /boot/efi ├─sda2 8:2 0 1K 0 part └─sda5 8:5 0 79,5G 0 part / sr0 11:0 1 1024M 0 rom ************************************************************************************** Aslında yukarıda yapılan işlemlerin sonucu olarak Disk Bölümleme Tablosu'ndaki iki giriş (32 byte) güncellenmiştir. -> fdisk programının başka komutları da vardır. Örneğin disk bölümlendirmesi yapıldıktan sonra bu bölümlendirme bilgileri "O" komutu ile bir dosyaya aktarılabilir. Sonra "I" komutu ile bu dosyadan yükleme yapılabilir. Böylece farklı diskler için aynı işlemlerin daha kolay yapılması sağlanabilmektedir. > Hatırlatıcı Notlar: >> Linux'un dosya sistemi önemli üç yapı vardır. Bunlar file, inode ve dentry yapılarıdır. file isimli yapıya biz "dosya nesnesi" demiştik. Anımsanacağı gibi ne zaman bir dosya açılsa dosya betimleyici tablosunda dosya betimleyicisi denilen bir indeks bu dosya nesnesini göstermektedir. Biz user mode'taki dosya işlemlerinde bu konuyu zaten açıklamıştık. Ancak kısaca bir anımsatma yapalım: Dosya Betimeleyici Tablosu -------------------------- 0 ----> dosya nesnesi (struct file) 1 ----> dosya nesnesi (struct file) 2 ----> dosya nesnesi (struct file) 3 ----> dosya nesnesi (struct file) .... Dosya nesnesi "açık dosyaların bilgilerini" tutmaktadır. Aşağıda file yapısının mevcut çekirdeklerdeki içeriğini görüyorsunuz: struct file { union { /* fput() uses task work when closing and freeing file (default). */ struct callback_head f_task_work; /* fput() must use workqueue (most kernel threads). */ struct llist_node f_llist; unsigned int f_iocb_flags; }; /* * Protects f_ep, f_flags. * Must not be taken from IRQ context. */ spinlock_t f_lock; fmode_t f_mode; atomic_long_t f_count; struct mutex f_pos_lock; loff_t f_pos; unsigned int f_flags; struct fown_struct f_owner; const struct cred *f_cred; struct file_ra_state f_ra; struct path f_path; struct inode *f_inode; /* cached value */ const struct file_operations *f_op; u64 f_version; #ifdef CONFIG_SECURITY void *f_security; #endif /* needed for tty driver, and maybe others */ void *private_data; #ifdef CONFIG_EPOLL /* Used by fs/eventpoll.c to link all the hooks to this file */ struct hlist_head *f_ep; #endif /* #ifdef CONFIG_EPOLL */ struct address_space *f_mapping; errseq_t f_wb_err; errseq_t f_sb_err; /* for syncfs */ } __randomize_layout __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */ inode yapısı dosyanın diskteki bilgilerini tutmaktadır. Yani aynı dosya üç kez açılsa çekirdek üç farklı file nesnesi oluşturmaktadır. Ancak bu dosya diskte bir tane olduğuna göre çekirdek bunun için toplamda bir tane inode yapısı oluşturacaktır. file yapısının içerisinde dosyanın diskteki bilgilerine ilişkin bu inode yapısınıa f_inode elemanı yoluyla erişilebilmektedir. Daha önceden de gördüğümüz gibi bir dosya isimleri diskte dizin girişlerinde tutulmaktadır. Örneğin "/home/kaan/Study/test.c" isimli dosyanın i-node elemanına erişmek için işletim sistemi sırasıyla "/home", "/home/kaan", "ome/kaan/Stduy", "/home/kaan/Study/test.txt" dizin girişlerini taramak zorundadır. Bu işleme işletim sistemlerinde "yol ifadelerinin çözümlenmesi (pathname resolution)" denilmektedir. Aynı dosyaların tekrar tekrar açılması durumunda bu işlemlerin yeniden yapılması oldukça zahmetlidir. Dolayısıyla bulunan dizin girişlerinin bir yapı ile temsil edilerek bir cache sisteminde saklanması uygundur. İşte Linux çekirdeğinde dizin girişleri "dentry" isimli bir yapıyla temsil edilmektedir. Linux sistemleri yukarıda açıkladığımız "inode" ve "dentry" nesnelerini bir cache sisteminde tutmaktadır. Böylece bir dosya yeniden açıldığında onun bilgilerine diske hiç başvurmadan hızlı bir biçimde erişilmektedir. Linux dünyasında bu cache sistemlerine "inode cache" ve "dentry cache" denilmektedir. file, inode ve denrty nesneleri için bu yapıların büyüklüğünde ayrı dilimli tahsisat sistemleri oluşturulmuştur. Pekiyi yukarıdaki nesneler arasındaki ilişki nasıldır? Dosya sistemine dosya betimleyicisi yoluyla erişildiğini anımsayınız. Dosya betimleyicisinden dosya nesnesi (file nesnesi) elde edilmektedir. Dosya nesnesinin içerisinde o dosyanın dizin girişi bilgilerini tutan dentry nesnesinin adresi tutulur. Bu nesnenin içerisinde de inode nesnesinin adresi tutulmaktadır: file ---> dentry ---> inode Ancak 2.6 çekirdekleriyle birlikte file yapısından inode bilgilerine kolay erişebilmek için ayrıca file yapısı içerisinde doğrudan inode nesnesinin adresi de tutulmaya başlanmıştır. Bir aygıt sürücü üzerinde dosya işlemi yapıldığında çekirdek aygıt sürücü fonksiyonlarına dosya nesnesinin adresini (filp göstericisi) geçirmektedir. Yalnızca aygıt sürücü open fonksiyonuyla açılırken ve close fonksiyonu ile kapatılırken inode nesnesinin adresi de bu fonksiyonlara geçirilmektedir. Aygıt sürücünün fonksiyonlarının parametrik yapılarını aşağıda yeniden veriyoruz: int open(struct inode *inodep, struct file *filp); int release(struct inode *inodep, struct file *filp); ssize_t read(struct file *filp, char *buf, size_t size, loff_t *off); ssize_t write(struct file *filp, const char *buf, size_t size, loff_t *off); loff_t llseek(struct file *filp, loff_t off, int whence);