Ana içeriğe geç

Bölüm 20:Dosya Sistemi ve I/O

14 dakikalık okumaGüncelleme:

Buraya kadar programın nasıl başladığını, belleğe nasıl yerleştiğini ve nasıl çoğaldığını gördük. Ama bir programın işe yarayabilmesi için dış dünyayla konuşması gerekir: dosya okumak, ağdan veri almak, terminale yazmak.

Linux bunların hepsini tek bir soyutlamaya indirger ve bu, sistemin en zarif tasarım kararlarından biridir.

Bu bölümde neyi çözüyoruz?

  • “Her şey dosyadır” felsefesinin pratikte ne anlama geldiğini göreceğiz.
  • Veriyi diskten belleğe gerçekte kimin taşıdığını, CPU’nun bu işin neresinde olduğunu çözeceğiz.
  • Bir read çağrısının file descriptor’dan VFS’e, oradan inode ve page cache’e uzanan yolunu izleyeceğiz.
  • Bloklanan I/O’nun neden ölçeklenmediğini ve epoll ile io_uring’in bunu nasıl çözdüğünü anlayacağız.

Her Şey Dosyadır

Linux’ta sıradan dosyalar da, dizinler de, klavyen de, ağ soketleri de, hatta çalışan process’lerin kendisi de aynı arayüzün arkasındadır: open, read, write, close.

Bu, sıradan bir kolaylık değil. Aynı read çağrısı bir metin dosyasında, bir TCP bağlantısında ve bir donanım aygıtında çalışır. Shell’deki yönlendirme ve pipe’lar da tam olarak bu tekdüzelik sayesinde mümkündür; 14. bölümde gördüğümüz komut > dosya yapısı, aslında process’in 1 numaralı file descriptor’ını başka bir nesneye bağlamaktan ibarettir.

Bunu somut görmek için /proc dosya sistemine bakabilirsin. Diskte hiçbir karşılığı olmayan, kernel tarafından anlık üretilen sanal bir dosya sistemidir:

cat /proc/cpuinfo | head -3

Burada okuduğun şey bir dosya değil; kernel’ın o anda ürettiği metin. Ama cat bunu bilmez, bilmesine de gerek yoktur.

File Descriptor: Küçük Bir Tam Sayı

Bir process bir dosya açtığında kernel ona küçük bir tam sayı verir: file descriptor (fd). Bu sayı, process’in kendi fd tablosundaki bir indekstir.

Her process üç fd ile başlar: 0 standart girdi, 1 standart çıktı, 2 standart hata. Yeni açılan her nesne, kullanılabilir en küçük numarayı alır — bu davranış tesadüfi değil, standartta garanti edilmiştir ve shell’in yönlendirme numaraları buna dayanır.

Çalışan bir process’in açık fd’lerini doğrudan görebilirsin:

ls -l /proc/self/fd

Kernel tarafında üç katmanlı bir yapı vardır ve bu ayrım 19. bölümdeki fork davranışını açıklar:

  1. fd tablosu — process’e özeldir, fd numarasını bir açık dosya tanımına bağlar.
  2. Açık dosya tanımı — dosya konumunu (offset) ve erişim kipini tutar; fork sonrasında ebeveyn ve çocuk aynı tanımı paylaşır.
  3. inode — dosyanın kendisidir; birden fazla açık dosya tanımı aynı inode’a bakabilir.

Bu yüzden fork sonrası çocuk bir şey okuduğunda ebeveynin okuma konumu da ilerler: offset paylaşılan tanımdadır. Buna karşılık open ile aynı dosyayı iki kez açarsan iki ayrı tanım, dolayısıyla iki ayrı offset elde edersin.

