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 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ı
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:
Alan |
Tür |
Açıklama |
|---|---|---|
|
|
|
|
|
Her zaman 4096 byte |
|
|
Format sırasında belirlenir |
|
|
Aygıt boyutu / 4096 |
|
|
Dinamik güncellenir |
|
|
Dinamik güncellenir |
|
|
Her zaman 3 |
|
|
|
|
|
|
|
|
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ının bit organizasyonu
Kısaltma |
Açıklama |
|---|---|
|
setuid biti |
|
setgid biti |
|
sticky bit |
|
Kullanıcı okuma (user read) |
|
Kullanıcı yazma (user write) |
|
Kullanıcı çalıştırma (user execute) |
|
Grup okuma (group read) |
|
Grup yazma (group write) |
|
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:
Süper blok bilgileri oluşturulup blok aygıtının ilk bloğuna yazılmalıdır.
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.
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.
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.
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:
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:
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.
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_ERRinline fonksiyonuyla yapılmaktadır.Adrese geri dönen çekirdek fonksiyonu eğer başarısızsa negatif errno değeri
PTR_ERRinline fonksiyonuyla elde edilir.Biz negatif bir errno değerini bir adres gibi geri döndüreceksek bunun için
ERR_PTRinline 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.