6. Dosya Sistemi - II. Bölüm

Bu bölümde Linux çekirdeğinin dosya sistemine ilişkin bazı ayrıntıları simplefs isimli bir dosya sistemini gerçekleşitrerek açıklayacağız. simplefs dosya sistemi Linux Kernel - İşletim Sistemlerinin Tasatımı ve Gerçekleştirilmesi kursuna sınıf içerisinde tasarlanmış oldukça basit bir dosya sistemidir. Bu dosya sistemini bugün yoğun biçimde kullandığımız ext2 gibi ext4 gibi dosya sistemlerinin basit bir biçimi gibi düşünebilirsiniz.

6.1. Hazırlık İşlemleri

simplfs dosya sistemiminin gerçekleştirimine başlamadan önce bazı hazırlık bilgileri vereceğiz.

6.1.1. Linux Çekirdeğinde Kullanılan Temel Türlere İlişkin typedef İsimleri

Linux çekirdeğinin kodlamasında tür ve uzunluk belirten bazı typedef tür isimleri kullanılmıştır. Çekirdeğin içsel kodlarında belli uzunlukta tamsayı türleri include/linux/types.h dosyası içerisinde aşağıdaki isimlerle typedef edilmiştir:

typedef unsigned char      __u8;
typedef signed char        __s8;
typedef unsigned short     __u16;
typedef signed short       __s16;
typedef unsigned int       __u32;
typedef signed int         __s32;
typedef unsigned long long __u64;
typedef signed long long   __s64;

typedef __u8    u8;
typedef __s8    s8;
typedef __u16   u16;
typedef __s16   s16;
typedef __u32   u32;
typedef __s32   s32;
typedef __u64   u64;
typedef __s64   s64;

typedef unsigned long  ulong;
typedef unsigned int   uint;
typedef unsigned short ushort;

Endian bilgisinin de vurgulandığı türler include/uapi/linux/types.h dosyası içerisinde aşağıdaki gibi typedef edilmiştir:

typedef __u16 __bitwise __le16;
typedef __u16 __bitwise __be16;
typedef __u32 __bitwise __le32;
typedef __u32 __bitwise __be32;
typedef __u64 __bitwise __le64;
typedef __u64 __bitwise __be64;

Burada işaretli tamsayı türlerinin bulunmadığına dikkat ediniz.

C’ye C99 ile eklenen intN_t ve uintN_t tür isimleri (örneğin int32_t ya da uint32_t gibi tür isimleri) Linux çekirdek kodlarında kullanılmamaktadır. Zaten bunların bildirimleri C’ye özgü <stdint.h> içerisindedir. Linux çekirdeğinde C’nin herhangi bir standart başlık dosyası kullanılmamaktadır. Bu nedenle çekirdek kodlamaları yapılırken yukarıda belirttiğimiz typedef türlerini tercih etmelisiniz.

6.1.2. Loop Aygıtları

Bir dosya sistemini gerçekleştirirken bizim bir diske gereksinimimiz olacaktır. Neyse ki Linux’ta bir dosyanın bir disk gibi kullanılmasını sağlayan ismine loop denilen aygıt sürücüler bulunmaktadır. Bu loop aygıtları sayesinde bir dosyayı disk gibi kullanarak denemelerimizi kolay bir biçimde yapabileceğiz. O halde önce bu loop aygıt sürücüsünün nasıl kullanıldığını açıklayalım.

6.1.2.1. Loop Aygıt Dosyaları

loop aygıt sürücülerine ilişkin aygıt dosyaları Linux’ta /dev dizininin altında bulunmaktadır:

$ ls /dev/loop* -l
brw-rw---- 1 root disk  7,   0 Kas 23 12:22 /dev/loop0
brw-rw---- 1 root disk  7,   1 Kas 23 12:22 /dev/loop1
brw-rw---- 1 root disk  7,   2 Kas 23 12:22 /dev/loop2
brw-rw---- 1 root disk  7,   3 Kas 23 12:22 /dev/loop3
brw-rw---- 1 root disk  7,   4 Kas 23 12:22 /dev/loop4
brw-rw---- 1 root disk  7,   5 Kas 23 12:22 /dev/loop5
brw-rw---- 1 root disk  7,   6 Kas 23 12:22 /dev/loop6
brw-rw---- 1 root disk  7,   7 Kas 23 12:22 /dev/loop7
crw-rw---- 1 root disk 10, 237 Kas 23 12:22 /dev/loop-control

Görüldüğü gibi bu sistemde majör numaraları aynı olan, minör numaraları farklı olan 8 loop aygıtı bulunmaktadır.

6.1.2.2. Loop Aygıtları İçin Dosya Oluşturma

Bir loop aygıtını kullanıma hazır hale getirmek için önce onun kullanacağı bir dosyanın oluşturulması gerekir. Linux’ta komut satırında içi 0’larla dolu olan bir dosya dd (disk dump) komutuyla oluşturulabilir. dd komutu aslında iki dosyayı blok blok kopyalamaktadır. Komutta if (input file) komut satırı argümanı kaynak dosyayı, of (output file) komut satırı argümanı ise hedef dosyayı belirtmektedir. Blok uzunluğu bs (block size) komut satırı argümanıyla, kopyalanacak blok sayısı ise count argümanıyla belirtilmektedir. bs argümanı kullanılmayabilir; bu durumda varsayılan blok büyüklüğü 512 alınmaktadır. Eğer count argümanı kullanılmazsa tüm kaynak dosya kopyalanana kadar işlemlere devam edilmektedir. Linux’ta /dev/zero aygıt sürücüsü okunduğunda hep 0 baytı veren bir aygıt sürücüdür. Bu bilgiler eşliğinde içi 0’larla dolu 10 MB civarında bir dosya şöyle oluşturulabilir:

$ dd if=/dev/zero of=mydisk.dat bs=512 count=20480

Bu komutla elimizde içi sıfırlarla dolu 10 MB’lık bir dosya elde etmiş olacağız.

6.1.2.3. Loop Aygıtlarını Yapılandırma

Dosyayı oluşturduktan sonra onun loop aygıtı tarafından disk gibi kullanılmasını sağlamalıyız. Bu işlem losetup programıyla yapılmaktadır. losetup programı şöyle kullanılmaktadır:

losetup <loop_aygıt_dosyası> <disk_olarak_kullanılacak_dosya>

Örneğin:

$ sudo losetup /dev/loop0 mydisk.dat

/dev/loop aygıtına erişebilmek için programın sudo ile çalıştırılması gerekmektedir.

Artık elimizde mydisk.dat dosyasını kullanan loop0 isimli bir blok aygıtı bulunmaktadır. Sistemdeki blok aygıtlarını lsblk komutu ile görüntülediğimizde bu aygıtı da görmeliyiz:

$ lsblk
NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
loop0    7:0    0    10M  0 loop
sda      8:0    0   120G  0 disk
├─sda1   8:1    0     1M  0 part
├─sda2   8:2    0   513M  0 part /boot/efi
└─sda3   8:3    0 119,5G  0 part /
sr0     11:0    1   2,8G  0 rom  /media/kaan/Linux Mint 22.1 Cinnamon 64-bit

6.1.2.4. Loop Aygıtına İlişkin Diskin Formatlanması ve Mount Edilmesi

Artık /dev/loop0 dosyasını bir disk gibi kullanabiliriz. Bu diske yazma yaptığımızda bu işlemden yalnızca bu dosya etkilenecektir. Örneğin bu diskimizi ext2 dosya sistemiyle formatlayalım:

$ sudo mkfs.ext2 /dev/loop0
mke2fs 1.47.0 (5-Feb-2023)
Discarding device blocks: bitti
Creating filesystem with 2560 4k blocks and 2560 inodes

Allocating group tables: bitti
Düğüm tabloları yazılıyor: bitti
Süperblokların ve dosya sisteminin hesap bilgileri yazılıyor: bitti

Şimdi de bu dosya sistemini mount edelim. Bunun için önce mount noktası olarak kullanılacak boş bir dizin oluşturmamız gerekir. (Aslında mount noktası içi dolu bir dizin de olabilir; bu durumda mount işleminden sonra o dizinin içeriğine unmount yapılana kadar erişilemez.):

$ mkdir ext2-test

Mount işlemi şöyle yapılabilir:

$ sudo mount /dev/loop0 ext2-test

Artık ext2-test dizinine geçtiğimizde yeni bir kök dizine geçmiş oluruz. Mount sonrasında mount noktasına ilişkin dizinin (örneğimizdeki ext2-test) sahibi ve grubu root olacaktır. Tabii isterseniz chown komutuyla bunu değiştirebilirsiniz:

$ sudo chown kaan:study ext2-test

Eğer dizinin sahibini root olarak bırakırsanız bu dizinde girdi yaratmak için hep sudo komutunu kullanmak zorunda kalırsınız.

6.1.2.5. Geri Alma İşlemleri

Peki bu işlemlerin hepsi nasıl geri alınacaktır? Geri alımları sırasıyla tersine yapmak gerekir. Önce unmount işlemi yapılmalıdır:

$ sudo umount ext2-test

Bundan sonra loop aygıtının dosyayla ilişkisinin kesilerek onun blok aygıtı durumundan çıkartılması gerekir. Bunun için “losetup -d” komutu kullanılmaktadır:

$ sudo losetup -d /dev/loop0

Artık lsblk komutunda loop0 aygıtını görmememiz gerekir:

$ lsblk
NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
sda      8:0    0   120G  0 disk
├─sda1   8:1    0     1M  0 part
├─sda2   8:2    0   513M  0 part /boot/efi
└─sda3   8:3    0 119,5G  0 part /
sr0     11:0    1   2,8G  0 rom  /media/kaan/Linux Mint 22.1 Cinnamon 64-bit

Tabii burada oluşturduğumuz mydisk.dat dosyası kalmaya devam etmektedir. Biz o dosyayı yine losetup ile blok aygıtı gibi kullanabiliriz ve işlemlerimize kaldığımız yerden devam edebiliriz.

6.1.2.6. Loop Aygıtlarının Kullanım Akışı

Aşağıdaki şema, loop aygıtının tipik kullanım akışını özetlemektedir:

┌─────────────────────────────────────────────────────────────┐
│                   Loop Aygıtı Kullanım Akışı                │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│  1. Disk dosyası oluştur                                    │
│     dd if=/dev/zero of=mydisk.dat bs=512 count=20480        │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│  2. Loop aygıtına bağla                                     │
│     sudo losetup /dev/loop0 mydisk.dat                      │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│  3. Dosya sistemi oluştur (formatla)                        │
│     sudo mkfs.ext2 /dev/loop0                               │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│  4. Mount et                                                │
│     mkdir ext2-test                                         │
│     sudo mount /dev/loop0 ext2-test                         │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│  5. Kullan  (ext2-test dizini üzerinden erişim)             │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│  6. Geri al (ters sırayla)                                  │
│     sudo umount ext2-test                                   │
│     sudo losetup -d /dev/loop0                              │
└─────────────────────────────────────────────────────────────┘

6.2. simplefs Dosya Sisteminin Tasarımı

Sıfırdan bir dosya sistemi tasarlanacaksa öncelikle dosya sisteminin disk organizasyonu üzerinde belirlemelerin yapılması gerekir. Her dosya sisteminin bir metadata alanı bir de data alanı vardır. Metadata alanında dosya sistemine ilişkin parametrik bilgiler ve önemli bölümlerin bilgileri bulundurulur. Data alanı dosyaların içindeki bilgilerin saklandığı alandır. Bugün kullandığımız dosya sistemleri evrim süreci içerisinde sürekli iyileştirilmiş ve bugünkü durumlarına getirilmiştir. Dolayısıyla örneğin ext4 gibi bir dosya sisteminin ya da FAT32 gibi bir dosya sisteminin gerçekleştirimini bir proje biçiminde yapmak gerekir. Yani bunun için belli bir süre düzenli çalışma zamanının ayrılması gerekir. Biz burada oldukça basit bir dosya sistemini gerçekleştirmeye çalışacağız. Bu dosya sistemini simplefs olarak isimlendireceğiz.

6.2.1. simplefs Dosya Sisteminin Disk Organizasyonu

simplefs dosya sisteminin disk organizasyonu aşağıdaki şekilde gösterilmektedir:

simplefs disk organizasyonu

simplefs dosya sisteminin disk organizasyonu

simplefs dosya sistemimizin ilk bloğu (yani ilk 4096 byte’ı) süper bloktur. Burada dosya sistemimize ilişkin önemli parametrik bilgiler tutulmaktadır. UNIX/Linux sistemlerinde her dosyanın bilgileri diskte inode blok denilen bir grup bloktaki inode elemanlarında tutulmaktadır.

6.2.1.1. Inode Blok Yapısı

Inode blok, aşağıdaki gibi inode elemanlarından oluşmaktadır:

inode blok yapısı