fd tablosu, açık dosya tanımı ve inode
parentfd 3açık dosya tanımıoffset: 0kip: okumainode 1347dosyanın kendisichild (fork)fd 3→ 100başka bir processfd 3açık dosya tanımıoffset: 0boyut, izinler, bloklar
Process dosyayı açar. open çağrısı üç şeyi birbirine bağlar: process'in tablosundaki 3 numaralı satırı, kernel'da yeni bir açık dosya tanımını (offset ve kip burada) ve dosyanın kendisi olan inode'u.
  1. Process dosyayı açar. open çağrısı üç şeyi birbirine bağlar: process'in tablosundaki 3 numaralı satırı, kernel'da yeni bir açık dosya tanımını (offset ve kip burada) ve dosyanın kendisi olan inode'u.
  2. fork: tablo kopyalanır. Child, parent'ın fd tablosunun kopyasını alır; ama satırlar aynı açık dosya tanımını gösterir. Tanımın kendisi kopyalanmaz, yalnızca ona bakan bir referans daha eklenir.
  3. Offset ortaktır. Child 100 bayt okuduğunda offset tanımın içinde ilerler. Parent'ın bir sonraki read çağrısı 100. bayttan başlar, çünkü ikisi aynı konumu paylaşıyor.
  4. Ayrı bir open, ayrı bir tanım. Başka bir process aynı dosyayı kendisi açarsa yeni bir tanım doğar ve kendi offset'iyle baştan başlar. Aynı process aynı dosyayı ikinci kez açsa da sonuç budur.
  5. Hepsinin ardında tek dosya. İki tanım da aynı inode'a bakar. Boyut, izinler ve veri bloklarının yeri inode'da durur; dosyanın asıl kimliği budur. Adı ise burada bile değildir, dizindedir.

inode: Dosyanın Kimliği

Bir dosyanın adı, dosyanın kendisi değildir. Ad, dizinde tutulan bir girdidir ve bir inode numarasına işaret eder. Dosyanın künyesi — boyut, izinler, sahip, zaman damgaları ve veri bloklarının diskte nerede olduğu — inode’da durur. Dikkat et: verinin kendisi inode’un içinde değildir; inode yalnızca o blokların adresini tutar. (Çok küçük dosyalarda bazı dosya sistemleri veriyi doğrudan inode’a sığdırır — buna inline data denir — ama bu bir istisnadır, kural değil.)

ls -li /etc/hostname

Baştaki sayı inode numarasıdır. NTFS’in de aynı fikri var, adı file ID: Windows’ta fsutil file queryfileid C:\yol\dosya onu döküyor. Bu ayrım birkaç davranışı bir anda açıklar:

VFS: Ortak Dil

Peki read çağrısı ext4, Btrfs, XFS, NFS ve /proc üzerinde nasıl aynı şekilde çalışır? Cevap VFS (Virtual File System): kernel içinde, gerçek dosya sistemlerinin üzerinde duran bir soyutlama katmanı.

VFS’in bütün modeli dört nesne üzerine kurulu ve dördünü tanımak, /home/emir/notlar.txt gibi bir yolun nasıl gerçek veriye dönüştüğünü de açıklıyor:

İsimlerin inode’da değil dentry’de olması ilk bakışta tuhaf gelebilir ama bir şeyi doğrudan açıklıyor: aynı inode’a birden fazla ad bağlanabilir. Hard link dediğimiz şey tam olarak budur ve dosyayı silmenin neden “adı kaldırmak” anlamına geldiğini de bu açıklar.

Yol çözümleme de bu nesneler üzerinde yürüyen bir zincirdir. /home/emir/notlar.txt için VFS önce kök dizinin inode’unu alır, içinde home dentry’sini arar, bulduğu inode’a geçer, orada emir’i arar, sonra notlar.txt’yi. Her adım bir dizin okuması demektir.

Bu yürüyüşün her seferinde diske inmesi felaket olurdu. Kernel bu yüzden dentry cache tutar: ad → inode eşleşmelerini bellekte saklar. Sık kullandığın yolların çözümlenmesi neredeyse bedavaya gelir; ls komutunu aynı dizinde ikinci kez çalıştırdığında hissettiğin hız farkının bir kısmı buradan gelir.

Peki VFS, tek bir read çağrısını ext4 ile NFS arasında nasıl doğru yere yönlendiriyor? Her dosya sisteminden bir fonksiyon işaretçisi tablosu ister. Fonksiyon işaretçisi, bir fonksiyonun adresini değişkende tutmak demektir; tabloya hangi adresi koyarsan read çağrısı oraya gider. Nesne yönelimli bir arayüzün C ile yazılmış hâli gibi düşünebilirsin.

Kernel kaynağında bu tablonun adı file_operations’dır ve içinde read, write, open gibi her işlem için bir satır bulunur. ext4 o satırlara kendi fonksiyonlarının adresini yazar, NFS bambaşka adresler yazar, /proc bir üçüncüsünü. Syscall geldiğinde VFS yalnızca ilgili satırdaki adrese atlar; hangi dosya sistemine gittiğini bilmesine bile gerek yoktur.

Yeni bir dosya sistemi yazmak, bu tabloyu doldurmak demektir. Kullanıcı tarafındaki hiçbir program değişmez.

