İşletim Sistemleri · Ders Notu
Bölüm 10: Dosya Sistemleri
Bir süreç bittiğinde belleği geri alınır; içindeki veriler de kaybolur. Kalıcı olması gereken her şey diske yazılır. Disk ise yalnızca numaralı bloklar tanır: “şu bloğu oku”, “şu bloğa yaz”. Dosya sistemi bu ham blokların üzerine adı, boyutu ve sahibi olan dosyaları, iç içe dizinleri ve erişim haklarını kurar. Not önce kullanıcının gördüğü yüzü anlatır: dosya, öznitelikler, sistem çağrıları, açık dosya tabloları, dizinler, bağlar ve izinler. Ardından gerçeklemeye geçer: diskin bölümlenmesi, blokların dosyalara bitişik, bağlı liste, FAT ve i-node yöntemleriyle dağıtılması, boş alanın izlenmesi ve önbellek. Son kesimler çökmeden sonra tutarlılığı ele alır: fsck, günlükleme, yazınca kopyalama ve günlük yapılı dosya sistemleri. VFS ve NFS ile kapanır.
Öğrenme çıktıları
- Dosya soyutlaması
- Dosyayı adı, türü, öznitelikleri ve işlemleriyle tanımlamak; açık dosya tablolarını ve dosya tanımlayıcılarını izlemek.
- Dizinler
- Dizin yapılarını karşılaştırmak, bir yolu adım adım çözmek, katı ve sembolik bağı, bağlamayı açıklamak.
- Koruma
- rwx izin bitlerini ve özel bitleri okumak; r, w ve x'in dosyada ve dizinde ne anlama geldiğini ayırt etmek.
- Tahsis
- Bitişik, bağlı liste, FAT ve i-node tahsisini aynı dosya üzerinde uygulamak; en büyük dosya boyutunu ve bir ofsetin disk bloğunu hesaplamak.
- Boş alan ve performans
- Biteşlem, liste, gruplama ve sayma yöntemlerinin boyutunu hesaplamak; tampon ve sayfa önbelleğini, önden okumayı açıklamak.
- Güvenilirlik ve gerçek sistemler
- fsck, günlükleme, yazınca kopyalama ve LFS'i karşılaştırmak; NTFS, ext4, FAT32/exFAT, VFS, NFS ve SSD'de TRIM'i tanımak.
10.1Neden dosya sistemi?
Bir programın verisi çalışırken adres alanında durur. Bu yer üç gereksinim için yetersizdir. Birincisi boyuttur: bir veritabanı ya da video arşivi adres alanına, hatta ana belleğe sığmayacak kadar büyük olabilir. İkincisi kalıcılıktır: süreç bitince, hatta makine kapanınca veri yerinde kalmalıdır. Üçüncüsü eş zamanlı erişimdir: birçok süreç aynı veriyi okuyup değiştirebilmelidir. Bu üç gereksinimi diske yazılan dosyalar karşılar.
Disk ise işletim sistemine çok sade bir arayüz sunar. Bir sabit disk de bir SSD de kendini numaralı, sabit boyutlu bloklardan oluşan uzun bir dizi olarak gösterir. Blokların numarasına mantıksal blok adresi (logical block address, LBA) denir. Disk iki işlem bilir: k. bloğu oku ve k. bloğa yaz. Hangi bloğun hangi dosyaya ait olduğu, dosyanın adı, kimin okuyabileceği gibi sorular diskin umurunda değildir. Bu soruları dosya sistemi yanıtlar.
Dosya (file), adı olan, kalıcı bir bayt dizisi ve ona eşlik eden özniteliklerdir. Dosya sistemi (file system), diskteki blokları dosyalara ve dizinlere dönüştüren; onları adlandıran, yerleştiren ve koruyan işletim sistemi bileşenidir.
Dosya, bellekteki adres alanına benzeyen mantıksal bir birimdir. Süreçler dosya oluşturur, okur, yazar ve siler. İşletim sistemi ise adlandırmayı, iç yapıyı, erişimi, korumayı ve diskteki yerleşimi yönetir. Şekil 10.1 bu katmanları gösterir. Uygulama open, read, write ve close çağrılarıyla dosyalarla konuşur. Dosya sistemi “dosyanın 5000. baytı” gibi bir isteği “disk bloğu 1729” gibi bir adrese çevirir. Blok aygıtı da yalnızca o bloğu okur.
Konuya iki yönden bakılabilir. Kullanıcı neyin göründüğüyle ilgilenir: dosyanın adı, türü, hangi dizinde olduğu, izinleri, hangi işlemlerin yapılabildiği. Gerçekleyici ise işin içini düşünür: bloklar dosyalara nasıl dağıtılır, boş alan nasıl izlenir, elektrik kesilirse yapılar nasıl tutarlı kalır? Not önce kullanıcının bakışını, sonra gerçekleyicinin bakışını izler.
10.2Dosya kavramı
10.2.1Dosya adları ve uzantılar
Dosyayı oluşturan süreç ona bir ad verir; süreç bittikten sonra başka süreçler dosyayı bu adla bulur. Adlandırma kuralları dosya sisteminden dosya sistemine değişir. Tablo 10.1 yaygın dört sistemi karşılaştırır. Eski MS-DOS'un FAT sistemi “8.3” biçimini kullanırdı: en çok sekiz karakterlik ad, nokta, üç karakterlik uzantı. Uzun adlar Windows 95 ile gelen VFAT'ta ek dizin girdilerine saklandı. Kısa ad ise geriye uyumluluk için RAPOR~1.TXT gibi kırpılmış biçimde durmaya devam etti.
| Dosya sistemi | Ad uzunluğu | Büyük/küçük harf | Not |
|---|---|---|---|
| FAT (MS-DOS) | 8.3 (ör. RAPOR~1.TXT) | duyarsız | VFAT uzun adı ek girdilerde: 255 UTF-16 |
| NTFS | 255 UTF-16 birimi | korur, duyarsız | \ / : * ? " < > | yasak |
| ext4, XFS | 255 bayt | duyarlı | yalnız / ve NUL yasak |
| APFS | 255 UTF-8 karakter | macOS'ta varsayılan duyarsız | Unicode normalizasyonu |
“Büyük/küçük harfe duyarlı” bir sistemde Rapor.txt ile rapor.txt iki ayrı dosyadır. ext4 böyledir. NTFS ise adı yazıldığı gibi saklar, ama ararken harf farkını önemsemez: iki ad aynı dosyayı bulur. Bugünkü sistemlerin hepsi adları Unicode olarak saklar, bu yüzden Türkçe karakterli adlar sorun çıkarmaz. ext4'ün sınırının karakter değil bayt olduğuna da dikkat etmek gerekir. UTF-8'de “ş” iki bayt tuttuğundan, Türkçe karakter ağırlıklı bir ad 255 karakterden önce sınıra ulaşır.
Adın son noktadan sonraki kısmına uzantı (extension) denir: tez.pdf dosyasının uzantısı pdf'tir. UNIX'te uzantı yalnızca bir gelenektir; çekirdek ona hiçbir anlam yüklemez. Programlar ise yükleyebilir. Örneğin gcc, .c uzantılı kaynak dosyayı derler, .o uzantılı nesne dosyasını yalnızca bağlar. Windows'ta uzantı kayıt defterinde (registry) bir programla ilişkilendirilir. Dosyaya çift tıklanınca o program açılır. Bu ilişkilendirme bir güvenlik tuzağını da doğurur. Explorer bilinen uzantıları varsayılan olarak gizlediği için fatura.pdf.exe adlı bir program ekranda fatura.pdf diye görünür.
Dosya sistemleri zamanla değişti. Eski Windows sürümleri FAT16 ve FAT32 kullanırdı; güncel Windows sistem diski için NTFS kullanır. FAT32 ve exFAT bugün USB bellek ve SD kart gibi taşınabilir ortamlarda yaşar, çünkü hemen her aygıt onları okuyabilir.
10.2.2Sihirli sayılar ve dosya yapısı
Uzantı, kullanıcının değiştirebileceği bir addır; dosyanın gerçek türünü kanıtlamaz. Daha güvenilir ipucu dosyanın ilk baytlarıdır. Pek çok biçim belirli bir bayt dizisiyle başlar. Bu diziye sihirli sayı (magic number) denir. Şekil 10.2 birkaç örnek verir: PNG görüntüsü 89 50 4E 47 ile, PDF %PDF ile, Linux'un çalıştırılabilir biçimi ELF 7F 45 4C 46 ile, ZIP arşivi PK ile başlar. UNIX'teki file rapor komutu türü uzantıya değil bu baytlara bakarak söyler. İlginç bir sonuç: docx, xlsx ve pptx dosyaları da PK ile başlar, çünkü bunlar içinde XML dosyaları bulunan ZIP arşivleridir.
Dosyanın içinin nasıl düzenlendiğine dosya yapısı denir. Şekil 10.3 tarihte görülen üç yapıyı gösterir. (a)'daki bayt dizisi yapısında işletim sistemi dosyayı yalnızca baytların sırası olarak görür. İçine her şey konabilir; yapıyı uygulama belirler. Bu, en esnek seçenektir ve UNIX, Windows ve macOS'un tümü bunu kullanır. (b)'deki sabit uzunluklu kayıt yapısında okuma ve yazma birimi kayıttır; 80 baytlık kayıtlar delikli kartların 80 sütununun mirasıdır. Bu yapı bugün anabilgisayarların veri kümelerinde yaşar. (c)'deki kayıt ağacında her kaydın bir anahtar alanı vardır. “Anahtarı Pony olan kaydı getir” gibi bir istek doğrudan desteklenir.
Bugün anahtarla erişim işletim sisteminde değil, kütüphanelerde ve veritabanlarında yapılır. SQLite, bir B+ ağacını sıradan bir bayt dizisi dosyasının içine yerleştirir. Böylece çekirdek sade kalır, karmaşık yapılar kullanıcı alanında kurulur.
İkili dosyaların da kendi iç düzeni vardır. Şekil 10.4 üç örnek verir. (a) Eski UNIX'in a.out biçiminde dosya bir başlıkla açılır. Başlıkta sihirli sayı, kod, veri ve BSS kesimlerinin boyları, sembol tablosunun boyu ve giriş noktası bulunur. Başlığı kod, veri, yer değiştirme bitleri ve sembol tablosu izler. (b) Linux'un bugünkü biçimi ELF de aynı fikri daha esnek kurar: program başlıkları yükleyiciye, bölüm başlıkları bağlayıcıya yöneliktir. Windows'ta karşılığı PE, macOS'ta Mach-O biçimidir. (c) Arşiv dosyası (.a ya da .lib) derlenmiş ama bağlanmamış nesne modüllerini (.o) art arda, her birinin önüne ad ve boy taşıyan bir üye başlığı koyarak saklar. Çekirdek bir dosyayı yalnızca doğru sihirli sayıyla başlıyorsa program olarak çalıştırır.
10.2.3Dosya türleri
UNIX dosyaları birkaç türe ayırır. Normal dosyalar kullanıcı verisini tutar. Bunlar ya metindir (yazdırılabilir karakterlerden oluşan satırlar; satır sonu UNIX'te LF, Windows'ta CRLF) ya da ikilidir (yapısını yalnızca onu kullanan program bilir). Dizinler, dosya sisteminin yapısını tutan sistem dosyalarıdır. Özel dosyalar giriş/çıkış aygıtlarını modeller. Karakter aygıtları (terminal, seri port) baytları sırayla verir. Blok aygıtları (disk) ise bloklar hâlinde rastgele erişime açıktır.
UNIX'in ünlü ilkesi “her şey bir dosyadır” şunu söyler: aygıtlar, borular ve soketler de aynı open/read/write arayüzüyle kullanılır. Örneğin /dev/null'a yazılan her şey kaybolur, ondan okumak hemen dosya sonu döndürür. Tablo 10.2 türleri ls -l çıktısının ilk karakteriyle birlikte verir.
| Harf | Tür | Örnek |
|---|---|---|
- | normal dosya (metin ya da ikili) | notlar.txt, /bin/ls |
d | dizin | /home |
l | sembolik bağ | /bin → usr/bin |
c | karakter aygıtı (seri, bayt bayt) | /dev/tty, /dev/null |
b | blok aygıtı (blok blok, rastgele) | /dev/nvme0n1, /dev/sda |
p | adlandırılmış boru (FIFO) | mkfifo kanal |
s | UNIX soketi | /run/systemd/notify |
10.2.4Öznitelikler, stat ve ls -l
Adın ve verinin dışında işletim sistemi her dosya için bir dizi bilgi tutar. Bunlara öznitelik (attribute) ya da üst veri (metadata) denir. Tablo 10.3 yaygın öznitelikleri listeler.
| Öznitelik | Anlam | Nerede / nasıl |
|---|---|---|
| Ad, tür, konum | ad dizin girdisinde; tür ve blok adresleri i-node ya da MFT kaydında | tüm sistemler |
| Boyut | mantıksal boyut (st_size) ve ayrılmış alan (st_blocks) | seyrek dosyada ikisi farklıdır |
| Sahip, grup | UID / GID; Windows'ta SID | chown, icacls |
| Koruma | rwx bitleri, ACL | chmod, setfacl |
| Zaman damgaları | erişim (atime), içerik değişimi (mtime), i-node değişimi (ctime), oluşturma (btime) | stat; Linux'ta relatime |
| Bağ sayısı | dosyaya işaret eden ad sayısı | 0 olunca bloklar serbest |
| Read-only, Hidden, System | salt okunur, gizli, sistem dosyası | Windows attrib; UNIX'te gizli = ad nokta ile başlar |
| Archive | son yedekten sonra değişti | yedekleme aracı sıfırlar |
| Compressed, Encrypted | sıkıştırılmış / şifrelenmiş | NTFS, Btrfs, APFS; fscrypt, BitLocker |
| Sparse, Temporary | sıfır blokları diskte yok; geçici (önbellek ipucu) | NTFS bayrakları |
| Reparse point | özel işleme: sembolik bağ, junction, bulut yer tutucu | NTFS |
| Genişletilmiş öznitelik | ad = değer çiftleri | xattr (SELinux etiketi), NTFS ADS |
Bazı öznitelikler açıklama ister. Bir seyrek dosyada (sparse file) hiç yazılmamış bölgeler diskte yer kaplamaz; okunduklarında sıfır döner. Bu yüzden 1 GiB boyutlu bir seyrek dosya diskte yalnızca birkaç kilobayt tutabilir: st_size 1 GiB'tır, st_blocks ise küçüktür. Üç zaman damgası sık karıştırılır. mtime içeriğin, ctime ise i-node'un (izin, sahip, bağ sayısı) son değiştiği andır; ctime “oluşturma zamanı” değildir. Oluşturma zamanı (btime) ayrı bir alandır. Her okumada atime'ı güncellemek her okumayı bir yazmaya çevirir. Linux bu yüzden varsayılan olarak relatime kullanır: atime yalnızca mtime'dan eskiyse ya da bir günden eskiyse güncellenir.
Windows'un bazı öznitelikleri de yanlış anlaşılır. Temporary özniteliği dosyayı kendiliğinden silmez; yalnızca önbellek yöneticisine “bunu diske yazmakta acele etme” der. Symlink, Device ve Junction öznitelik değil dosya türüdür. Creator ve Max size gibi alanlar eski sistemlere aittir.
Bir programın öznitelikleri okuması stat sistem çağrısıyla olur. Çağrı şu yapıyı doldurur:
struct stat {
dev_t st_dev; /* aygıt (dosya sistemi) */
ino_t st_ino; /* i-node numarası */
mode_t st_mode; /* tür + izin bitleri */
nlink_t st_nlink; /* katı bağ sayısı */
uid_t st_uid; /* sahip kullanıcı */
gid_t st_gid; /* sahip grup */
dev_t st_rdev; /* aygıt dosyası ise no. */
off_t st_size; /* boyut (bayt) */
blksize_t st_blksize; /* tercih edilen G/Ç boyu */
blkcnt_t st_blocks; /* ayrılmış 512 B birim */
struct timespec st_atim; /* son erişim */
struct timespec st_mtim; /* son içerik */
struct timespec st_ctim; /* son i-node */
};Alanların çoğu tablodaki özniteliklerin karşılığıdır. st_ino dosyanın i-node numarasıdır; aynı dosya sisteminde bir dosyayı tek başına tanımlar. st_mode tür ve izin bitlerini birlikte taşır. st_nlink katı bağ sayısıdır. st_blocks, dosyaya ayrılmış alanı 512 baytlık birimlerle verir; blok boyu 4 KiB olsa bile birim 512 B'tır. POSIX.1-2008'den beri zamanlar nanosaniye çözünürlüklü struct timespec alanlarıdır. Eski st_atime adı geriye uyumluluk için bir makro olarak kalır. Linux'ta oluşturma zamanı ayrı bir çağrıyla, statx() ile okunur.
ls -l çıktısının alanları: tür, izinler, bağ sayısı, sahip, grup, boyut, değişiklik zamanı ve ad.ls -l bu yapıyı insanın okuyacağı biçime çevirir (Şekil 10.5). İlk karakter tür, sonraki dokuz karakter sahip, grup ve diğerlerinin izinleridir. Ardından bağ sayısı, sahip ve grup adı, bayt cinsinden boyut, mtime ve ad gelir. Ad, stat yapısında yoktur: ad i-node'da değil, dizin girdisinde durur. Aynı i-node'a birden çok ad verilebilmesi de buradan gelir.
10.2.5Dosya işlemleri ve sistem çağrıları
Dosya bir soyut veri türüdür: onu tanımlayan, üzerinde yapılabilen işlemlerdir. Tablo 10.4 temel işlemleri ve POSIX karşılıklarını verir.
| İşlem | POSIX çağrısı | Ne yapar? |
|---|---|---|
| Oluşturma | open(O_CREAT), creat | boş dosya ve dizin girdisi oluşturur, öznitelikleri atar |
| Silme | unlink | adı siler; bağ sayısı 0 olup son tanımlayıcı da kapanınca alan boşalır |
| Açma | open | yolu çözer, i-node'u belleğe alır, tanımlayıcı (fd) döndürür |
| Kapatma | close | fd'yi ve gerekirse tablo girdilerini serbest bırakır |
| Okuma / yazma | read, write | dosya konumundan itibaren okur / yazar, konumu ilerletir |
| Sona ekleme | open(O_APPEND) | her yazma atomik olarak dosya sonuna gider |
| Konumlama | lseek | konumu değiştirir (rastgele erişim); G/Ç yapmaz |
| Öznitelik okuma / yazma | stat, chmod, chown, utimensat | öznitelikleri okur / değiştirir |
| Yeniden adlandırma | rename | adı değiştirir; aynı dosya sisteminde atomiktir |
| Kısaltma, eşleme | ftruncate, mmap | boyutu kısaltır ya da uzatır; dosyayı adres alanına eşler |
Tablodaki birkaç ayrıntı önemlidir. unlink dosyayı değil bir adı siler. Dosyanın blokları ancak hiçbir ad kalmadığında ve onu açık tutan son tanımlayıcı da kapandığında serbest kalır. Bu yüzden açık bir dosya silinse bile onu kullanan süreç okumaya devam edebilir. lseek yalnızca çekirdekteki konum sayısını değiştirir; diske dokunmaz. O_APPEND ile açılmış bir dosyada “sona git ve yaz” işlemi bölünmez. İki süreç aynı günlük dosyasına aynı anda yazsa bile kayıtları birbirinin üstüne binmez.
rename'in atomik olması, uygulamalarda yaygın bir kalıbın temelidir (Şekil 10.6). Bir belgeyi güvenle kaydetmek için yeni içerik önce geçici bir dosyaya yazılır. Ardından fsync ile verinin diske ulaşması beklenir. En sonda rename(gecici, asil) çağrılır. Çökme hangi anda olursa olsun, diskte ya eski dosyanın tamamı ya da yeni dosyanın tamamı görünür; yarım yazılmış bir dosya görünmez.
Belgeyi yerinde açıp O_TRUNC ile kesmek ve yeni içeriği yazmak. Kesme ile yazmanın arasında bir çökme olursa ortada ne eski ne yeni içerik kalır. fsync adımını atlamak da tehlikelidir: rename diske verinin kendisinden önce ulaşabilir ve sonuç sıfır baytlık bir “yeni” dosya olur.
10.2.6Açık dosya tabloları
Her read çağrısında dosyanın yolunu baştan çözmek çok pahalı olurdu. Bu yüzden dosya önce açılır. open yolu bir kez çözer, dosyanın i-node'unu belleğe alır ve sürece küçük bir tam sayı döndürür. Sonraki çağrılar dosyayı bu sayıyla anar.
Dosya tanımlayıcısı (file descriptor, fd), çekirdeğin süreç başına tuttuğu tanımlayıcı tablosunda bir indistir. 0, 1 ve 2 sırasıyla standart girdi, standart çıktı ve standart hatadır. open her zaman en küçük boş numarayı verir.
Çekirdek açık dosyaları üç düzeyde izler (Şekil 10.7). En üstte her sürecin kendi tanımlayıcı tablosu vardır; girdileri ortadaki tabloyu gösterir. Ortada sistem genelindeki açık dosya tablosu (open-file table) bulunur. Her girdisi bir açılışı temsil eder ve iki önemli bilgi taşır: dosyadaki geçerli konum (ofset) ve erişim modu (salt okuma, okuma-yazma). En altta bellek içi i-node tablosu durur. Burada her dosya, kaç kez açılmış olursa olsun, tek bir kez bulunur.
Bu ayrımın sonucu şöyle özetlenebilir. fork ve dup tanımlayıcı tablosundaki girdiyi kopyalar; kopyalar aynı açık dosya girdisini gösterir. Dolayısıyla konumu paylaşırlar: biri 10 bayt okursa öbürünün konumu da 10 bayt ilerler. Aynı dosyayı ikinci kez open ile açmak ise yeni bir girdi kurar. Bu girdinin konumu ayrıdır ve 0'dan başlar. Her iki durumda da i-node tektir. Açık dosya girdisindeki referans sayısı onu kaç tanımlayıcının gösterdiğini, i-node'daki referans sayısı da kaç girdinin gösterdiğini tutar. Sayı 0'a inince ilgili yapı serbest bırakılır. Windows'ta aynı rolü her sürecin tanıtıcı (handle) tablosu oynar.
harf.txt dosyasının içeriği ABCDEFGHIJ (10 bayt). Bir süreç dosyayı açar ve 4 bayt okur. Sonra fork çağırır; çocuk aynı tanımlayıcıdan 3 bayt okuyup sonlanır. Ebeveyn çocuğu bekler, dosyayı ikinci kez açar ve yeni tanımlayıcıdan 2 bayt okur. Son olarak ilk tanımlayıcıdan 2 bayt okur. Hangi baytlar okunur?
- İlk okuma.
openfd 3'ü verir; girdi E1 kurulur, konum 0. OkunanABCD, E1'in konumu 4. - fork. Çocuğun fd 3'ü de E1'i gösterir; E1'in referans sayısı 2.
- Çocuk. Ortak konum 4'ten 3 bayt okur:
EFG. E1'in konumu 7. Çocuk sonlanınca referans sayısı 1'e iner, konum 7 kalır. - İkinci open. Yeni girdi E2 kurulur, fd 4, konum 0. Okunan
AB; E1 etkilenmez. - Ebeveynin fd 3'ü. E1'in konumu 7'dir: okunan
HI, konum 9.
Ebeveynin son okuması EF değil HI'dir, çünkü çocuk ortak konumu ilerletmiştir.
10.2.7Kod izleme: open, read, close
Aşağıdaki program 6000 baytlık notlar.txt dosyasını 4096 baytlık bir tamponla üç kez okur ve kapatır.
#include <fcntl.h>
#include <unistd.h>
int main(void) {
char buf[4096];
int fd = open("notlar.txt", O_RDONLY);
ssize_t n = read(fd, buf, sizeof buf);
n = read(fd, buf, sizeof buf);
n = read(fd, buf, sizeof buf);
close(fd);
return 0;
}open yolu çözer ve dosyanın i-node'unu (numarası 4711 olsun) bellek içi tabloya alır. Konumu 0 olan bir açık dosya girdisi kurar ve en küçük boş numarayı, yani 3'ü döndürür; 0, 1 ve 2 kabuktan miras kalan terminal tanımlayıcılarıdır. İlk read dosyanın baştaki bloğunu önbelleğe alır (gerekirse diskten okur) ve 4096 baytı buf'a kopyalar. Konum 4096 olur. İkinci read de 4096 bayt ister, ama dosyada yalnızca 6000 − 4096 = 1904 bayt kalmıştır. Çağrı 1904 döndürür; konum 6000 olur. Bu bir hata değildir: istenenden az bayt döndüren bir okuma, yani kısa okuma, olağan bir durumdur. Üçüncü read 0 döndürür. Sıfır dönüşü dosya sonu (EOF) demektir; hata olsaydı −1 döner ve errno ayarlanırdı. close fd 3'ü boşaltır. Girdinin referans sayısı 0'a iner ve girdi silinir. i-node'un sayısı da 0 olur; artık bellekten atılabilir. Tablo 10.5 bu adımları özetler.
| Adım | Dönüş | Konum | Girdi ref. | i-node ref. |
|---|---|---|---|---|
open | 3 | 0 | 1 | 1 |
1. read | 4096 | 4096 | 1 | 1 |
2. read | 1904 | 6000 | 1 | 1 |
3. read | 0 (EOF) | 6000 | 1 | 1 |
close | 0 | — | 0 (silinir) | 0 |
read'den sonra tablolar: fd 3, konumu 4096 olan açık dosya girdisini; girdi de i-node 4711'i gösterir.10.2.8Kod izleme: dosya kopyalama
Kopyalama, okuma ve yazmanın birlikte kullanıldığı en sade programdır:
#define BUF 4096
int kopyala(const char *src, const char *dst) {
char buf[BUF];
int in = open(src, O_RDONLY);
int out = open(dst, O_WRONLY | O_CREAT
| O_TRUNC, 0644);
ssize_t n;
while ((n = read(in, buf, BUF)) > 0)
write(out, buf, n);
close(in);
close(out);
return 0;
}Kaynak salt okuma için açılır ve 3 numarayı alır. Hedef üç bayrakla açılır ve 4 numarayı alır. O_WRONLY yalnız yazma ister. O_CREAT dosya yoksa onu oluşturur. O_TRUNC ise dosya varsa içeriğini siler. O_CREAT kullanıldığında üçüncü argüman yeni dosyanın izinleridir: 0644, yani sahip için okuma-yazma, diğerleri için yalnız okuma. Döngü her turda en çok BUF bayt okur ve okuduğu kadarını yazar. Koşuldaki atama dikkat ister: read'in dönüşü önce n'ye yazılır, sonra 0'dan büyük olup olmadığına bakılır.
abc 10.000 bayt olsun. İzleme Tablo 10.6'da verilir. İlk iki tur 4096'şar bayt kopyalar; toplam 8192. Üçüncü read kalan 10.000 − 8192 = 1808 baytı döndürür ve write tam 1808 bayt yazar. Dördüncü read 0 döndürür ve döngü biter. Toplam 4 read ve 3 write çağrısı yapılır.
| Tur | read dönüşü (n) | write | Kopyalanan toplam |
|---|---|---|---|
| 1 | 4096 | 4096 bayt | 4096 |
| 2 | 4096 | 4096 bayt | 8192 |
| 3 | 1808 | 1808 bayt | 10.000 |
| 4 | 0 (EOF) | — | 10.000; döngü biter |
Döngüde write(out, buf, BUF) yazmak. Son turda tamponda yalnız 1808 geçerli bayt vardır; geri kalan 2288 bayt önceki turdan kalan çöptür. Hedef dosya 12.288 bayt olur ve sonu bozuk çıkar. Yazılacak miktar her zaman read'in döndürdüğü n'dir.
Sunumdaki kod kısa tutulmak için hataları denetlemez. Gerçek bir programda her çağrının −1 dönüşüne bakılmalıdır. write da istenenden az bayt yazabilir (ör. disk dolarken ya da bir boruya yazarken); bu yüzden kalan kısım bir iç döngüyle tamamlanmalıdır. Tampon boyu da önemlidir. 6 MiB'lık (6.291.456 bayt) bir dosya bayt bayt kopyalanırsa 6.291.457 read ve 6.291.456 write gerekir. Çekirdeğe her geçiş 0,3 µs sürerse yalnızca bu ek yük yaklaşık 3,77 saniye tutar. 4096 baytlık tamponla çağrı sayısı 1537 + 1536 = 3073'e, ek yük de yaklaşık 0,92 ms'ye iner. 4096 bayt sayfa ve blok boyuna da denk gelir.
10.2.9Erişim yöntemleri
Bir dosyanın içine hangi sırayla erişildiğine erişim yöntemi denir (Şekil 10.9). Sıralı erişimde dosya baştan sona okunur ya da yazılır. Geçerli konum her işlemde ilerler; geri gitmek için başa sarmak (rewind) gerekir. Teyp ve kaset böyle çalışırdı. Bugün de günlük dosyaları, video akışları ve derleyicinin kaynak dosyayı okuması en yaygın kalıptır.
Doğrudan (rastgele) erişimde dosya numaralı blokların ya da sabit uzunluklu kayıtların dizisidir. İstenen kayıttan başlanabilir: B baytlık kayıtlarda 5. kayıt için lseek(fd, 5·B, SEEK_SET) yeterlidir. pread konumlamayı ve okumayı tek çağrıda yapar, ortak konumu da değiştirmez. Diskler ve SSD'ler doğrudan erişimi destekler; veritabanları bunu kullanır. İndisli erişim doğrudan erişimin üzerine kurulur. Küçük bir indis, anahtarı kaydın bulunduğu bloğa eşler. İndis büyürse indisin de indisi tutulur. IBM'in ISAM yöntemi ve bugünkü B+ ağaçları bu fikrin örnekleridir. Son olarak bellek eşlemeli erişimde (mmap) dosyanın sayfaları adres alanına eşlenir; okuma ve yazma sıradan bellek erişimine dönüşür.
10.3Dizinler
10.3.1Dizin işlemleri
Dizin (directory), dosya adlarını dosyalara eşleyen özel bir dosyadır. Her eşlemeye dizin girdisi (directory entry) denir; UNIX'te bir girdi bir adı bir i-node numarasıyla eşler.
Dizin de bir dosyadır, ama kullanıcı onu write ile değiştiremez. Bozuk bir dizin bütün alt ağacı ulaşılmaz kılabileceği için çekirdek dizinlerin içeriğini yalnızca belirli çağrılarla değiştirir. Tablo 10.7 bu çağrıları listeler.
| Çağrı | İşlev |
|---|---|
mkdir | boş dizin oluşturur (içinde yalnız . ve ..) |
rmdir | dizini siler; dizin boş olmalıdır |
opendir / closedir | dizin akışını açar / kapatır |
readdir | açık dizindeki bir sonraki girdiyi (ad, i-node no., tür) döndürür |
rename | dosya ya da dizini yeniden adlandırır veya taşır (aynı dosya sisteminde atomik) |
link | var olan bir dosyaya yeni bir ad (katı bağ) ekler; i-node'un bağ sayısı artar |
symlink | hedef yolunu içeren sembolik bağ oluşturur |
unlink | bir adı kaldırır; bağ sayısı 0 olursa dosya silinir |
Yeni bir dizinin iki girdisi baştan vardır: . dizinin kendisini, .. üst dizini gösterir. Bu iki girdi bağ sayılarını da etkiler (Şekil 10.10). /home'un i-node'u 20 olsun ve içinde başka alt dizin bulunmasın. Bağ sayısı 2'dir: kökteki home girdisi ve kendi . girdisi. mkdir /home/ali yeni dizine i-node 31'i verir. /home'a ali → 31 girdisi eklenir; yeni dizinde . → 31 ve .. → 20 bulunur. Sonuçta i-node 31'in bağ sayısı 2 olur (ali ve .). i-node 20'ninki 3'e çıkar, çünkü yeni dizinin .. girdisi de onu gösterir. Genel kural şudur: bir dizinin bağ sayısı 2 artı alt dizin sayısıdır.
mkdir /home/ali: üst dizine ali girdisi eklenir; yeni dizinde . kendisini, .. üst dizini gösterir.link'in adı yanıltabilir. Bu çağrı bir dizini başka bir dizine bağlamaz; var olan bir dosyaya ikinci bir ad verir. Dizinlere katı bağ vermek sıradan kullanıcılara yasaktır, çünkü dizin ağacında döngü oluşturabilir.
10.3.2Dizin yapıları
Dizinlerin birbirine göre nasıl düzenleneceği zaman içinde değişti. En basit yapı tek düzeyli dizindir (Şekil 10.11): bütün dosyalar tek bir kök dizinde durur. CDC 6600 gibi ilk süper bilgisayarlar ve bazı gömülü aygıtlar böyleydi. Yapı basittir ama iki sorun doğurur. İki kullanıcı aynı adı, örneğin test'i, kullanamaz. Dosyalar da konularına göre gruplanamaz.
İki düzeyli dizinde bir ana dosya dizini (master file directory, MFD) her kullanıcıya ayrı bir kullanıcı dosya dizini (UFD) verir (Şekil 10.12). Ad çakışması ortadan kalkar: ali/test ile can/test farklı dosyalardır. Bir dosyanın tam adı artık “kullanıcı/dosya” biçimindeki bir yoldur. Sistem programları için ayrı bir “kullanıcı” dizini gerekir. Bir komut yazıldığında hangi dizinlere bakılacağını söyleyen arama yolu (search path) fikri buradan doğar. Paylaşım ve gruplama ise yine zordur.
Bugünkü genel amaçlı sistemlerin hepsi ağaç (hiyerarşik) yapı kullanır. Kullanıcı istediği kadar alt dizin açabilir ve her dosyanın kökten başlayan tek bir yolu vardır. Ağaca paylaşım eklenince yapı döngüsüz çizgeye (directed acyclic graph, DAG) dönüşür (Şekil 10.13). Şekilde proje.c hem ali/odev hem ayşe/ortak altında görünür: aynı dosyanın iki adı vardır. Paylaşım iki yeni soru getirir. Ağacı dolaşan bir yedekleme ya da sayma programı aynı dosyayı iki kez sayabilir. Paylaşılan dosya silinirken de hangi adın silindiği, verinin ne zaman serbest kalacağı belirsizleşir. Bağ sayısı ikinci soruyu yanıtlar: veri, son ad da silinince serbest kalır.
proje.c yapıyı döngüsüz çizgeye çevirir. ortak/geri adlı bir bağ home'a geri dönerse genel çizgede döngü oluşur.Bir dizine bağ verilmesine izin verilirse genel çizge ortaya çıkar. Şekildeki ortak/geri → home bağı bir döngü kurar. Ağacı dolaşan bir arama sonsuza gidebilir. Hiçbir yerden ulaşılamayan bir döngünün bağ sayıları da sıfıra inmez; bu dizinleri bulmak için pahalı bir çöp toplama gerekir. UNIX bu yüzden dizinlere katı bağı yasaklar.
10.3.3Yol adları
Mutlak yol kök dizinden başlar: /home/sercan/os/slaytlar. Göreli yol sürecin çalışma dizininden başlar. Çalışma dizini pwd ile görülür, cd ile değiştirilir.
Her dizinde bulunan iki özel ad göreli yolları güçlü kılar: . bulunulan dizin, .. üst dizindir. Kökte .. yine kökü gösterir. Şekil 10.14'da çalışma dizini /home/sercan/ders'tir. ../os/slaytlar önce üst dizine, yani /home/sercan'a çıkar, oradan os'a ve slaytlar'a iner. Ayırıcı UNIX'te /, Windows'ta \'dir. Windows API'si /'yi de kabul eder. Windows'ta mutlak yol bir sürücü harfiyle (C:\Users\sercan\os), ağ paylaşımı ise iki ters bölüyle (\\sunucu\pay) başlar.
/home/sercan/ders, göreli yol ../os/slaytlar.Dizin adlarının da tarihi vardır. İlk UNIX sistemlerinde kullanıcı dizinleri /usr altındaydı; Tanenbaum'un örneklerindeki /usr/ast buradan gelir. Bugün kullanıcı dizinleri /home (macOS'ta /Users) altındadır. /usr ise salt okunur sistem yazılımına ayrılmıştır.
Çalışma dizini /home/elif/odev/os. ../../notlar/./bolum10.txt hangi dosyayı gösterir?
- İlk
..:/home/elif/odev. - İkinci
..:/home/elif. notlar:/home/elif/notlar..bulunulan dizindir; yol değişmez.bolum10.txt: sonuç/home/elif/notlar/bolum10.txt.
10.3.4Yol çözümleme
Bir yolun i-node numarasına çevrilmesine yol çözümleme denir. Çekirdek yolu bileşen bileşen yürür. Her dizin için önce dizinin i-node'unu okur; i-node dizin içeriğinin hangi blokta olduğunu söyler. Sonra o bloğu okur ve bir sonraki bileşenin adını arar. Bulduğu girdi bir sonraki i-node numarasını verir.
Tanenbaum'un klasik örneği /usr/ast/mbox yolunu çözer (Şekil 10.15). Yol mutlak olduğu için arama kökten başlar. Kök dizinin i-node numarası bilinir: bu örnekte 1, V7 UNIX'te ve ext4'te 2'dir. Kök i-node'u bağlama sırasında belleğe alınmıştır.
- Kök dizin bloğu okunur.
usraranır ve i-node 6 bulunur. - i-node 6 okunur.
/usrbir dizindir; içeriği blok 132'dedir. Bu adımda izin denetimi de yapılır: dizinin içinden geçmek için x hakkı gerekir. - Blok 132 okunur.
astaranır ve i-node 26 bulunur. - i-node 26 okunur.
/usr/ast'in içeriği blok 406'dadır. - Blok 406 okunur.
mboxaranır ve i-node 60 bulunur.
/usr/ast/mbox yolunun çözülmesi: kök dizin → i-node 6 → blok 132 → i-node 26 → blok 406 → i-node 60.Önbellek yoksa bu çözümleme 5 disk okuması tutar. Dosyanın kendi i-node'unu (60) ve ilk veri bloğunu okumak için iki okuma daha gerekir. Genel kural kolay çıkarılır. Kök i-node'u bellekte ve her dizin tek blokta olsun. k bileşenli bir yolun son i-node'una ulaşmak 2k okuma, ilk veri bloğunu da okumak 2k + 1 okuma ister. Göreli bir yolda arama kök yerine çalışma dizininin i-node'undan başlar; çalışma dizini zaten bellekte olduğu için daha ucuzdur. Linux, yeni çözülmüş ad → i-node eşlemelerini dentry önbelleğinde tutar. Aynı yol ikinci kez açıldığında disk okumalarının çoğu ortadan kalkar.
/var/log/sistem/gunluk.txt dosyasının ilk veri bloğu okunacak. Önbellekte yalnız kök i-node'u var; her dizin tek bloğa sığıyor. En az kaç disk okuması gerekir?
- Yolda dört bileşen var:
var,log,sistem,gunluk.txt. - Her bileşen için iki okuma yapılır: içinde arandığı dizinin bloğu ve bulunan i-node. Kök dizinin bloğu ilk okumadır; dört bileşen 4 × 2 = 8 okuma eder.
- Son olarak dosyanın ilk veri bloğu okunur: toplam 2 · 4 + 1 = 9 okuma.
10.3.5Katı ve sembolik bağlar
Katı bağ (hard link), var olan bir i-node'u gösteren ikinci bir dizin girdisidir. Sembolik bağ (symbolic link), içeriği başka bir dosyanın yolu olan, kendi i-node'u olan küçük bir özel dosyadır.
İki bağ türünün farkı en iyi bir izlemeyle görülür (Tablo 10.8). echo merhaba > a.txt yeni bir dosya oluşturur. Dizinde a.txt → 812 girdisi vardır ve i-node 812'nin bağ sayısı 1'dir. ln a.txt b.txt katı bağ kurar. Yeni girdi b.txt → 812 aynı i-node'u gösterir ve bağ sayısı 2 olur. İki ad tamamen eşittir; hangisinin “asıl” olduğu sorusunun yanıtı yoktur. ln -s a.txt c.txt ise sembolik bağ kurar: i-node 905 olan yeni bir dosya oluşur ve içeriği "a.txt" dizgisidir. i-node 812'nin bağ sayısı değişmez.
| Komut | Dizin girdileri | i-node 812 bağ sayısı | Sonuç |
|---|---|---|---|
echo merhaba > a.txt | a.txt → 812 | 1 | yeni dosya |
ln a.txt b.txt | a.txt → 812, b.txt → 812 | 2 | iki eşit ad |
ln -s a.txt c.txt | + c.txt → 905 (içerik "a.txt") | 2 | sembolik bağın kendi i-node'u var |
rm a.txt | b.txt → 812, c.txt → 905 | 1 | cat b.txt: merhaba; cat c.txt: ENOENT |
rm a.txt sonrası: b.txt aynı i-node'u göstermeye devam eder; c.txt artık var olmayan bir adı gösterir ve askıda kalır.Son satır iki türün özünü gösterir (Şekil 10.16). rm a.txt yalnızca bir adı siler ve bağ sayısını 1'e indirir. Veri yerinde durur; b.txt çalışır. Sembolik bağ ise i-node'u değil adı saklar. c.txt açılırken çekirdek "a.txt" yolunu yeniden çözer, adı bulamaz ve ENOENT (böyle bir dosya yok) hatası döndürür. Bu duruma askıda bağ (dangling link) denir. Aynı adla yeni bir dosya oluşturulursa sembolik bağ sessizce o yeni dosyayı açmaya başlar; katı bağ ise eski veriyi göstermeye devam eder.
Tanenbaum katı bağların ince bir yan etkisine dikkat çeker. C kullanıcısının dosyasına B bir katı bağ verir; sonra C dosyayı siler. B'nin adı ve veri kalır, ama i-node'un sahibi hâlâ C'dir. Dosyanın kapladığı alan da C'nin kotasından düşülmeye devam eder.
İki türü karşılaştıralım. Katı bağın ek maliyeti yoktur; ama yalnızca aynı dosya sistemi içinde kurulabilir (i-node numarası başka bir dosya sisteminde anlamsızdır) ve dizinlere verilemez. Sembolik bağ dizinlere, başka dosya sistemlerine, hatta ağ yollarına gösterebilir. Bedeli her erişimde ek bir yol çözümüdür. Sembolik bağlar döngü de kurabilir. Çekirdek bu yüzden bir yol çözülürken izlenen bağ sayısını sınırlar; sınır aşılırsa ELOOP hatası döner. Windows'ta katı bağ mklink /H, sembolik bağ mklink ile kurulur. Dizinler için bir de junction türü vardır.
Boş bir dizinde (ext4) mkdir d, echo x > d/a, ln d/a d/b, ln -s a d/c ve mkdir d/e çalıştırılıyor. d, d/a, d/b, d/c ve d/e'nin bağ sayıları nedir?
- d: 3. Üst dizindeki
dgirdisi, kendi.girdisi ve alt dizine'nin..girdisi. - d/a ve d/b: 2. Aynı i-node'un iki adıdır.
- d/c: 1. Sembolik bağın kendi i-node'udur;
statbağı izlemediği için hedefin değil bağın sayısı gelir. - d/e: 2.
d'deki girdi ve kendi.girdisi.
Ardından rm d/a yapılırsa d/b'nin bağ sayısı 1'e iner ve veri durur. d/c ise a adını aradığı için askıda kalır.
10.3.6Bağlama
Bir makinede birden çok dosya sistemi bulunur: sistem diskinin bölümleri, takılan bir USB bellek, ağdan gelen bir paylaşım. UNIX bunları tek bir ağaçta birleştirir.
Bağlama (mounting), bir dosya sisteminin kökünü var olan dizin ağacındaki bir dizine, yani bağlama noktasına, eklemektir.
Şekil 10.17 işlemi üç adımda gösterir. Başlangıçta iki ayrı dosya sistemi vardır: ext4 biçimli kök sistem ve exFAT biçimli bir USB bellek. /mnt/usb sıradan bir dizindir ve içinde eski.txt bulunur. mount /dev/sdb1 /mnt/usb çekirdeğin bağlama tablosuna bir kayıt ekler. Bundan sonra yol çözümü /mnt/usb'ye geldiğinde USB'nin kök i-node'una atlar. /mnt/usb/foto artık USB'deki foto dizinidir. eski.txt silinmez, yalnızca gizlenir. umount /mnt/usb, o dosya sisteminde açık dosya yoksa bağlamayı kaldırır; açık dosya varsa EBUSY hatası verir. Bağlama kalkınca eski.txt yeniden görünür.
mount /dev/sdb1 /mnt/usb sonrası: USB'nin kökü /mnt/usb'nin yerini alır; eski içerik geçici olarak gizlidir.Açılışta önce kök dosya sistemi bağlanır. Diğerleri /etc/fstab dosyasındaki ya da systemd birimlerindeki tanımlara göre bağlanır. findmnt komutu bağlama ağacını gösterir. Bağlarken seçenek de verilebilir: ro salt okunur bağlar, noexec o sistemdeki programların çalıştırılmasını, nosuid de setuid bitinin etkisini kapatır. Takılan bir USB belleğe bu seçeneklerle güvenli bir sınır çizilir. Windows her birime bir sürücü harfi (C:, D:) verir, ama NTFS bir birimi boş bir klasöre de bağlayabilir.
10.4Koruma
10.4.1rwx izin bitleri
Paylaşılan bir sistemde her dosyaya kimin ne yapabileceği belirlenmelidir. UNIX bunun için kaba ama hızlı bir yöntem kullanır. Kullanıcılar üç sınıfa ayrılır: dosyanın sahibi (u), dosyanın grubu (g) ve diğerleri (o). Her sınıfın üç hakkı vardır: okuma (r), yazma (w) ve çalıştırma (x). Dokuz bit böylece sekizli üç basamakla yazılır: r = 4, w = 2, x = 1. chmod 754 betik.sh, sahibe 7 = rwx, gruba 5 = r-x, diğerlerine 4 = r-- verir ve ls -l'de rwxr-xr-- görünür.
Hakların anlamı dosyada ve dizinde farklıdır (Tablo 10.9). Dosyada r içeriği okumak, w değiştirmek, x program olarak çalıştırmaktır. Dizinin içeriği ise ad → i-node eşlemeleridir. Dizinde r girdileri listelemek (ls), w girdi eklemek, silmek ya da yeniden adlandırmak, x ise dizinin içinden geçmektir: yol çözümü ve cd bunu ister.
| Hak | Dosyada | Dizinde |
|---|---|---|
| r | içeriği oku | girdileri listele (ls) |
| w | içeriği değiştir | girdi ekle, sil, yeniden adlandır (dosyanın kendi izni önemsiz) |
| x | program olarak çalıştır | içinden geç (yol çözümü, cd) |
Bu tablonun iki şaşırtıcı sonucu vardır. Birincisi: bir dosyayı silmek dosyanın değil, bulunduğu dizinin w hakkını ister. Silme bir dizin girdisini kaldırmaktır; dosyanın içeriğine dokunmaz. Salt okunur bir dosya, w hakkı olan bir dizinde silinebilir. İkincisi: dizinde x olup r olmaması anlamlı bir birleşimdir. /home/ali dizini drwx--x--x ise başka bir kullanıcı dizini listeleyemez. Ama içindeki bir dosyanın adını biliyorsa yol çözülür ve dosyanın kendi r biti izin veriyorsa okuyabilir.
Denetim sırası da önemlidir. Çekirdek önce isteyenin dosyanın sahibi olup olmadığına bakar; sahipse yalnızca sahip haklarını uygular. Sahip değilse ve dosyanın grubundaysa grup haklarını, hiçbiri değilse diğerlerinin haklarını uygular. İlk eşleşen sınıf kullanılır, sonrakilere bakılmaz.
-r--rw-rw- 1 ali ogr 120 rapor.txt. ali, ogr grubunun da üyesidir. ali dosyaya yazabilir mi?
- İsteyen ali, dosyanın sahibi de ali: sahip sınıfı eşleşir.
- Sahip hakları
r--'dir; w yoktur. - Grup ve diğerleri sınıflarına hiç bakılmaz. Yazma
EACCESile reddedilir.
Sonuç ilk bakışta tuhaftır: grubun ve hatta herkesin yazabildiği bir dosyaya sahibi yazamaz. Sahip isterse chmod u+w ile kendine w verebilir; çünkü izinleri değiştirme hakkı sahibe aittir.
10.4.2Özel bitler, umask ve ACL
Dokuz bitin önünde üç özel bit daha vardır (Şekil 10.18): setuid (4), setgid (2) ve sticky (1). setuid bitli bir program, onu çalıştıranın değil dosya sahibinin kimliğiyle çalışır. passwd komutu root'a ait ve setuid bitlidir; sıradan bir kullanıcı parolasını değiştirirken yalnız root'un yazabildiği parola dosyasını bu sayede günceller. ls -l, setuid'i sahibin x yerinde s olarak gösterir: 4755 → -rwsr-xr-x. sticky bit dizinlerde kullanılır. Herkesin yazabildiği bir dizinde bir girdiyi yalnızca dosyanın sahibi, dizinin sahibi ya da root silebilir veya yeniden adlandırabilir. /tmp böyledir: drwxrwxrwt (1777). Sticky bit açık ama diğerlerinin x hakkı kapalıysa son karakter büyük T olur: 1770 → -rwxrwx--T.
Yeni bir dosyanın izinlerini umask belirler. Programlar dosyaları genellikle 666, dizinleri 777 izniyle oluşturmak ister. Çekirdek bu istekten umask'teki bitleri kapatır: izin = istek & ~umask. umask 027 iken yeni dosya 666 & ~027 = 640 (rw-r-----), yeni dizin 777 & ~027 = 750 (rwxr-x---) olur. Bu ayarda grup yalnızca okuyabilir, diğerleri hiçbir şey yapamaz.
umask'i istekten çıkarmak. Sekizli çıkarma 666 − 027 = 637 verir: grup yazabilir ve çalıştırabilir, diğerleri her şeyi yapabilir. umask bit bit maskedir: 666 & ~027 = 640'tır. İki işlem ancak umask'in bütün bitleri istekte de açıksa aynı sonucu verir.
Sahip, grup ve diğerleri ayrımı kaba tanelidir. “Bu dosyayı ali okuyabilsin, can okuyamasın, ikisi de aynı gruptaysa” gibi bir isteği karşılamaz. Bunun için erişim denetim listesi (access control list, ACL) kullanılır. ACL adlı kullanıcılar ve gruplar için ayrı girdiler tutar; Linux'ta setfacl ile kurulur ve ls -l çıktısında izinlerin sonunda bir + görünür. NTFS'in bütün koruması ACL tabanlıdır.
10.5Disk düzeni
10.5.1Bölümleme: MBR ve GPT
Bir disk bir ya da daha çok bölüme (partition) ayrılır. Her bölümde ayrı bir dosya sistemi bulunabilir; bir bölüm takas alanı, LVM fiziksel birimi ya da şifreli bir kap da olabilir. Bölümlerin listesi diskin başındaki bir tabloda durur. Bu tablonun iki biçimi vardır (Şekil 10.19).
MBR (master boot record) 1983'ten beri kullanılan eski biçimdir. Diskin ilk sektörü olan LBA 0, 512 bayttır. İlk 446 baytta önyükleyici kodu, ardından dört adet 16 baytlık bölüm girdisi, sonunda da 55 AA imzası bulunur. Her girdi bölümün başlangıcını ve uzunluğunu 32 bitlik sektör numarasıyla tutar. Bundan iki sınır doğar. En çok dört birincil bölüm açılabilir; daha fazlası için bir “genişletilmiş” bölümün içine mantıksal bölümler zincirlenir. İkinci ve daha ciddi sınır, adreslenebilen en büyük alandır: 232 × 512 B = 2 TiB. 3 TiB'lık bir disk MBR ile bölümlenirse son 1 TiB'a hiçbir bölüm tanımlanamaz.
GPT (GUID partition table) UEFI standardının parçasıdır ve 64 bitlik LBA kullanır. LBA 0'da bir koruyucu MBR durur; bu sahte tablo, GPT'yi tanımayan eski araçların diski “boş” sanıp üzerine yazmasını önler. LBA 1'de GPT başlığı, LBA 2–33'te 128 baytlık 128 bölüm girdisi bulunur. Her bölümün kendine özgü bir GUID'i ve türünü gösteren bir tür GUID'i vardır. Başlık ve girdiler CRC32 sağlama toplamıyla korunur. Diskin sonunda yedekleri tutulur. 2 TiB'tan büyük diskler ve Windows 11 GPT gerektirir.
10.5.2Açılış
Bölüm tablosunun biçimi makinenin nasıl açıldığını da belirler (Şekil 10.20). Güç verilince donanım kendini sınar (POST). Sonraki adımlar iki düzende ayrılır.
Eski BIOS 16 bitlik gerçek modda çalışır ve dosya sistemi tanımaz. Diskin ilk sektörünü, yani MBR'yi belleğe alıp çalıştırır. MBR kodu bölüm tablosunda “etkin” işaretli bölümü bulur ve onun ilk bloğunu, önyükleme bloğunu, çalıştırır. Bu blok asıl önyükleyiciyi (GRUB, eski Windows'ta NTLDR) yükler. UEFI ise 32 ya da 64 bit çalışır, kendi sürücüleri ve FAT dosya sistemi desteği vardır. Sektör değil dosya arar. Kalıcı bellekteki (NVRAM) önyükleme girdilerini okur ve EFI sistem bölümündeki (ESP, FAT32 biçimli) .efi programını yükler. Etkin bayrağı kullanılmaz. Her iki düzende de bundan sonra önyükleyici çekirdeği yükler; fark yalnızca önyükleyiciye nasıl ulaşıldığındadır.
PC'ler 2012 civarından beri UEFI ile gelir. BIOS uyumluluk modu (CSM) 2020'den sonra çoğu yeni sistemden kaldırıldı.
10.5.3Bir dosya sisteminin düzeni
Bir bölümün içi, dosya sistemine göre değişse de benzer parçalardan oluşur (Şekil 10.21). Bölümün ilk bloğu önyükleme bloğudur. Bölüm açılış için kullanılmasa bile bu yer ayrılır. Diskin ilk bloğu ise MBR'dir; ikisini karıştırmamak gerekir.
Süperblok (superblock), dosya sisteminin kimliğini ve temel parametrelerini tutan bloktur: sihirli sayı, blok boyutu, blok ve i-node sayıları, boş sayaçlar, bağlama durumu. Dosya sistemi bağlanırken ilk okunan yapıdır.
Süperbloğu boş alanı izleyen yapı (biteşlem ya da liste), dosya başına birer i-node'dan oluşan i-node tablosu, kök dizin ve geri kalan veri blokları izler. Bu düzende her dosya erişimi önce diskin başındaki i-node'a, sonra uzaktaki veri bloklarına gider. Sabit diskte bu, uzun kol hareketleri demektir. ext4 bu yüzden bölümü blok gruplarına ayırır. 4 KiB blokla her grup 32.768 blok, yani 128 MiB'tır. Her grubun kendi biteşlemleri ve i-node tablosu vardır. Bir dosyanın i-node'u ve verisi aynı gruba konarak baş hareketi azaltılır. Fikir BSD'nin hızlı dosya sistemindeki (FFS) silindir gruplarından gelir. Süperblok ve grup tanımlayıcılarının yedekleri bazı gruplarda tutulur; asıl süperblok bozulursa yedekten kurtarılır.
10.5.4i-node numarasından disk adresine
i-node tablosu ardışık bloklarda durduğu için bir i-node'un yeri numarasından hesaplanır. Bunun için süperbloktan üç bilgi gerekir: tablonun başladığı blok, blok boyu ve i-node boyu. Hesap üç adımlıdır:
ofset = n × i-node boyu, (tablo bloğu, blok içi) = (ofset div blok, ofset mod blok), disk bloğu = tablonun başı + tablo bloğu
Blok 4 KiB, i-node 128 B. Blok 0 süperblok, blok 1–2 biteşlemler, blok 3–10 i-node tablosudur. i-node numaraları 0'dan başlar; disk 512 baytlık sektörlerle adreslenir. i-node 77 diskte nerededir?
- Blok başına i-node. 4096 / 128 = 32. Tablo 8 blokta 256 i-node tutar.
- Tablo başından ofset. 77 × 128 = 9856 = 2 × 4096 + 1664. Tablonun 2. bloğu (0'dan sayarak), blok içinde 1664. bayt; 1664 / 128 = 13, yani o bloğun 13. i-node'u.
- Disk bloğu. 3 + 2 = blok 5.
- Sektör. Bayt adresi 5 × 4096 + 1664 = 22.144; 22.144 / 512 = 43,25 → sektör 43, içinde 0,25 × 512 = 128. bayt. Bir sektörde 512 / 128 = 4 i-node vardır.
Süperbloktaki “i-node tablosu nerede” bilgisi bu hesap için vazgeçilmezdir. ext4'te iki ayrıntı eklenir. Numaralar 1'den başlar ve tablo blok gruplarına bölünür. Grup başına g i-node varsa grup = (n − 1) div g, grup içi indis = (n − 1) mod g'dir. g = 8192 ve i-node boyu 256 B iken i-node 20.000 için: 19.999 div 8192 = 2, 19.999 mod 8192 = 3615. Grubun tablosundaki ofset 3615 × 256 = 925.440 = 225 × 4096 + 3840'tır: grubun tablosunda blok 225, ofset 3840.
Tablo bloğunu disk bloğu sanmak. 9856 div 4096 = 2, tablonun 2. bloğudur; disk bloğu tablonun başlangıcı eklenerek bulunur (3 + 2 = 5). Numaraların 0'dan mı 1'den mi başladığı da hesabı bir i-node kaydırır.
10.6Tahsis yöntemleri
Dosya sistemini gerçeklemenin merkezindeki soru şudur: bir dosyanın bloklarının diskte nerede olduğu nasıl kaydedilir? Kayıt yöntemi üç şeyi belirler: boş alanın ne kadar verimli kullanıldığını, dosyanın büyüyüp büyüyemeyeceğini ve dosyanın ortasındaki bir bloğa ne kadar hızlı ulaşıldığını. Dört temel yöntem vardır: bitişik, bağlı liste, FAT ve indisli tahsis.
10.6.1Aynı dosya, dört yöntem
Silberschatz'ın örneğindeki jeep dosyası beş bloktur. Şekil 10.23 onu 32 bloklu bir diske dört yöntemle yerleştirir.
- Bitişik: beş blok yan yana, 9–13'te durur. Dizin girdisinde yalnız başlangıç (9) ve uzunluk (5) vardır. i. blok = başlangıç + i.
- Bağlı liste: bloklar diske dağılmıştır: 9, 16, 1, 10, 25. Her blok bir sonrakinin numarasını taşır; dizin girdisi ilk ve son bloğu bilir.
- FAT: aynı zincir, ama işaretçiler bloklarda değil, bellekteki bir tabloda: FAT[9] = 16, FAT[16] = 1, FAT[1] = 10, FAT[10] = 25, FAT[25] = EOF. Blokların tamamı veriye kalır.
- İndisli: beş adresin tamamı tek bir indis bloğunda (19) sırayla durur: 9, 16, 1, 10, 25. i. bloğa doğrudan erişilir.
jeep dosyasının indisli tahsisi: dizin girdisi indis bloğunu (19), indis bloğu da beş veri bloğunu sırayla gösterir.10.6.2Bitişik tahsis
Bitişik tahsis (contiguous allocation), her dosyayı diskte ardışık bloklara yerleştirmektir. Dizin girdisi (başlangıç, uzunluk) çiftini tutar.
Yöntemin artıları belirgindir. Gerçeklemesi basittir: yalnızca iki sayı saklanır. Rastgele erişim kolaydır: dosyanın i. bloğu başlangıç + i'dir. Bütün dosya tek bir arama ve ardışık okumalarla okunur; sabit diskte bundan daha hızlı bir düzen yoktur.
Eksileri ise zaman geçtikçe ortaya çıkar. Tablo 10.10 Tanenbaum'un örneğini izler. 40 bloklu disk yedi dosyayla doludur: A 4, B 3, C 6, D 5, E 12, F 6, G 4 blok. D ve F silinir. Geride iki delik kalır: 13–17'de 5 blok, 30–35'te 6 blok. Toplam 11 boş blok vardır. Şimdi 8 blokluk H dosyası gelir. Toplam boş alan yeter, ama hiçbir delik 8 blok değildir: H sığmaz.
| Durum | Delikler (başlangıç, boy) | Toplam boş | En büyük delik |
|---|---|---|---|
| 7 dosya, disk dolu | — | 0 | — |
| D ve F silindi | (13, 5), (30, 6) | 11 | 6 |
| H (8 blok) geliyor | (13, 5), (30, 6) | 11 | 6 < 8 → sığmaz |
| sıkıştırma sonrası | H 29–36'ya yerleşir, (37, 3) | 3 | 3 |
Bu, bellek yönetimindeki dış parçalanmanın aynısıdır. Çözümlerden biri sıkıştırmadır (birleştirme): E ve G sola kaydırılır, tek bir 11 blokluk delik oluşur ve H yerleşir (Şekil 10.24). Bedeli yüksektir: diskte 12 + 4 = 16 bloğun okunup yeniden yazılması gerekir. Bitişik tahsisin ikinci sorunu, dosyanın son boyunun baştan bilinmesi gerekmesidir. Bir dosya büyümek isterse arkasındaki blok doluysa ya büyüyemez ya da bütünüyle başka yere taşınmalıdır.
Bu eksiklere karşın bitişik tahsis, içeriği bir kez yazılıp değişmeyen ortamlarda bugün de kullanılır: CD-ROM ve DVD dosya sistemleri (ISO 9660, UDF) böyledir. Yöntemin bir de modern torunu vardır. Dosya tek bir parça yerine birkaç bitişik parçaya bölünür. Bu parçalara extent denir; ext4 ve NTFS'in temeli budur.
10.6.3Bağlı liste ile tahsis
Bağlı liste ile tahsiste (linked allocation) her blok, dosyanın bir sonraki bloğunun numarasını taşır. Dizin girdisi yalnızca ilk bloğu bilir.
Bu yöntemde dış parçalanma yoktur: herhangi bir boş blok dosyanın sonuna eklenebilir. Dosya kolayca büyür ve boyutunun baştan bilinmesi gerekmez. İç parçalanma ise her yöntemde olduğu gibi burada da vardır: son bloğun doldurulmayan kısmı boşa gider.
Sorun rastgele erişimdedir. Şekil 10.25'deki A dosyasının blokları 4 → 7 → 2 → 10 → 12 zincirindedir. Mantıksal 3. bloğu (0'dan sayarak) okumak isteyelim. Dizin girdisi yalnızca fiziksel 4'ü bilir. Fiziksel 4 okunur: sonraki 7'dir. 7 okunur: sonraki 2. 2 okunur: sonraki 10. Ancak şimdi fiziksel 10'un mantıksal 3. blok olduğu anlaşılır ve okunur. Tek bir blok için 4 disk okuması yapılmıştır. Mantıksal k. blok k + 1 okuma ister; rastgele erişim O(n)'dir. Sabit diskte bu okumaların her biri ayrı bir arama da olabilir.
İki küçük sorun daha vardır. Her bloğun birkaç baytı işaretçiye gittiği için bloktaki veri miktarı 4096 − 4 = 4092 bayt olur. Bu sayı ikinin kuvveti değildir; 4096 baytlık sayfalarla çalışan programlar için her okuma iki bloğa yayılır. İkincisi güvenilirliktir: tek bir işaretçi bozulursa zincirin geri kalanı, yani dosyanın sonu, kaybolur.
10.6.4FAT: tablo ile tahsis
Bağlı listenin iki sorununun da çaresi işaretçileri bloklardan çıkarmaktır. İşaretçiler diskin başındaki tek bir tabloda toplanır: dosya tahsis tablosu (file allocation table, FAT). Tablonun i. girdisi, i. bloktan sonra gelen bloğun numarasıdır. Boş bloklar 0, zincirin sonu özel bir EOF değeriyle işaretlenir. Bloklar artık tamamen veridir.
Şekil 10.26'deki tabloda iki dosya vardır. A için: girdi 4 = 7, girdi 7 = 2, girdi 2 = 10, girdi 10 = 12, girdi 12 = EOF. B için: 6 → 3 → 11 → 14 → EOF. Rastgele erişim hâlâ zinciri izlemeyi gerektirir, ama tablo bellekte durduğu için zincir bellekte izlenir: disk okuması yoktur. Tablo çok önemli olduğu için diskte iki kopya halinde tutulur.
FAT'ın asıl sorunu boyutudur. Tablonun diskteki her blok için bir girdisi vardır; tablo disk boyutuyla orantılı büyür ve hızlı olması için tamamı bellekte durmalıdır.
200 GiB'lık bir disk 1 KiB'lık bloklara bölünmüş; her FAT girdisi 4 bayt. Tablo ne kadar bellek ister?
- Blok sayısı: 200 × 230 / 210 = 200 × 220 = 209.715.200.
- Tablo: 209.715.200 × 4 B = 838.860.800 B = 800 MiB. Girdi 3 bayta sıkıştırılsa bile 600 MiB.
Gerçek FAT32'de blok yerine birkaç bloktan oluşan kümeler (cluster) kullanılır. 2 TiB'lık bir birim 32 KiB'lık kümelerle 241 / 215 = 67.108.864 küme eder; 4 baytlık girdilerle tablo 256 MiB tutar.
10.6.5Kod izleme: FAT zinciri
Aşağıdaki fonksiyon, ilk bloğu ilk olan bir dosyada ofset. baytı içeren disk bloğunu bulur. Blok boyu BLOK = 1024 bayttır; fat bellekteki tablodur, SON zincirin sonunu gösterir.
int blok_bul(int ilk, long ofset) {
int b = ilk;
long n = ofset / BLOK; /* blok sırası */
while (n > 0 && b != SON) {
b = fat[b]; /* zinciri izle */
n--;
}
return b; /* SON: dosya dışı */
}n = ofset / BLOK tam sayı bölmesidir ve aranan baytın dosyanın kaçıncı bloğunda (0'dan sayarak) olduğunu verir. Döngü zincirde n adım ilerler; her adımda b bir sonraki bloğa geçer. Döngünün ikinci koşulu b != SON zincirin erken bitmesine karşı bir korumadır. İstenen blok dosyada yoksa fonksiyon SON döndürür. Çalışma süresi O(n)'dir, ama her adım yalnızca bir bellek erişimidir.
İki çağrı izleyelim (Tablo 10.11). blok_bul(4, 3500)'de A dosyası blok 4'ten başlar. 3500 / 1024 = 3: aranan bayt dosyanın 3. bloğundadır ve zincirde 3 adım ilerlemek gerekir. fat[4] = 7 (n = 2), fat[7] = 2 (n = 1), fat[2] = 10 (n = 0). Döngü biter; disk bloğu 10'dur. Bayt 3500 bu bloğun 3500 − 3 · 1024 = 428. baytıdır.
| Çağrı | Başta n | İzlenen zincir (b, n) | Dönüş |
|---|---|---|---|
blok_bul(4, 3500) | 3 | (4, 3) → (7, 2) → (2, 1) → (10, 0) | 10; blok içi 428. bayt |
blok_bul(6, 5000) | 4 | (6, 4) → (3, 3) → (11, 2) → (14, 1) → (SON, 0) | SON: dosya dışı |
blok_bul(4, 3500)'ün son adımı: izlenen zincir 4 → 7 → 2 → 10.blok_bul(6, 5000) daha öğreticidir. B dosyası 4 bloktur, yani en çok 4096 bayt tutar. 5000 / 1024 = 4: dosyanın 4. bloğu (beşinci blok) istenir. Zincir 6 → 3 → 11 → 14 diye ilerler; dördüncü adımda fat[14] = SON gelir ve n = 0 olur. Dosyanın 4. bloğu yoktur, ofset dosyanın dışındadır. Bu ofsetten okuyan read 0 bayt döndürür (dosya sonu). Aynı ofsete write yapılırsa dosya sistemi yeni bir blok ayırıp 14'ün girdisine bağlar.
Küme boyu 4 KiB; FAT'ta FAT[5] = 9, FAT[9] = 2, FAT[2] = 11, FAT[11] = EOF. sunum.pptx'in ilk kümesi 5. Dosya hangi kümelerde durur, 9000. bayt hangi kümededir?
- 9000 / 4096 = 2 (tam sayı bölmesi): bayt, dosyanın 2. kümesinde (0'dan sayarak).
- Zincir: 5 (0. küme) → 9 (1.) → 2 (2.) → 11 (3.) → EOF. Dosya 4 kümededir; en çok 16 KiB tutar ve diskte dağınıktır.
- 2. küme fiziksel küme 2'dir. Küme içindeki konum 9000 − 2 × 4096 = 808. bayttır.
10.6.6FAT ailesi
FAT adı, MS-DOS'tan bugüne bir aileyi anlatır. Aile üyeleri FAT girdisinin bit sayısıyla adlandırılır (Tablo 10.12). Girdi ne kadar genişse o kadar çok küme adreslenir.
| FAT12 | FAT16 | FAT32 | exFAT | |
|---|---|---|---|---|
| FAT girdisi | 12 bit | 16 bit | 28 bit kullanılır | 32 bit |
| En büyük birim | ≈ 16 MiB | 2 GiB (32 KiB küme) | 2 TiB (512 B sektör) | 128 PiB |
| En büyük dosya | birim kadar | 2 GiB | 4 GiB − 1 bayt | 16 EiB (kuramsal) |
| Boş alan | FAT'ta 0 | FAT'ta 0 | FAT'ta 0 + FSInfo ipucu | ayrı biteşlem |
| Kullanım | disket | eski diskler | USB, UEFI ESP | SDXC/SDUC, büyük USB |
FAT'ta dizin girdisi her şeyi taşır: ad, uzantı, öznitelikler, zaman, tarih, ilk küme ve boyut (Şekil 10.28). Boyut alanı 32 bit olduğundan FAT32'de bir dosya en çok 4 GiB − 1 bayt olabilir. 4 GiB'lık bir film dosyası FAT32 biçimli USB belleğe kopyalanamaz. exFAT bu sınırı kaldırır. Ayrıca boş alanı ayrı bir biteşlemde tutar ve bitişik olduğu bilinen dosyalarda FAT zincirini hiç kullanmayabilir. Windows'un grafik biçimlendirme aracı FAT32'yi tarihsel olarak 32 GB ile sınırlar; bu sınır dosya sisteminin değil aracındır.
10.6.7İndisli tahsis ve i-node
İndisli tahsiste (indexed allocation) bir dosyanın bütün blok adresleri bir indis bloğunda toplanır; i. girdi i. bloğun adresidir. UNIX'te bu yapıya i-node (indis düğümü) denir. i-node, dosyanın blok adreslerini ve özniteliklerini birlikte tutar.
İndisli tahsis bağlı listenin iyi yanlarını korur: dış parçalanma yoktur, dosya boş blok olan her yere büyür. Üstüne rastgele erişimi de hızlandırır: i. bloğun adresi indis bloğunun i. girdisindedir, zincir izlemek gerekmez.
i-node'un FAT'a göre büyük bir üstünlüğü bellek kullanımındadır. Bir i-node yalnızca dosya açıkken bellekte bulunur. K dosya açıksa gereken bellek K × i-node boyutudur. FAT gibi disk boyutuyla değil, açık dosya sayısıyla orantılıdır. 1000 açık dosya ve 256 baytlık i-node'larla 256.000 bayt (yaklaşık 250 KiB) yeter; disk ister 1 TB ister 100 TB olsun.
Bedeli iki yönlüdür. Küçük bir dosyada bile indis için yer harcanır. i-node sabit boyutlu olduğu için büyük bir dosyanın bütün adresleri ona sığmaz. Çözüm, i-node'un son birkaç girdisini veri bloğu yerine başka indis bloklarını gösterecek biçimde kullanmaktır: dolaylı bloklar.
Şekil 10.29 ext2/ext3'ün ve klasik UNIX'in yapısını gösterir. i-node'da 12 doğrudan işaretçi vardır; dosyanın ilk 12 bloğunun adresleri doğrudan buradadır. 13. işaretçi tek dolaylıdır: veri değil, adreslerle dolu bir indis bloğunu gösterir. 14. işaretçi çift dolaylıdır: indis bloklarını gösteren bir indis bloğunu gösterir. 15. işaretçi üç dolaylıdır: bir düzey daha. Küçük dosyalar yalnızca doğrudan işaretçileri kullanır ve dolaylı blok hiç ayrılmaz. Dosya büyüdükçe ağaç derinleşir. V7 UNIX'te doğrudan işaretçi sayısı 10'du.
10.6.8En büyük dosya boyutu
Bu yapıyla bir dosyanın alabileceği en büyük boyut hesaplanabilir. Varsayımlar: blok 4 KiB = 4096 B, blok adresi 4 B. Bir indis bloğu 4096 / 4 = 1024 adres tutar; P = 1024 diyelim.
i-node'da 12 doğrudan, 1 tek, 1 çift ve 1 üç dolaylı işaretçi var. En büyük dosya kaç bayttır?
- Doğrudan: 12 blok × 4 KiB = 48 KiB (49.152 B).
- Tek dolaylı: bir indis bloğu 1024 veri bloğu gösterir: 1024 × 4 KiB = 4 MiB.
- Çift dolaylı: 1024 indis bloğunun her biri 1024 veri bloğu gösterir: 10242 = 1.048.576 blok = 4 GiB.
- Üç dolaylı: bir düzey daha: 10243 = 1.073.741.824 blok = 4 TiB.
- Toplam: 12 + 1024 + 10242 + 10243 = 1.074.791.436 blok; × 4096 = 4.402.345.721.856 B ≈ 4,004 TiB.
Boyutun neredeyse tamamı üç dolaylı bloktan gelir; doğrudan işaretçilerin katkısı ihmal edilecek kadar küçüktür.
Genel formül şudur: D doğrudan işaretçi, blok boyu B ve adres boyu a ise P = B / a ve en büyük boyut (D + P + P2 + P3) × B'dir. Tanenbaum'un örneğinde blok 1 KiB, adres 4 B (P = 256) ve 10 doğrudan işaretçi vardır: 10 + 256 + 65.536 + 16.777.216 = 16.843.018 blok, yaklaşık 16 GiB.
Bu hesap yalnızca işaretçi yapısının sınırıdır. Gerçek sistemlerde başka sınırlar daha önce devreye girer. ext2/ext3'te i-node'daki blok sayacı 32 bittir ve 512 baytlık birimleri sayar. 4 KiB blokla gerçek sınır 232 × 512 B = 2 TiB'tır. ext4 extent'lerle bu sınırı 16 TiB'a çıkarır.
Çift dolaylı kapasiteyi 2 × 1024 blok sanmak. Çift dolaylı blok 1024 indis bloğunu, her biri de 1024 veri bloğunu gösterir: çarpım 10242'dir. Her dolaylılık düzeyi kapasiteyi P ile çarpar, P kadar artırmaz.
10.6.9Ofsetten disk bloğuna
Bir read çağrısında dosya sistemi bir bayt ofsetini disk bloğuna çevirir. Önce mantıksal blok numarası bulunur: lb = ofset div 4096. Sonra bu numaranın hangi bölgeye düştüğüne bakılır. Aynı yapıda (12 doğrudan, P = 1024) bölgeler şöyledir:
- lb < 12: doğrudan işaretçi lb. i-node bellekteyse 1 okuma (veri).
- 12 ≤ lb < 12 + 1024: tek dolaylı bloğun (lb − 12). girdisi. 2 okuma.
- sonraki 10242 blok: çift dolaylı. r = lb − 1036; dış indis r div 1024, iç indis r mod 1024. 3 okuma.
- sonraki 10243 blok: üç dolaylı. r = lb − 1.049.612; indisler r div 10242, (r div 1024) mod 1024 ve r mod 1024. 4 okuma.
| Ofset (B) | Mantıksal blok | Düzey ve indisler | Blok içi konum | Okuma |
|---|---|---|---|---|
| 40.000 | 9 | doğrudan 9 | 3136 | 1 |
| 1.000.000 | 244 | tek dolaylı [232] | 576 | 2 |
| 5.000.000 | 1220 | çift dolaylı [0][184] | 2880 | 3 |
| 6.000.000.000 | 1.464.843 | üç dolaylı [0][405][511] | 3072 | 4 |
Tablonun satırları tek tek izlenebilir. 40.000 / 4096 = 9 ve 9 < 12'dir: doğrudan işaretçi 9 bloğun adresini verir. 1.000.000 / 4096 = 244; 244 − 12 = 232 < 1024 olduğundan tek dolaylı bloğun 232. girdisine bakılır. 5.000.000 / 4096 = 1220; 1220 − 12 − 1024 = 184 çift dolaylı bölgeye düşer: dış indis 184 div 1024 = 0, iç indis 184. 6 GB için 6.000.000.000 / 4096 = 1.464.843 bulunur. 12 + 1024 + 10242 = 1.049.612 çıkarılınca 415.231 kalır. Üç indis 415.231 div 10242 = 0, (415.231 div 1024) mod 1024 = 405 ve 415.231 mod 1024 = 511'dir.
Okuma sayıları en kötü durumdur. Sıralı okuyan bir program aynı dolaylı bloklara tekrar tekrar başvurur; önbellek ilk düzeyleri bellekte tutar ve ek okumaların çoğu kaybolur.
1000 bloklu bir dosyanın mantıksal 699. bloğu okunacak. Dizin girdisi ve i-node bellekte; başka hiçbir şey önbellekte değil. Blok 4 KiB, adres 4 B, i-node'da 12 doğrudan işaretçi var. Dört yöntemde kaç disk okuması gerekir?
- Bitişik: 1. Adres = başlangıç + 699.
- Bağlı liste: 700. 0 … 698 numaralı bloklar okunmadan 699'un adresi öğrenilemez; 699 da okunur.
- FAT: 1. Zincirde 699 adım bellekte izlenir, sonra tek okuma yapılır.
- i-node: 2. 699 − 12 = 687 < 1024: tek dolaylı blok ve veri bloğu.
10.6.10Extent'ler ve NTFS
Klasik i-node her blok için ayrı bir adres tutar. Büyük ve çoğunlukla bitişik bir dosyada bu çok israflıdır: art arda 32.768 bloğu anlatmak için 32.768 adres yazılır. ext4 bunun yerine extent kullanır.
Extent, bitişik bir blok dizisini tek kayıtla anlatan (mantıksal başlangıç, uzunluk, fiziksel başlangıç) üçlüsüdür.
Şekil 10.31'deki dosya üç extent'tir. Mantıksal 0'dan başlayan 1000 blok fiziksel 34.000'de, mantıksal 1000'den başlayan 32.768 blok fiziksel 120.000'de, mantıksal 33.768'den başlayan 200 blok fiziksel 9100'dedir. Dosya toplam 33.968 bloktur. Her extent 12 bayt tuttuğu için üç extent 36 bayta sığar; i-node'un 60 baytlık i_block alanında yer vardır, ek blok gerekmez. Aynı dosya klasik blok haritasıyla 33.968 işaretçi ister. Bunlar 12 doğrudan işaretçi, 1 tek dolaylı blok, 1 çift dolaylı blok ve onun gösterdiği 33 indis bloğuna yayılır: 35 indis bloğu, yani 140 KiB.
ext4'te fiziksel adres 48 bittir: 4 KiB blokla birim 1 EiB'a kadar büyüyebilir. Bir dosya en çok 16 TiB olabilir. Bir extent en çok 32.768 blok (128 MiB) anlatır. i-node'a 4 extent sığar; daha fazlası gerekirse girdiler bir extent ağacının indis düğümlerini gösterir. Extent'ler ancak dosya gerçekten bitişik yazılırsa işe yarar. ext4 bunun için gecikmeli tahsis (delayed allocation) kullanır: veri önce bellekte bekler, bloklar ancak diske yazılırken seçilir. O anda dosyanın ne kadar büyüdüğü bilindiği için çoklu blok ayırıcı büyük bitişik alanlar bulabilir. XFS, Btrfs, APFS ve NTFS de extent kullanır.
NTFS'in merkezinde ana dosya tablosu (master file table, MFT) durur (Şekil 10.32). NTFS'te her şey bir dosyadır; MFT'nin kendisi de MFT'nin 0. kaydıdır. İlk kayıtlar sistemin kendi meta dosyalarına ayrılmıştır: $MFTMirr (yedek), $LogFile (günlük), $Volume, kök dizin (kayıt 5), $Bitmap (boş küme biteşlemi) gibi. Kullanıcı dosyaları 24. kayıttan itibaren başlar.
Her dosya en az bir 1 KiB'lık MFT kaydıdır ve kaydın içeriği bir öznitelikler listesidir. $STANDARD_INFORMATION zamanları ve bayrakları, $FILE_NAME adı ve üst dizini tutar. Verinin kendisi de bir özniteliktir: $DATA. Küçük bir dosyanın verisi kaydın içine sığar; buna yerleşik (resident) veri denir ve dosyayı okumak için başka bir blok okunmaz. Büyük veri ise koşu listesi (run list) olarak tutulur. Örnekteki (20, 4), (64, 2), (80, 3) üç extent'tir. Dizinler B+ ağacıdır ($INDEX_ROOT, $INDEX_ALLOCATION). NTFS katı bağ, reparse point, alternatif veri akışı (ADS), sıkıştırma, EFS şifreleme ve kota da destekler. Windows sunucularında bunlara ek olarak yazınca kopyalama kullanan ve sağlama toplamı tutan ReFS vardır.
10.6.11Dosya sistemlerinin karşılaştırması
Tablo 10.14 bugün yaygın dosya sistemlerini sınırları ve çökme tutarlılığı yöntemleriyle karşılaştırır.
| Dosya sistemi | En büyük dosya | En büyük birim | Çökme tutarlılığı | Tipik kullanım |
|---|---|---|---|---|
| FAT32 | 4 GiB − 1 B | 2 TiB | yok (chkdsk/fsck) | USB, UEFI ESP |
| exFAT | 16 EiB* | 128 PiB | yok | SD kart, harici disk |
| NTFS | 16 EiB* (Windows: 256 TiB–8 PiB) | 256 TiB–8 PiB | meta veri günlüğü | Windows |
| ext4 | 16 TiB (4 KiB blok) | 1 EiB | günlük (jbd2) | Linux, Android |
| XFS | 8 EiB | 8 EiB | meta veri günlüğü | RHEL, büyük sunucular |
| Btrfs | 16 EiB | 16 EiB | yazınca kopyalama + sağlama toplamı | Fedora, openSUSE, NAS |
| ZFS | 16 EiB | 2128 B (kuramsal) | yazınca kopyalama + sağlama toplamı | FreeBSD, TrueNAS, sunucu |
| APFS | 8 EiB | 8 EiB | yazınca kopyalama, anlık görüntü | macOS, iOS |
Windows 10'un 1709 sürümünden sonra NTFS 2 MiB'lık kümelerle 8 PiB'lık birimlere ulaşır. Apple'ın eski HFS+ sistemi 2017'de yerini APFS'e, Linux'ta ext3 de ext4'e bıraktı.
10.7Dizinlerin gerçeklenmesi
10.7.1Dizin girdileri ve uzun adlar
Bir dosya açılırken dizin girdisi, dosyanın verisini bulmaya yetecek bilgiyi vermelidir. Bu bilgi tahsis yöntemine göre değişir. Bitişik tahsiste ilk bloğun adresi, bağlı liste ve FAT'ta ilk bloğun numarası, UNIX'te ise i-node numarası yeterlidir. Özniteliklerin nerede durduğu da iki yolla çözülür (Şekil 10.33). (a) MS-DOS'ta öznitelikler ve disk adresi doğrudan dizin girdisinin içindedir. (b) UNIX'te dizin girdisi yalnızca ad ve i-node numarasıdır; öznitelikler i-node'dadır. İkinci düzen katı bağları mümkün kılar: aynı i-node'u gösteren iki girdi aynı öznitelikleri paylaşır.
Uzun adlar ayrı bir sorundur. Her girdiye 255 karakterlik sabit bir ad alanı ayırmak çok yer harcar, çünkü adların çoğu kısadır. İki çözüm vardır. (c) Değişken uzunluklu girdilerde her girdi kendi uzunluğunu taşır, ad da girdinin içinde durur. ext4'ün girdisi böyledir: i-node, girdi uzunluğu (rec_len), ad uzunluğu, tür ve ad. Bir dosya silinince girdinin yerinde bir delik kalır. Yeni bir girdi bu deliğe sığmayabilir. Bir girdi sayfa sınırına denk gelirse okurken bir sayfa hatası daha yaşanabilir. (d) İkinci çözümde girdiler sabit uzunluklu ve hizalıdır. Her girdi adın kendisi yerine bir ad işaretçisi taşır; adlar dizin bloğunun sonundaki bir öbekte (heap) durur. Girdileri taramak kolaylaşır, ama öbek ayrıca yönetilmelidir.
10.7.2Dizinde arama
Yol çözümlemenin her adımında bir dizinde bir ad aranır. Aramanın hızı dizinin nasıl tutulduğuna bağlıdır (Şekil 10.34).
Doğrusal liste en basit yapıdır: girdiler art arda durur ve arama baştan sona tarar. Şekil 10.34'deki listede rapor.pdf'i bulmak için ., .., notlar.txt, foto.png, odev, makefile ve rapor.pdf ile 7 karşılaştırma yapılır. Arama O(n)'dir. Yeni bir ad eklenmeden önce aynı adın olmadığı denetlenmelidir, bu da bir tam tarama demektir. Silmede girdi boş işaretlenir ya da son girdi silinenin yerine taşınır.
rapor.pdf ve tez.docx aynı kovaya (5) düşer; çakışma zincirle çözülür.Hash tablosu adın özetinden bir kova seçer: h("rapor.pdf") mod 8 = 5. Arama ortalama O(1)'dir. İki ad aynı kovaya düşebilir; şekilde tez.docx da 5'e düşer. Bu çakışma kovadan çıkan bir zincirle çözülür ve zincirde en çok 2 karşılaştırma yapılır. Sabit kova sayısı ise büyüyen dizinde sorun olur: 100.000 girdili bir dizinde 8 kovanın her zinciri ortalama 12.500 girdi uzunluğundadır ve arama yeniden doğrusala döner. Hash ayrıca sırayı korumaz; ls alfabetik çıktı için girdileri ayrıca sıralamak zorundadır.
Modern sistemler bu yüzden ağaç kullanır. ext4 büyük dizinlerde ad özetine göre sıralı bir htree kurar. NTFS, XFS ve Btrfs B+ ağacı kullanır. B+ ağacında arama O(log n)'dir ve girdiler sıralı listelenebilir; iki üstünlük bir arada elde edilir. Linux en son kullanılan ad → i-node eşlemelerini ayrıca bellekteki dentry önbelleğinde tutar; sık açılan yollar diske hiç inmeden çözülür.
10.8Boş alan yönetimi
10.8.1Blok boyutu seçimi
Disk sabit boyutlu bloklara bölünür; blok boyutunun seçimi iki hedef arasında bir ödünleşimdir. Büyük blok bir aramada çok veri getirir ve veri hızını artırır. Küçük dosyalarda ise bloğun büyük kısmı boş kalır ve alan boşa gider. Küçük blok tersini yapar.
Tanenbaum'un modeli bu ödünleşimi sayılarla gösterir (Şekil 10.35). Diskin ortalama arama süresi 5 ms, dönüş süresi 8,33 ms'dir; bir izde 1 MB vardır. Bir bloğu okuma süresi, arama, yarım dönüşlük ortalama bekleme ve aktarma süresinin toplamıdır:
süre = 5 + 4,165 + (b / 1 MB) × 8,33 ms
Bütün dosyalar 4 KiB olsun. 4 KiB'lık bloklarla bir okuma 9,198 ms sürer: veri hızı 4096 B / 9,198 ms ≈ 0,45 MB/sn. 128 KiB'lık bloklarla okuma 10,206 ms sürer, ama 131.072 bayt getirir: ≈ 12,8 MB/sn. Veri hızı yaklaşık 29 kat artmıştır. Öte taraftan 4 KiB'lık bir dosya 128 KiB'lık bir bloğun yalnızca 4/128 = %3,1'ini doldurur. Blok ≤ 4 KiB iken alan verimliliği %100'dür, 8 KiB'ta %50'ye, 128 KiB'ta %3,1'e düşer.
| Blok | Okuma süresi (ms) | Veri hızı (MB/sn) | Alan verimliliği |
|---|---|---|---|
| 1 KiB | 9,173 | 0,11 | %100 |
| 4 KiB | 9,198 | 0,45 | %100 |
| 16 KiB | 9,295 | 1,76 | %25 |
| 64 KiB | 9,686 | 6,77 | %6,25 |
| 128 KiB | 10,206 | 12,84 | %3,1 |
Gerçek dosya boyutları üzerine yapılan ölçümler ilginç bir tablo çizer. Dosyaların çoğu küçüktür, ama disk alanının çoğunu az sayıdaki büyük dosya kaplar. Bugün varsayılan blok boyu sayfa boyuyla aynı olan 4 KiB'tır. Büyük dosyalar ise extent'ler ve küme seçenekleriyle desteklenir: blok küçük kalır, ama büyük dosyanın blokları bitişik ayrılır.
10.8.2Dört yöntem
Dosya sistemi hangi blokların boş olduğunu bilmelidir. Silberschatz'ın örneğinde 32 bloklu diskin 15 bloğu boştur: 2–5, 8–13, 17–18 ve 25–27. Aynı boş blok kümesi dört yöntemle tutulabilir.
Biteşlem (bitmap), her blok için bir bit tutan dizidir. Bu notta ve sunumdaki örnekte 1 = boş, 0 = dolu kabul edilir; bazı sistemler tersini kullanır.
Biteşlemde 32 blok 32 bit, yani 4 bayt tutar (Şekil 10.36). Örneğin biteşlemi 00111100 11111100 01100000 01110000'dır. Biteşlemin boyutu sabittir ve küçüktür. Bitişik boş blok aramak kolaydır: art arda 1'ler aranır. İşlemciler bir sözcükteki ilk 1 bitini tek komutla bulabildiği için ilk boş bloğu bulmak hızlıdır: ilk sıfır olmayan sözcük bulunur, içindeki ilk 1 biti alınır. Biteşlemin hızlı olması için bellekte tutulması gerekir.
Bağlı listede boş bloklar birbirine bağlanır. Bir baş işaretçisi ilk boş bloğu (2) gösterir; her boş blok bir sonrakinin numarasını taşır: 2 → 3 → 4 → 5 → 8 → … → 13 → 17 → 18 → 25 → 26 → 27 → −1. Ek alan gerekmez, çünkü bilgi boş blokların içindedir. Tek bir boş blok almak ucuzdur: listenin başı verilir. Ama çok sayıda ya da bitişik boş blok bulmak pahalıdır; listeyi dolaşmak her blok için bir G/Ç demektir.
Gruplama bağlı listeyi iyileştirir. İlk boş blok, sonraki n − 1 boş bloğun adresini ve bir sonraki grubun bloğunu tutar. n = 4 ile örnekte gruplar şöyledir: blok 2 → {3, 4, 5}, sonraki grup 8; blok 8 → {9, 10, 11}, sonraki 12; blok 12 → {13, 17, 18}, sonraki 25; blok 25 → {26, 27}, son. Tek bir okumayla n − 1 boş blok bulunur. Klasik UNIX'in boş liste blokları böyle çalışırdı.
Sayma yöntemi boş blokların çoğu zaman bitişik öbekler hâlinde bulunmasından yararlanır. Her öbek (ilk blok, ardışık blok sayısı) çiftiyle tutulur (Şekil 10.37). Örnekteki 15 boş blok yalnızca 4 çifttir: (2, 4), (8, 6), (17, 2), (25, 3). Bu, extent fikrinin boş alana uygulanmasıdır. XFS sayma çiftlerini biri başlangıca, biri uzunluğa göre sıralı iki B+ ağacında tutar; böylece hem “şu bloğun yakınında boş yer” hem de “en az şu büyüklükte boş öbek” soruları hızla yanıtlanır.
32 bloklu bir diskte boş bloklar 3–6, 10–11, 20–24 ve 30'dur. Sayma yöntemiyle ve biteşlemle (1 = boş, blok 0 en solda) nasıl gösterilir?
- Sayma: her öbeğin başlangıcı ve uzunluğu: (3, 4), (10, 2), (20, 5), (30, 1).
- Biteşlem: blok 0–7 için 00011110, 8–15 için 00110000, 16–23 için 00001111, 24–31 için 10000010.
Biteşlem disk ne kadar dolu olursa olsun 32 bittir. Sayma listesinin uzunluğu ise öbek sayısına bağlıdır; disk doluyken ya da boş alan büyük öbeklerdeyken çok kısadır.
10.8.3Kod izleme: biteşlemle blok ayırma
Aşağıdaki fonksiyon N = 16 bloklu bir diskin biteşleminde boş blok arar. Bu kodda biteşlemin anlamı terstir: bitesl[i] == 1 blok doludur, 0 boştur. Arama ipucu numaralı bloktan başlar ve dizinin sonuna gelince başa sarar.
int blok_ayir(int ipucu) {
for (int k = 0; k < N; k++) {
int i = (ipucu + k) % N;
if (bitesl[i] == 0) { /* boş mu? */
bitesl[i] = 1;
return i;
}
}
return -1; /* disk dolu */
}i = (ipucu + k) % N, ipucu'dan başlayıp k adım ilerideki konumdur; mod işlemi aramanın 15'ten sonra 0'a dönmesini sağlar. İlk boş blok bulunduğunda biti 1 yapılır ve numarası döndürülür. Bütün bloklar doluysa döngü N turdan sonra biter ve −1 döner. En kötü durumda süre O(N)'dir. Bu parametrenin amacı yerelliktir: dosyanın yeni bloğu, son bloğunun hemen arkasından aranırsa bloklar birbirine yakın düşer ve sıralı okumada kafa hareketi azalır.
Başlangıçta biteşlem 1 1 0 1 1 1 0 0 1 1 1 0 1 0 1 1'dir (blok 0'dan 15'e). Boş bloklar 2, 6, 7, 11 ve 13'tür. Bir dosya blok 3'te bitiyor ve 3 blok daha istiyor. Her çağrının ipucu, dosyanın o anki son bloğunun bir fazlasıdır. Tablo 10.16 izlemeyi verir.
| Çağrı | Bakılan konumlar (i) | Dönüş | Dosyanın blokları |
|---|---|---|---|
blok_ayir(4) | 4 (dolu), 5 (dolu), 6 (boş) | 6 | …, 3, 6 |
blok_ayir(7) | 7 (boş) | 7 | …, 3, 6, 7 |
blok_ayir(8) | 8, 9, 10 (dolu), 11 (boş) | 11 | …, 3, 6, 7, 11 |
Dosya 6, 7 ve 11 bloklarını aldı. Bloklar bitişik değildir ama birbirine yakındır. Arama ipucusuz, hep 0'dan başlasaydı ilk çağrı blok 2'yi verirdi; dosya diskin başına geri dönerdi. İpucu sona yakınsa başa sarma devreye girer: aynı başlangıç biteşleminde blok_ayir(14) 14 ve 15'i dolu bulur, 0 ve 1'i de geçer ve blok 2'yi döndürür.
10.8.4Sayısal karşılaştırma
Biteşlem ile boş blok listesinin ne kadar yer tuttuğu, büyük bir disk üzerinde hesaplanabilir (Tablo 10.17). Listede her blok numarası 32 bittir. 1 KiB'lık bir liste bloğu 256 numara tutar: 255 boş blok adresi ve sonraki liste bloğuna 1 bağ.
| Tanenbaum örneği | Modern örnek | |
|---|---|---|
| Disk / blok | 500 GB, 1 KiB blok | 1 TiB, 4 KiB blok |
| Blok sayısı | 500 × 109 / 1024 ≈ 488,3 milyon | 240 / 212 = 268.435.456 |
| Liste bloğu | 1 KiB / 4 B = 256 → 255 adres + 1 bağ | 4096 / 4 = 1024 → 1023 adres + 1 bağ |
| Liste, disk boşken | 488,3 M / 255 ≈ 1,91 milyon blok | 268,4 M / 1023 ≈ 262.401 blok (≈ 1 GiB) |
| Biteşlem | 488,3 M bit / 8192 ≈ 59.605 blok | 228 bit = 32 MiB = 8192 blok |
Tanenbaum örneğinde blok sayısı 500 × 109 / 1024 = 488.281.250'dir. Disk tamamen boşken liste 488.281.250 / 255 ≈ 1.914.828 blok tutar. Biteşlem ise bit başına bir blok demektir: 1 KiB'lık blok 8192 bit taşır ve 488.281.250 / 8192 ≈ 59.605 blok yeter. Biteşlem listeden yaklaşık 32 kat küçüktür. Liste yalnızca disk neredeyse doluyken küçüktür, çünkü yalnız boş blokları listeler. Biteşlemin boyutu ise disk doluluğundan bağımsızdır.
Listenin bir üstünlüğü de vardır: bellekte yalnızca tek bir liste bloğu tutmak yeterlidir. Blok dolunca diske yazılır, boşalınca yenisi okunur. Bu basit politikanın bir tuzağı vardır. Bellekteki blok tam doluyken üç bloklu geçici bir dosya silinirse blok taşar ve diske yazılır. Hemen ardından aynı dosya yeniden oluşturulursa blok boşalır ve yenisi diskten okunur. Geçici dosyalar böylece sürekli disk G/Ç'sine yol açar. Tanenbaum'un önerdiği çözüm bellekteki işaretçi bloğunu yarı dolu tutmaktır; hem ayırma hem serbest bırakma disk G/Ç'si olmadan bir süre sürer.
Gerçek sistemler bu yöntemlerin birleşimlerini kullanır. ext4 her blok grubu için ayrı bir biteşlem tutar. XFS sayma yöntemini iki B+ ağacıyla uygular. ZFS ayırma ve serbest bırakma kayıtlarını bir günlük gibi biriktiren alan haritası (space map) kullanır. Btrfs boş ve dolu alanı extent ağacında izler.
10.8.5Disk kotaları
Çok kullanıcılı bir sistemde tek bir kullanıcı bütün diski doldurmamalıdır. Kota (quota) her kullanıcının kullanabileceği blok ve dosya sayısını sınırlar (Şekil 10.40). Bir dosya açılınca açık dosya tablosundaki girdisi, sahibinin kota kaydını gösterir. Her yeni blok ya da i-node tahsisinde kullanıcının sayaçları artar ve sınırlarla karşılaştırılır.
Her sınırın iki düzeyi vardır. Yumuşak sınır aşılabilir; kullanıcı uyarılır ve bir süre (ör. 7 gün) başlar. Kullanıcı bu süre içinde dosya silerek sınırın altına inmelidir. Sert sınır hiçbir koşulda aşılamaz. Süre biterse ya da sert sınıra ulaşılırsa yazma EDQUOT hatasıyla reddedilir. Şekildeki ali 9,4 GiB kullanmış, 9 GiB'lık yumuşak sınırı aşmıştır; 6 günü kalmıştır ve 10 GiB'lık sert sınıra kadar yazabilir. Dosya sayısında ise 41.250 dosyayla 90.000'lik yumuşak sınırın çok altındadır. Bugün Linux'ta quota araçları (ext4 ve XFS proje kotaları), NTFS'te birim kotaları, ZFS'te veri kümesi kotaları ve Btrfs'te alt birim (qgroup) kotaları vardır.
10.9Performans
10.9.1Bir dosyayı oluşturmak kaç G/Ç'dir?
Dosya sistemi çağrılarının diske ne kadar iş yüklediğini görmek için önbelleği olmayan basit bir dosya sistemi düşünelim. i-node ve biteşlem kullanılsın, her dizin tek bir bloğa sığsın. Kök dizinin i-node'unun yeri süperbloktan biliniyor olsun. Arpaci-Dusseau'ların OSTEP kitabındaki bu tür sayım, çağrıların arkasındaki gizli maliyeti ortaya çıkarır. Tablo 10.18 dört işlemin G/Ç'lerini sıralar.
| Çağrı | G/Ç sırası | Toplam |
|---|---|---|
open("/ders/not.txt"), dosya var | oku kök i-node, kök veri, ders i-node, ders veri, not i-node | 5 = 5 okuma |
read(fd, buf, 4096) | oku not i-node, not veri[0]; yaz not i-node (erişim zamanı) | 3 = 2 okuma + 1 yazma |
creat("/ders/not.txt"), dosya yok | oku kök i-node, kök veri, ders i-node, ders veri, i-biteşlem; yaz i-biteşlem, ders veri; oku not i-node; yaz not i-node, ders i-node | 10 = 6 okuma + 4 yazma |
write(fd, buf, 4096), yeni blok | oku not i-node, v-biteşlem; yaz v-biteşlem, not veri, not i-node | 5 = 2 okuma + 3 yazma |
Açmak yol uzunluğuyla orantılıdır: her dizin için bir i-node ve bir veri bloğu, sonda dosyanın i-node'u. Okuma i-node'dan blok adresini bulur, bloğu okur ve i-node'daki erişim zamanını günceller. Son yazma, noatime ya da relatime bağlama seçenekleriyle çoğu zaman ortadan kalkar.
Oluşturma çok daha pahalıdır. Önce yol çözülür (4 okuma). Sonra i-node biteşleminden boş bir i-node seçilir ve işaretlenir (1 okuma, 1 yazma). Dizine yeni girdi eklenir (1 yazma). Yeni i-node'u içeren blok okunur, i-node ilklendirilir ve yazılır (1 okuma, 1 yazma). Son olarak dizinin i-node'unda boyut ve zaman güncellenir (1 yazma). Toplam 10 G/Ç'dir. Yeni blok isteyen her write da 5 G/Ç tutar: i-node okunur, veri biteşleminden blok seçilip işaretlenir, veri yazılır ve i-node'a yeni adres ile boyut yazılır. Bu 5 G/Ç'nin yalnızca biri asıl veridir.
creat ve üç 4 KiB'lık write: 10 + 3 × 5 = 25 G/Ç. Kalın çerçeveli üç kutu kullanıcı verisidir; kalan 22 G/Ç üst veridir.Önbelleksiz bir dosyayı oluşturup 12 KiB yazmak böylece 10 + 3 × 5 = 25 G/Ç eder (Şekil 10.41). Bunların yalnızca üçü kullanıcının verisidir. Önbellek bu sayıyı dramatik biçimde azaltır. Okumalar bellekten karşılanır. Gecikmeli yazma da aynı bloğa yapılan yazmaları birleştirir: i-node biteşlemi, veri biteşlemi, dizin verisi, dizin i-node'u, dosyanın i-node'u ve üç veri bloğu birer kez yazılır. 25 G/Ç'den 8 yazma kalır. Bu sayım, tampon önbelleğinin ve gecikmeli yazmanın neden vazgeçilmez olduğunu gösterir. Yazmaların hangi sırayla yapıldığı ise çökme tutarlılığını belirler.
10.9.2Tampon önbelleği
Tampon önbelleği (buffer cache), disk bloklarının kopyalarını bellekte tutan çekirdek önbelleğidir. İstenen blok önbellekteyse isabet, değilse ıska olur.
Önbelleğin değeri, bellek ile disk arasındaki büyük hız farkından gelir (Tablo 10.19). Bir NVMe SSD'den rastgele 4 KiB okumak DRAM erişiminin yaklaşık bin katı sürer; sabit diskte arama ve dönüş bir o kadar daha yavaştır.
| Erişim | Gecikme |
|---|---|
| DRAM'den 64 bayt | ≈ 80–120 ns |
| NVMe SSD 4 KiB rastgele okuma | ≈ 50–100 µs |
| HDD arama + dönüş | ≈ 5–10 ms (100–280 MB/sn sıralı) |
Klasik tampon önbelleği iki yapıdan oluşur (Şekil 10.42). Bir hash tablosu, (aygıt, blok numarası) çiftinden tamponu O(1) sürede bulur; aynı kovaya düşen tamponlar zincirlenir. Bir LRU listesi de tamponları son kullanım sırasına dizer. Yer gerektiğinde listenin başındaki, en uzun süre kullanılmamış tampon seçilir. i-node ve dizin blokları gibi önemli bloklar değiştirildiğinde beklemeden yazılabilir; bunlar bozulursa bütün dosya sistemi zarar görür.
Yazmalar için iki politika vardır. Anında yazma (write-through) her değişikliği hemen diske gönderir. Güvenlidir ama yavaştır; eski MS-DOS ve çıkarılabilir ortamlar böyle çalışır, çünkü kullanıcı USB belleği her an çekebilir. Geri yazma (write-back) değişen, yani kirli blokları bellekte biriktirir ve sonra yazar. Linux 30 saniyeden eski kirli sayfaları arka planda diske yazar. Uygulama verinin diske ulaşmasını beklemek isterse fsync çağırır.
write başarıyla döndü diye verinin diskte olduğunu sanmak. Geri yazmalı önbellekte write veriyi yalnızca sayfa önbelleğine kopyalar. Birkaç saniye içinde elektrik kesilirse veri kaybolur. close da diske yazmayı garanti etmez. Kalıcılık için fsync(fd) ya da O_SYNC gerekir.
10.9.3Kod izleme: bread
Aşağıdaki bread fonksiyonu bir bloğu önbellek üzerinden okur:
Tampon *bread(int blok) {
Tampon *t = hash_ara(blok);
if (t != NULL) { /* isabet */
lru_sona(t);
return t;
}
t = lru_bas(); /* en eski */
if (t->kirli)
diske_yaz(t); /* geri yaz */
t->blok = blok; t->kirli = 0;
diskten_oku(t); /* ıska: G/Ç */
lru_sona(t);
return t;
}Fonksiyon önce bloğu hash tablosunda arar. Bulursa (isabet) tamponu LRU listesinin sonuna, en yeni konuma taşır ve döndürür; disk G/Ç'si yoktur. Bulamazsa (ıska) listenin başındaki en eski tamponu alır. Tampon kirliyse içeriği kaybolmasın diye önce diske yazılır. Sonra tampon yeni bloğa atanır, kirli biti temizlenir, içerik diskten okunur ve tampon listenin sonuna eklenir.
Önbellekte 3 tampon olsun: LRU sırasıyla bloklar 5, 9 ve 2. Blok 9 kirlidir, yani değiştirilmiş ama henüz diske yazılmamıştır. İstekler sırasıyla 9, 7, 2 ve 4'tür. Tablo 10.20 izlemeyi verir.
| İstek | Sonuç | Atılan | LRU sonra (eski → yeni) | Okuma / yazma |
|---|---|---|---|---|
| 9 | isabet | — | 5, 2, 9* | 0 / 0 |
| 7 | ıska | 5 (temiz) | 2, 9*, 7 | 1 / 0 |
| 2 | isabet | — | 9*, 7, 2 | 1 / 0 |
| 4 | ıska | 9 (kirli → yazılır) | 7, 2, 4 | 2 / 1 |
İlk istek bir isabettir; blok 9 en yeni konuma taşınır ve en eski blok artık 5'tir. bread(7) ıskadır. Blok 5 temizdir: diskteki kopyası güncel olduğu için geri yazmaya gerek yoktur. Tampon blok 7 için yeniden kullanılır ve 1 okuma yapılır. bread(2) yine isabettir. Bu adımdan sonra kirli blok 9 listenin başına gelmiştir. bread(4) ıskadır ve en eski tampon 9'u tutar. Değişiklik kaybolmasın diye blok 9 önce diske yazılır, sonra blok 4 okunur. Kirli bir tamponu atmak ıskanın maliyetini ikiye katlar. Sonuçta 4 istekte 2 isabet, toplam 2 okuma ve 1 yazma yapılmıştır. Önbellek olmasaydı 4 okuma ve blok 9'un değişikliği için ayrı bir yazma gerekirdi.
10.9.4Sayfa önbelleği ve birleşik önbellek
Dosyalara iki yoldan erişilir: read/write çağrılarıyla ve mmap ile bellek eşlemeli G/Ç'yle. Eski sistemlerde bu iki yol ayrı önbellekler kullanırdı (Şekil 10.44). mmap'in verisi sanal bellek sisteminin sayfa önbelleğinde, read/write'ın verisi tampon önbelleğinde dururdu. Aynı dosya iki yoldan birden kullanılınca aynı veri bellekte iki kez bulunurdu. Bu çift önbellekleme hem belleği israf ederdi hem de iki kopyanın tutarlı kalmasını zorlaştırırdı.
mmap ve read/write verisi iki ayrı önbellekte, aynı veri iki kez.Birleşik tampon önbelleğinde dosya sayfaları tek bir yerde durur ve iki yol da aynı sayfaları görür. Linux 2.4'ten beri böyledir; Windows'un önbellek yöneticisi ve Solaris de aynı yolu izler. Sayfa önbelleği sanal bellekle aynı sayfa değiştirme algoritmalarını paylaşır: bir dosya sayfası ile bir sürecin anonim sayfası aynı havuzda yarışır. İstisnalar da vardır. ZFS kendi ARC önbelleğini tutar. Kendi önbelleğini yöneten veritabanları ise O_DIRECT bayrağıyla işletim sisteminin önbelleğini tamamen atlar.
10.9.5Önden okuma, yerleşim ve birleştirme
Önden okuma (read-ahead), sıralı erişimi önceden sezmektir. k. blok istendiğinde k + 1. blok da (önbellekte değilse) okunur; program onu istediğinde hazırdır. Önden okuma yalnızca sıralı erişimde işe yarar. Rastgele erişimde gereksiz okumalar diski meşgul eder. Bu yüzden dosya başına bir “bu dosya sıralı mı okunuyor?” izi tutulur. Linux pencereyi erişim kalıbına göre büyütür ya da küçültür; varsayılan pencere 128 KiB'tır. Uygulama da posix_fadvise ile ipucu verebilir.
Sabit diskte en pahalı iş kol hareketidir. Ardışık okunacak blokları diskte birbirine yakın koymak bu hareketi azaltır. Biteşlemle bitişik boş blok bulmak kolaydır. Eski bir öneri blokları ikişer ikişer ayırmaktı. Bugün extent'ler, gecikmeli tahsis ve çoklu blok ayırıcı bu işi yapar. i-node'ların yeri de önemlidir (Şekil 10.45). (a) i-node'lar diskin başında toplanırsa her dosya erişimi i-node ile veri arasında uzun bir kol hareketi ister. (b) Disk silindir ya da blok gruplarına bölünür ve her grubun kendi i-node'ları olursa i-node ve verisi aynı grupta durur. BSD FFS ve ext2/3/4 böyledir.
Zamanla dosyalar parçalanır: blokları diske dağılır. Birleştirme (defragmentation) dosyaları yeniden bitişik hâle getirir. Windows'ta bu aracın adı “Sürücüleri birleştir ve iyileştir”dir. Linux'ta da benzer araçlar vardır: e4defrag, xfs_fsr, btrfs filesystem defragment. ext4'ün tahsis politikası bu ihtiyacı büyük ölçüde azaltır. SSD'de arama süresi olmadığı için birleştirmenin kazancı yoktur; tersine gereksiz yazma, yani aşınma demektir. Windows SSD'de birleştirme yerine TRIM gönderir.
10.9.6SSD: FTL ve TRIM
Flaş belleğin bir sayfası yerinde güncellenemez; yazılmadan önce ait olduğu büyük silme bloğunun bütünüyle silinmesi gerekir. SSD'nin denetleyicisindeki flaş çeviri katmanı (flash translation layer, FTL) bu yüzden her yazmayı yeni, boş bir sayfaya yönlendirir ve mantıksal blok adresini o sayfaya eşler. Eski sayfa geçersiz olur. SSD'nin içi böylece günlük yapılı bir sisteme benzer. Boş sayfa azalınca denetleyici çöp toplama yapar: bir silme bloğundaki geçerli sayfaları başka yere kopyalar ve bloğu siler.
Burada bir bilgi eksikliği doğar. Dosya sistemi bir dosyayı silince blokları yalnızca kendi biteşleminde boş işaretler. SSD bundan habersizdir; o sayfaları hâlâ geçerli sanar ve çöp toplamada boşuna kopyalar. Gereksiz kopyalar yazma büyütmesini artırır, hem hızı düşürür hem aşınmayı hızlandırır. Çözüm, dosya sisteminin boşalan blokları SSD'ye TRIM komutuyla (NVMe'de Deallocate) bildirmesidir. Linux'ta bildirim ya haftalık fstrim.timer ile toplu olarak ya da discard bağlama seçeneğiyle her silmede yapılır.
10.10Tutarlılık ve kurtarma
10.10.1Yedekleme
Yedeklemenin iki amacı vardır. Birincisi felaketten kurtarmadır: disk arızası, yangın, fidye yazılımı. İkincisi dikkatsizlikten kurtarmadır: yanlışlıkla silinen ya da üzerine yazılan bir dosyayı geri getirmek. Pratikte ikincisi çok daha sık gerekir.
Yedeklemenin iki temel biçimi vardır (Tablo 10.21). Fiziksel döküm diski blok 0'dan sona kadar kopyalar. Çok hızlı ve basittir, ama dizin atlayamaz, artımlı yedek alamaz, tek bir dosyayı geri yüklemeyi de zorlaştırır. Mantıksal döküm seçilen dizinleri ve dosyaları yollarıyla birlikte kopyalar. Daha yavaş ve karmaşıktır; buna karşılık yalnızca değişen dosyaları alabilir ve tek bir dosyayı başka bir makineye bile kolayca geri yükler.
| Fiziksel döküm | Mantıksal döküm | |
|---|---|---|
| Ne kopyalanır? | blok 0'dan sona tüm (kullanılan) bloklar | seçilen dizinler ve dosyalar, yollarıyla |
| Hız, basitlik | çok hızlı, basit | daha yavaş, karmaşık |
| Dizin atlama, artımlı yedek | yapamaz | yapabilir (değişenler) |
| Tek dosyayı geri yükleme | zor | kolay; başka makinede de |
| Bozuk bloklar | denetleyici yeniden eşlemeli ya da İS listeyi bilmeli | sorun değil |
Yaygın bir strateji tam ve artımlı dökümü birleştirir (Şekil 10.46). Örneğin ayda bir tam döküm, her gün de yalnızca son dökümden beri değişenler alınır. Geri yüklemede boş dosya sistemine önce son tam döküm yazılır: önce dizinler, sonra dosyalar. Ardından artımlı dökümler sırayla uygulanır. Geçici dosyalar ve özel dosyalar (/dev, /proc, /tmp) yedeklenmez. Sistem kullanılırken alınan bir yedek tutarsız olabilir: dökümün başında kopyalanan bir dizin, sonunda değişmiş olabilir. Bu yüzden önce bir anlık görüntü (LVM, ZFS/Btrfs, Windows VSS) alınır ve yedek o donmuş görüntüden çıkarılır.
Yedeklerin nerede durduğu da önemlidir. 3-2-1 kuralı şunu söyler: 3 kopya, 2 farklı ortam, 1 kopya başka bir yerde (çevrimdışı ya da değiştirilemez). Fidye yazılımı ağdaki her diski şifreleyebileceği için çevrimdışı kopya son güvencedir. Teyp bugün de arşivde yaşar; LTO-10 kartuşu sıkıştırmasız 40 TB’a kadar veri tutar.
10.10.2Mantıksal döküm algoritması
UNIX'in artımlı mantıksal döküm algoritması dört aşamada çalışır ve bir i-node biteşlemi kullanır. Şekil 10.47'deki ağaçta kareler dizinleri, daireler dosyaları gösterir; sayılar i-node numaralarıdır. Kök 1'in altında 2, 3 ve 4 vardır. 2'nin altında 5 ve 6, 4'ün altında 7 ve 8, 7'nin altında 9, 8'in altında 10 bulunur. Son dökümden beri değişen dosyalar 3, 6 ve 10'dur.
- Aşama 1. Ağaç kökten başlayarak dolaşılır. Değişen her dosya ve değişmiş olsun olmasın bütün dizinler işaretlenir: 1, 2, 3, 4, 6, 7, 8, 10.
- Aşama 2. Ağaç yeniden dolaşılır. Altında değişen hiçbir şey olmayan dizinlerin işareti kaldırılır: 7, çünkü yalnızca değişmemiş 9'u içerir.
- Aşama 3. İşaretli dizinler i-node sırasıyla dökülür: 1, 2, 4, 8. Bu dizinler değişmemiş olsa da dökülür, çünkü değişen dosyaların yollarını geri kurmak için gereklidir.
- Aşama 4. İşaretli dosyalar dökülür: 3, 6, 10.
Dizinlerin dosyalardan önce dökülmesi geri yüklemeyi kolaylaştırır: önce dizin ağacı kurulur, sonra dosyalar yerlerine konur. Birkaç ayrıntı daha vardır. Boş liste dökülmez; geri yüklemeden sonra yeniden kurulur. Birden çok katı bağı olan bir dosya yalnızca bir kez dökülmeli, geri yüklemede bütün adları yeniden bağlanmalıdır. Seyrek dosyaların delikleri sıfırlarla doldurulmamalıdır; yoksa geri yüklenen dosya diskte gerçek boyuta şişer. FIFO'lar ve aygıt dosyaları da içerik olarak değil, tür olarak dökülür.
10.10.3Tutarlılık denetimi: fsck
Bir dosya sistemi çökme sonrasında tutarsız kalabilir: değişikliklerin bir kısmı diske ulaşmış, bir kısmı ulaşmamıştır. UNIX'in fsck (Windows'ta chkdsk) programı bu tutarsızlıkları bulup onarır. İki tür denetim yapar: blok tutarlılığı ve dosya tutarlılığı.
Blok tutarlılığında fsck her blok için iki sayaç tutar. Bütün i-node'ları okur ve her i-node'un kullandığı blokların “kullanımda” sayacını artırır. Sonra boş listeyi ya da biteşlemi okur ve “boş” sayacını artırır. Tutarlı bir dosya sisteminde her blok için iki sayacın toplamı 1'dir: blok ya bir dosyadadır ya boştur (Şekil 10.48). Üç sorun çıkabilir:
- Eksik blok (0, 0): blok hiçbir yerde görünmez. Zararsızdır ama alan kaybıdır; blok boş listeye eklenir.
- Boş listede yinelenen blok (0, 2): aynı blok ileride iki dosyaya birden verilebilirdi. Boş liste yeniden kurulur. Biteşlemde bu durum oluşamaz, çünkü bir bit ya 0'dır ya 1.
- Yinelenen veri bloğu (2, 0): iki dosya aynı bloğu kullanıyor. En tehlikelisidir: dosyalardan biri değişince öbürü de bozulur. fsck yeni bir blok ayırır, içeriği kopyalar, kopyayı dosyalardan birine verir ve kullanıcıyı bilgilendirir.
Bir blok aynı anda hem kullanımda hem boş listede görünürse (1, 1) boş listeden çıkarılır.
Dosya tutarlılığında fsck kökten başlayarak bütün dizinleri dolaşır ve her i-node için bir görülme sayacı tutar. Katı bağlar yüzünden bir i-node birden çok dizinde görünebilir. Sayaç sonunda i-node'daki bağ sayısıyla karşılaştırılır. Bağ sayısı görülme sayısından büyükse yalnızca alan israfı vardır: bütün adlar silinse bile bağ sayısı 0'a inmez ve dosya hiç serbest kalmaz. Küçükse durum tehlikelidir: adlardan biri silinince bağ sayısı 0'a iner ve dosya, başka bir adı hâlâ varken serbest bırakılır. Her iki durumda bağ sayısı görülme sayısına eşitlenir. Hiçbir dizinde görünmeyen ama kullanımda olan i-node'lar lost+found dizinine konur.
fsck'in sayaçları şöyle çıktı (blok 0–9): kullanımda 1 0 1 1 0 2 1 0 0 1; boş listede 0 1 0 0 1 0 0 0 2 0. Sorunlu bloklar hangileridir?
- Her blok için toplam 1 olmalı. Blok 5: (2, 0), yinelenen veri bloğu; en tehlikelisi.
- Blok 7: (0, 0), eksik blok; boş listeye eklenir.
- Blok 8: (0, 2), boş listede iki kez; liste yeniden kurulur.
fsck'in büyük sorunu süresidir. Bütün i-node'ları ve bütün dizinleri okumak gerekir. Terabaytlık bir diskte tam tarama saatler sürer ve sistem bu sürede kullanılamaz. Günlükleme ve yazınca kopyalama bu yüzden doğdu.
10.10.4Çökme ve atomiklik
Sorunun kökü şudur: bir dosya işlemi birden çok bloğu değiştirir, ama disk yalnızca tek bir sektörün yazılmasını atomik olarak garanti eder. Bir dosyayı silmek üç adımdır (Şekil 10.49): dizin girdisini kaldırmak, i-node'u boş havuza vermek ve blokları boş havuza vermek. Birinci adımdan sonra çökme olursa i-node ve bloklar sahipsiz kalır; bu bir sızıntıdır, fsck bulur. İkinci adımdan sonra çökme olursa bloklar sızar. Sıra ters olsaydı durum çok daha kötü olurdu. i-node önce serbest bırakılıp başka bir dosyaya verilirse ve dizin girdisi henüz silinmeden çökme olursa dizin yanlış dosyayı gösterir. İki dosyanın verisi karışır.
Bu soruna dört çözüm ailesi vardır. (1) Sonra onarmak: fsck. (2) Önce niyeti yazmak: günlükleme. (3) Hiçbir zaman üzerine yazmamak: yazınca kopyalama ve günlük yapılı dosya sistemleri. (4) Yazma sırasını dikkatle ayarlamak: BSD'nin soft updates yöntemi, yazmaları çökme hangi anda olursa olsun yalnızca zararsız sızıntılar bırakacak sırada yapar.
Bir işlem eşgüçlüdür (idempotent), eğer bir kez de birçok kez de uygulansa aynı sonucu veriyorsa.
Günlükleme eşgüçlülüğe dayanır. Kurtarma sırasında bir işlem birden çok kez yeniden oynatılabilir: çökme, kurtarmanın ortasında bile olabilir. “Blok n'yi boş işaretle” eşgüçlüdür; kaç kez yapılsa blok boştur. “Boş listenin sonuna n'yi ekle” eşgüçlü değildir; iki kez yapılırsa n listede iki kez bulunur. Günlüğe bu yüzden işlemler değil, blokların yeni içerikleri yazılır.
10.10.5Günlükleme
Günlükleme (journaling), değişiklikleri önce diskteki ayrı bir alana, günlüğe, yazıp ancak sonra son yerlerine uygulamaktır. Veritabanlarında bu fikre önceden yazma günlüğü (write-ahead logging) denir.
Bir dosyaya yeni bir blok eklendiğini düşünelim. Üç blok değişir: dosyanın i-node'u (I), veri biteşlemi (B) ve yeni veri bloğu (Db). ext4'ün varsayılan sıralı (ordered) modunda işlem beş adımda yürür (Şekil 10.50).
- Veri. Yeni veri bloğu Db önce son yerine yazılır.
- Günlüğe yazma. İşlem başı kaydı TxB ve değişecek üst veri blokları, yani I ve B'nin yeni sürümleri, günlüğe sıralı yazılır.
- Onaylama. Önceki yazmaların diske ulaştığı kesinleşince (bariyer ya da FLUSH komutu) işlem sonu kaydı TxE yazılır. TxE diskteyse işlem kesinleşmiştir.
- Kontrol noktası. I ve B son yerlerine yazılır. Bu yazmalar arada kesilse de sorun yoktur; günlükten tekrarlanabilir.
- Serbest bırakma. Günlüğün süperbloğu işlemi tamamlandı olarak işaretler. Günlük dairesel bir tampon gibi yeniden kullanılır.
Kurtarma basittir. Açılışta günlük taranır. TxE'si olan işlemler baştan oynatılır: blokları son yerlerine yeniden yazılır (redo). Yeniden yazmak eşgüçlü olduğu için blokların bir kısmının çökme öncesinde zaten yazılmış olması sorun değildir. TxE'si olmayan işlemler atılır; son yerlerine hiç dokunulmamış olduğu için dosya sistemi eski, tutarlı durumundadır. fsck gerekmez; kurtarma saniyeler sürer.
Çökmenin TxE'den önce mi sonra mı olduğu bu yüzden belirleyicidir. TxE yazılmadan çökme olursa işlem hiç olmamış sayılır ve değişiklik kaybolur. TxE yazıldıktan sonra, örneğin kontrol noktasının ortasında çökme olursa işlem günlükten yeniden oynatılır ve değişiklik korunur. Her iki durumda da dosya sistemi tutarlı kalır.
ext4'ün üç modu vardır. journal modunda veri de günlüğe yazılır; en güvenlisi ama en yavaşıdır, çünkü her veri iki kez yazılır. ordered modunda yalnızca üst veri günlüğe girer, ama veri her zaman üst veriden önce yazılır; böylece bir i-node hiçbir zaman çöp içeren bir bloğu göstermez. writeback modunda veri için sıra garantisi yoktur. Günlük kullanan sistemler arasında ext3/ext4 (jbd2), XFS, NTFS ($LogFile) ve JFS sayılabilir. Apple'da günlüklü HFS+ yerini yazınca kopyalama kullanan APFS'e bıraktı.
10.10.6Kod izleme: günlükten kurtarma
Aşağıdaki kurtar fonksiyonu n kayıtlık bir günlüğü baştan tarar. Kayıtların türü TXB, BLOK ya da TXE'dir; BLOK kayıtları hedef bloğun numarasını ve yeni içeriğini taşır.
void kurtar(Kayit g[], int n) {
int i = 0;
while (i < n && g[i].tur == TXB) {
int j = i + 1;
while (j < n && g[j].tur == BLOK)
j++;
if (j == n || g[j].tur != TXE)
break; /* onaysız: at */
for (int k = i + 1; k < j; k++)
yaz(g[k].hedef, g[k].veri);
i = j + 1; /* sonraki */
}
}i bir işlemin başını gösterir. Dış döngü, g[i] bir TxB olduğu sürece sürer. İç döngü j'yi blok kayıtlarının üzerinden geçirir ve işlemin sonuna götürür. j günlüğün sonuna geldiyse ya da orada bir TxE yoksa işlem onaylanmamıştır: döngü kırılır ve işlem atılır. Onaylı işlemin blokları yaz ile son yerlerine kopyalanır ve i TxE'nin bir sonrasına geçer. Fonksiyon günlüğü bir kez tarar ve her blok kaydını en çok bir kez yazar: O(n).
Sistem üçüncü işlem yazılırken çökmüştür. Günlükte on kayıt vardır: TxB 1, I7 v1, B v1, TxE 1, TxB 2, I9 v2, D40 v2, TxE 2, TxB 3, I7 v3. Son yerlerdeki bütün bloklar hâlâ v0'dır. Tablo 10.22 izlemeyi verir.
| i | İç döngü sonunda j | g[j] | Karar | Son yerler sonra (I7, B, I9, D40) |
|---|---|---|---|---|
| 0 (TxB 1) | 3 | TxE 1 | oynat: I7 ← v1, B ← v1 | v1, v1, v0, v0 |
| 4 (TxB 2) | 7 | TxE 2 | oynat: I9 ← v2, D40 ← v2 | v1, v1, v2, v2 |
| 8 (TxB 3) | 10 = n | — | onaysız: at, döngüden çık | v1, v1, v2, v2 |
İşlem 1 ve 2 tam olarak uygulanır, işlem 3 hiç olmamış gibidir. I7'nin v1 sürümü çökmeden önce son yerine yazılmış olabilir; yeniden yazmak bir şey değiştirmez. Dosya sistemi tutarlıdır ve diski baştan sona tarayan bir fsck gerekmez.
Günlükte bulunan her bloğu yeniden oynatmak. İşlem 3'ün I7 v3 kaydı günlükte durur, ama TxE'si yoktur. Onu yazmak, işlemin yalnızca bir kısmını uygulamak demektir; tutarsızlığın ta kendisidir. Yalnızca onaylanmış işlemler oynatılır.
10.10.7Yazınca kopyalama
Günlükleme her değişikliği iki kez yazar: önce günlüğe, sonra son yerine. Yazınca kopyalama (copy-on-write, CoW) kullanan dosya sistemleri başka bir yol izler: hiçbir bloğun üzerine yazmazlar. Dosya sistemi bir blok ağacıdır (Şekil 10.52). En üstte süperblok (ZFS'te uberblock), onun altında kök, iç düğümler ve en altta veri blokları vardır.
- Yaprak. L3 bloğu değişecek olsun. L3 yerinde güncellenmez; yeni sürümü L3' boş bir yere yazılır. Eski L3 hâlâ sağlamdır.
- Yol. L3'ü gösteren üst düğümler de değişmek zorundadır, çünkü işaretçileri değişecektir. I2 ve kök de kopyalanır: I2', R'. Değişmeyen alt ağaçlar (I1, L4) yeni sürümle paylaşılır.
- Atomik geçiş. Son olarak süperblok R''yü gösterecek biçimde tek bir atomik yazmayla güncellenir. Çökme bu yazmadan önce olursa eski ağaç, sonra olursa yeni ağaç görünür. Ara durum yoktur; günlük gerekmez.
Bu tasarımın iki güzel yan ürünü vardır. Eski kök saklanırsa eski ağacın tamamı hâlâ geçerlidir: anlık görüntü bedavaya gelir. ZFS ve Btrfs ayrıca her işaretçinin yanında gösterdiği bloğun sağlama toplamını tutar. Diskin bir bloğu sessizce bozulursa okunurken yakalanır ve ayna kopyadan onarılır. Bütün diski okuyup bu denetimi yapan işleme scrub denir.
Yazınca kopyalama kullanan sistemler ZFS (Sun, 2005; bugün OpenZFS), Btrfs (Linux), APFS (Apple, 2017) ve ReFS'tir (Windows Server). Bedeli rastgele üzerine yazmalarda ortaya çıkar. Dosyanın ortasındaki her küçük değişiklik yeni bir yere yazıldığı için dosya zamanla parçalanır. Bir yaprak değişikliği bütün yolun kopyalanmasını gerektirdiği için yazma büyütmesi de artar. Veritabanı ve sanal makine disk dosyalarında CoW bu yüzden kapatılabilir.
10.10.8Günlük yapılı dosya sistemi
Rosenblum ve Ousterhout 1991'de Sprite işletim sistemi için radikal bir tasarım önerdi: günlük yapılı dosya sistemi (log-structured file system, LFS). Tasarımın varsayımı şuydu: bellek büyüdükçe okumaların çoğu önbellekten karşılanır, diske giden trafik ağırlıkla yazmadır. O hâlde yazmalar en hızlı biçimde, yani tek bir uzun sıralı yazmayla yapılmalıdır.
LFS'te bütün disk tek bir günlüktür. Yazmalar bellekte biriktirilir: veri blokları, değişen i-node'lar ve i-node haritasının (imap) parçaları. Tampon dolunca hepsi tek bir kesim (segment, ör. 1–2 MiB) olarak günlüğün sonuna sıralı yazılır (Şekil 10.53). i-node'ların sabit bir yeri yoktur; her yazmada yer değiştirirler. imap bir i-node numarasından onun son konumunu bulur. Diskin sabit bir yerindeki kontrol bölgesi (CR) en son imap parçalarını gösterir.
A dosyasının bir bloğu değişince yeni sürümü, yeni i-node'u ve yeni imap parçası yeni bir kesime yazılır. Kesim 1'deki eski kopyalar artık ölüdür, ama yer kaplamaya devam eder. Disk dolmadan bu alanın geri kazanılması gerekir. Bu işi temizleyici (cleaner) yapar: az canlı bloğu olan kesimleri okur, canlı blokları yeni bir kesime sıkıştırır ve eski kesimleri boşaltır. Disk doluyken ve yük altındayken temizleme maliyeti büyür; LFS'in zayıf noktası budur.
LFS, yazma bant genişliğini neredeyse diskin sıralı hızına çıkarır. Fikir bugün de birçok yerde yaşar: Android'in F2FS'i, Linux'un NILFS2'si, NetApp'ın WAFL'ı, ZFS ve Btrfs'in yazınca kopyalaması ve SSD'lerin FTL'leri. SSD'nin içi zaten bir günlük yapılı sistemdir.
10.11VFS ve ağ dosya sistemi
10.11.1Sanal dosya sistemi
Bir Linux makinesinde aynı anda birçok dosya sistemi bağlıdır: kök dizinde ext4, bir veri diskinde XFS, takılı bir USB bellekte exFAT, ağdan bağlanmış bir NFS paylaşımı, bellekte yaşayan /proc. Uygulama bunların hepsine aynı open ve read çağrılarıyla erişir. Bu birliği çekirdeğin bir katmanı sağlar.
Sanal dosya sistemi (virtual file system, VFS), farklı dosya sistemlerini ortak bir nesne arayüzünün arkasında birleştiren çekirdek katmanıdır. Her somut dosya sistemi bu arayüzün işlemlerini kendi işlevleriyle gerçekler.
Linux VFS'i dört nesne türüyle çalışır: superblock (bağlı bir dosya sistemi), inode (bir dosya; Sun'ın özgün tasarımındaki adıyla v-node), dentry (yoldaki bir ad bileşeni) ve file (bir açılış). Her nesne bir işlem tablosu taşır: işlevlere işaretçilerden oluşan bir yapı. Mekanizma dört adımda kurulur (Şekil 10.54).
- Kayıt. Bir dosya sistemi, açılışta ya da modülü yüklenince
register_filesystem()ile VFS'ye kaydolur ve VFS'nin çağıracağı işlevlerin adres tablosunu verir. - Bağlama. VFS o dosya sisteminden bir süperblok nesnesi kurmasını ister. Nesne süperblok işlemleri tablosunu (
s_op) taşır. - open. Yol dentry'lerle çözülür. Somut dosya sistemi i-node bilgisini bir VFS i-node'una yerleştirir; i-node işlemleri (
i_op) ve dosya işlemleri (f_op) tabloları bağlanır. Bir file nesnesi oluşur ve sürecin tanımlayıcı tablosuna eklenir. - read. VFS yalnızca
file->f_op->read_iterişlev işaretçisini çağırır. Dosya ext4'teyse ext4'ün işlevi çalışır ve sayfa önbelleği, blok katmanı ve NVMe sürücüsü üzerinden veriyi getirir. Dosya NFS'teyse NFS istemcisinin işlevi çalışır ve ağ üzerinden bir uzak yordam çağrısı (RPC) gönderir.
read çağrısı file nesnesindeki f_op->read_iter işaretçisiyle somut dosya sisteminin işlevine yönlenir.Bu, C'de yazılmış çok biçimliliktir: nesne yönelimli bir dildeki sanal metot çağrısının aynısı. Uygulama farkı görmez. Ek maliyet yalnızca bir dolaylı çağrıdır; performans kaybı küçüktür. VFS'nin amacı farklı dosya sistemlerine aynı anda tek bir arayüz sunmaktır. Aynı diskte birden çok dosya sistemi bulundurmak için VFS gerekmez; bölümleme bunu zaten sağlar. Windows'ta aynı rolü G/Ç yöneticisi ve dosya sistemi sürücüleri oynar.
Uygulamanın dosya sistemine göre farklı bir sistem çağrısı kullandığını ya da çekirdeğin bütün dosya sistemlerinin kodunu sırayla denediğini sanmak. Sistem çağrısı aynıdır. Doğru kod, open sırasında file nesnesine bağlanan işlem tablosundan tek bir dolaylı çağrıyla seçilir.
10.11.2NFS
Ağ dosya sistemi (network file system, NFS), uzaktaki bir makinenin dizinini yerel ağaca bağlar. Uygulama uzak dosyaları yerel dosyalar gibi kullanır. Sun, NFS'i 1984'te SunOS için tasarladı ve protokolü açık bir standart olarak yayımladı. Bu yüzden bugün hemen her UNIX benzeri sistemde bulunur.
Şekil 10.55 katmanları gösterir. İstemcide mount sunucu:/export /mnt/proje uzak dizini bağlar. Süreç /mnt/proje altındaki bir dosyayı okuyunca çağrı VFS'den NFS istemcisine gider. İstemci bir READ(dosya tanıtıcısı, ofset, n) isteği hazırlar ve TCP 2049 portu üzerinden sunucuya gönderir. Sunucudaki nfsd isteği kendi VFS'si üzerinden yerel dosya sistemine (ext4, XFS, ZFS) iletir ve yanıtı geri yollar.
NFS makine ve mimari bağımsızlığını iki araçla sağlar. Uzak yordam çağrısı (RPC) istek-yanıt alışverişini bir işlev çağrısı gibi gösterir. Dış veri gösterimi (XDR) ise sayıları ve yapıları bayt sırasından bağımsız bir biçimde kodlar; böylece farklı mimarilerdeki istemci ve sunucu anlaşır. NFS'in sürümleri tasarımda önemli bir ayrımı da gösterir. NFSv3 durumsuzdur: sunucu hangi istemcinin hangi dosyayı açtığını hatırlamaz, her istek gereken her şeyi (dosya tanıtıcısı, ofset) taşır. Sunucu çöküp yeniden başlasa da istemciler yalnızca isteklerini tekrarlar. NFSv4 ise durumludur. Yalnızca TCP ve tek bir port (2049) kullanır; Kerberos güvenliği, ACL, dosya kilitleme ve devretme (delegation) içerir. v4.1'deki pNFS, verinin birden çok sunucudan paralel okunmasını sağlar. NFS başlangıçta UDP/IP kullanıyordu; bugün TCP tercih edilir. Tek şart bir IP ağıdır. Windows dünyasında karşılığı şifreleme ve çok kanallı bağlantı destekleyen SMB 3'tür.
10.12Özet
- Disk yalnızca numaralı bloklar sunar. Dosya sistemi bunun üzerine adlı, kalıcı bayt dizileri olan dosyaları ve onları eşleyen dizinleri kurar. Dosya türü uzantıyla değil, sihirli sayıyla güvenilir biçimde anlaşılır.
- Öznitelikler i-node'da, ad dizin girdisindedir.
statöznitelikleri okur; mtime içeriğin, ctime i-node'un değiştiği andır. - Açık bir dosya üç düzeyde izlenir: süreç başına tanımlayıcı tablosu → sistem geneli açık dosya tablosu (konum, mod) → bellek içi i-node.
forkvedupkonumu paylaşır, ikinciopenayrı bir konum açar. Kısa okuma hata değildir; 0 dönüşü dosya sonudur. - Güvenli kaydetme: geçici dosyaya yaz →
fsync→rename. - Dizin yapıları tek düzeyden ağaca ve döngüsüz çizgeye gelişti; UNIX dizinlere katı bağı yasaklar. Yol bileşen bileşen çözülür: kök i-node'u bellekteyse k bileşen 2k okuma, ilk veri bloğuyla 2k + 1 okuma ister.
- Katı bağ aynı i-node'a ikinci addır ve bağ sayısını artırır; sembolik bağ hedefin yolunu saklar ve hedef silinince askıda kalır. Bağlama ayrı dosya sistemlerini tek ağaçta birleştirir.
- rwx bitleri sahip, grup ve diğerleri için ayrı tutulur; ilk eşleşen sınıf uygulanır. Dizinde w girdi ekleyip silmeyi, x içinden geçmeyi sağlar. setuid, sticky ve umask (izin = istek & ~umask) bu düzeni tamamlar.
- MBR 32 bit LBA ile 2 TiB'ta sınırlıdır; GPT 64 bit LBA, 128 bölüm ve yedek başlık sunar. i-node'un yeri numarasından hesaplanır: ofset = n × boy, disk bloğu = tablo başı + ofset div blok.
- Bitişik tahsis hızlıdır ama dış parçalanma üretir. Bağlı liste esnektir ama rastgele erişimi O(n) disk okumasıdır. FAT zinciri belleğe taşır ama tablo diskle büyür. i-node yalnızca açık dosyalar için bellek ister; 12 doğrudan, tek, çift ve üç dolaylı işaretçiyle 4 KiB blokta ≈ 4 TiB'a ulaşır. Extent'ler bitişik blokları tek kayıtla anlatır.
- Dizinler doğrusal liste, hash ya da B+ ağacıyla tutulur; büyük dizinlerde ağaç hem O(log n) arama hem sıralı listeleme verir.
- Boş alan biteşlem, bağlı liste, gruplama ya da sayma ile izlenir. Biteşlem sabit boyutludur; liste yalnızca disk doluyken küçüktür. Blok boyutu veri hızı ile alan verimliliği arasında bir ödünleşimdir.
- Önbelleksiz bir dosya oluşturup 12 KiB yazmak 25 G/Ç'dir. Tampon önbelleği hash ve LRU ile çalışır; kirli bir tamponu atmak ıskanın maliyetini ikiye katlar. Birleşik sayfa önbelleği çift önbelleklemeyi önler. Önden okuma ve yakın yerleşim sıralı erişimi hızlandırır; SSD'ye silinen bloklar TRIM ile bildirilir.
- Yedek için 3-2-1 kuralı geçerlidir; mantıksal döküm dört aşamalıdır. fsck blok ve bağ sayılarını denetler ama yavaştır. Günlükleme TxE'si olan işlemleri yeniden oynatır, olmayanları atar. Yazınca kopyalama hiçbir bloğun üzerine yazmaz ve süperbloğu atomik değiştirir. LFS her şeyi sıralı bir günlüğe yazar ve temizleyiciyle alan kazanır.
- VFS işlev işaretçisi tablolarıyla farklı dosya sistemlerine tek arayüz sunar. NFS uzak dizinleri RPC ve XDR ile yerel ağaca bağlar.
10.13Kavram listesi
Terimlerin İngilizce karşılıkları ve öteki bölümlerdeki kullanımları terim sözlüğünde bulunur.
- dosya sistemi file system
- dosya tanımlayıcısı file descriptor
- açık dosya tablosu open-file table
- dizin, dizin girdisi directory, directory entry
- yol adı (mutlak / göreli) path name
- katı bağ / sembolik bağ hard link / symbolic link
- bağlama mount
- izin bitleri permission bits (rwx)
- erişim denetim listesi (ACL) access control list
- süperblok superblock
- i-node
- bitişik tahsis contiguous allocation
- bağlı liste ile tahsis linked allocation
- indisli tahsis indexed allocation
- iç / dış parçalanma internal / external fragmentation
- biteşlem bitmap
- tampon önbelleği buffer cache
- sayfa önbelleği page cache
- günlükleme journaling
- yazınca kopyalama copy-on-write
- sanal dosya sistemi (VFS) virtual file system
- ağ dosya sistemi (NFS) network file system
10.14Kendini Dene
9000 baytlık bir dosya 2048 baytlık bir tamponla,
read0 döndürene kadar okunuyor.readçağrıları sırasıyla ne döndürür?Cevap
2048, 2048, 2048, 2048, 808 ve 0. Dört tam okuma 8192 bayt getirir; kalan 9000 − 8192 = 808 bayt kısa bir okumayla gelir, son çağrı dosya sonunu bildirir. Toplam 6 çağrı.
mkdir p,mkdir p/x,mkdir p/y,echo z > p/f,ln p/f p/gveln -s f p/hçalıştırılıyor.p,p/x,p/fvep/h'nin bağ sayıları nedir?Cevap
p: 4 (üst dizindeki girdi, kendi., x ve y'nin..girdileri).p/x: 2.p/f: 2 (f ve g aynı i-node).p/h: 1 (sembolik bağın kendi i-node'u).i-node tablosu blok 3'ten başlıyor; blok 4 KiB, i-node 128 B, numaralar 0'dan. i-node 50 hangi disk bloğunda ve hangi sektörde (512 B) başlar?
Cevap
50 × 128 = 6400 = 1 × 4096 + 2304: tablonun 1. bloğu, yani disk bloğu 3 + 1 = 4, blok içinde 2304. bayt. Bayt adresi 4 × 4096 + 2304 = 18.688; 18.688 / 512 = 36,5 → sektör 36, içinde 256. bayt.
Küme 2 KiB; FAT'ta FAT[8] = 3, FAT[3] = 13, FAT[13] = 6, FAT[6] = EOF. İlk kümesi 8 olan dosyanın 5000. baytı hangi kümede ve kümenin kaçıncı baytındadır?
Cevap
5000 div 2048 = 2: dosyanın 2. kümesi (0'dan). Zincir 8 → 3 → 13 → 6 olduğundan 2. küme fiziksel 13'tür. Küme içi konum 5000 mod 2048 = 904. Zincir bellekte izlendiği için disk okuması yalnızca veri kümesi içindir.
Blok 8 KiB, adres 4 B; i-node'da 12 doğrudan, 1 tek, 1 çift, 1 üç dolaylı işaretçi var. En büyük dosya yaklaşık ne kadardır?
Cevap
P = 8192 / 4 = 2048. Blok sayısı 12 + 2048 + 20482 + 20483 = 8.594.130.956; × 8192 B ≈ 70,4 × 1012 B ≈ 64 TiB. Blok boyunu ikiye katlamak kapasiteyi yaklaşık 24 = 16 kat artırır: hem blok iki kat büyür hem üç düzeyin her biri iki kat fazla adres tutar.
4 KiB blok, 4 B adres, 12 doğrudan işaretçi. 10.000.000. bayt hangi düzeydedir ve i-node bellekteyse kaç disk okuması gerekir?
Cevap
10.000.000 div 4096 = 2441. 2441 − 12 − 1024 = 1405 ≥ 0 olduğundan çift dolaylı bölgededir: dış indis 1405 div 1024 = 1, iç indis 1405 mod 1024 = 381. Blok içi konum 1664. Okumalar: çift dolaylı blok, tek dolaylı blok, veri = 3.
24 bloklu bir diskte boş bloklar 1, 2, 3, 7, 8, 9, 10, 15, 20 ve 21. Bu boş alan sayma yöntemiyle ve biteşlemle (1 = boş) nasıl gösterilir?
Cevap
Sayma: (1, 3), (7, 4), (15, 1), (20, 2). Biteşlem: 01110001 11100001 00001100. On boş blok dört çifte iner.
3 tamponlu bir önbellekte LRU sırası 3, 8, 6 (3 en eski ve kirli). İstekler 8, 1, 3, 6.
breadkaç okuma ve kaç yazma yapar?Cevap
8 isabettir (sıra 3, 6, 8). 1 ıskadır; en eski 3 kirli olduğu için önce yazılır, sonra 1 okunur (6, 8, 1). 3 ıskadır: az önce atılan blok yeniden okunur, 6 atılır (8, 1, 3). 6 ıskadır, 8 atılır (1, 3, 6). Toplam 3 okuma, 1 yazma. Bu küçük önbellekte LRU, hemen geri istenecek blokları atar.
Çökmeden sonra günlükte şu kayıtlar var: TxB 1, I4 v1, TxE 1, TxB 2, D8 v2, B v2. Son yerlerde her şey v0.
kurtarsonunda I4, D8 ve B hangi sürümdedir?Cevap
İşlem 1 onaylıdır: I4 ← v1. İşlem 2'nin TxE'si yoktur; j günlüğün sonuna (6) ulaşır ve işlem atılır. Sonuç I4 v1, D8 v0, B v0. D8 v2 günlükte bulunsa da yazılmaz.
Bir dosyanın izni
-rw-r--r--, bulunduğu dizinin iznidrwxrwxrwx. Dosyanın sahibi olmayan sıradan bir kullanıcı dosyayı silebilir mi? Dizinin iznidrwxrwxrwtolsaydı ne değişirdi?Cevap
Silebilir. Silme bir dizin girdisini kaldırmaktır ve dizinin w hakkını ister; dosyanın kendi izni önemsizdir. Sticky bit (sondaki t) açık olsaydı girdiyi yalnızca dosyanın sahibi, dizinin sahibi ya da root silebilirdi.
10.15İleri okuma
- R. H. Arpaci-Dusseau ve A. C. Arpaci-Dusseau, Operating Systems: Three Easy Pieces, “File System Implementation”, “Crash Consistency: FSCK and Journaling” ve “Log-structured File Systems” bölümleri: bu nottaki G/Ç sayımlarını ve günlükleme adımlarını çok daha ayrıntılı anlatır. Kitap ostep.org'da ücretsizdir.
- M. K. McKusick, W. N. Joy, S. J. Leffler, R. S. Fabry, “A Fast File System for UNIX”, ACM TOCS, 1984: silindir gruplarını ve yakın yerleşim fikrini ortaya koyan, bugünkü ext4'ün düzenini açıklayan klasik makale.
- M. Rosenblum ve J. K. Ousterhout, “The Design and Implementation of a Log-Structured File System”, ACM TOCS, 1992: LFS'in tasarımını, temizleyici politikalarını ve ölçümlerini anlatır.
- Linux çekirdek belgeleri, “Overview of the Linux Virtual File System” ve “ext4 Data Structures and Algorithms”: VFS nesnelerini ve ext4'ün disk düzenini, extent ağacını ve günlüğünü birinci elden anlatır.
10.16Kaynaklar
- A. Silberschatz, P. B. Galvin, G. Gagne, Operating System Concepts, 10. baskı, Wiley, 2018 — Bölüm 13 (File-System Interface), 14 (File-System Implementation), 15 (File-System Internals).
- A. S. Tanenbaum, H. Bos, Modern Operating Systems, 4. baskı (2014) / 5. baskı (2023), Pearson — Bölüm 4: File Systems.
- R. H. Arpaci-Dusseau, A. C. Arpaci-Dusseau, Operating Systems: Three Easy Pieces (OSTEP), ostep.org — “Files and Directories”, “File System Implementation”, “Fast File System”, “Crash Consistency: FSCK and Journaling”, “Log-structured File Systems”, “Flash-based SSDs”, “Data Integrity”, “Sun's NFS”.
- M. Rosenblum, J. K. Ousterhout, “The Design and Implementation of a Log-Structured File System”, ACM TOCS, 1992; M. K. McKusick ve diğ., “A Fast File System for UNIX”, ACM TOCS, 1984.
- Linux çekirdek belgeleri: “Overview of the Linux Virtual File System”, “ext4 Data Structures and Algorithms”; Microsoft Learn: NTFS ve exFAT belirtimi; OpenZFS ve Btrfs belgeleri; UEFI belirtimi (GPT).
İlgili sayfalar: Bölüm 10 sunumu · Çalışma soruları · Terim sözlüğü · © Sercan Külcü. Tüm hakları saklıdır.