Inode blok yapısı

Her dosya ve dizin için dosya sisteminin diskte bir inode elemanını tahsis etmesi gerekir. Boş bir inode elemanının belirlenebilmesi için inode tabanlı dosya sistemlerinde genellikle bir inode bitmap kullanılmaktadır. Bu inode bitmap içerisindeki her bit ilgili inode elemanının boş mu dolu mu olduğunu göstermektedir. Dosya sistemimizde inode bitmap bir blok yer kapladığına göre toplamda 4096 × 8 = 32768 inode elemanının durumu tutulabilmektedir.

Inode tabanlı dosya sistemlerinde data blok içerisindeki blokların da boş mu dolu mu olduğu bilgisi benzer biçimde bir data bitmap ile tutulmaktadır. Bu data bitmap içerisindeki her bit diskteki data bloğunun boş mu dolu mu olduğu bilgisini tutmaktadır.

Pek çok dosya sisteminin disk organizasyonunda dizinler de birer dosyaymış gibi ele alınmaktadır. Dolayısıyla her dizin data bloğunda bir blok yer kaplamaktadır. Genellikle kök dizin belli bir yerde bulundurulur. Kök dizinin yeri de süper blokta belirtilir. Tabii kök dizin için en uygun yer data bloklarının ilk bloğudur.

simplefs dosya sistemimizde her dosya ve dizin en fazla bir blok (yani varsayılan olarak 4096 byte) uzunluğunda olabilmektedir. Bu kısıtı koyduğumuzda artık dosyanın bloklarının yerlerini tutmamıza gerek kalmaz. simplefs dosya sistemimizdeki inode elemanlarının sayısı formatlama sırasında belirlenebilmektedir. Ancak data bitmap ve inode bitmap 4096 × 8 = 32768 bitten oluştuğuna göre dosya sistemi de en fazla 32768 × 4096 = 134 MB büyüklüğünde bir diski desteklemektedir.

6.2.1.2. Süper Blok Yapısı

Diskimizin süper blok bilgileri aşağıdaki C yapısıyla tanımlanmıştır:

struct simplefs_disk_super_block {
    __le32 magic;              /* 0x53494D46 ("SIMF") */
    __le32 block_size;         /* 4096 */
    __le32 inode_count;        /* Total inodes */
    __le32 block_count;        /* Total blocks */
    __le32 free_inodes;        /* Free inodes */
    __le32 free_blocks;        /* Free blocks */
    __le32 inode_table_block;  /* Start of the inode table (3) */
    __le32 inode_table_size;   /* Size of the inode table */
    __le32 data_block_start;   /* Start of data blocks */
    __u8   padding[4060];      /* Padding to 4096 bytes */
};

Buradaki __le32 türü little endian 4 byte’lık tamsayı türünü temsil etmektedir. Buradaki little endian belirlemesiyle derleyici özel bir işlem uygulamaz; yalnızca dosya sistemini gerçekleştirenler için okunabilirliği artırmaktadır. Yani burada söylenmek istenen şey “makineniz big endian olsa bile bu bilgiler diskte little endian biçiminde tutulmaktadır” bilgisidir.

Her dosya sisteminde bir sihirli sayı (magic number) bulundurulur. simplefs dosya sistemimizdeki sihirli sayı süper blokta 0x53494D46 (“SIMF”) biçiminde tutulmaktadır. Süper blok içerisindeki block_size elemanı her zaman 4096 biçimindedir. İleride bu dosya sistemini farklı blok uzunluklarıyla da çalışabilir hale getirebilirsiniz. Ancak bizim dosya sistemimizde bir blok her zaman 4096 byte’tır.

Süper bloktaki inode_count elemanı inode elemanlarının toplam sayısını belirtmektedir. Disk bölümü içerisinde toplamda buradaki sayıdan daha fazla dosya ve dizin bulunamaz; çünkü her dosya ve dizin için bir inode elemanı kullanılmaktadır. block_count alanında ise diskteki tüm blokların sayısı tutulmaktadır; bu bloklara metadata blokları da dahildir. free_inodes ve free_blocks elemanları kullanılmayan inode elemanlarının ve blokların sayısını belirtmektedir.

Inode tablosunun yeri inode_table_block alanında tutulmaktadır. Dosya sistemimizde burada her zaman 3 değeri bulunacaktır. Ancak bu dosya sistemini iyileştirmek isterseniz geleceğe uyum için bu alan bulundurulmaktadır. Inode elemanlarının sayısı formatlama sırasında belirtilmektedir. Dolayısıyla inode bloktaki blok sayısı da değişebilmektedir. Inode bloğun bir blok uzunlukta olmayabileceğine dikkat ediniz. data_block_start alanında data bloğun başlangıç blok numarası bulunmaktadır. Data bloğun ilk bloğunda kök dizinin içeriğinin bulunduğunu anımsayınız.

Bu alanların anlamları aşağıdaki tabloda özetlenmiştir:

simplefs Süper Blok Alanları

Alan

Tür

Açıklama

magic

__le32

0x53494D46 — sihirli sayı (“SIMF”)

block_size

__le32

Her zaman 4096 byte

inode_count

__le32

Format sırasında belirlenir

block_count

__le32

Aygıt boyutu / 4096

free_inodes

__le32

Dinamik güncellenir

free_blocks

__le32

Dinamik güncellenir

inode_table_block

__le32

Her zaman 3

inode_table_size

__le32

(inode_count + 63) / 64 — yukarı yuvarlanır

data_block_start

__le32

3 + inode_table_size + 1

padding[4060]

__u8[]

Bloğu 4096 byte’a tamamlamak için dolgu

İzleyen paragraflarda da göreceğimiz gibi bir inode elemanı inode blokta 64 byte yer kaplamaktadır. Dolayısıyla bir blokta 64 inode elemanı bulunmaktadır. Yukarıdaki tabloda inode_table_size açıklamasında inode elemanlarının sayısı 64’e doğru yukarı yuvarlanmıştır.

Disk simplefs dosya sistemi ile formatlanırken inode sayısı da formatlama sırasında belirtilmektedir. Örneğin:

$ ./mkfs.simplefs /dev/loop0 512

6.2.1.3. Disk Inode Elemanının Yapısı

Inode bloğun inode elemanlarından oluştuğunu ve simplefs dosya sistemimizde bu bloğun değişken uzunlukta olabileceğini belirtmiştik. simplefs dosya sisteminde diskteki bir inode elemanının alanları simplefs_disk_inode adlı C yapısıyla tanımlanmıştır:

struct simplefs_disk_inode {
    __le32 mode;            /* File type + permissions */
    __le32 uid;             /* Owner user ID */
    __le32 gid;             /* Owner group ID */
    __le32 size;            /* File size in bytes */
    __le32 nlink;           /* Hard link count */
    __le32 blocks;          /* Block count (0 or 1) */
    __le32 block_no;        /* Data block number */
    __le32 ctime;           /* Creation time */
    __le32 mtime;           /* Modification time */
    __le32 atime;           /* Access time */
    __u8   padding[24];     /* Padding to 64 bytes */
};

Inode elemanının mode alanında dosyanın tür bilgisi ve erişim hakları tutulmaktadır. Burada Linux’un uyguladığı bitsel organizasyon kullanılmaktadır:

inode mode alanı bit organizasyonu

inode mode alanının bit organizasyonu

mode Alanı Bit Açıklamaları

Kısaltma

Açıklama

UID

setuid biti

GID

setgid biti

STK

sticky bit

UR

Kullanıcı okuma (user read)

UW

Kullanıcı yazma (user write)

UX

Kullanıcı çalıştırma (user execute)

GR

Grup okuma (group read)

GW

Grup yazma (group write)

GX

Grup çalıştırma (group execute)

Inode elemanının uid ve gid alanları dosyaya ilişkin kullanıcı ve grup kimliklerini belirtmektedir. size alanı dosyanın uzunluğunu, nlink alanı hard link sayacını belirtmektedir. simplefs sistemimizde dosyalar en fazla bir blok yer kapladığından yalnızca tek bir bloğun yeri tutulacaktır. Inode elemanının block_no alanı dosyanın bulunduğu bloğu belirtmektedir.

Inode elemanının ctime, mtime ve atime alanları sırasıyla inode bilgilerinin son güncelleme zamanını, dosyanın son değiştirilme zamanını ve dosyanın son okunma zamanını belirtmektedir. Buradaki zamanlar 01/01/1970’ten geçen saniye sayısı biçiminde tutulmaktadır. Biz burada her ne kadar dosya terimini kullandıysak da dizinleri de kastetmekteyiz; çünkü dizinlerin de inode elemanları vardır.

Inode tabanlı dosya sistemlerinde inode bloktaki ilk inode elemanı reserved bırakılmaktadır. Bu inode elemanının inode numarası 0’dır. (Eski sistemlerde 0 numaralı inode elemanı “başarısızlık” anlamında kullanılıyordu.) Linux’un ext dosya sistemlerinde ilk 2 inode elemanı reserved yapılmıştır. Biz simplefs dosya sistemimizde yalnızca 0 numaralı inode elemanını reserved yapacağız. Bizim dosya sistemimizde kök dizinin bilgileri her zaman 1 numaralı inode elemanında bulunacaktır.

6.2.1.4. Dizin Girişleri

simplefs dosya sistemimizdeki her dizin girişi eşit uzunluktadır. (ext dosya sistemlerinde bunların eşit uzunlukta olmadığını anımsayınız.) simplefs dosya sistemimizdeki dizin girişlerinin formatı simplefs_disk_dentry yapısıyla tanımlanmıştır:

#define SIMPLEFS_FILENAME_MAXLEN        32

struct simplefs_disk_dentry {
    __le32 inode;                           /* İnode number */
    char name[SIMPLEFS_FILENAME_MAXLEN];    /* File name */
};

Dizin girişinin ilk alanında ilgili dosyanın inode numarası bulunmaktadır. Dosya ismi her zaman 32 byte yer kaplamaktadır. Dosya isminin sonunda null karakter vardır. Biz genel olarak dizin girişlerindeki isimlerin sonuna null padding uygulayacağız. Bu durumda dosya isimleri en fazla 31 karakter olabilmektedir.

6.2.2. Formatlama Programı: mkfs.simplefs

Bir dosya sistemini gerçekleştirirken ilk yapacağımız işlemlerden biri formatlama programının yazılmasıdır. Formatlama programı disk bölümünü (yani blok aygıtını) metadata alanlarını oluşturarak kullanıma hazır hale getirmektedir. Dosya sistemi aygıt sürücümüz bu metadata alanlarını kullanacaktır. Biz formatlama programımıza mkfs.simplefs ismini vereceğiz.

simplefs dosya sisteminin formatlanması sırasında şu işlemler yapılmalıdır:

  1. Süper blok bilgileri oluşturulup blok aygıtının ilk bloğuna yazılmalıdır.

  2. Inode bitmap’ta 0 ve 1 numaralı inode elemanlarının bitleri 1 yapılmalıdır. Anımsayacağınız gibi 0 numaralı inode elemanı reserved durumdaydı, 1 numaralı inode elemanı ise kök dizine ilişkindi.

  3. Data bitmap’in de ilk duruma getirilmesi gerekir. 0 numaralı data bloğu kök dizini içerdiği için onun bitinin 1 yapılması gerekir.

  4. Inode bloğun da ilk durumuna getirilmesi gerekir. Inode bloğun ilk inode elemanı 0’lar içerecek biçimde boş bırakılmalıdır. Ancak sonraki inode elemanı (yani 1 numaralı inode elemanı) kök dizin bilgilerini tutacak biçimde güncellenmelidir.

  5. Sıra kök dizindeki dizin girişlerinin başlangıçtaki durumunu oluşturmaya gelmiştir. Kök dizinde “.” ve “..” isimli iki dizin girişinin bulundurulması gerekir. Bu dizin girişlerinin inode numaralarının yine kök dizinin inode numarasını (örneğimizde 1) içermesi gerekmektedir.

Format programımızda önce komut satırı argümanları kontrol edilmiştir:

int n_inodes = DEF_NUMBER_OF_INODES;
int fd;

if (argc > 3) {
    fprintf(stderr, "too many arguments!\n");
    fprintf(stderr, "usage: mkfs.simplefs <device_path> [number_of_inodes]\n");
    exit(EXIT_FAILURE);
}
if (argc == 1) {
    fprintf(stderr, "too few arguments!\n");
    fprintf(stderr, "usage: mkfs.simplefs <device_path> [number_of_inodes]\n");
    exit(EXIT_FAILURE);
}

if (argc == 3) {
    n_inodes = atoi(argv[2]);
    if (n_inodes < MIN_NUMBER_OF_INODES || n_inodes > MAX_NUMBER_OF_INODES) {
        fprintf(stderr, "incorrect number of inodes!...\n");
        exit(EXIT_FAILURE);
    }
}