Page Cache: Diskin Önündeki Bellek

7. bölümde diskin RAM’e göre binlerce kat yavaş olduğunu görmüştük. Kernel bu farkı page cache ile kapatır: diskten okunan her sayfa, kullanılmayan RAM’de saklanır. Aynı veri tekrar istendiğinde disk hiç dönmez.

Bu yüzden bir dosyayı ikinci kez okumak dramatik biçimde hızlıdır ve bu yüzden free -h çıktısında “kullanılan” bellek beklediğinden yüksek görünür. Boş RAM boşa harcanmış RAM’dir; kernel onu cache olarak değerlendirir ve bir program bellek istediğinde cache’i anında geri verir.

Yazma tarafında varsayılan davranış write-back’tir: write çağrısı veri page cache’e kopyalandığında döner, disk yazımı sonra yapılır. Hızlıdır, ama güç kesilirse son yazılanlar kaybolabilir. Verinin gerçekten diskte olduğundan emin olmak gerektiğinde fsync çağrılır — veritabanlarının her commit’te ödediği bedel tam olarak budur.

Bir yana: sync ve düşen elektrik

Bir editörün “kaydedildi” demesi ile verinin fiziksel olarak diskte olması aynı an değildir. Aradaki pencere sandığından uzundur: Linux’un varsayılan ayarlarında kirli bir sayfa page cache’te otuz saniyeye kadar bekleyebilir. Süreyi /proc/sys/vm/dirty_expire_centisecs belirler (varsayılan değeri 3000, yani 30 saniye); arka plandaki yazıcı da beş saniyede bir uyanıp süresi dolanları diske gönderir. Kritik veri yazan yazılımlar bu pencereyi fsync ile kapatır; kapatmayanlar ise elektrik kesintisinde tutarsız dosya bırakabilir.

Derinleşmefsyncgate: "diske yazdım" demek neden bu kadar zor?

fsync çağırdın ve döndü. Verinin diskte olduğundan emin olabilir misin?

Sorunun cevabı 2018’e kadar herkesin sandığından farklı çıktı ve PostgreSQL geliştiricileri bunu zor yoldan öğrendi. Olay literatüre fsyncgate olarak geçti.

Mekanizma şöyleydi. write çağrısı veriyi page cache’e koyar ve döner; sayfa “kirli” işaretlenir. fsync bu kirli sayfaların diske inmesini ister. Peki disk yazımı başarısız olursa ne olur?

Linux’un o günkü davranışı şuydu: hata işaretlenir, sayfanın kirli bayrağı temizlenir ve hata bir sonraki fsync çağrısına bildirilir. Yalnızca bir kez. Sonraki fsync çağrıları başarı döner — çünkü kirli sayfa kalmamıştır.

Sonuç felaket bir bileşim yarattı: PostgreSQL bir fsync hatası gördüğünde, o zamanki tasarımıyla işlemi yeniden deniyordu. İkinci fsync başarı dönüyordu. Veritabanı “tamam, yazıldı” diyor ve checkpoint’i ilerletiyordu. Oysa veri hiçbir zaman diske inmemişti ve artık cache’te de yoktu. Sessiz veri kaybı.

İşin daha tuhaf tarafı: hata, dosyayı o an açık tutan file descriptor’lara bildiriliyordu. Yazan process ile fsync çağıran process farklıysa — ki PostgreSQL’in mimarisinde tam olarak öyleydi — hata hiç kimseye ulaşmayabiliyordu.

Sonrasında iki şey değişti. Kernel tarafında hata raporlaması sıkılaştırıldı; PostgreSQL tarafında ise mimari karar değişti: fsync hatası artık yeniden denenmez, süreç derhal panic eder ve veritabanı kurtarma moduna girer. Yani “tekrar dene” yerine “asla devam etme”.

Buradan çıkarılacak iki ders var. Birincisi teknik: kalıcılık (durability) bir çağrının dönüş değeri değil, bir protokoldür. Veritabanlarının write-ahead log, checksum ve kurtarma prosedürü tutmasının sebebi budur.

İkincisi daha genel: bir soyutlama en çok hata yolunda sızar. write/fsync arayüzü mutlu yolda kusursuz görünüyordu; sözleşmenin belirsiz olduğu yer, işler ters gittiğinde ne olacağıydı. Bu, sistem tasarımında tekrar tekrar karşına çıkacak bir kalıptır.

Veriyi Diskten Belleğe Kim Taşıyor?

Şimdiye kadar “kernel diskten okur, page cache’e koyar” dedim ve taşımanın kendisini geçtim. O cümlenin içinde, performansın büyük kısmını açıklayan bir ayrıntı saklı.

Naif cevap şu olurdu: CPU diskten bir bayt ister, gelir, belleğe yazar, sonra bir sonrakini ister. Buna programlı I/O denir ve gerçekten böyle çalışan sistemler vardı. Sorun, 4 KiB’lık tek bir sayfa için CPU’nun binlerce kez bu döngüyü dönmesi. 6. bölümde saydığımız çevrimlerin tamamı veri kopyalamaya gider ve disk zaten yavaşken CPU da onunla birlikte oturur.

Çözüm, taşımayı CPU’nun elinden almak. DMA (Direct Memory Access — doğrudan bellek erişimi), çevre biriminin belleğe CPU’dan geçmeden yazabilmesidir. Akış şöyle işler: CPU denetleyiciye kaynağı, hedef bellek adresini ve uzunluğu söyler, sonra kenara çekilip başka iş yapar. Denetleyici veriyi belleğe kendisi yazar. İş bitince bir interrupt üretir ve CPU’ya haber verir.

“Doğrudan” sözcüğü CPU’nun hiç karışmadığı anlamına gelmiyor; kurulumu ve bitiş bildirimini o yapar. Doğrudan olan şey verinin yolu: aygıt ile bellek arasında, işlemciye uğramadan.

Bunun iki sonucu var ve ikisi de kitabın başka yerlerine bağlanıyor. Birincisi, 9. bölümde anlattığım bekleme mantığı: process I/O beklerken kernel onu uyutup CPU’yu başkasına verebiliyor, çünkü taşıma zaten CPU’yu meşgul etmiyor. İkincisi, DMA belleğe doğrudan yazdığı için cache ile bellek arasında bir tutarlılık sorusu doğuruyor — 8. bölümdeki sorunun donanım tarafından gelen hâli. Modern sistemlerde bunu da donanım hallediyor.

Buraya kadar anlattığım her şeyi tek bir çağrının yolculuğunda birleştirelim:

Bir read çağrısının yolu
user spacekernelread(fd, buf, 4096)VFS: fd → file → file_operationsext4'ün read fonksiyonuvar mı?page cachehit: buf'a kopyaladiskmissDMAkesme: uyan
Program read çağırır. read(fd, buf, 4096) bir syscall'dır: CPU kernel mode'a geçer ve çağrı kernel'ın ortak giriş kapısından içeri girer.
  1. Program read çağırır. read(fd, buf, 4096) bir syscall'dır: CPU kernel mode'a geçer ve çağrı kernel'ın ortak giriş kapısından içeri girer.
  2. VFS yönlendirir. Kernel fd numarasından açık dosya tanımını, oradan file nesnesini bulur ve file_operations tablosundaki read satırına atlar. O satırda ext4'ün kendi fonksiyonu yazılı.
  3. Önce page cache. ext4 istenen sayfanın page cache'te olup olmadığına bakar. Varsa veri buf'a kopyalanır ve read döner; disk hiç dönmez. Sık okunan dosyaların hızı buradan gelir.
  4. Miss: disk ve DMA. Sayfa yoksa kernel diske istek gönderir ve process'i uyutur; CPU bu sırada başka işlere bakar. Disk denetleyicisi veriyi DMA ile doğrudan page cache'teki boş sayfaya yazar.
  5. Kesme ve uyanış. Aktarım bitince denetleyici bir kesme üretir. Kernel process'i uyandırır, veriyi page cache'ten buf'a kopyalar ve read döner. Aynı sayfanın bir sonraki okuması artık bir hit.

Buffered, Direct ve mmap

Aynı dosyayı üç ayrı yoldan okuyabilirsin ve hangisini seçtiğin, programının ne kadar hızlı çalışacağını doğrudan belirler. Üçünü sırayla tanıyalım.

Buffered I/O (varsayılan): veri diskten page cache’e, oradan process’in tamponuna kopyalanır. İki kopya vardır ama cache isabetleri bunu fazlasıyla telafi eder.

Direct I/O (O_DIRECT): page cache atlanır, veri doğrudan process’in tamponuna gider. Yalnızca kendi cache’ini yöneten yazılımlar için mantıklıdır — veritabanları genelde bu gruptadır, çünkü hangi sayfanın sıcak olduğunu kernel’dan daha iyi bilirler. Yanlış yerde kullanıldığında performansı çökertir.