Sonra aygıt dosyası open fonksiyonuyla açılmıştır:

if ((fd = open(argv[1], O_WRONLY)) == -1)
    exit_sys(argv[1]);

Süper bloğun yazılması write_super_block fonksiyonu tarafından yapılmaktadır:

int write_super_block(int fd, int n_inodes, struct simplefs_disk_super_block *sbd)
{
    uint64_t size;

    sbd->magic = SIMPLEFS_MAGIC;
    sbd->block_size = SIMPLEFS_BLOCK_SIZE;
    sbd->inode_count = n_inodes;
    /*
    if (ioctl(fd, BLKGETSIZE64, &size) == -1)
        return -1;
    */
    if ((size = lseek(fd, 0, SEEK_END)) == -1)
        return -1;

    sbd->block_count = size / SIMPLEFS_BLOCK_SIZE;
    sbd->free_inodes = n_inodes - 2;
    sbd->inode_table_block = 3;
    sbd->inode_table_size = (n_inodes + INODE_SIZE - 1) / INODE_SIZE;
    sbd->data_block_start = 3 + sbd->inode_table_size;
    sbd->free_blocks = sbd->block_count - sbd->data_block_start - 1;

    lseek(fd, 0, SEEK_SET);
    if (write(fd, sbd, sizeof(*sbd)) != (ssize_t)sizeof(*sbd))
        return -1;

    return 0;
}

Burada diskin uzunluğunu bulmak için dosya göstericisini sona çekip onun konumunu aldık. Aslında aynı işlem blok aygıt sürücülerine BLKGETSIZE64 ioctl komutu gönderilerek de yapılabilmektedir. write_super_block fonksiyonunun ikinci parametresinin inode elemanlarının sayısını belirttiğine dikkat ediniz.

Süper bloğu oluşturduktan sonra inode bitmap ve data bitmap blokları oluşturulmalıdır. Inode bitmap içerisindeki her bit bir inode elemanının dolu mu boş mu olduğunu tutmaktadır. Başlangıçta ilk iki inode elemanı doludur. Anımsayacağınız gibi 0’ıncı inode elemanı pek çok inode tabanlı dosya sisteminde hiç kullanılmamaktadır. Bizim dosya sistemimizde kök dizinin bilgileri 1 numaralı inode elemanındadır. Inode bitmap’in ilk iki bitini 1’leyip geri kalan bitlerini 0’lamak için en pratik yöntem önce içi sıfırlarla dolu bir dizi almak, sonra dizinin ilk elemanının düşük anlamlı iki bitini 1’lemektir. Bu işlem formatlama programımızda şöyle yapılmıştır:

unsigned char bitmap[SIMPLEFS_BLOCK_SIZE] = {0};
/* ... */

bitmap[0] = 0x03;           /* first two bits in inode bitmap must be 1 */
if (write(fd, bitmap, SIMPLEFS_BLOCK_SIZE) != SIMPLEFS_BLOCK_SIZE) {
    fprintf(stderr, "cannot write inode bitmap!..\n");
    exit(EXIT_FAILURE);
}

Data bitmap’in yalnızca ilk biti 1 olmalıdır. Çünkü diskin data bloğunda yalnızca ilk blok (orada kök dizinin olduğunu anımsayınız) doludur. Bu işlem de formatlama programımızda şöyle yapılmıştır:

bitmap[0] = 0x01;       /* first bit in data bitmap must be 1 */
if (write(fd, bitmap, SIMPLEFS_BLOCK_SIZE) != SIMPLEFS_BLOCK_SIZE) {
    fprintf(stderr, "cannot write data bitmap!..\n");
    exit(EXIT_FAILURE);
}

Sıra inode tablosunun yazılmasına gelmiştir. Inode tablosundaki ilk inode elemanının boş olması gerektiğini anımsayınız. Sonraki inode elemanı (yani 1 numaralı inode elemanı) kök dizinine ilişkin inode elemanı olmalıdır. Format programımızda inode tablosu write_inode_table isimli bir fonksiyonla ilk haline getirilmiştir:

int write_inode_table(int fd, struct simplefs_disk_super_block *sbd)
{
    unsigned char buf[SIMPLEFS_BLOCK_SIZE] = {0};
    struct simplefs_disk_inode inoded = {0};
    time_t curtime;
    off_t pos;

    pos = lseek(fd, 0, SEEK_CUR);
    for (int i = 0; i < sbd->inode_table_size; ++i)
        if (write(fd, buf, SIMPLEFS_BLOCK_SIZE) != SIMPLEFS_BLOCK_SIZE)
            return -1;

    lseek(fd, pos, SEEK_SET);
    if (write(fd, &inoded, sizeof(inoded)) != (ssize_t)sizeof(inoded))
        return -1;

    inoded.mode = S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO;
    inoded.uid = 0;
    inoded.gid = 0;
    inoded.size = SIMPLEFS_BLOCK_SIZE;
    inoded.nlink = 3;
    inoded.blocks = 1;
    inoded.block_no = sbd->data_block_start;

    curtime = time(NULL);
    inoded.ctime = curtime;
    inoded.mtime = curtime;
    inoded.atime = curtime;

    if (write(fd, &inoded, sizeof(inoded)) != (ssize_t)sizeof(inoded))
        return -1;

    return 0;
}

Kök dizine ilişkin inode elemanının size alanına SIMPLEFS_BLOCK_SIZE değerini yerleştirdiğimize dikkat ediniz. Pek çok dosya sisteminde dizin dosyalarının uzunluğu onlar için ayrılan blokların toplam uzunluğu ile ifade edilmektedir. Biz de simplefs dosya sistemimizde tüm dizinlerin uzunluklarını SIMPLEFS_BLOCK_SIZE yani 4096 biçiminde set edeceğiz.

Artık son olarak kök dizinin girişlerinin oluşturulması gerekir. Kök dizinin ilk girişinin “.” ve “..” dizinlerinden oluşması zorunludur. Bu dizin girişlerini kök dizine ilişkin bloğun ilk iki girişine yazmak için formatlama programımızda write_dentries fonksiyonu kullanılmıştır:

int write_dentries(int fd, struct simplefs_disk_super_block *sbd)
{
    unsigned char buf[SIMPLEFS_BLOCK_SIZE] = {0};
    struct simplefs_disk_dentry de = {0};

    lseek(fd, sbd->data_block_start * SIMPLEFS_BLOCK_SIZE, SEEK_SET);
    if (write(fd, buf, SIMPLEFS_BLOCK_SIZE) != SIMPLEFS_BLOCK_SIZE)
        return -1;

    lseek(fd, sbd->data_block_start * SIMPLEFS_BLOCK_SIZE, SEEK_SET);
    de.inode = 2;
    strcpy(de.name, ".");
    if (write(fd, &de, SIMPLEFS_DENTRY_LEN) != SIMPLEFS_DENTRY_LEN)
        return -1;

    de.inode = 2;
    strcpy(de.name, "..");
    if (write(fd, &de, SIMPLEFS_DENTRY_LEN) != SIMPLEFS_DENTRY_LEN)
        return -1;

    return 0;
}

Formatlama programımızın tamamı mkfs.simplefs.c ismiyle aşağıda verilmiştir:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <linux/types.h>

#define SIMPLEFS_BLOCK_SIZE         4096
#define DEF_NUMBER_OF_INODES        1024
#define MAX_NUMBER_OF_INODES        (4096 * 8)
#define MIN_NUMBER_OF_INODES        50
#define INODE_SIZE                  64
#define SIMPLEFS_FILENAME_MAXLEN    32
#define SIMPLEFS_DENTRY_LEN         36
#define SIMPLEFS_MAGIC              0x53494D46

struct simplefs_disk_super_block {
    __le32 magic;              /* 0x464D4953 ("SIMF") */
    __le32 block_size;         /* 4096 */
    __le32 inode_count;        /* Total inodes */
    __le32 block_count;        /* Total blocks */
    __le32 free_inodes;        /* Free inodes */
    __le32 free_blocks;        /* Free blocks */
    __le32 inode_table_block;  /* Start of the inode table (3) */
    __le32 inode_table_size;   /* Size of the inode table */
    __le32 data_block_start;   /* Start of data blocks */
    __u8   padding[4060];      /* Padding to 4096 bytes */
};

struct simplefs_disk_inode {
    __le32 mode;            /* File type + permissions */
    __le32 uid;             /* Owner user ID */
    __le32 gid;             /* Owner group ID */
    __le32 size;            /* File size in bytes */
    __le32 nlink;           /* Hard link count */
    __le32 blocks;          /* Block count (0 or 1) */
    __le32 block_no;        /* Data block number */
    __le32 ctime;           /* Creation time */
    __le32 mtime;           /* Modification time */
    __le32 atime;           /* Access time */
    __u8   padding[24];     /* Padding to 64 bytes */
};

struct simplefs_disk_dentry {
    __le32 inode;                           /* Inode number */
    char name[SIMPLEFS_FILENAME_MAXLEN];    /* File name */
};

int write_super_block(int fd, int n_inodes, struct simplefs_disk_super_block *sbd);
int write_inode_table(int fd, struct simplefs_disk_super_block *sbd);
int write_dentries(int fd, struct simplefs_disk_super_block *sbd);
void exit_sys(const char *msg);

/* usage: mkfs.simplefs <device_path> [number_of_inodes] */

int main(int argc, char *argv[])
{
    int n_inodes = DEF_NUMBER_OF_INODES;
    int fd;
    unsigned char bitmap[SIMPLEFS_BLOCK_SIZE] = {0};
    struct simplefs_disk_super_block sbd = {0};

    if (argc > 3) {
        fprintf(stderr, "too many arguments!\n");
        fprintf(stderr, "usage: mkfs.simplefs <device_path> [number_of_inodes]\n");
        exit(EXIT_FAILURE);
    }
    if (argc == 1) {
        fprintf(stderr, "too few arguments!\n");
        fprintf(stderr, "usage: mkfs.simplefs <device_path> [number_of_inodes]\n");
        exit(EXIT_FAILURE);
    }

    if (argc == 3) {
        n_inodes = atoi(argv[2]);
        if (n_inodes < MIN_NUMBER_OF_INODES || n_inodes > MAX_NUMBER_OF_INODES) {
            fprintf(stderr, "incorrect number of inodes!. Number of inodes must be "
                            "between 50 and 32768...\n");
            exit(EXIT_FAILURE);
        }
    }

    if ((fd = open(argv[1], O_WRONLY)) == -1)
        exit_sys(argv[1]);

    if (write_super_block(fd, n_inodes, &sbd) == -1)
        exit_sys("write_super_block");

    bitmap[0] = 0x03;       /* first two bits in inode bitmap must be 1 */
    if (write(fd, bitmap, SIMPLEFS_BLOCK_SIZE) != SIMPLEFS_BLOCK_SIZE) {
        fprintf(stderr, "cannot write inode bitmap!..\n");
        exit(EXIT_FAILURE);
    }

    bitmap[0] = 0x01;       /* first bit in data bitmap must be 1 */
    if (write(fd, bitmap, SIMPLEFS_BLOCK_SIZE) != SIMPLEFS_BLOCK_SIZE) {
        fprintf(stderr, "cannot write data bitmap!..\n");
        exit(EXIT_FAILURE);
    }

    if (write_inode_table(fd, &sbd) == -1) {
        fprintf(stderr, "cannot write inode table!..\n");
        exit(EXIT_FAILURE);
    }

    if (write_dentries(fd, &sbd) == -1) {
        fprintf(stderr, "cannot write dentries!..\n");
        exit(EXIT_FAILURE);
    }

    printf("mkfs.simplefs completed successfully...\n");

    return 0;
}

int write_super_block(int fd, int n_inodes, struct simplefs_disk_super_block *sbd)
{
    uint64_t size;

    sbd->magic = SIMPLEFS_MAGIC;
    sbd->block_size = SIMPLEFS_BLOCK_SIZE;
    sbd->inode_count = n_inodes;
    /*
    if (ioctl(fd, BLKGETSIZE64, &size) == -1)
        return -1;
    */
    if ((size = lseek(fd, 0, SEEK_END)) == -1)
        return -1;

    sbd->block_count = size / SIMPLEFS_BLOCK_SIZE;
    sbd->free_inodes = n_inodes - 2;
    sbd->inode_table_block = 3;
    sbd->inode_table_size = (n_inodes + INODE_SIZE - 1) / INODE_SIZE;
    sbd->data_block_start = 3 + sbd->inode_table_size;
    sbd->free_blocks = sbd->block_count - sbd->data_block_start - 1;

    lseek(fd, 0, SEEK_SET);
    if (write(fd, sbd, sizeof(*sbd)) != (ssize_t)sizeof(*sbd))
        return -1;

    return 0;
}