Bellek eşleme (mmap): dosya doğrudan process’in adres alanına eşlenir. Artık read çağrısı yoktur; dosyaya sıradan bir dizi gibi erişilir ve eksik sayfalar 17. bölümde gördüğümüz demand paging ile page fault üzerinden getirilir.

mmap-ornegi.c
int fd = open("veri.bin", O_RDONLY);
struct stat st;
fstat(fd, &st);

// Dosya artık bellekteymiş gibi: veri[0] okumak page fault üretir
char *veri = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);

mmap kopya sayısını azaltır ve rastgele erişimde çok rahattır. Buna karşılık her page fault bir kernel geçişidir; sıralı ve büyük okumalarda düz read çoğu zaman daha hızlıdır.

Aynı mekanizma 15. bölümde gördüğümüz program yüklemenin de temelidir: kernel bir ELF’i çalıştırırken dosyayı okumaz, mmap’ler.

OverlayFS: Katmanları Üst Üste Bindirmek

Şimdiye kadar tek bir dosya sisteminden söz ettik. Peki birden fazla dizini tek bir dizinmiş gibi göstermek mümkün mü?

Mümkün ve bunun adı OverlayFS. VFS’in en zarif kullanımlarından biridir: gerçek bir depolama sürücüsü değildir, kendi diski yoktur; yaptığı tek şey başka dosya sistemlerinin üstüne oturup onları birleşik bir görünüm hâlinde sunmaktır.

Mount ederken üç dizin verirsin, dördüncüsü sonucun göründüğü yerdir:

DizinRolü
lowerdirSalt okunur alt katman ya da katmanlar. Birden fazlaysa üst üste bindirilir.
upperdirYazılabilir katman. Bütün değişiklikler buraya gider.
workdirOverlayFS’in kendi iç işlemleri için kullandığı geçici alan.
mergedKullanıcının gördüğü birleşik görünüm.

Okuma isteği geldiğinde OverlayFS dosyayı önce upperdir’de arar, bulamazsa alt katmanlara iner. İlk bulduğu kazanır; bu yüzden üst katman, alttakini “gölgeleyebilir”.

Yazma tarafı ise doğrudan 19. bölümde gördüğümüz fikre bağlanıyor. Salt okunur bir katmandaki dosyayı değiştirmek istediğinde OverlayFS onu önce upperdir’e kopyalar, sonra değişikliği orada yapar. Bu bir copy-on-write işlemidir ve fork’un bellek sayfaları için yaptığının dosya düzeyindeki karşılığıdır: kopyalamayı gerçekten gerekene kadar ertele.

Sonuç, aynı salt okunur tabanın onlarca farklı yazılabilir katmanla paylaşılabilmesidir. Bir sonraki bölümde göreceğimiz container imajlarının hem az yer kaplamasının hem de saniyeler içinde başlayabilmesinin sebebi tam olarak budur.

Bloklanan I/O ve Ölçek Sorunu

Sıradan bir read çağrısı veri hazır değilse bloklar: process uyku durumuna geçer, scheduler CPU’yu başkasına verir. Tek bir bağlantı için bu gayet iyidir.

Peki on bin eşzamanlı bağlantıyı dinleyen bir sunucu? Her bağlantıya bir thread ayırmak, on bin stack ve sürekli context switch demektir — 9. bölümde gördüğümüz gibi her geçişin bir bedeli vardır.

Çözüm, “hangisi hazır olursa onu işle” diyebilmektir. Bu fikrin Linux’taki evrimi:

epoll Nasıl Çalışıyor?

Bugün internetin büyük kısmı bu üç çağrının üzerinde dönüyor, o yüzden açalım.

Bir olay döngüsünün iskeleti bundan ibarettir:

int ep = epoll_create1(0);
struct epoll_event ev = { .events = EPOLLIN, .data.fd = sock };
epoll_ctl(ep, EPOLL_CTL_ADD, sock, &ev);

struct epoll_event olaylar[64];
for (;;) {
  int n = epoll_wait(ep, olaylar, 64, -1);   // sadece hazır olanlar
  for (int i = 0; i < n; i++)
    isle(olaylar[i].data.fd);
}

On bin bağlantın olsa ve bunlardan yalnızca üçünde veri gelse, epoll_wait sana üç eleman döner. select aynı durumda on bin fd’yi baştan sona tarardı. Aradaki fark, C10K probleminin çözümünün adıdır.

İki çalışma kipi var ve aralarındaki fark başa çok iş açar. Level-triggered (varsayılan) kipte, fd’de okunmamış veri kaldığı sürece epoll_wait onu tekrar tekrar bildirir. Edge-triggered kipte ise yalnızca durum değiştiğinde bir kez bildirir; veriyi EAGAIN alana kadar okumazsan kalan kısım için ikinci bir uyarı gelmez ve bağlantı sessizce asılı kalır. Edge-triggered daha az sistem çağrısı demektir ama hata affetmez.

Son bir uyarı: epoll düzenli dosyalarda işe yaramaz. Bir disk dosyası kernel’ın gözünde her zaman “hazır”dır; okuma gerekiyorsa zaten bloklanır. epoll’ün anlamlı olduğu yer soketler, pipe’lar ve terminaller gibi gerçekten beklenebilen nesnelerdir. Dosya I/O’sunu asenkron yapmak isteyenlerin io_uring’e yönelmesinin sebeplerinden biri de bu.

io_uring Syscall’ı Nasıl Ortadan Kaldırıyor?

epoll çağrı sayısını azaltır ama sıfırlayamaz: her epoll_wait ve her read yine bir syscall’dır. io_uring bu son maliyeti de hedefler.

Fikir, halka tampon üzerine kurulu: başı ve sonu birbirine değen sabit boyutlu bir dizi; bir taraf yazar, diğer taraf okur. io_uring böyle iki halka kurar — biri istekler (submission), biri sonuçlar (completion) için.

Kritik ayrıntı şu: bu iki halka mmap ile hem process’in hem kernel’ın adres alanında görünür. Yani aynı fiziksel bellek. İstek eklemek bir syscall değil, sıradan bir bellek yazımıdır. Syscall yalnızca kernel’ı “bak, yeni iş var” diye uyandırmak gerektiğinde atılır. SQPOLL kipinde kernel tarafında bir thread halkayı sürekli yokladığı için o son syscall bile ortadan kalkar.

Sonuç, doğru yapılandırılmış bir kurulumda saniyede yüz binlerce işlemin tek bir syscall bile atılmadan tamamlanabilmesi. Varsayılan kipte syscall büsbütün kaybolmaz — kaybolan şey, istek başına syscall ödemek. Tek bir io_uring_enter çağrısıyla halkaya koyduğun onlarca isteği birden teslim edersin; maliyet ortadan kalkmaz, bölünür.

epoll’ün “hazır olanı bildir” mantığı, çoğu modern async runtime’ının (libuv, tokio) temelidir. Yani JavaScript’te yazdığın await fetch(...) ifadesinin altında, döngünün bir yerinde bu mekanizma çalışıyordur.

Özet

Peki, ne öğrendik?

  • Linux’ta dosya, aygıt, soket ve process bilgisi aynı open/read/write arayüzünün arkasındadır.
  • File descriptor, process’e özel bir indekstir; açık dosya tanımı offset’i tutar ve fork sonrasında paylaşılır, inode ise dosyanın kendisidir.
  • Dosyanın adı inode’da değil dizin girdisindedir; hard link, silinen ama açık kalan dosya ve ucuz taşıma hep bu ayrımdan çıkar.
  • VFS, farklı dosya sistemlerini tek bir fonksiyon tablosu arkasında birleştirir.
  • Page cache disk erişimini RAM hızına yaklaştırır; write varsayılan olarak diske değil cache’e yazar, garanti isteyen fsync çağırır.
  • DMA, veriyi aygıt ile bellek arasında CPU’ya uğratmadan taşır; işlemci yalnızca kurulumu yapar ve bitişte interrupt alır.
  • mmap dosyayı adres alanına eşler ve program yüklemenin de temelidir.
  • OverlayFS, birden fazla dizini tek bir görünümde birleştirir ve yazmayı copy-on-write ile üst katmana alır.
  • Bloklanan I/O bağlantı başına thread gerektirir; epoll yalnızca hazır olanları bildirir, io_uring istek başına syscall ödemeyi de ortadan kaldırır.

Artık tek bir makinede olan biten her şeyi gördük: donanımdan process’e, bellekten dosyaya. Sıradaki bölümde bu resmi bir kez daha katlıyor ve modern altyapının üstünde durduğu iki fikre bakıyoruz: sanal makineler ve container’lar.

21. bölüme devam et: Sanal Makineler ve Container'lar