int write_inode_table(int fd, struct simplefs_disk_super_block *sbd)
{
    unsigned char buf[SIMPLEFS_BLOCK_SIZE] = {0};
    struct simplefs_disk_inode inoded = {0};
    time_t curtime;
    off_t pos;

    pos = lseek(fd, 0, SEEK_CUR);
    for (int i = 0; i < sbd->inode_table_size; ++i)
        if (write(fd, buf, SIMPLEFS_BLOCK_SIZE) != SIMPLEFS_BLOCK_SIZE)
            return -1;

    lseek(fd, pos, SEEK_SET);
    if (write(fd, &inoded, sizeof(inoded)) != (ssize_t)sizeof(inoded))
        return -1;

    inoded.mode = S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO;
    inoded.uid = 0;
    inoded.gid = 0;
    inoded.size = SIMPLEFS_BLOCK_SIZE;
    inoded.nlink = 3;
    inoded.blocks = 1;
    inoded.block_no = sbd->data_block_start;

    curtime = time(NULL);
    inoded.ctime = curtime;
    inoded.mtime = curtime;
    inoded.atime = curtime;

    if (write(fd, &inoded, sizeof(inoded)) != (ssize_t)sizeof(inoded))
        return -1;

    return 0;
}

int write_dentries(int fd, struct simplefs_disk_super_block *sbd)
{
    unsigned char buf[SIMPLEFS_BLOCK_SIZE] = {0};
    struct simplefs_disk_dentry de = {0};

    lseek(fd, sbd->data_block_start * SIMPLEFS_BLOCK_SIZE, SEEK_SET);
    if (write(fd, buf, SIMPLEFS_BLOCK_SIZE) != SIMPLEFS_BLOCK_SIZE)
        return -1;

    lseek(fd, sbd->data_block_start * SIMPLEFS_BLOCK_SIZE, SEEK_SET);
    de.inode = 2;
    strcpy(de.name, ".");
    if (write(fd, &de, SIMPLEFS_DENTRY_LEN) != SIMPLEFS_DENTRY_LEN)
        return -1;

    de.inode = 2;
    strcpy(de.name, "..");
    if (write(fd, &de, SIMPLEFS_DENTRY_LEN) != SIMPLEFS_DENTRY_LEN)
        return -1;

    return 0;
}

void exit_sys(const char *msg)
{
    perror(msg);

    exit(EXIT_FAILURE);
}

6.3. simplefs Dosya Sistemi Aygıt Sürücüsünün Gerçekleştirimi

simplefs dosya sistemini bir aygıt sürücü olarak yazacağız. Yazıma iskelet bir çekirdek modülü ile başlayabiliriz:

/* simplefs.c */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kaan Aslan");
MODULE_DESCRIPTION("simplefs");

static int __init simplefs_module_init(void)
{
    printk(KERN_INFO "simplefs module init\n");

    return 0;
}

static void __exit simplefs_module_exit(void)
{
    printk(KERN_INFO "simplefs module exit\n");
}

module_init(simplefs_module_init);
module_exit(simplefs_module_exit);

Dosya sistemi aygıt sürücüleri için ilk yapılacak şey file_system_type türünden global bir nesne tenımlayıp onu çekirdek modülünün init fonksiyonunda register_filesystem fonksiyonu ile register ettirmektir:

static struct file_system_type simplefs_type = {
    .owner = THIS_MODULE,
    .name = "simplefs",
    .mount = simplefs_mount,
    .kill_sb = simplefs_kill_sb,
    .fs_flags = FS_REQUIRES_DEV,
};

static int __init simplefs_module_init(void)
{
    int result;

    if ((result = register_filesystem(&simplefs_type)) != 0)
        return result;

    printk(KERN_INFO "simplefs module init\n");

    return 0;
}

Anımsayacağınız gibi kullanıcı dosya sistemimizi mount etmeye çalıştığında file_system_type yapısının mount elemanına yerleştirilen fonksiyon çağrılmaktadır. Biz bu fonksiyon içerisinde çekirdeğin daha yüksek seviyeli mount_bdev fonksiyonunu çağırarak mount işlemlerini bu fonksiyona yaptırabiliriz. Yine anımsayacağınız gibi en yeni çekirdeklerde artık mount_bdev fonksiyonu yerine get_tree_bdev fonksiyonu kullanılıyordu. Biz simplefs dosya sistemimizde önce mount_bdev fonksiyonunu kullanacağız. Sonra bunu get_tree_bdev fonksiyonunu kullanacak biçimde değiştireceğiz. simplefs dosya sistemimizdeki simplefs_mount fonksiyonu şöyle yazılmıştır:

static struct dentry *simplefs_mount(struct file_system_type *type, int flags, const char *dev, void *data)
{
    return mount_bdev(type, flags, dev, data, simplefs_fill_super);
}

Burada gerekli işlemlerin çoğu zaten mount_bdev fonksiyonu tarafından yapılmaktadır. Bizim mount_bdev fonksiyonu tarafından oluşturulan süper blok nesnesini doldurmamız gerekir. mount_bdev bu doldurma işlemi için bizim simplefs_fill_super fonksiyonumuzu çağırmaktadır. Peki bu süper blok nesnesinin içini nasıl doldurmalıyız?

static int simplefs_fill_super(struct super_block *sb, void *data, int silent)
{
    /* ... */

    return 0;
}

6.3.1. super_block Nesnesinin İçinin Doldurulması

Bizim super_block yapısı ile temsil edilen süper blok nesnesinin tüm elemanlarını doldurmamıza gerek yoktur. Ancak yapının aşağıdaki elemanlarını mutlaka doldurmamız gerekir:

s_magic: Bu elemana bizim dosya sistemimize ilişkin kendi belirlediğimiz 4 byte’lık bir sihirli sayı atamamız gerekir. Dosya sistemimizdeki sihirli sayı şöyledir:

#define SIMPLEFS_MAGIC           0x53494D46
/* ... */

sb->s_magic = SIMPLEFS_MAGIC;

s_blocksize: super_block yapısının s_blocksize ve s_blocksize_bits elemanlarına dosya sisteminde bir bloğun byte uzunluğu ve onun log2 değeri yazılmalıdır. Bu işlem için sb_set_blocksize fonksiyonundan faydalanılabilir. Örneğin:

sb_set_blocksize(sb, SIMPLEFS_BLOCKSIZE);

Bu fonksiyon başarı durumunda bizim ikinci parametreye geçtiğimiz blok uzunluğuna, başarısızlık durumunda 0 değerine geri dönmektedir. Ancak fonksiyon normal bir işleyişte başarısız olamayacağı için hata kontrolünün yapılmasına gerek yoktur.

s_maxbytes: Bu elemana bir dosyada bulunabilecek maksimum byte sayısı yerleştirilmelidir. Bizim simplefs dosya sistemimizde bir dosyada en fazla SIMPLEFS_BLOCKSIZE kadar byte tutulabilmektedir. Bu elemana şöyle değer atayabiliriz:

sb->s_maxbytes = SIMPLEFS_BLOCK_SIZE;

s_op: super_block yapısının s_op elemanına çekirdek tarafından çeşitli durumlarda çağrılacak fonksiyonların adresleri yerleştirilmelidir. Biz çekirdeğin bu çok biçimli davranışından daha önce bahsetmiştir. Bu elemana super_operations isimli bir yapı nesnesinin adresi yerleştirilmelidir. Anımsanacağı gibi bu super_operations yapısı fonksiyon göstericilerinden oluşmaktadır. Ancak bu yapının da tüm elemanlarının doldurulması gerekmez. Bizim simplefs dosya sistemimizde bu yapının aşağıdaki elemanlarına yerleştirme yapacağız:

static const struct super_operations simplefs_super_ops = {
    .alloc_inode = simplefs_alloc_inode,
    .free_inode  = simplefs_free_inode,
    .write_inode = simplefs_write_inode,
    .evict_inode = simplefs_evict_inode,
    .statfs      = simple_statfs,
};

Bu elemanlara girilecek fonksiyonların parametrik yapısı aşağıdaki gibi olmalıdır:

static struct inode *simplefs_alloc_inode(struct super_block *sb);
static void simplefs_free_inode(struct inode *inode);
static int simplefs_write_inode(struct inode *inode, struct writeback_control *wbc);
static void simplefs_evict_inode(struct inode *inode);

simplefs dosya sistemimiz için süper blok nesnesinin s_op elemanına yerleştirmeyi şöyle yapabiliriz:

sb->s_op = &simplefs_super_ops;

Biz bu fonksiyonların ne zaman çağrılacağını ve bunların içerisinde ne yapılması gerektiğini izleyen paragraflarda açıklayacağız.

s_fs_info: Çekirdek süper blok nesnelerini super_block isimli yapıyla temsil etmektedir. Ancak her dosya sisteminin kendine özgü bir süper blok formatı da vardır. İşte çekirdeğin super_block yapısından hareketle sistem programcısının kendi dosya sistemine ilişkin süper blok bilgilerine erişebilmesi gerekir. super_block yapısının s_fs_info elemanı bunun için kullanılmaktadır. Bizim bu noktada dosya sistemimize ilişkin süper blok bilgilerini diskten okuyup bunu bir yapı nesnesi içerisinde depolamamız gerekir. Ancak aslında bizim yalnızca kendi dosya sistemimize ilişkin diskteki süper blok bilgilerine değil aynı zamanda onun yönetimine yönelik bilgileri de bulundurmamız gerekmektedir. Bunun için dosya sistemi tasarımcıları tipik olarak kendi dosya sistemlerinin süper blok yönetimine ilişkin bir yapı oluşturup diskteki süper blok bilgilerini bu yapı nesnesinin içerisinde saklamaktadır. super_block yapısının s_fs_info elemanına da bu nesnenin adresini yerleştirmektedir. Biz simplefs dosya sistemimizdeki süper blok yönetimi için aşağıdaki gibi bir yapı oluşturabiliriz:

struct simplefs_super_block {
    struct simplefs_disk_super_block *sbd;
    struct buffer_head *sb_bh;
    struct buffer_head *inode_bitmap_bh;
    struct buffer_head *data_bitmap_bh;
    unsigned long *inode_bitmap;
    unsigned long *data_bitmap;
    spinlock_t lock;
};

Burada yapının sbd elemanı bizim diskte tuttuğumuz süper blok bilgilerini göstermektedir. Biz zaten formatlama programında bu yapıyı aşağıdaki gibi oluşturmuştuk:

struct simplefs_disk_super_block {
    __le32 magic;              /* 0x464D4953 ("SIMF") */
    __le32 block_size;         /* 4096 */
    __le32 inode_count;        /* Total inodes */
    __le32 block_count;        /* Total blocks */
    __le32 free_inodes;        /* Free inodes */
    __le32 free_blocks;        /* Free blocks */
    __le32 inode_table_block;  /* Start of the inode table (3) */
    __le32 inode_table_size;   /* Size of the inode table */
    __le32 data_block_start;   /* Start of data blocks */
    __u8   padding[4060];      /* Padding to 4096 bytes */
};

Buradaki super_block yapısının s_fs_info elemanı için oluşan durumu aşağıdaki şekil betimlemektedir:

super_block → simplefs_super_block → simplefs_disk_super_block gösterici zinciri

Artık çekirdek bize super_block nesnesini verdiğinde biz kendi sistemimize ilişkin tüm süper blok bilgilerine erişiyor olacağız. simplefs_super_block yapısının sbd dışındaki diğer elemanlarını izleyen paragraflarda açıklayacağız.

s_root: super_block yapısının bu elemanına bizim kök dizine ilişkin dentry nesne adresini yerleştirmemiz gerekir. Tabii dentry nesnesinin elde edilmesi için önce inode nesnesinin elde edilmesi gerekir. Bu işlemlerin nasıl yapılacağı izleyen paragraflarda açıklanmaktadır.

Biz yukarıdaki bazı süreçlerle doldurulacak elemanlar dışında simplefs_fill_super fonksiyonumuzun ilk kısmını şöyle oluşturabiliriz:

static int simplefs_fill_super(struct super_block *sb, void *data, int silent)
{
    sb->s_magic = SIMPLEFS_MAGIC;
    sb_set_blocksize(sb, SIMPLEFS_BLOCK_SIZE);
    sb->s_maxbytes = SIMPLEFS_BLOCK_SIZE;
    sb->s_op = &simplefs_super_ops;
    sb->s_flags |= SB_NOATIME;

    /* ... */

    return 0;
}

sb->s_flags |= SB_NOATIME işlemini dosya sisteminin basit tutulmasını sağlamak amacıyla uyguladık. Bu bayrak dosyaların erişim zamanlarının güncellenmesini engelleyecektir. İzleyen paragraflarda super_block yapısının diğer elemanlarının nasıl doldurulacağını açıklayacağız.

Önce super_block yapısının s_fs_info elemanını doldurmaya çalışalım. Anımsanacağı gibi bu eleman bizim dosya sistemimizdeki süper blok işlemlerini yönetecek simplefs_super_block nesnesinin adresini tutmaktadır. Bizim bu nesneyi çekirdeğin heap sisteminde tahsis etmemiz gerekir. Biz çekirdeğin heap sistemini henüz incelemedik. Burada kzalloc isimli çekirdek fonksiyonuyla bu tahsisatı yapacağız. kzalloc fonksiyonu kmalloc fonksiyonun tahsis edilen alanı sıfırlayan bir biçimidir. kzalloc (ya da kmalloc) ile tahsis edilmiş alanlar kfree fonksiyonuyla serbest bırakılmaktadır. Tahsisatı şöyle yapabiliriz:

struct simplefs_super_block *sfs_sb;
/* ... */

if ((sfs_sb = kzalloc(sizeof(struct simplefs_super_block), GFP_KERNEL)) == NULL) {
    printk(KERN_INFO "cannot allocate simplefs super block!..\n");
    return -ENOMEM;
}

Biz içi sıfırlarla doldurulmuş simplefs_super_block türünden bir nesneyi tahsis etmiş olduk. Bu nesnenin içini doldurmamız gerekir. Bu nesne bizim kendi dosya sistemimizin süper blok işlemleri için faydalanacağımız elemanlardan oluşmaktadır. Biz önce yapının sbd elemanını dolduralım. Bu eleman anımsayacağınız gibi bizim dosya sistemimizin diskteki süper blok bilgilerini tutmaktadır. O halde bizim bu noktada diskin süper bloğunu (yani 0 numaralı bloğunu) okumamız gerekir. Bu işlemi yapmanın klasik yolu çekirdeğin biraz daha yüksek seviyeli sb_bread fonksiyonunu kullanmaktır. Çekirdeğin sb_bread fonksiyonu süper blok içerisindeki blok aygıtı bilgilerinden ve blok uzunluğundan faydalanarak diskin belirttiğimiz numaralı bloğunu okumaktadır. sb_bread fonksiyonun prototipi şöyledir:

struct buffer_head *sb_bread(struct super_block *sb, sector_t block);

Fonksiyonun birinci parametresi super_block nesnesinin adresini, ikinci parametresi ise okunacak bloğun numarasını almaktadır. (Tabii bizim bu fonksiyonu çağırmadan önce super_block nesnesine blok büyüklüğünü yerleştirmemiz gerekir.) Fonksiyon okunan bloğu çekirdekte temsil eden buffer_head nesnesinin adresine geri dönmektedir. Biz buffer_head yapısını ve organizasyonunu bellek yönetimi kısmında göreceğiz. Linux’ta bu buffer_head tasarımına yeni ve modern bir alternatif de eklenmiştir. Buna bio sistemi de denilmektedir. Ancak bu buffer_head sistemi çekirdekten atılabilecek bir tasarım değildir. Çünkü pek çok dosya sistemi halen bu buffer_head tasarımını kullanmaktadır. Güncel çekirdeklerde buffer_head yapısı include/linux/buffer_head.h dosyasında şöyle bildirilmiştir:

struct buffer_head {
    unsigned long b_state;              /* buffer state bitmap (see above) */
    struct buffer_head *b_this_page;    /* circular list of page's buffers */
    union {
        struct page  *b_page;           /* the page this bh is mapped to */
        struct folio *b_folio;          /* the folio this bh is mapped to */
    };
    sector_t b_blocknr;                 /* start block number */
    size_t   b_size;                    /* size of mapping */
    char    *b_data;                    /* pointer to data within the page */

    struct block_device *b_bdev;
    bh_end_io_t         *b_end_io;      /* I/O completion */
    void                *b_private;     /* reserved for b_end_io */
    struct list_head     b_assoc_buffers;   /* associated with another mapping */
    struct address_space *b_assoc_map;  /* mapping this buffer is associated with */
    atomic_t    b_count;                /* users using this buffer_head */
    spinlock_t  b_uptodate_lock;        /* Used by the first bh in a page, to
                                         * serialise IO completion of other
                                         * buffers in the page */
};

Okunan bloğun bilgileri yapının b_data elemanından elde edilmektedir. Bizim bu aşamada bu buffer_head tasarımını bilmemize gerek yoktur.

sb_bread fonksiyonu başarısızlık durumunda NULL adrese geri dönmektedir.

sb_bread fonksiyonuyla okunan blok brelse fonksiyonuyla geri bırakılmaktadır. Fonksiyonun parametrik yapısı şöyledir:

void brelse(struct buffer_head *bh);

Pek çok çekirdek nesnesinde olduğu gibi buffer_head nesnelerinde de bir sayaç bulunmaktadır. sb_bread bu sayacı artırmaktadır. brelse önce sayacı 1 azaltır, eğer sayaç 0’a düşerse buffer_head nesnesini dilim önbelleğine (slab cache) iade eder. buffer_head nesneleri ayrı bir dilim önbelleğinden tahsis edilmektedir. Dilimli tahsisat sistemi konusunu Bellek Yönetimi bölümünde ayrıntılı bir biçimde ele alacağız.

Biz simplefs_fill_super fonksiyonumuzda diskimizin süper bloğunu şöyle okuyabiliriz:

if ((sfs_sb->sb_bh = sb_bread(sb, 0)) == NULL) {
    result = -EIO;
    goto EXIT1;
}

Görüldüğü gibi biz diskimizin süper bloğunu kendi süper bloğumuzu yönetmekte kullanacağımız simplefs_super_block yapısının sb_bh elemanına yerleştirdik. Çünkü bu bloktan elde ettiğimiz disk süper blok bilgilerinin bulunduğu bellek alanının da yaşıyor olması gerekmektedir.

if ((sfs_sb->sb_bh = sb_bread(sb, 0)) == NULL) {
    printk(KERN_INFO "cannot read simplefs disk super block!..\n");
    result = -EIO;
    goto EXIT1;
}

Şimdi artık biz diskimizin süper bloğunu okuduk. Kendi disk süper blok bilgilerimizi simplefs_super_block nesnesinin sbd elemanına yerleştirebiliriz:

struct simplefs_disk_super_block *sbd;
/* ... */

sbd = (struct simplefs_disk_super_block *) sfs_sb->sb_bh->b_data;
sfs_sb->sbd = sbd;

Biz burada disk süper blok bilgilerine erişirken birden fazla -> operatörü kullanmamak için sbd isminde bir gösterici kullandık. Aslında derleyiciler optimizasyon seçenekleri açıksa birden fazla -> operatörünü zaten optimize etmektedir.

6.3.2. Dosya Sisteminde Güncellenen Disk Bloklarının Aygıta Yazılması

Linux çekirdek kodlamalarında blok aygıtlarından okuma işlemleri doğrudan yapılıyor olmasına karşın yazma işlemleri doğrudan değil gecikmeli bir biçimde (yani asenkron biçimde) yapılmaktadır. Örneğin biz bir bloğu sb_bread fonksiyonu ile blok aygıtından okumak istediğimizde bu fonksiyon bloğu önce sayfa belleğinde arar, eğer blok sayfa önbelleğinde varsa doğrudan oradan verir. Eğer blok sayfa önbelleğinde yoksa bu fonksiyon bloke oluşturarak senkron bir biçimde aygıttan okuma yapılmasını sağlamaktadır.

Şimdi okuduğumuz bloğun üzerinde değişiklikler yaptıktan sonra onu yeniden diske yazmak isteyelim. İşte yazma işlemini doğrudan yapmayız. mark_buffer_dirty isimli fonksiyonla onu kirli olarak işaretleriz. Kirli sayfaların diske yazılması bdi_writeback isimli bir thread tarafından bir süre sonra yapılmaktadır. Dosya sistemini kodlarken ne zaman okuduğumuz bir blok üzerinde değişiklik yapsak onun yazımını sağlamak için mark_buffer_dirty fonksiyonu ile onun kirlenmiş olduğunu belirtmemiz gerekir. mark_buffer_dirty fonksiyonunun çağrı zinciri şöyledir:

mark_buffer_dirty(bh)
└─ __set_buffer_dirty(bh)
    ├─ set_buffer_dirty(bh)                 ← BH_Dirty flag'i set et
    └─ __set_page_dirty()
            └─ __set_page_dirty_nobuffers() veya
            __set_page_dirty_buffers()
                └─ mapping->host → mark_inode_dirty_pages()
                    └─ I_DIRTY_PAGES flag → __mark_inode_dirty()

Blok kirli olarak işaretlendikten sonra çekirdeğin bdi_writeback thread’i onu aşağıdaki çağrı zinciriyle flush etmektedir:

wb_writeback()
└─ writeback_sb_inodes()
    └─ __writeback_single_inode()
            └─ do_writepages()                      ← buraya gelir
                └─ mapping->a_ops->writepages()
                    └─ (örn.) ext4_writepages()
                        └─ mpage_writepages() / iomap_writepages()
                                └─ submit_bio()     ← block layer'a ilet

En sonunda ilgili bloğun yazım için blok aygıt sürücüsüne iletildiğini görüyorsunuz.

Benzer biçimde inode önbelleğindeki inode nesneleri üzerinde de değişiklikler yaptığımızda mark_inode_dirty fonksiyonu ile onun kirlenmiş olduğunu belirtmeliyiz. mark_inode_dirty fonksiyonunun çağrı zinciri şöyledir:

mark_inode_dirty()
└─ __mark_inode_dirty()
    ├─ inode_io_list_move_locked()   ← b_dirty listesine ekle
    └─ wb_wakeup()                   ← writeback thread'i uyandır

Kirli inode nesneleri de yine çekirdeğin bdi_writeback thread’i tarafından dosya sisteminin super_operations nesnesinde belirtilen write_inode fonksiyonu çağrılarak diske yazılmaktadır. Buradaki akışı şöyle açıklayabiliriz:

wb_workfn()                                  ← kworker thread
 └─ wb_do_writeback()
      └─ wb_writeback()
           ├─ writeback_sb_inodes()
           │    └─ __writeback_single_inode()
           │         ├─ do_writepages()      ← sayfa verisi
           │         └─ write_inode()        ← inode metadata
           └─ inode_io_list_del()            ← listeden çıkar

Biz sayfa önbelleği ve tampon (buffer) yönetimi konularını kitabımızın “Bellek Yönetimi” bölümünde ayrıntılarıyla açıklayacağız.

6.3.3. Little-Endian/Big Endian Sorunu

Eğer Linux çekirdeği için yazılan kodların hem little-endian hem de big-endian makinelerde sorunsuz çalışması isteniyorsa diskten okunan bilgilerin endian dönüştürmesine sokulması gerekir. Biz simplefs dosya sistemimizde diskteki bilgilerin little-endian olduğunu varsaymıştık. Buradaki veri yapısı bir yapıya aktarıldığında orada bilgi little-endian biçiminde oluşacaktır. Eğer kodun çalışacağı makine big-endian ise sorun oluşacaktır. İşte bunun için Linux çekirdeğinde endian dönüştürmesi yapan yardımcı fonksiyonlar bulundurulmuştur. Bu fonksiyonların listesini aşağıda veriyoruz:

le16_to_cpu    be16_to_cpu
le32_to_cpu    be32_to_cpu
le64_to_cpu    be64_to_cpu

Bu fonksiyonların (aslında birer makro olarak yazılmıştır) başındaki le öneki parametrenin little-endian olduğunu, be öneki ise big-endian olduğunu belirtmektedir. Örneğin le32_to_cpu fonksiyonu parametre olarak 32 bitlik işaretsiz little-endian bir değeri alır. Eğer o anda çalışılan CPU little-endian ise onu dönüştürmez, big-endian ise onu big-endian formata dönüştürür. Bu fonksiyonların parametresi gösterici olan biçimleri de vardır:

le16_to_cpup    be16_to_cpup
le32_to_cpup    be32_to_cpup
le64_to_cpup    be64_to_cpup

Tabii yukarıdakilerin bir de ters biçimleri bulunmaktadır:

cpu_to_le16    cpu_to_be16    cpu_to_le16p    cpu_to_be16p
cpu_to_le32    cpu_to_be32    cpu_to_le32p    cpu_to_be32p
cpu_to_le64    cpu_to_be64    cpu_to_le64p    cpu_to_be64p

6.3.4. simplefs Süper Bloğunun ve Bitmap Bloklarının Diskten Okunması

Diskimizdeki süper bloğu okuduktan sonra ilk yapacağımız şey sihirli sayı karşılaştırmasıdır. Sihirli sayı karşılaştırması ile diskimizin simplefs dosya sistemiyle formatlanıp formatlanmadığını kesin olarak anlayamayabiliriz. Ancak olasılığı azaltırız. Örneğin:

if (le32_to_cpu(sbd->magic) != SIMPLEFS_MAGIC) {
    printk(KERN_INFO "invalid magic number for simplefs: %08X\n", sbd->magic);
    result = -EINVAL;
    goto EXIT2;
}

Sihirli sayı tesadüfen de (olasılık oldukça az) doğrulanabilir. Olasılığı azaltmak için dosya sistemine ilişkin başka değerler de kontrol edilebilir. Örneğin bizim dosya sistemimizde blok büyüklükleri sabit 4096 byte’tır. Bu karşılaştırmayı da yapabiliriz. Örneğin bizim dosya sistemimizde inode tablosunun blok numarası her zaman 3’tür. Bu karşılaştırmayı da yapabiliriz:

if (le32_to_cpu(sbd->block_size) != SIMPLEFS_BLOCK_SIZE) {
    printk(KERN_INFO "invalid block size for simplefs: %08X\n", sbd->block_size);
    result = -EINVAL;
    goto EXIT2;
}
if (le32_to_cpu(sbd->inode_table_block) != 3) {
    printk(KERN_INFO "invalid inode table for simplefs: %08X\n", sbd->inode_table_block);
    result = -EINVAL;
    goto EXIT2;
}

Şimdi sıra inode bitmap ve data bitmap bloklarının okunarak simplefs_super_block yapısının içerisinde saklanmasına gelmiştir. simplefs_super_block yapısını anımsatmak istiyoruz:

struct simplefs_super_block {
    struct simplefs_disk_super_block *sbd;
    struct buffer_head *sb_bh;
    struct buffer_head *inode_bitmap_bh;
    struct buffer_head *data_bitmap_bh;
    unsigned long *inode_bitmap;
    unsigned long *data_bitmap;
    spinlock_t lock;
};

Bizim önce inode bitmap’i diskten okuyup onun buffer_head adresini yapının inode_bitmap_bh elemanına, sonra da onun bilgilerin bulunduğu yerin adresini de yapının inode_bitmap elemanına yerleştirmemiz gerekir. Aynı şeyi tabii data bitmap için de yapmalıyız. Data bitmap’i diskten okuyup onun buffer_head adresini yapının data_bitmap_bh elemanına, onun bilgilerinin bulunduğu yerin adresini de yapının data_bitmap elemanına yerleştirmemiz gerekir. Bu işlemleri şöyle yapabiliriz:

#define SIMPLEFS_INODE_BITMAP_LOCATION    1
#define SIMPLEFS_DATA_BITMAP_LOCATION     2
/* ... */

if ((sfs_sb->inode_bitmap_bh = sb_bread(sb, SIMPLEFS_INODE_BITMAP_LOCATION)) == NULL) {
    printk(KERN_INFO "cannot read simplefs inode bitmap!..\n");
    result = -EIO;
    goto EXIT2;
}
sfs_sb->inode_bitmap = (unsigned long *) sfs_sb->inode_bitmap_bh->b_data;

if ((sfs_sb->data_bitmap_bh = sb_bread(sb, SIMPLEFS_DATA_BITMAP_LOCATION)) == NULL) {
    printk(KERN_INFO "cannot read simplefs data bitmap!..\n");
    result = -EINVAL;
    goto EXIT3;
}
sfs_sb->data_bitmap = (unsigned long *) sfs_sb->data_bitmap_bh->b_data;

Burada simplefs_super_block yapısının inode_bitmap ve data_bitmap elemanlarının neden char * değil de unsigned long * türünden olduğunu merak edebilirsiniz. Linux çekirdeğindeki bitmap’ler üzerinde işlem yapan yardımcı çekirdek fonksiyonları genel olarak bitmap adreslerini unsigned long olarak almaktadır. İzleyen paragraflarda Linux çekirdeğindeki bitmap’ler üzerinde işlem yapan fonksiyonları gözden geçireceğiz.

Yukarıdaki işlemlerden sonra simplefs_fill_super fonksiyonumuzda geldiğimiz yere kadarki kısmı özetleme amacıyla vermek istiyoruz:

static int simplefs_fill_super(struct super_block *sb, void *data, int silent)
{
    struct simplefs_super_block *sfs_sb;
    struct simplefs_disk_super_block *sbd;
    struct inode *root_inode;
    int result;

    sb->s_magic = SIMPLEFS_MAGIC;
    sb_set_blocksize(sb, SIMPLEFS_BLOCK_SIZE);
    sb->s_maxbytes = SIMPLEFS_BLOCK_SIZE;
    sb->s_op = &simplefs_super_ops;
    sb->s_flags |= SB_NOATIME;

    if ((sfs_sb = kzalloc(sizeof(struct simplefs_super_block), GFP_KERNEL)) == NULL) {
        printk(KERN_INFO "cannot allocate simplefs super block!..\n");
        return -ENOMEM;
    }
    sb->s_fs_info = sfs_sb;
    spin_lock_init(&sfs_sb->lock);

    if ((sfs_sb->sb_bh = sb_bread(sb, 0)) == NULL) {
        printk(KERN_INFO "cannot read simplefs disk super block!..\n");
        result = -EIO;
        goto EXIT1;
    }

    sbd = (struct simplefs_disk_super_block *) sfs_sb->sb_bh->b_data;
    sfs_sb->sbd = sbd;

    if (le32_to_cpu(sbd->magic) != SIMPLEFS_MAGIC) {
        printk(KERN_INFO "invalid magic number for simplefs: %08X\n", sbd->magic);
        result = -EINVAL;
        goto EXIT2;
    }
    if (le32_to_cpu(sbd->block_size) != SIMPLEFS_BLOCK_SIZE) {
        printk(KERN_INFO "invalid block size for simplefs: %08X\n", sbd->block_size);
        result = -EINVAL;
        goto EXIT2;
    }
    if (le32_to_cpu(sbd->inode_table_block) != 3) {
        printk(KERN_INFO "invalid inode table for simplefs: %08X\n", sbd->inode_table_block);
        result = -EINVAL;
        goto EXIT2;
    }

    if ((sfs_sb->inode_bitmap_bh = sb_bread(sb, SIMPLEFS_INODE_BITMAP_LOCATION)) == NULL) {
        printk(KERN_INFO "cannot read simplefs inode bitmap!..\n");
        result = -EIO;
        goto EXIT2;
    }
    sfs_sb->inode_bitmap = (unsigned long *) sfs_sb->inode_bitmap_bh->b_data;

    if ((sfs_sb->data_bitmap_bh = sb_bread(sb, SIMPLEFS_DATA_BITMAP_LOCATION)) == NULL) {
        printk(KERN_INFO "cannot read simplefs data bitmap!..\n");
        result = -EIO;
        goto EXIT3;
    }
    sfs_sb->data_bitmap = (unsigned long *) sfs_sb->data_bitmap_bh->b_data;

    /* ... */

EXIT3:
    brelse(sfs_sb->inode_bitmap_bh);
EXIT2:
    brelse(sfs_sb->sb_bh);
EXIT1:
    kfree(sfs_sb);

    return result;
}

6.3.5. Kök Dizine İlişkin Inode Nesnesinin Oluşturulması

Artık simplefs_fill_super fonksiyonumuzda sıra kök dizine inode elemanını ve dentry nesnesini oluşturmaya gelmiştir. Bu aşamada durum biraz daha karmaşık hale gelecektir. Bizim diskteki bir inode elemanını okuyacak bir fonksiyon yazmamız gerekir. Çünkü kök dizinin değil ileride belli bir inode numarasına ilişkin inode elemanının da diskten okunması gerekecektir. Biz bu aşamada belli bir inode numarasına ilişkin inode elemanını diskten okuyan aşağıdaki parametrik yapıya sahip bir fonksiyon yazacağız:

static struct inode *simplefs_iget(struct super_block *sb, unsigned long ino)
{
    /* ... */
}

Fonksiyonun birinci parametresi çekirdeğin süper blok nesne adresini, ikinci parametresi ise inode numarasını almaktadır. Fonksiyon başarı durumunda diskten okunan inode nesnesinin adresine, başarısızlık durumunda ise başarısızlığı belirten geçersiz adrese geri dönmektedir. Linux çekirdeğinde adrese geri dönen fonksiyonlar istenirse aynı zamanda errno değerini de tutabilmektedir. Çok yüksek adreslerin bir bölümü aslında Linux çekirdeğinde geçerli bir adres belirtmemektedir. İşte bu biçimdeki Linux için geçersiz adresler aslında errno değerini barındırmaktadır. Bu işlemler için çekirdekte inline fonksiyon bulundurulmuştur: IS_ERR, PTR_ERR, ERR_PTR makroları. IS_ERR fonksiyonu bir adres bilgisini alır. Onun Linux için geçerli bir adres olup olmadığına bakar. Eğer adres geçersizse onun içerisine depolanmış olan negatif errno değeri PTR_ERR fonksiyonuyla elde edilmektedir. İçerisinde negatif errno değerini barındıran bir adres bilgisi de ERR_PTR fonksiyonuyla oluşturulmaktadır. Linux çekirdeğindeki başarısızlık ve errno değerlerinin hangi biçimlerde bulunduğunu aşağıda maddeler halinde özetliyoruz:

  1. Bir çekirdek fonksiyonu tamsayı türlerine ilişkin bir değere geri dönüyorsa geri dönüş değerinin negatif olması fonksiyonun başarısız olduğunu gösterir. Bu negatif değerin pozitiflisi errno değerini vermektedir.

  2. Bir çekirdek fonksiyonu bir adrese geri dönüyorsa fonksiyonun başarısı verilen adresin değerine bağlıdır. Eğer verilen adres çok büyük bir adresse fonksiyon başarısız olmuştur. Bu kontrol IS_ERR inline fonksiyonuyla yapılmaktadır.

  3. Adrese geri dönen çekirdek fonksiyonu eğer başarısızsa negatif errno değeri PTR_ERR inline fonksiyonuyla elde edilir.

  4. Biz negatif bir errno değerini bir adres gibi geri döndüreceksek bunun için ERR_PTR inline fonksiyonunu kullanmalıyız.

Biz de çekirdek kodlaması yaparken çekirdekte uygulanan bu biçime (convention) uymalıyız.

Biz simplefs_iget fonksiyonunun içini yazdığımızı varsayarsak simplefs_fill_super fonksiyonumuz içerisinde kök dizine ilişkin inode elemanını diskten şöyle okuyabiliriz:

#define SIMPLEFS_ROOT_INO    1
/* ... */
struct inode *root_inode;
/* ... */

root_inode = simplefs_iget(sb, SIMPLEFS_ROOT_INO);
if (IS_ERR(root_inode)) {
    printk(KERN_INFO "cannot read root inode!..\n");
    result = PTR_ERR(root_inode);
    goto EXIT4;
}
/* ... */

EXIT4:
    brelse(sfs_sb->data_bitmap_bh);
EXIT3:
    brelse(sfs_sb->inode_bitmap_bh);
EXIT2:
    brelse(sfs_sb->sb_bh);
EXIT1:
    kfree(sfs_sb);

Şimdi dosya sistemimiz için diskten bir inode elemanını okuyan simplefs_iget fonksiyonunun içini yazalım. Anımsayacağınız gibi inode nesneleri çekirdek tarafından bir önbellek içerisinde saklanıyordu. Bizim dosya sistemimize ilişkin inode elemanları da yine bu önbellek içerisinde saklanacaktır. Dolayısıyla biz bir inode nesnesini elde etmek istediğimizde önce inode önbelleğine bakıp eğer talep ettiğimiz inode elemanı zaten inode önbelleği içerisinde varsa hiç disk okuması yapmadan onu önbellekten alıp geri döndürmeliyiz. Eğer talep ettiğimiz inode nesnesi inode önbelleğinde yoksa bu durumda gerçek disk okuması yapmalıyız.

Çekirdekte bir inode nesnesini inode önbelleğinden alan, yoksa onu tahsis edip adresini bize veren iget_locked yüksek seviyeli bir fonksiyon bulunmaktadır. Eskiden bu fonksiyonun ismi iget biçimindeydi. Sonra ismi iget_locked olarak değiştirildi. iget_locked fonksiyonu fs/inode.c dosyasında aşağıdaki gibi tanımlanmıştır:

struct inode *iget_locked(struct super_block *sb, unsigned long ino)
{
    struct hlist_head *head = inode_hashtable + hash(sb, ino);
    struct inode *inode;
again:
    inode = find_inode_fast(sb, head, ino, false);
    if (inode) {
        if (IS_ERR(inode))
            return NULL;
        wait_on_inode(inode);
        if (unlikely(inode_unhashed(inode))) {
            iput(inode);
            goto again;
        }
        return inode;
    }

    inode = alloc_inode(sb);
    if (inode) {
        struct inode *old;

        spin_lock(&inode_hash_lock);
        /* We released the lock, so.. */
        old = find_inode_fast(sb, head, ino, true);
        if (!old) {
            inode->i_ino = ino;
            spin_lock(&inode->i_lock);
            inode->i_state = I_NEW;
            hlist_add_head_rcu(&inode->i_hash, head);
            spin_unlock(&inode->i_lock);
            spin_unlock(&inode_hash_lock);
            inode_sb_list_add(inode);

            /* Return the locked inode with I_NEW set, the
             * caller is responsible for filling in the contents
             */
            return inode;
        }

        /*
         * Uhhuh, somebody else created the same inode under
         * us. Use the old inode instead of the one we just
         * allocated.
         */
        spin_unlock(&inode_hash_lock);
        destroy_inode(inode);
        if (IS_ERR(old))
            return NULL;
        inode = old;
        wait_on_inode(inode);
        if (unlikely(inode_unhashed(inode))) {
            iput(inode);
            goto again;
        }
    }
    return inode;
}
EXPORT_SYMBOL(iget_locked);

Burada önce inode nesnesi inode önbelleğinde aranmış, eğer bulunamamışsa yeni bir inode nesnesi alloc_inode fonksiyonuyla oluşturularak verilmiştir. Tabii oluşturulan bu inode nesnesi de aynı zamanda inode önbelleğine yerleştirilmiştir. Çekirdekte yeni bir inode nesnesinin tahsis edilmesi alloc_inode fonksiyonu tarafından yapılmaktadır. Ancak bu fonksiyon da aslında tahsisatı çok biçimli olarak super_block nesnesine yerleştirdiğimiz super_operations nesnesi içerisindeki alloc_inode fonksiyonunu çağırarak yapmaktadır. Yani sonuç olarak aslında iget_locked fonksiyonu inode nesnesini inode önbelleğinde bulmazsa bizim süper blok nesnesine yerleştirdiğimiz fonksiyonu çağırarak tahsisatı bize yaptırmaktadır. O halde bizim super_operations nesnesine yerleştirdiğimiz alloc_inode fonksiyonunda kendi inode nesnemizi tahsis etmemiz gerekir. Çekirdekteki fs/inode.c dosyası içerisinde bulunan alloc_inode fonksiyonu aşağıdaki gibi tanımlanmıştır:

struct inode *alloc_inode(struct super_block *sb)
{
    const struct super_operations *ops = sb->s_op;
    struct inode *inode;

    if (ops->alloc_inode)
        inode = ops->alloc_inode(sb);
    else
        inode = alloc_inode_sb(sb, inode_cachep, GFP_KERNEL);

    if (!inode)
        return NULL;

    if (unlikely(inode_init_always(sb, inode))) {
        if (ops->destroy_inode) {
            ops->destroy_inode(inode);
            if (!ops->free_inode)
                return NULL;
        }
        inode->free_inode = ops->free_inode;
        i_callback(&inode->i_rcu);
        return NULL;
    }

    return inode;
}

Burada önce super_operations nesnesi içerisindeki alloc_inode fonksiyon göstericisinin NULL olup olmadığına bakılmış, eğer bu elemana bir fonksiyon adresi yerleştirilmişse o fonksiyon çağrılmıştır:

if (ops->alloc_inode)
    inode = ops->alloc_inode(sb);
else
    inode = alloc_inode_sb(sb, inode_cachep, GFP_KERNEL);

Eğer super_operations nesnesinin alloc_inode elemanı NULL ise bu durumda inode tahsisatı çekirdek içerisindeki alloc_inode_sb fonksiyonuna yaptırılmıştır. Ancak dosya sistemlerinde çoğu kez her dosya sisteminin kendi inode nesnesini kendisinin tahsis etmesi gerekmektedir. Bunun nedenini izleyen paragraflarda anlayacaksınız.

O halde bizim simplefs_iget fonksiyonumuzda inode nesnesini şöyle elde edebiliriz:

struct inode *inode;
/* ... */

if ((inode = iget_locked(sb, ino)) == NULL)
    return ERR_PTR(-ENOMEM);

Tabii mademki iget_locked inode nesnesini inode önbelleğinde bulamazsa bizim süper bloğumuza ilişkin alloc_inode fonksiyonunu çağırmaktadır, o halde bizim bu noktada artık alloc_inode fonksiyonumuzun içini yazmamız gerekir. Bizim süper bloğuna yerleştirdiğimiz alloc_inode fonksiyonu şöyleydi:

static struct inode *simplefs_alloc_inode(struct super_block *sb)
{
    /* ... */
}

Görüldüğü gibi fonksiyon tahsis edilen inode nesnesiyle geri dönmektedir. Ancak burada bazı ayrıntılar vardır. Aslında sistem programcısı bu tahsisat fonksiyonunda struct inode türünden bir nesne tahsis etmemektedir. Sistem programcısı bu fonksiyonda kendi dosya sistemine ilişkin inode nesnesini tahsis eder fakat onu sanki struct inode nesnesiymiş gibi geri döndürür. Dosya sistemlerinin standart inode yapısından farklı inode temsilleri olabilir. Bu nedenle her dosya sistemi aslında kendine özgü bir inode yapısı oluşturmaktadır. Bu durumu nesne yönelimli programlama tekniğindeki taban sınıf-türemiş sınıf olgusuna benzetebiliriz. Bu bağlamda struct inode adeta bir taban sınıf gibidir. Sistem programcısının kendisinin oluşturduğu inode yapısı da adeta türemiş sınıf gibidir. Biz simplefs dosya sistemimiz için aşağıdaki gibi bir inode yapısı oluşturabiliriz:

struct simplefs_inode {
    __u32 block_no;
    struct inode vfs_inode;
};

Görüldüğü gibi aslında bizim kendi inode yapımız çekirdeğin inode yapısını da tutmaktadır. Böylece sistem programcısı alloc_inode fonksiyonu içerisinde aslında struct inode türünden değil struct simplefs_inode türünden nesne tahsis eder, ancak nesnenin vfs_inode elemanının adresiyle geri döner. Böylece çekirdek ne zaman bize bizim bir inode nesnemizin adresini verse container_of makrosuyla biz kendi inode nesnemize erişebiliriz. Dosya sistemimize ilişkin simplefs_inode yapısı içerisinde standart inode nesnesinin yanı sıra aynı zamanda ilgili inode elemanının diskteki hangi blokta tutulduğu bilgisinin de bulunduğuna dikkat ediniz:

simplefs_inode
┌────────────┐
│  block_no  │
├────────────┤ ──────────▶ fonksiyonun döndürdüğü adres
│ vfs_inode  │
└────────────┘

Pekiyi biz alloc_inode fonksiyonumuz içerisinde simplefs_inode nesnesini nasıl tahsis edeceğiz? İşte aslında bu konu ileride alacağımız dilimli tahsisat sistemi (slab allocator) denilen çekirdek tahsisat mekanizması ile ilgilidir. Çekirdek içerisinde dinamik tahsisatlar hızlı yapılsın diye hep aynı büyüklükte bloklardan oluşan ve ismine dilim (slab) denilen ayrık heap alanları oluşturulabilmektedir. Ayrıca Linux çekirdeği başlangıçta bazı sabit uzunluklu bloklar barındıran hazır dilimler de oluşturmaktadır. Çekirdeğin kmalloc ve kzalloc gibi genel tahsisat fonksiyonları bu hazır dilimlerden tahsisat yapmaktadır. Biz simplefs_inode nesnelerimizi kmalloc veya kzalloc gibi fonksiyonlarla tahsis edebiliriz. Ancak tam olarak simplefs_inode kadar uzunlukta bloklara sahip olan ayrı bir dilimli tahsisat nesnesi oluşturmak daha etkin bir yöntemdir. Biz dilimli tahsisat nesnelerinin nasıl oluşturulacağını ileride göreceğiz. Ancak burada bu işlemi yapan fonksiyon çağrısını vermekle yetineceğiz:

static struct kmem_cache *simplefs_inode_cachep;
/* ... */

simplefs_inode_cachep = kmem_cache_create("simplefs_inode_cache",
        sizeof(struct simplefs_inode), 0, SLAB_HWCACHE_ALIGN, NULL);

Fonksiyon başarı durumunda dilimli tahsisat sistemine ilişkin bilgilerin saklandığı kmem_cache türünden yapı nesnesinin adresine, başarısızlık durumunda NULL adrese geri dönmektedir. Başarısızlık durumunda modülün init fonksiyonunun -ENOMEM errno değeri ile geri döndürülmesi uygun olur.

Biz yarattığımız bu dilimli tahsisat nesnesinden blok tahsisatını kmem_cache_alloc fonksiyonuyla yaparız. Örneğin:

struct simplefs_inode *inode_sfs;
/* ... */

inode_sfs = kmem_cache_alloc(simplefs_inode_cachep, GFP_KERNEL);

Fonksiyon başarısızlık durumunda NULL adrese geri dönmektedir.

kmem_cache_alloc fonksiyonuyla tahsis edilen alan kmem_cache_free fonksiyonuyla serbest bırakılmaktadır:

kmem_cache_free(simplefs_inode_cachep, inode_sfs);

kmem_cache_create fonksiyonu ile yaratılmış olan dilimli tahsisat nesnesi kmem_cache_destroy fonksiyonuyla yok edilmektedir:

kmem_cache_destroy(simplefs_inode_cachep);

Yukarıda da belirttiğimiz gibi iget_locked çekirdek fonksiyonu eğer belirtilen numaralı inode nesnesi inode önbelleği içerisinde varsa doğrudan onu oradan alıp geri dönmektedir. Ancak belirtilen numaralı inode nesnesi inode önbelleğinde yoksa bu durumda bizim super_operations nesnesine yerleştirdiğimiz alloc_inode fonksiyonu ile tahsisat yapılmaktadır. O halde bizim bu noktada dosya sistemimize ilişkin inode nesnesini tahsis edecek fonksiyonu yazmamız gerekir. Bu fonksiyonu şöyle yazabiliriz:

static struct inode *simplefs_alloc_inode(struct super_block *sb)
{
    struct simplefs_inode *inode_sfs;

    if ((inode_sfs = kmem_cache_alloc(simplefs_inode_cachep, GFP_KERNEL)) == NULL)
        return NULL;
    inode_init_once(&inode_sfs->vfs_inode);

    return &inode_sfs->vfs_inode;
}

inode nesnesi tahsis edildikten sonra onun içerisindeki elemanlara inode_init_once fonksiyonu ile ilk değerlerin verildiğine dikkat ediniz.

Tabii bu inode tahsisatını serbest bırakan super_operations fonksiyonunun da bu noktada yazılması gerekir:

static void simplefs_free_inode(struct inode *inode)
{
    struct simplefs_inode *inode_sfs = container_of(inode, struct simplefs_inode, vfs_inode);

    kmem_cache_free(simplefs_inode_cachep, inode_sfs);
}

simplefs_inode yapımızın block_no elemanına henüz bir değer atamadık. Bu eleman inode elemanının bulunduğu disk bloğunun numarasını tutmaktadır. inode nesnesinin içi, inode elemanı diskten elde edilerek doldurulacaktır. simplefs_iget fonksiyonumuzun içi henüz şu durumdadır:

static struct inode *simplefs_iget(struct super_block *sb, unsigned long ino)
{
    struct inode *inode;

    if ((inode = iget_locked(sb, ino)) == NULL)
        return ERR_PTR(-ENOMEM);

    /* ... */

    return NULL;
}

Artık biz inode nesnesini tahsis etmiş olduk. Şimdi diskte ilgili inode elemanını bulup onun içerisindeki bilgilerden hareketle bu nesnenin içini doldurmamız gerekir. Bunu da bir fonksiyona yaptıralım. Bu işlemi yapan fonksiyonumuzun parametrik yapısı şöyledir:

static struct simplefs_disk_inode *simplefs_get_inode_disk(struct super_block *sb,
            unsigned long ino, struct buffer_head **bhpp)
{
    /* ... */
}

Fonksiyonun birinci parametresi süper blok nesnesinin adresini, ikinci parametresi inode numarasını almaktadır. Fonksiyon diskteki inode elemanı ile geri dönmektedir. Ancak fonksiyonun geri döndürdüğü adresin fonksiyon çıkışında da yaşıyor olması gerekir. Bu adres okunan bloktaki bir yer olduğu için bloğun da referans sayacının artırılarak page cache içerisinde muhafaza edilmesi gerekmektedir. Bu nedenle okunan bloğun buffer_head adresi ayrıca dışarıya iletilmiştir. Biz fonksiyonun son parametresine bir buffer_head göstericisinin adresini geçiririz. Fonksiyon da buffer_head nesnesinin adresini bu göstericiye yerleştirir.

Bizim simplefs_get_inode_disk fonksiyonunda öncelikle elde etmek istediğimiz inode elemanının diskimizin hangi bloğunda ve o bloğun hangi offset’inde olduğunu belirlememiz gerekir. Bu belirlemeyi şöyle yapabiliriz:

#define SIMPLEFS_DISK_INODE_SIZE    sizeof(struct simplefs_disk_inode)

block_no     = ino * SIMPLEFS_DISK_INODE_SIZE / SIMPLEFS_BLOCK_SIZE;
block_offset = ino * SIMPLEFS_DISK_INODE_SIZE % SIMPLEFS_BLOCK_SIZE;

Ancak burada bir noktaya dikkat ediniz. inode elemanlarının diskte bir blokta başlayıp diğer bloktan devam etmesi iyi bir tasarım değildir. Bunun engellenmesi için inode elemanlarının uzunluğunun blok uzunluğuna tam bölünmesi gerekir. Bizim simplefs dosya sistemimizde diskteki inode elemanlarımız 64 byte uzunluktadır. Bu değer de 4096’ya tam bölünebilmektedir. Ancak eğer blok uzunluğu inode elemanlarının diskteki uzunluğuna tam bölünemeseydi her bloğun sonunda kullanılmayan alan oluşurdu. Dolayısıyla yukarıdaki hesap da hatalı olurdu. Bu durumda (bizim tasarımızda geçerli değil) inode elemanının blok numarası ve blok offseti şöyle elde edilebilirdi:

#define SIMPLEFS_INODE_TABLE_LOCATION       3
#define SIMPLEFS_DISK_INODE_SIZE            sizeof(struct simplefs_disk_inode)
#define SIMPLEFS_DISK_INODE_PER_BLOCK       (SIMPLEFS_BLOCKSIZE / SIMPLEFS_DISK_INODE_SIZE)

block_no     = SIMPLEFS_INODE_TABLE_LOCATION + ino / SIMPLEFS_INODES_PER_BLOCK;
block_offset = (ino % SIMPLEFS_DISK_INODE_PER_BLOCK) * SIMPLEFS_DISK_INODE_SIZE;

ino numaralı inode elemanının diskimizin hangi bloğunda ve hangi offset’inde olduğunu belirledikten sonra artık bloğu sb_bread fonksiyonu ile okuyabiliriz:

if ((bh = sb_bread(sb, block_no)) == NULL)
    return ERR_PTR(-EIO);

Artık simplefs_get_inode_disk fonksiyonumuzu tamamlayabiliriz:

static struct simplefs_disk_inode *simplefs_get_inode_disk(struct super_block *sb,
        unsigned long ino, struct buffer_head **bhpp)
{
    int block_no, block_offset;
    struct buffer_head *bh;
    struct simplefs_disk_inode *disk_inode;

    block_no     = SIMPLEFS_INODE_TABLE_LOCATION
                   + ino * SIMPLEFS_DISK_INODE_SIZE / SIMPLEFS_BLOCK_SIZE;
    block_offset = ino * SIMPLEFS_DISK_INODE_SIZE % SIMPLEFS_BLOCK_SIZE;

    if ((bh = sb_bread(sb, block_no)) == NULL)
        return ERR_PTR(-EIO);

    disk_inode = (struct simplefs_disk_inode *)(bh->b_data + block_offset);
    *bhpp = bh;

    return disk_inode;
}

Artık diskten inode elemanını okumuş durumdayız. Şimdi bu bilgilerden hareketle simplefs_iget fonksiyonunda inode nesnesinin içini doldurmamız gerekir. Aşağıda simplefs_iget fonksiyonunun yeni durumunu veriyoruz:

#define SIMPLEFS_SB(sb)      ((struct simplefs_super_block *)(((sb)->s_fs_info)))
#define SIMPLEFS_DISK_SB(sb) (SIMPLEFS_SB(sb)->sbd)

static struct inode *simplefs_iget(struct super_block *sb, unsigned long ino)
{
    struct inode *inode;
    struct simplefs_disk_inode *disk_inode;
    struct buffer_head *bh;
    struct simplefs_disk_super_block *sfs_sbd;

    sfs_sbd = SIMPLEFS_DISK_SB(sb);
    if (ino >= sfs_sbd->inode_count)
        return ERR_PTR(-EINVAL);

    if ((inode = iget_locked(sb, ino)) == NULL)
        return ERR_PTR(-ENOMEM);

    if (!(inode->i_state & I_NEW))
        return inode;

    disk_inode = simplefs_get_inode_disk(sb, ino, &bh);
    if (IS_ERR(disk_inode)) {
        iget_failed(inode);
        return (struct inode *)disk_inode;
    }

    /* ... */

    return inode;
}

Burada önce super_block nesnesinden hareketle dosya sistemimize ilişkin simplefs_disk_super_block nesnesine SIMPLEFS_DISK_SB makrosu ile erişilmiştir. Bizim dosya sistemimizde olabilecek en büyük inode değeri zaten bu yapının içerisindeki inode_count elemanında bulunuyordu. Böylece simplefs_iget fonksiyonu içerisinde yanlışlıkla olmayan bir inode elemanı okunmak istenirse fonksiyon hemen başarısızlıkla geri dönmektedir:

sfs_sbd = SIMPLEFS_DISK_SB(sb);
if (ino >= sfs_sbd->inode_count)
    return ERR_PTR(-EINVAL);

Anımsayacağınız gibi iget_locked fonksiyonu eğer inode nesnesi inode önbelleğinde varsa onu alıyor, yoksa tahsis edip bize veriyordu. İşte bizim inode nesnesinin yeni tahsis edilmiş olup olmadığını anlamamız gerekir. Çekirdekteki kodlar eğer inode elemanı yeni tahsis edilmişse inode yapısının i_state elemanında I_NEW bayrağını set etmektedir. Biz de fonksiyonda bu bayrağı kontrol ettik:

if (!(inode->i_state & I_NEW))
    return inode;

Artık akış ancak yeni tahsis edilmiş bir inode nesnesi söz konusuysa aşağıya geçecektir. Fonksiyonumuzda bundan sonra diskten inode elemanı okunmuştur:

disk_inode = simplefs_get_inode_disk(sb, ino, &bh);
if (IS_ERR(disk_inode)) {
    iget_failed(inode);
    return (struct inode *)disk_inode;
}

Artık tahsis edilen inode nesnesinin elemanları diskten alınan inode bilgileri ile doldurulmalıdır. Tabii biz tahsis ettiğimiz inode nesnesinin tüm elemanlarını doldurmak zorunda değiliz. Yalnızca dosya sistemimize yönelik bazı elemanların doldurulması yeterli olacaktır. Doldurma işlemini şöyle yapabiliriz:

inode->i_mode = le32_to_cpu(disk_inode->mode);
i_uid_write(inode, le32_to_cpu(disk_inode->uid));
i_gid_write(inode, le32_to_cpu(disk_inode->gid));
set_nlink(inode, le32_to_cpu(disk_inode->nlink));
inode->i_blocks = le32_to_cpu(disk_inode->blocks);
inode->i_size   = le32_to_cpu(disk_inode->size);
inode_set_atime(inode, le32_to_cpu(disk_inode->atime), 0);
inode_set_mtime(inode, le32_to_cpu(disk_inode->mtime), 0);
inode_set_ctime(inode, le32_to_cpu(disk_inode->ctime), 0);

inode nesnesinin elemanları doldurulurken bazı sarma çekirdek fonksiyonlarının da kullanıldığına dikkat ediniz. i_uid_write ve i_gid_write fonksiyonları dosyanın kullanıcı id’si ve grup id’si değerlerini inode nesnesi içerisine yerleştirmektedir. Burada bu işlem için neden fonksiyon çağrıldığını merak edebilirsiniz. Daha önce de belirttiğimiz gibi Linux çekirdeklerine belli bir süreden sonra çeşitli alt sistemler için isim alanları (name spaces) eklenmiştir. Belli bir kullanıcı id’si ve grup id’si o isim alanına ilişkin kullanıcı id’si ve grup id’sine dönüştürülmektedir. Linux çekirdeği farklı isim alanlarında aynı id’ler bulunabilmesine izin veriyor olsa da aslında kendi içerisinde bu id’leri ayırmaktadır. Bu fonksiyonlar isim alanlarındaki id’leri çekirdeğin kullandığı id’lere dönüştürmektedir. Aslında varsayılan isim alanında bulunuluyorsa bu çekirdekler aynı id değerlerini inode nesnesinin içerisine yerleştirmektedir. Örneğimizde dosyanın hard link sayacı da set_nlink fonksiyonuyla set edilmiştir. Çünkü hard link sayacının atomik bir biçimde birkaç durum gözetilerek yerleştirilmesi gerekmektedir.

Linux çekirdeklerinde dosyanın tarih zaman bilgilerinin tutulma biçimi versiyonlarla üç kez değiştirilmiştir. Eskiden yalnızca 01/01/1970’ten geçen saniye sayısı tutuluyordu. Sonra timespec yapısı kullanılmaya başlandı. Bu yapıyı kullanıcı modu uygulamalarından da anımsayacaksınız. Bu timespec yapısı hem 01/01/1970’ten geçen saniye sayısını hem de bu saniyeden sonraki nano saniye sayısını tutuyordu. Nihayet yeni çekirdeklerde artık tarih ve zaman bilgisi inode nesnesinde iki ayrı yapı elemanıyla tutulmaktadır. Çekirdekteki bu değişimlerden etkilenmek istenmiyorsa tarih zaman bilgisi doğrudan değil çekirdeğin içerisindeki inode_set_atime, inode_set_mtime, inode_set_ctime fonksiyonlarıyla set edilmelidir. Bu fonksiyonlar uzunca bir süredir çekirdeklerde bulunmaktadır ve set işlemini çekirdeğin o versiyonundaki formata göre yapmaktadır.

inode nesnesinin doldurulurken inode nesnesinin belirttiği dosyanın bilgilerinin hangi data block içerisinde olduğu da saklanmalıdır. Çünkü biz ileride inode nesnesinden hareketle dosyaya ilişkin bilgilere ulaşmak isteyeceğiz. Bu bilgi zaten diskteki inode elemanında bulunuyordu. O halde bu bilginin inode nesnesine yerleştirilmesi şöyle yapılabilir:

struct simplefs_inode *inode_sfs;
/* ... */

inode_sfs = container_of(inode, struct simplefs_inode, vfs_inode);
inode_sfs->block_no = disk_inode->block_no;

inode nesne adresinin bizim simplefs_inode yapımızın içerisindeki vfs_inode isimli elemanın adresi olduğunu anımsayınız. Biz container_of makrosuyla yukarı çıkarak asıl nesnemizin adresini elde etmekteyiz.

simplefs_iget fonksiyonundan çıkarken nesne sayacını artırdığımız iki nesneyi de geri bırakmamız gerekir:

brelse(bh);
unlock_new_inode(inode);

Artık simplefs_iget fonksiyonunda inode nesnesimizin içini de doldurmuş olduk. Fonksiyonun son hali şöyledir:

static struct inode *simplefs_iget(struct super_block *sb, unsigned long ino)
{
    struct inode *inode;
    struct simplefs_disk_inode *disk_inode;
    struct buffer_head *bh;
    struct simplefs_disk_super_block *sfs_sbd;
    struct simplefs_inode *inode_sfs;

    sfs_sbd = SIMPLEFS_DISK_SB(sb);
    if (ino >= sfs_sbd->inode_count)
        return ERR_PTR(-EINVAL);

    if ((inode = iget_locked(sb, ino)) == NULL)
        return ERR_PTR(-ENOMEM);

    if (!(inode->i_state & I_NEW))
        return inode;

    disk_inode = simplefs_get_inode_disk(sb, ino, &bh);
    if (IS_ERR(disk_inode)) {
        iget_failed(inode);
        return (struct inode *)disk_inode;
    }

    inode->i_mode = le32_to_cpu(disk_inode->mode);
    i_uid_write(inode, le32_to_cpu(disk_inode->uid));
    i_gid_write(inode, le32_to_cpu(disk_inode->gid));
    set_nlink(inode, le32_to_cpu(disk_inode->nlink));
    inode->i_blocks = le32_to_cpu(disk_inode->blocks);
    inode->i_size   = le32_to_cpu(disk_inode->size);
    inode_set_atime(inode, le32_to_cpu(disk_inode->atime), 0);
    inode_set_mtime(inode, le32_to_cpu(disk_inode->mtime), 0);
    inode_set_ctime(inode, le32_to_cpu(disk_inode->ctime), 0);

    inode_sfs = container_of(inode, struct simplefs_inode, vfs_inode);
    inode_sfs->block_no = disk_inode->block_no;

    /* ... */

    brelse(bh);
    unlock_new_inode(inode);

    return inode;
}

Şimdi bizim son olarak inode nesnesine ilişkin inode_operations ve file_operations adreslerini oluşturmamız gerekmektedir. Çekirdek, izleyen paragraflarda da ayrıntılandıracağımız gibi inode kavramına ilişkin işlemler yapılırken inode_operations fonksiyonlarını, dosyaların bilgileri üzerinde işlemler yapılırken file_operations fonksiyonlarını çağırmaktadır.