İşletim Sistemleri · Ders Notu

Bölüm 11: Giriş/Çıkış

Bir bilgisayar aynı anda çok farklı aygıtları yönetir. Klavye saniyede birkaç bayt gönderir; NVMe disk ise saniyede milyonlarca isteği karşılar. Bu aygıtların hızları dokuz basamak, arayüzleri ise birbirinden tamamen farklıdır. İşletim sisteminin G/Ç (giriş/çıkış) kısmı bu çeşitliliği iki yoldan denetler. Donanım tarafında aygıtlara komut verir, durumlarını okur, kesmeleri yakalar ve veriyi CPU'yu yormadan taşır. Yazılım tarafında ise bütün aygıtları uygulamaya tek tip bir arayüzle sunar. Not; denetleyiciler, aygıt yazmaçları ve iki adresleme yöntemiyle başlar. Ardından yoklama, kesme güdümlü G/Ç ve DMA aynı yazıcı örneği üzerinde karşılaştırılır. G/Ç yazılımının katmanları, tamponlama, eş zamansız arayüzler ve saatler bunu izler. Son kesimler sabit diskin erişim süresini, disk kolu çizelgeleme algoritmalarını, SSD'leri, RAID dizilerini ve kararlı depolamayı ele alır. Kafa hareketleri, erişim süreleri, tampon izlemeleri ve XOR eşliği gibi bütün sayısal örnekler algoritmalar gerçekten çalıştırılarak denetlenmiştir.

Öğrenme çıktıları

Donanım
Aygıt denetleyicisini, aygıt yazmaçlarını ve PCIe, USB, NVMe gibi veri yollarını tanımak; kapı eşlemeli ve bellek eşlemeli G/Ç'yi açıklamak.
Aktarım yöntemleri
Yoklama, kesme güdümlü G/Ç ve DMA'yı CPU maliyetiyle karşılaştırmak; MSI-X ve IOMMU'nun görevini söylemek.
Yazılım katmanları
Kesme işleyicisi, aygıt sürücüsü, aygıttan bağımsız katman ve kullanıcı kütüphanesini bir read() çağrısının yolunda izlemek; dairesel tamponu ve zamanlayıcı listesini kodla izlemek.
Arayüzler
Bloke eden, bloke etmeyen ve eş zamansız G/Ç'yi ayırt etmek; epoll ile io_uring'in modellerini karşılaştırmak.
Diskler
Erişim süresini hesaplamak; FCFS, SSTF, SCAN, C-SCAN, LOOK ve C-LOOK için kafanın yolunu ve toplam hareketini bulmak.
SSD ve RAID
FTL, aşınma dengeleme, TRIM ve blk-mq'yu açıklamak; RAID 0/1/5/6/10 düzenlerini, XOR eşliğini, küçük yazmayı ve kurtarmayı hesapla göstermek.

11.1G/Ç sistemine genel bakış

11.1.1İşletim sistemi aygıtlarla ne yapar?

Bir programın dosya okuması, ekrana yazı basması ya da ağdan paket alması, sonunda bir aygıtla konuşmayı gerektirir. Bu konuşmayı programın kendisi yapmaz. İşletim sistemi aygıtlara komut verir, durumlarını okur, işin bittiğini haber veren kesmeleri yakalar ve hataları ele alır. Uygulamaya ise aygıttan bağımsız, katmanlı ve tek tip bir arayüz sunar. Aynı read() çağrısı bir SSD'den, bir USB bellekten ya da bir seri porttan okuyabilir.

İşin büyüklüğünü bir sayı iyi anlatır. Linux kaynak kodunun yaklaşık %60–70'i drivers/ dizinindedir. Çekirdeğin en büyük parçası süreç yönetimi ya da bellek yönetimi değil, binlerce farklı aygıtın sürücüleridir. Her yeni ağ kartı, ekran kartı ya da sensör, çekirdeğe yeni bir sürücü ekler.

İşlemci (SoC)çekirdekler · bellek denetleyicisiPCIe kök kompleksi · APICDRAM (DDR5)ana bellekbellek yoluGPUPCIe 5.0 x16NVMe SSDPCIe 4.0/5.0 x4USB4 / Thunderbolt40–80 Gb/sYonga seti (PCH)G/Ç denetleyicileriDMI ≈ PCIe x4/x8SATAHDD / optikUSB 3.xklavye, fareEthernetNICWi-Fi / BTM.2 kartSesHD Audiodenetleyici + aygıt
Şekil 11.1: Modern bir bilgisayarın G/Ç düzeni. Bellek denetleyicisi ve PCIe kök kompleksi işlemcinin içindedir. Hızlı aygıtlar (GPU, NVMe SSD) PCIe ile doğrudan işlemciye bağlanır. Yavaş aygıtlar yonga setindeki (PCH) denetleyiciler üzerinden gelir.

Şekil 11.1 bugünkü bir masaüstü bilgisayarın G/Ç düzenini gösterir. Her aygıt iki parçadan oluşur: bir denetleyici ve onun sürdüğü asıl aygıt. İşletim sistemi yalnız denetleyiciyle konuşur. Denetleyiciler de çeşitli veri yollarıyla işlemciye bağlanır.

11.1.2Veri yolları: PCIe, USB4, SATA

Tanım 11.1

Veri yolu (bus), bileşenler arasında veri, adres ve denetim sinyallerini taşıyan bağlantıdır. Eski PC'lerde birçok aygıtın paylaştığı paralel yollar (ISA, PCI) vardı. Bugün noktadan noktaya seri hatlar kullanılır: PCIe, USB, SATA.

Paylaşılan paralel yolda aynı anda yalnız bir aygıt konuşabilir. Yol genişledikçe teller arasındaki zamanlama farkı da sorun olur. Seri hatlar bu iki sorunu birlikte çözer. Her bağlantı yalnız iki uç arasındadır ve veri tek bir hat çifti üzerinde çok yüksek frekansla akar. PCIe'de bir hat (lane) tam çift yönlüdür; bağlantı x1, x4, x8 ya da x16 hattı birleştirir. Bant genişliği her yönde ayrı ayrıdır: x4 bir NVMe disk okurken de yazarken de aynı hızı kullanabilir.

Tablo 11.1: Bugünkü başlıca bağlantılar ve hızları.
BağlantıHam hızEtkin veri hızıNot
PCIe 4.016 GT/s / hat≈ 1,97 GB/s / hat; x4 ≈ 7,9 GB/s128b/130b kodlama; NVMe SSD'ler
PCIe 5.032 GT/s / hat≈ 3,94 GB/s / hat; x16 ≈ 63 GB/sGPU, 14 GB/s sınıfı SSD
PCIe 6.064 GT/s (PAM4)≈ 7,6 GB/s / hat; x16 ≈ 121 GB/sFLIT + FEC; sunucularda yaygınlaşıyor
USB 2.0 / 3.2480 Mb/s / 5–20 Gb/s≤ 60 MB/s / ≤ 2,5 GB/sAna makine yoklamalı protokol
USB4 v1 / v240 / 80 Gb/sPCIe, DisplayPort tünellemeThunderbolt 4/5 ile uyumlu
SATA III6 Gb/s≈ 600 MB/s (8b/10b)AHCI: tek kuyruk, 32 komut
Ethernet1 / 10 / 100 / 400 Gb/s125 MB/s … 50 GB/sVeri merkezi: 400–800 GbE
Örnek 11.1

PCIe 4.0'ın bir hattı 16 GT/s (saniyede 16 milyar aktarım) hızındadır ve 128b/130b kodlama kullanır. Bir hattın ve x4 bağlantının etkin veri hızı nedir?

  1. Kodlama payı. Her 130 bitin 128'i veridir; kalan 2 bit eşzamanlama başlığıdır. Etkin bit hızı 16 × 128 / 130 ≈ 15,75 Gb/s olur.
  2. Bayta çevirme. 15,75 / 8 ≈ 1,97 GB/s. Bu, bir hattın tek yöndeki hızıdır.
  3. x4 bağlantı. 4 × 1,97 ≈ 7,9 GB/s. Piyasadaki PCIe 4.0 NVMe disklerin 7 GB/s civarında tıkanması bu yüzdendir: protokol yükü de eklenince sınır buraya yakındır.

Aynı hesap PCIe 5.0 için 32 × 128 / 130 / 8 ≈ 3,94 GB/s verir; x16 bağlantıda bu 63 GB/s eder. PCIe 6.0 kodlamayı tamamen değiştirir: PAM4 sinyali her darbede iki bit taşır ve veri, hata düzeltme kodu (FEC) içeren 256 baytlık FLIT paketlerinde gider. Bir FLIT'in 242 baytı yararlı yüktür. Böylece 64 × 242 / 256 / 8 ≈ 7,6 GB/s bulunur.

Tablodaki bazı satırlar da tarihseldir. FireWire, 56K modem ve 10/100 Mb/s Ethernet artık ders kitabı örneği olarak kalmıştır. Pentium döneminin “kuzey köprüsü” de ortadan kalktı. Bellek denetleyicisi işlemcinin içine taşındı (AMD 2003, Intel 2008). Bugün hızlı aygıtlar PCIe ile doğrudan işlemciye, yavaş aygıtlar ise yonga setine bağlanır.

11.1.3Aygıt veri hızları

Aygıtları ayıran ilk özellik hızlarıdır. Şekil 11.2 tipik üst sınırları logaritmik bir eksende gösterir. Eksendeki her çizgi bir öncekinin 1000 katıdır.

1 B/s1 KB/s1 MB/s1 GB/s100 GB/sKlavye10 B/sFare (1 kHz oyun faresi)8 KB/sGigabit Ethernet125 MB/sHDD (7200 rpm, ardışık)250 MB/sSATA SSD550 MB/sUSB 3.2 Gen 21,25 GB/sUSB4 (40 Gb/s)5 GB/sNVMe PCIe 4.0 x47 GB/sNVMe PCIe 5.0 x414 GB/s400 GbE50 GB/sDDR5-6400 (bir kanal)51,2 GB/sPCIe 5.0 x1663 GB/slogaritmik eksen: her çizgi 1000 kat
Şekil 11.2: Aygıt veri hızları logaritmik eksende: klavye yaklaşık 10 B/s, Gigabit Ethernet 125 MB/s, PCIe 4.0 x4 NVMe disk 7 GB/s, PCIe 5.0 x16 bağlantı 63 GB/s.

Klavye ile PCIe 5.0 x16 arasında 63 × 109 / 10 ≈ 6 × 109 kat fark vardır. Aynı işletim sistemi hem saniyede birkaç tuşu hem de saniyede milyonlarca NVMe isteğini yönetmek zorundadır. Bu yüzden tek bir aktarım yöntemi her aygıta uymaz. Yavaş aygıtta CPU'nun her baytla tek tek ilgilenmesi sorun yaratmaz. Hızlı aygıtta ise CPU'nun veri yoluna hiç girmemesi gerekir. Şekildeki değerler tipik üst sınırlardır. Gerçek hız sinyal kalitesine, protokol yüküne ve aygıtın kendisine bağlıdır.

11.1.4Aygıt sınıfları: blok, karakter, ağ

Hızdan sonra ikinci ayrım, verinin nasıl adreslendiğidir. Unix geleneği aygıtları üç sınıfa ayırır.

Tanım 11.2

Blok aygıtı (block device) veriyi sabit boyutlu, numaralı bloklarda tutar; her blok ötekilerden bağımsız okunup yazılabilir. Karakter aygıtı (character device) bir bayt akışı üretir ya da tüketir; bu akış adreslenemez ve konumlanma (seek) işlemi yoktur. Ağ aygıtı paketlerle çalışır ve soket arayüzüyle kullanılır.

Tablo 11.2: Aygıt sınıfları.
SınıfÖrnekÖzellikTipik arayüz
BlokHDD, SSD, USB bellek, SD kartSabit boyutlu, adreslenebilir bloklar (512 B / 4 KiB); her blok bağımsız okunur ve yazılırread, write, lseek; dosya sistemi, mmap
KarakterKlavye, fare, seri port, yazıcı, sesBayt akışı; adreslenemez, konum yokread/write, ioctl; /dev/ttyS0
AğEthernet, Wi-FiPaket tabanlı; ne blok ne karakterSoket API'si (send, recv); /dev'de dosyası yok
DiğerSaatler, zamanlayıcılar, GPUNe akış ne blok: kesme üretir ya da bellek eşlemelidirtimerfd, DRM/KMS, mmap

Linux'ta blok ve karakter aygıtları /dev altında özel dosyalar olarak görünür. ls -l çıktısının ilk harfi sınıfı söyler:

$ ls -l /dev/nvme0n1 /dev/ttyS0
brw-rw---- 1 root disk    259, 0  nvme0n1   b = blok, ana/yan numara 259,0
crw-rw---- 1 root dialout   4, 64 ttyS0     c = karakter

b blok, c karakter aygıtıdır. Dosya boyutunun bulunacağı yerdeki iki sayı ana ve yan numaradır. Ana numara (259) çekirdeğe hangi sürücünün çağrılacağını, yan numara (0) o sürücünün hangi birimi kastedildiğini söyler. Dosya sistemleri yalnız blok aygıtlarının üzerine kurulabilir, çünkü bir dosyanın herhangi bir bloğuna numarasıyla gitmek gerekir.

Sık yapılan hata

Ağ arabirimini karakter aygıtı saymak. Unix ve Linux'ta ağ ayrı bir sınıftır; eth0 ya da wlan0 için /dev'de bir dosya bulunmaz ve bu arabirimlere open() ile değil, soketlerle erişilir. Sınıflandırma her aygıta da tam oturmaz. Teyp sürücüsü blok yazar ama konumlanması çok yavaştır. Saat ise ne bayt akışı ne de blok üretir; yalnız kesme üretir.

11.2G/Ç donanımı

11.2.1Aygıt denetleyicileri

Tanım 11.3

Bir G/Ç birimi iki parçadan oluşur: mekanik ya da asıl aygıt kısmı ve onu süren elektronik aygıt denetleyicisi (device controller). Denetleyici bir yonga ya da bir karttır ve aygıta bir bağlayıcıyla bağlanır. İşletim sistemi aygıtla değil, denetleyiciyle konuşur.

Mekanik kısımplaka, kafa, motorDenetleyici (elektronik kısım)Yazmaçlardurum, komutTamponsektör / izECC + DMAhata düzeltmeSistem yoluPCIe / SATA / USBbağlayıcıseri bit akışı:öncülveri: 512 B = 4096 bit (ya da 4 KiB)ECC
Şekil 11.3: Disk denetleyicisi mekanik kısım ile sistem yolu arasında durur. İçinde yazmaçlar, bir sektör tamponu ve hata düzeltme birimi vardır. Diskten gelen seri bit akışı öncül, veri ve ECC alanlarından oluşur.

Denetleyicinin işi, aygıtın ham sinyallerini işletim sisteminin anlayacağı birimlere çevirmektir. Şekil 11.3'teki disk denetleyicisini düşünelim. Okuma kafasının altından bir bit akışı geçer. Akış bir öncülle (preamble) başlar. Öncül, denetleyicinin saatini akışa eşleyen bir desen ile silindir ve sektör numarasını taşır. Ardından 512 bayt (4096 bit) ya da 4 KiB veri, en sonda da hata düzeltme kodu (ECC) gelir. Denetleyici bit akışını bloğa çevirir, ECC ile hataları düzeltir ve bloğu kendi tamponuna koyar. Blok ancak bundan sonra belleğe aktarılır.

Denetleyicinin standart bir arayüzü olması işletim sistemi için büyük kolaylıktır. SATA ya da NVMe arayüzü sayesinde tek bir sürücü, birçok üreticinin diskini sürebilir. İşletim sistemi açılışta ve bir aygıt sıcak takıldığında yolları tarar. PCIe yapılandırma alanını ve USB tanımlayıcılarını okur, aygıtın kimliğini öğrenir ve uygun sürücüyü yükler. Bu düzene tak-çalıştır denir.

Disklerin sektör boyutu da değişti. Modern diskler 4 KiB fiziksel sektör kullanır (Advanced Format). Eski yazılımla uyum için dışarıya 512 B sektör gösterenlere 512e denir. Böyle bir diske 512 baytlık hizasız bir yazma geldiğinde denetleyici önce 4 KiB'lık sektörün tamamını okur, değişen kısmı yerleştirir ve sektörü geri yazar.

11.2.2Aygıt yazmaçları: durum, komut, veri

Denetleyici, işletim sistemiyle yazmaçları üzerinden konuşur. Bir denetleyicinin genellikle dört yazmacı vardır.

Durum (status)BUSYRDYERRDRQ––––CPU okurKomut (command)WRITECMDRDYREADIRQEN––––CPU yazarVeri çıkışı (data-out)01000001CPU → aygıtVeri girişi (data-in)????????aygıt → CPUbit 7bit 0
Şekil 11.4: Örnek bir denetleyicinin dört yazmacı. Durum yazmacında meşgul (BUSY), hazır (RDY) ve hata (ERR) bitleri, komut yazmacında yaz (WRITE) ve komut hazır (CMDRDY) bitleri bulunur. Veri çıkış yazmacındaki 0100 0001, 'A' karakterinin kodudur (0x41).
  • Durum yazmacı (status): CPU okur. Aygıtın meşgul olup olmadığını, sonucun hazır olup olmadığını, hata oluşup oluşmadığını bildirir.
  • Komut yazmacı (command): CPU yazar. Denetleyiciye ne yapacağını söyler: okuma mı, yazma mı, hangi kipte.
  • Veri çıkış yazmacı (data-out): CPU'dan aygıta giden veri buraya konur.
  • Veri giriş yazmacı (data-in): aygıttan gelen veri CPU'nun okuması için burada bekler.

Kural basittir: yazmaca yazmak komut vermek, yazmacı okumak durum öğrenmektir. Bazı denetleyicilerde yazmaçlara ek olarak büyük veri tamponları da bulunur. Ekran kartının çerçeve tamponu ya da ağ kartının (NIC) gönderme ve alma halkaları buna örnektir. Yazmaçların düzeni aygıta özgüdür ve bu ayrıntıyı yalnız sürücü bilir. Şekil 11.4'teki bit adları da örnek amaçlıdır; gerçek bir SATA ya da NVMe denetleyicisinin yazmaç düzeni farklıdır.

11.2.3Kapı eşlemeli ve bellek eşlemeli G/Ç

CPU'nun bir aygıt yazmacına nasıl eriştiği sorusunun iki klasik cevabı vardır.

Tanım 11.4

Kapı eşlemeli G/Ç (port-mapped I/O) aygıt yazmaçlarını bellekten ayrı bir adres uzayına, G/Ç kapılarına yerleştirir. CPU bu kapılara özel IN/OUT komutlarıyla erişir. Bellek eşlemeli G/Ç (memory-mapped I/O, MMIO) ise yazmaçları sıradan bellek adreslerine koyar. Erişim normal yükleme ve saklama komutlarıyla yapılır; adres çözücü erişimi ilgili aygıta yönlendirir.

(a) Ayrı uzaylar(b) Bellek eşlemeli(c) HibritBellekG/Çkapıları0 … 2ⁿ−10 … 0xFFFFaygıt yazmaçlarıBellek← adres aralığıtek adres alanıtamponlarBellekkontrolkapılarıör. eski PC’de VGA
Şekil 11.5: Üç yaklaşım: (a) bellek ve G/Ç kapıları için ayrı adres uzayları, (b) tek adres alanının bir aralığı aygıt yazmaçlarına ayrılmış bellek eşlemeli düzen, (c) denetim yazmaçları kapılarda, veri tamponları bellekte duran hibrit düzen.

x86 ailesi iki yöntemi birlikte taşır. IBM PC'den kalma birçok aygıt hâlâ sabit kapı numaralarındadır (Tablo 11.3). Şekil 11.5'teki hibrit düzenin klasik örneği eski PC'nin VGA kartıdır: denetim yazmaçları kapılarda, ekran belleği ise bellek adreslerindeydi.

Tablo 11.3: x86'da bazı sabit G/Ç kapıları.
Kapı (x86)Aygıt
0x20–0x218259 kesme denetleyicisi
0x40–0x438254 zamanlayıcı (PIT)
0x60, 0x648042 klavye denetleyicisi
0x3F8–0x3FFCOM1 seri port
0xCF8–0xCFFPCI yapılandırma

İki erişim biçimi makine kodunda şöyle görünür:

in  al, 0x60          ; kapı: özel komut
mov eax, [rbx+0x14]   ; MMIO: sıradan yükleme

Linux: inb()/outb()  ·  ioremap() + readl()/writel()
/proc/ioports  ·  /proc/iomem

İlk satır 0x60 kapısından, yani klavye denetleyicisinin veri yazmacından bir bayt okur. Bunun için ayrı bir komut (in) gerekir. İkinci satır sıradan bir bellek yüklemesidir. rbx bir aygıtın eşlendiği adresi gösteriyorsa bu komut o aygıtın 0x14 konumundaki yazmacını okur; CPU açısından bellekten okumaktan farkı yoktur. Linux çekirdeğinde kapılara inb()/outb() ile, bellek eşlemeli yazmaçlara ise önce ioremap() ile eşleyip sonra readl()/writel() ile erişilir. Hangi kapıların ve hangi fiziksel adres aralıklarının hangi aygıta ait olduğu /proc/ioports ve /proc/iomem dosyalarında görülür.

Bugün ağırlık açıkça bellek eşlemeli G/Ç'dedir. ARM ve RISC-V işlemcilerinde kapı komutları hiç yoktur; yalnız MMIO kullanılır. PCIe aygıtları da x86'da bile yazmaçlarını BAR (base address register) denen adres pencereleriyle bellek eşlemeli olarak sunar.

11.2.4Bellek eşlemeli G/Ç: artılar ve eksiler

Bellek eşlemeli G/Ç'nin yaygınlaşmasının üç nedeni vardır. Birincisi, özel komut gerekmez. Sürücü tamamen C ile yazılabilir; yazmaç bir volatile işaretçi üzerinden okunup yazılır. İkincisi, koruma sayfa tablosuyla gelir. Yazmaçların bulunduğu sayfa kullanıcı uzayına eşlenmezse hiçbir kullanıcı programı aygıta dokunamaz. İstenirse bu sayfa tek bir sürece verilebilir; VFIO ve DPDK gibi kullanıcı alanı sürücüleri bu yolla çalışır. Üçüncüsü, belleğe erişen her komut yazmaçla da çalışır: TEST ya da AND gibi komutlar doğrudan yazmacın üzerinde işlem yapabilir.

Yöntemin bedelleri de vardır.

  • Önbellek. Durum yazmacı önbelleğe alınırsa CPU sonraki okumalarda yazmacın kendisine değil, önbellekteki eski kopyaya bakar. Aygıt hazır olsa bile program bunu göremez. Bu yüzden yazmaç sayfaları önbelleklenemez olarak işaretlenir (x86'da PAT/MTRR; Linux'ta ioremap bunu yapar).
  • Birden çok yol. Bellek ayrı ve hızlı bir yoldaysa G/Ç aygıtları o yoldaki adresleri göremez (Şekil 11.6). Hangi adres aralığının belleğe, hangisinin aygıtlara gideceğine köprü ya da kök kompleks karar verir.
  • Derleyici ve işlemci sıralaması. Derleyici, değeri değişmiyor gibi görünen bir okumayı döngü dışına alabilir ya da iki yazmayı yer değiştirebilir. Yazmaç işaretçisi volatile olmalı, sıranın önemli olduğu yerlere bellek bariyerleri konmalıdır.
(a) tek yolCPUBellekG/Çtüm adresler tek yolda(b) çift yolCPUBellekG/Çhızlı bellek yoluG/Ç yolu
Şekil 11.6: (a) Tek veri yolunda CPU, bellek ve G/Ç aygıtları bütün adresleri görür. (b) Çift yolda bellek ayrı ve hızlı bir yoldadır; G/Ç aygıtları bellek adreslerini göremez, yönlendirme adres aralıklarıyla yapılır.
Sık yapılan hata

Durum yazmacını while ((*durum & HAZIR) == 0) ; döngüsüyle bekleyen sürücüde işaretçiyi volatile yapmamak. Derleyici *durum değerinin döngü içinde değişmediğini varsayar, okumayı bir kez yapar ve döngü hiç bitmez. Aynı belirti, yazmaç sayfası önbelleklenebilir eşlendiğinde de görülür. İki önlem birlikte gerekir: volatile işaretçi ve önbelleklenemez eşleme.

11.3Programlanmış G/Ç

11.3.1Yoklama ve el sıkışma

CPU ile denetleyicinin bir baytı nasıl devrettiğini en yalın biçimiyle inceleyelim. İki taraf, durum ve komut yazmaçlarındaki birkaç bit üzerinden anlaşır. Bu anlaşmaya el sıkışma (handshaking) denir.

Tanım 11.5

Yoklama (polling), CPU'nun aygıtın durum yazmacını aygıt hazır olana kadar tekrar tekrar okumasıdır. Bekleme süresince CPU başka iş yapmadığı için buna meşgul bekleme (busy waiting) de denir. Verinin her baytını CPU'nun kendisinin taşıdığı bu yönteme programlanmış G/Ç (programmed I/O, PIO) adı verilir.

while (status & BUSY)
    ;          /* meşgul bekleme */
command = WRITE;
data_out = 'A';
command |= CMDRDY;
/* --- denetleyici tarafı --- */
/* CMDRDY görüldü → BUSY = 1 */
/* komutu oku, baytı aygıta yaz */
/* CMDRDY = ERR = BUSY = 0 */

Kodun üst yarısını sürücü, alt yarısındaki yorumlarla anlatılan adımları denetleyici yürütür. Bir baytın, 'A' karakterinin (0x41) yazılması sekiz adımda biter:

  1. Bekleme. Sürücü durum yazmacını okur. Denetleyici önceki işi bitirmediği için BUSY = 1'dir ve while döngüsü döner. CPU bu sırada başka iş yapamaz.
  2. Çıkış. BUSY 0 olunca döngüden çıkılır. Bu bekleme birkaç döngüden milisaniyelere kadar sürebilir; süre aygıta bağlıdır.
  3. Komut. Komut yazmacına WRITE biti yazılır: yapılacak iş yazmadır.
  4. Veri. Gönderilecek bayt (0x41) veri çıkış yazmacına konur.
  5. Komut hazır. CMDRDY = 1 yapılır. Bu bit denetleyiciye işin başlayabileceğini söyler. Sıra önemlidir: CMDRDY, komut ve veri yerine konduktan sonra kurulmalıdır.
  6. Denetleyici meşgul. Denetleyici CMDRDY'yi görür ve BUSY = 1 yapar. Böylece ana makineye meşgul olduğunu bildirir.
  7. İş. Denetleyici komut yazmacını okur (WRITE), veri çıkış yazmacındaki baytı alır ve aygıta gönderir.
  8. Bitiş. CMDRDY, ERR ve BUSY temizlenir. ERR = 0, işin başarıyla bittiğini gösterir. Sonraki bayt için döngü baştan başlar.
CPUsürücüDenetleyici0BUSY0CMDRDY1WRITE0ERRdata-out = 0x41denetleyici yazmaçları
Şekil 11.7: El sıkışmanın sonu: BUSY = 0, CMDRDY = 0, ERR = 0. Veri çıkış yazmacında son gönderilen bayt (0x41) durur.

Programlanmış G/Ç basittir ve ek donanım istemez. Ama CPU bekleme boyunca boşa döner. Daha kötüsü, CPU başka bir sürece geçer ve bir baytı zamanında almazsa veri kaybolabilir. Denetleyicinin tamponu taşarsa yeni gelen baytın konacak yeri kalmaz.

11.3.2Yazıcı örneği

Tanenbaum'un klasik örneği, bir kullanıcı sürecinin “ABCD” dizisini yazıcıya göndermesidir. Sistem çağrısının çekirdekteki kısmı şöyledir:

copy_from_user(buffer, p, count);
for (i = 0; i < count; i++) {
    while (*printer_status_reg != READY)
        ;   /* meşgul bekleme */
    *printer_data_register = p[i];
}
return_to_user();

İlk satır kullanıcının dizisini çekirdek tamponuna kopyalar (copy_from_user). Çekirdek kullanıcı belleğini doğrudan kullanmaz, çünkü süreç o belleği başka bir iş parçacığıyla değiştirebilir ya da adres geçersiz olabilir. Ardından her karakter için iki iş yapılır. İç while döngüsü durum yazmacı READY olana kadar döner. Yazıcı hazır olunca karakter veri yazmacına yazılır; yazıcı karakteri basar ve yeniden meşgul olur. Dört karakterin dördü de basıldıktan sonra sürece dönülür.

CPUPIO döngüsüyazıcıABCD
Şekil 11.8: PIO döngüsünün sonu: yazıcı A, B, C, D karakterlerini bastı. CPU bütün süre boyunca bu sürece bağlı kaldı.
Örnek 11.2

Yazıcı saniyede 100 karakter basıyor. PIO ile her karakter için CPU ne kadar bekler? 2 GHz'lik bir CPU bu sürede kaç tur harcar?

  1. Karakter süresi. 1 s / 100 = 10 ms. CPU her karakterden sonra yaklaşık 10 ms durum yazmacını yoklar.
  2. Boşa giden tur. 10 ms × 2 × 109 tur/s = 2 × 107 tur. CPU bu sürede on milyonlarca komut çalıştırabilirdi.
  3. Sonuç. Dört karakterlik iş yaklaşık 40 ms sürer ve tamamen yazıcının hızıyla sınırlıdır. CPU'nun hızlı olması hiçbir şeyi değiştirmez.

Yine de PIO her zaman kötü değildir. Gömülü bir sistemde CPU'nun başka işi yoksa ya da aygıt çok hızlı yanıt veriyorsa yoklama en basit ve en hızlı çözümdür. Aşağıdaki örnek, yoklamanın ne zaman zararsız, ne zaman imkânsız olduğunu sayılarla gösterir.

Örnek 11.3

2 GHz'lik bir CPU'da bir yoklama (durumu oku, karşılaştır, veriyi al) 600 tur sürüyor. Saniyede 100 kez yoklanan bir klavye ile 50 MB/s veri üreten ve her yoklamada 32 bayt veren bir aygıt için CPU zamanının yüzde kaçı gider?

  1. Oran. CPU payı = (yoklama/s × tur/yoklama) / (tur/s).
  2. Klavye. 100 × 600 = 60 000 tur/s; 60 000 / (2 × 109) = %0,003. Önemsizdir.
  3. Hızlı aygıt. 50 × 106 / 32 ≈ 1,56 × 106 yoklama/s. Bu, 1,56 × 106 × 600 ≈ 9,4 × 108 tur/s eder; CPU zamanının yaklaşık %47'si yalnız yoklamaya gider.
  4. Yorum. Seyrek olaylı yavaş aygıtta yoklama zararsızdır. Yüksek hızlı blok aktarımında ise CPU'nun her parçayla tek tek ilgilenmesi kabul edilemez; çözüm DMA'dır.

11.4Kesmeler

11.4.1Kesme donanımı: hattan MSI'ya

Yoklamanın kusuru, CPU'nun aygıtı sürekli sorgulamasıdır. Daha iyi bir düzen, işi bitince aygıtın CPU'ya haber vermesidir.

Tanım 11.6

Kesme (interrupt), bir aygıtın CPU'ya “dikkatini iste” sinyali göndermesidir. CPU o anki komutu bitirir, çalışan programı askıya alır ve aygıta ait kesme işleyicisini (interrupt handler) çalıştırır. İşleyici bitince askıya alınan programa dönülür.

DiskdenetleyiciKlavyedenetleyiciYazıcıdenetleyiciKesme denetleyicisi(I/O APIC)CPUyerel APICBellek / PCIe kök kompleksiMSI: 0xFEE0_0000 bölgesine vektör 0x41 yazılır
Şekil 11.9: Kesme donanımı. Aygıt denetleyicileri kesme denetleyicisine (I/O APIC), o da çekirdeğin yerel APIC'ine bağlıdır. Modern PCIe aygıtı ise kablo kullanmaz: 0xFEE0_0000 bölgesine vektör numarasını (burada 0x41) içeren bir bellek yazması gönderir.

Klasik düzende her aygıtın kesme denetleyicisine giden bir hattı (IRQ) vardır. Denetleyici CPU'nun tek kesme girişini aygıtlar arasında paylaştırır ve öncelik belirler. Bir disk okuması şu sırayla haber verilir:

  1. Sinyal. Disk denetleyicisi işini bitirince kendi kesme hattına sinyal verir.
  2. İletme. Kesme denetleyicisi o anda başka kesme işlenmiyorsa ya da yeni kesme daha öncelikliyse CPU'ya kesme iletir. Değilse istek bekletilir; CPU onaylayana kadar hat etkin kalır.
  3. Vektör. CPU kesmeyi kabul eder ve denetleyici bir vektör numarası verir. CPU bu sayıyı kesme vektör tablosunda indis olarak kullanır ve işleyicinin adresini bulur.

Bu anlatım tarihsel PC düzenine dayanır: iki 8259 PIC ve 15 IRQ hattı. Denetleyicinin vektörü “adres yoluna koyması” da yalnız o düzen için geçerlidir. Bugün x86'da bir I/O APIC ve her çekirdekte bir yerel APIC (x2APIC) bulunur; ARM'da bu görevi GIC üstlenir.

Tanım 11.7

MSI (message-signaled interrupts) ve onun genişletilmiş biçimi MSI-X, kesmeyi bir kablo yerine bellek yazmasıyla iletir. Aygıt özel bir adres bölgesine (x86'da 0xFEE0_0000) vektör numarasını içeren bir yazma gönderir; hedef çekirdeğin yerel APIC'i bunu kesmeye çevirir.

MSI'nın iki üstünlüğü vardır. Hat paylaşımı ortadan kalkar, çünkü her aygıt istediği kadar ayrı vektör alabilir. Kesme ayrıca istenen çekirdeğe yönlendirilebilir. MSI-X bir PCIe işlevine 2048 vektöre kadar izin verir. NVMe diskler ve ağ kartları her kuyruk için ayrı vektör alır ve o kuyruğun kesmesini kuyruğu kullanan çekirdeğe gönderir.

11.4.2Vektörler, öncelik, NMI ve istisnalar

Tanım 11.8

Kesme vektörü (interrupt vector), kesme vektör tablosundaki bir indistir. Tablonun her girdisi bir işleyicinin adresini tutar. x86'da bu tabloya IDT denir; 256 girdisi vardır ve yerini IDTR yazmacı gösterir.

vektör 14IDT (IDTR yazmacı gösterir)0bölme_hatası…14sayfa_hatası…32aygıt kesmesiişleyici
Şekil 11.10: Vektör 14 (sayfa hatası) IDT'nin 14. girdisini seçer; girdi işleyicinin adresini verir.

Vektörlerin bir kısmı aygıtlara değil, işlemcinin kendisine aittir. x86'da 0–31 arası işlemci istisnalarına ayrılmıştır (Tablo 11.4).

Tablo 11.4: x86'da bazı kesme vektörleri.
VektörOlayTür
0Bölme hatasıhata (fault)
1 / 3Hata ayıklama / kesme noktasıtuzak (trap)
2NMI (maskelenemez kesme)kesme
6Geçersiz işlem koduhata
13Genel koruma hatasıhata
14Sayfa hatasıhata
18Makine denetimiiptal (abort)
32–255Aygıt kesmeleri, IPI, APIC zamanlayıcısımaskelenebilir
Tanım 11.9

İstisna (exception), çalışan komutun kendisinin yol açtığı, eş zamanlı bir olaydır. İki türü öne çıkar. Hata (sayfa hatası gibi) komutun tamamlanmasını engeller; işleyiciden sonra aynı komut yeniden çalıştırılır. Tuzak (trap; sistem çağrısı, int3) bilerek tetiklenir; işleyiciden sonra bir sonraki komuttan devam edilir.

Kesmelerin de kendi aralarında farkları vardır. Maskelenebilir kesmeler kritik bir bölgede ertelenebilir; x86'da cli komutu kesmeleri kapatır, sti yeniden açar. NMI (non-maskable interrupt) ise ertelenemez. Donanım hataları, bellek ECC hataları ve izleme köpeği (watchdog) bu yolu kullanır, çünkü bu olaylar beklemeye tahammül etmez. Öncelik de önemlidir. APIC kesmeleri vektör sınıfına göre sıralar; yüksek öncelikli bir kesme, düşük öncelikli bir işleyiciyi kesebilir.

Aygıt sayısı vektör sayısından fazla olduğunda bir vektör birden çok aygıta düşebilir. Bu durumda tablonun girdisi tek bir işleyiciyi değil, bir işleyici listesini gösterir. Kesme geldiğinde listedeki işleyiciler sırayla “bu kesme benim mi?” diye kendi aygıtına bakar. Bu düzene kesme zinciri ya da paylaşılan IRQ denir. Linux'ta hangi vektörün hangi çekirdekte kaç kez geldiği /proc/interrupts dosyasında görülür; irqbalance hizmeti kesmeleri çekirdeklere dağıtır.

11.4.3Kesme işleme adım adım

Bir kesmenin baştan sona nasıl işlendiğini somut bir durumda izleyelim. Süreç A çalışıyor; süreç B ise bir disk okumasının bitmesini bekliyor. Disk okuması biter ve denetleyici kesme ister.

A çalışırkullanıcıDonanımPC, PSW kaydıIDT → ISRgiriş koduISRaygıt + EOIB uyanırhazır kuyruğuÇizelgeleyiciMMU, yazmaçlariretkullanıcıya dönüşçekirdek yığınıRIP, CSRFLAGS (PSW)RSP, SSmod:çekirdekkesmeler:maskeliçalışan:—bekleyen: B (okuma)
A çalışırkullanıcıDonanımPC, PSW kaydıIDT → ISRgiriş koduISRaygıt + EOIB uyanırhazır kuyruğuÇizelgeleyiciMMU, yazmaçlariretkullanıcıya dönüşçekirdek yığınıB’nin bağlamımod:kullanıcıkesmeler:açıkçalışan:BB hazır
Şekil 11.11: Kesme işleme akışının iki anı. Üstte donanım bağlamı kaydediyor; altta iret ile seçilen sürece dönülüyor.
  1. Kabul. CPU o anki komutu bitirir, sonra kesmeyi kabul eder. Kesme ancak komut sınırında alınır.
  2. Donanımın kaydı. Donanım program sayacını (x86-64'te RIP), durum sözcüğünü (PSW, RFLAGS) ve yığın işaretçisini çekirdek yığınına iter. Çekirdek moduna geçer ve kesmeleri maskeler.
  3. İşleyiciyi bulma. Vektör numarası IDT'de indis olur ve işleyicinin adresi bulunur. Giriş kodu (assembly) donanımın kaydetmediği yazmaçları da kaydeder. Artık C ile yazılmış kesme hizmet yordamı (ISR) için bağlam ve yığın hazırdır.
  4. ISR. İşleyici aygıtın durumunu okur, veriyi alır ve kesme denetleyicisine onay (EOI, end of interrupt) gönderir. Bundan sonra yeni kesmeler gelebilir. Uzun sürecek iş daha sonraya, alt yarıya ertelenir.
  5. Uyandırma. Bekleyen süreç B, bloke durumundan hazır kuyruğuna alınır.
  6. Çizelgeleme. Çizelgeleyici sıradaki süreci seçer; burada B'yi seçtiğini varsayalım. B'nin sayfa tablosu MMU'ya yüklenir (x86'da CR3 yazmacı), yazmaçları süreç tablosundan ya da yığından geri alınır.
  7. Dönüş. iret komutu kaydedilen PC ve PSW'yi geri yükler, kullanıcı moduna döner ve kesmeleri açar. B, kaldığı komuttan devam eder.

Bu sıra Tanenbaum'un kesme işleyicisi adımlarını izler. Çizelgeleyici A'yı da seçebilirdi; bu seçim politikaya bağlıdır. Kesme, bekleyen bir süreci hazır yapmak dışında çizelgeleme kararını kendiliğinden değiştirmez.

Sık yapılan hata

Kesme işleyicisinin, durumu kaydetmeden önce çalıştığını sanmak. İşleyici A'nın yazmaçlarını kullanacağı için önce donanım dönüş adresini ve durum sözcüğünü, sonra giriş kodu öteki yazmaçları kaydetmelidir. Kayıt yapılmasaydı dönüş adresi kaybolur ve A kaldığı yerden devam edemezdi. Doğru sıra: aygıt ister, CPU komut sınırında durur, durum kaydedilir, vektörle işleyici bulunur, işleyici aygıtı onaylar ve bekleyeni uyandırır, çizelgeleyici seçer, iret ile dönülür.

11.4.4Kesin ve kesin olmayan kesmeler

Kesmeden sonra programın kaldığı yerden devam edebilmesi için “kaldığı yer”in iyi tanımlanmış olması gerekir. Boru hattıyla çalışan işlemcide bu kendiliğinden sağlanmaz.

Tanım 11.10

Bir kesme şu dört koşulu sağlıyorsa kesindir (precise interrupt): (1) PC bilinen bir yere kaydedilmiştir; (2) PC'den önceki bütün komutlar tamamen bitmiştir; (3) PC'den sonraki komutların hiçbiri başlamamıştır; (4) PC'deki komutun durumu bilinir. Bu koşulları sağlamayan kesmeye kesin olmayan kesme denir.

(a) Kesin kesme: PC = 316300bitti304bitti308bitti312bitti316başlamadı320başlamadı324başlamadı328başlamadı332başlamadıPC
(b) Kesin olmayan: komutlar farklı aşamalarda300bitti304%60308bitti312%30316%80320başlamadı324%50328başlamadı332%20
Şekil 11.12: (a) Kesin kesme: PC = 316; 300–312 adresindeki komutlar bitmiş, 316 ve sonrası başlamamış. (b) Kesin olmayan kesme: komutlar farklı aşamalarda kalmış (304 %60, 312 %30, 316 %80 …).

Boru hattında aynı anda birçok komut farklı aşamalardadır. Üstün ölçekli bir işlemcide komutlar sıra dışı da tamamlanabilir. Şekil 11.12'nin (b) kısmındaki durumda tek bir PC değeri programın nerede kaldığını anlatmaz. Yeniden başlamak için ya donanım yarım işleri geri almalı ya da işletim sistemi çok miktarda iç durumu kaydedip çözümlemelidir. İkincisi işleyiciyi hem yavaşlatır hem karmaşıklaştırır.

Modern x86, ARM ve RISC-V işlemcileri kesin kesme sağlar. Bunu yeniden sıralama arabelleği (ROB, reorder buffer) ile yapar. Komutlar sıra dışı yürütülse de ROB onları program sırasıyla “emekliye ayırır”, yani sonuçlarını kalıcı hâle getirir. Kesme geldiğinde henüz emekliye ayrılmamış komutların etkileri atılır. Bedeli yonga alanı ve tasarım karmaşıklığıdır, ama işletim sistemi bu sayede sade kalır.

11.4.5Kesme güdümlü G/Ç: yazıcı örneği

Kesmelerle yazıcı örneği baştan yazılabilir. Amaç, CPU'nun yazıcıyı beklerken boşa dönmemesidir.

Tanım 11.11

Kesme güdümlü G/Ç (interrupt-driven I/O) yönteminde CPU aygıta bir iş verir ve başka bir süreci çalıştırmaya gider. Aygıt işi bitirince kesme üretir; kesme işleyicisi sonraki işi verir ya da bekleyen süreci uyandırır.

/* sistem çağrısı */
copy_from_user(buffer, p, count);
enable_interrupts();
while (*printer_status_reg != READY) ;
*printer_data_register = p[0];
i = 1; count = count - 1;
scheduler();      /* süreç bloke */
/* kesme hizmet yordamı */
if (count == 0) {
    unblock_user();
} else {
    *printer_data_register = p[i];
    count = count - 1;
    i = i + 1;
}
acknowledge_interrupt();
return_from_interrupt();

Kod iki parçadan oluşur. Üst kısım sistem çağrısıdır. Diziyi çekirdek tamponuna kopyalar, yazıcının kesmelerini açar, yazıcı hazır olunca yalnız ilk karakteri yazar, sayaçları günceller ve çizelgeleyiciyi çağırır. Süreç bloke olur ve CPU başka bir süreci çalıştırır. Alt kısım kesme hizmet yordamıdır. Yazıcı her karakteri bastığında bir kesme gelir. Yordam count'a bakar: sıfırsa bütün karakterler basılmıştır ve kullanıcı süreci uyandırılır. Değilse sıradaki karakter yazılır, count bir azalır, i bir artar. Her iki durumda kesme onaylanır ve kesilen sürece dönülür.

Tablo 11.5: “ABC” dizisinin kesme güdümlü yazdırılması (başta count = 3).
OlayYazılanicountSüreç
Sistem çağrısıp[0] = 'A'12bloke
1. kesme ('A' basıldı)p[1] = 'B'21bloke
2. kesme ('B' basıldı)p[2] = 'C'30bloke
3. kesme ('C' basıldı)—30hazır (unblock_user)
yazıcıABCsüreç: hazırCPU: ISR
Şekil 11.13: Üçüncü kesmeden sonra: A, B, C basıldı; ISR kullanıcı sürecini hazır kuyruğuna aldı.

Tablo 11.5 bir ayrıntıyı ortaya koyar: üç karakter için üç kesme gelir, ama üçüncü kesme yeni bir karakter yazmaz. Yalnız işin bittiğini bildirir. Sistem çağrısı ilk karakteri yazdığı için kesmelerin sayısı karakter sayısına eşittir. Yöntemin bedeli kesme başına bağlam kaydı ve işleyici süresidir; bu da tipik olarak 1–2 µs'dir. Yazıcı beklerken CPU başka süreçleri çalıştırır; bu kazanç, birkaç mikrosaniyelik kesme maliyetinden çok büyüktür.

11.4.6Üst yarı, alt yarı ve hibrit yoklama

Kesme işleyicisi çalışırken en azından aynı hattaki kesmeler kapalıdır. İşleyici uzun sürerse başka aygıtların kesmeleri gecikir, hatta kaybolur. Bu yüzden işletim sistemleri işleyiciyi ikiye böler.

Tanım 11.12

Üst yarı (top half, Linux'ta hardirq) asıl kesme işleyicisidir: kısa tutulur, aygıtı onaylar, veriyi bir tampona alır ve geri kalan işi planlar. Alt yarı (bottom half) ertelenmiş iştir ve kesmeler açıkken çalışır. Linux'ta softirq, tasklet, workqueue ya da iş parçacıklı IRQ (request_threaded_irq) biçiminde, Windows'ta ISR + DPC (deferred procedure call) biçiminde bulunur.

Kesme sayısını azaltmanın başka yolları da vardır.

  • Kesme birleştirme (interrupt coalescing). Ağ kartları ve NVMe diskler birkaç tamamlanmayı tek bir kesmede bildirir. Kesme sayısı azalır, gecikme biraz artar.
  • NAPI: kesme ile yoklamanın birleşimi. Yoğun ağ trafiğinde Linux sürücüsü kesmeyi kapatır ve kuyruğu yoklar. Trafik azalınca kesmeye döner. Seyrek olayda kesme, sık olayda yoklama verimlidir; hibrit yöntem ikisinin iyi yanını alır.
  • Saf yoklama. Olay hızı çok yüksek ve gecikme kritikse kesme hiç kullanılmaz. DPDK ve SPDK, NVMe yoklamalı kuyrukları ve io_uring'in IOPOLL kipi bu yolu seçer. Bir çekirdek tamamen yoklamaya ayrılır.

Kesmelerin bir de arıza durumu vardır. Hatalı bir aygıt sürekli kesme üretirse işlemci yalnız işleyiciyi çalıştırır ve sistem kilitlenir. Buna kesme fırtınası denir. Linux, hiçbir işleyicinin sahiplenmediği kesmeler çok sayıda gelirse “nobody cared” iletisiyle hattı kapatır; kesme hızını sınırlayan mekanizmalar da vardır.

11.5Doğrudan bellek erişimi (DMA)

11.5.1DMA denetleyicisi

Kesme güdümlü G/Ç beklemeyi ortadan kaldırır, ama her bayt ya da sözcük için hâlâ bir kesme gelir. 4 KiB'lık bir disk bloğunu sözcük sözcük taşımak binlerce kesme demektir. Çözüm, taşıma işini CPU'dan tamamen almaktır.

Tanım 11.13

Doğrudan bellek erişimi (DMA, direct memory access), bir aygıt ile bellek arasında blok aktarımını CPU'nun katılımı olmadan yapan donanım düzenidir. CPU yalnız aktarımı kurar ve aktarım bitince gelen tek bir kesmeyle ilgilenir.

Bir DMA denetleyicisinin yazmaçları aktarımı tarif eder: bellek adresi, bayt sayısı ve denetim yazmacı. Denetim yazmacı G/Ç kapısını, yönü (okuma mı, yazma mı), aktarım birimini (bayt mı, sözcük mü) ve bir seferde kaç birim taşınacağını (patlama boyutu) belirler. Çok kanallı bir denetleyici her kanal için ayrı bir yazmaç takımı tutar ve aynı anda birçok aktarımı yönetir.

(a) üçüncü taraf DMA (8237, anakartta)CPUDMAkanal 0–7AygıtBellek(b) veri yolu yöneticisi (bus master)CPUNVMe / NIC / GPUkendi DMA motoruBellek
Şekil 11.14: (a) Üçüncü taraf DMA: anakartta tek bir DMA denetleyicisi (8237) aygıtlar adına aktarım yapar. (b) Veri yolu yöneticisi (bus master): NVMe disk, ağ kartı ya da GPU kendi DMA motoruyla belleğe doğrudan yazar.

Anakartta tek bir DMA denetleyicisinin bütün aygıtlara hizmet ettiği düzen yalnız tarihsel 8237 yongası için geçerlidir (Şekil 11.14 (a)). Bugün aygıtların çoğunda kendi DMA motoru vardır ve bu aygıtlar belleğe doğrudan PCIe yazması yapar. Kavram aynı kalır: aktarımı kuran CPU'dur, taşıyan aygıttır.

11.5.2DMA aktarımı adım adım

Bir disk sektörünün DMA ile belleğe okunmasını izleyelim. Sektör 4096 bayttır ve bellekte 0x7A000 adresine yazılacaktır.

CPUDMA denetleyicisiadres0x7B000sayaç0denetimoku → bellekDisk denetleyicisitampon (sektör)ECC doğrulandıDiskAna bellektampon @ 0x7A000sistem yolu⑤ sayaç = 0 → kesme
Şekil 11.15: DMA aktarımının son adımı: sayaç 0'a indi, DMA denetleyicisi CPU'ya kesme gönderiyor. Veri zaten bellekte.
  1. Kurulum. CPU, DMA denetleyicisini (adres = 0x7A000, sayaç = 4096, yön = okuma) ve disk denetleyicisini (“sektörü oku”) programlar, sonra başka işe döner.
  2. Tampon. Disk denetleyicisi sektörü kendi tamponuna okur ve ECC ile doğrular. Tampon sayesinde yol o an meşgulse veri kaybolmaz.
  3. İstek. DMA denetleyicisi yolu ister, bellek adresini yola koyar ve disk denetleyicisine okuma isteği gönderir.
  4. Yazma. Disk denetleyicisi sözcüğü doğrudan belleğe yazar. CPU bu aktarımın içinde değildir.
  5. Onay. Disk denetleyicisi onay gönderir; DMA denetleyicisi adresi artırır, sayacı azaltır. Sayaç 0 olana kadar istek, yazma ve onay adımları tekrarlanır.
  6. Kesme. Sayaç 0'a inince DMA denetleyicisi CPU'yu kesmeyle uyarır. İşletim sistemi kesmeyi işlediğinde veri zaten bellektedir.

DMA'nın veri yolunu nasıl kullandığı da önemlidir. Döngü çalma (cycle stealing) kipinde denetleyici her sözcük için yolu birkaç turluğuna CPU'dan alır. CPU tamamen durmaz, yalnız belleğe erişmek istediği anlarda biraz bekler. Patlama (burst) kipinde yol bir blok boyunca tutulur; aktarım hızlanır ama CPU daha uzun bekleyebilir. Bir ayrım daha vardır. Geçişsiz (fly-by) kipte veri aygıttan belleğe doğrudan gider. Öteki kipte DMA denetleyicisi sözcüğü önce kendisi okur, sonra hedefe yazar; bu iki yol döngüsü harcar ama bellekten belleğe kopyalamaya da izin verir.

11.5.3Dağıt-topla ve IOMMU

Sanal bellek kullanan bir sistemde bir sürecin 12 KiB'lık tamponu sanal adreslerde bitişiktir, ama fiziksel bellekte üç ayrı sayfaya dağılmış olabilir. Basit bir DMA denetleyicisi yalnız tek bir bitişik bölgeye yazabilir. Bu durumda çekirdek ya veriyi bitişik bir tampona alıp sonra kopyalamalı ya da üç ayrı aktarım yapmalıdır.

Dağıt-topla (scatter-gather)tanımlayıcı listesi0x7A00040960x3100040960x9C0002048fiziksel bellek (bitişik değil)0x7A0000x310000x9C000tek komut, üç parça: ek kopya yokIOMMU (Intel VT-d, AMD-Vi, Arm SMMU)AygıtIOVA 0x1000Kötü aygıtIOVA 0xDEAD000IOMMUtablosuRAM0x7A000✗ engellendi
Şekil 11.16: Solda dağıt-topla: tanımlayıcı listesi üç parçayı (0x7A000/4096, 0x31000/4096, 0x9C000/2048) tek komutta verir. Sağda IOMMU: aygıtın gördüğü adres (IOVA) tablo üzerinden fiziksel adrese çevrilir; tabloda olmayan adrese yapılan erişim engellenir.
Tanım 11.14

Dağıt-topla (scatter-gather) DMA'da aygıta bir (adres, uzunluk) çiftleri listesi verilir; aygıt bitişik olmayan sayfalara tek işlemde yazar ya da onlardan okur. IOMMU, aygıtın kullandığı adresleri (IOVA) bir sayfa tablosu üzerinden fiziksel adreslere çeviren birimdir.

Dağıt-topla, Linux'ta readv/writev çağrılarının, parçalı ağ paketlerinin ve NVMe'deki PRP ve SGL listelerinin temelidir. Ek kopya gerekmez. IOMMU'nun ise üç görevi vardır. Birincisi korumadır: aygıt yalnız kendisine eşlenmiş sayfalara erişebilir. Thunderbolt gibi dışarıdan takılan bir aygıtın bütün belleği okumaya çalıştığı DMA saldırıları bu sayede engellenir. İkincisi sanallaştırmadır: bir aygıt doğrudan bir sanal makineye atanabilir (VFIO); konuk işletim sistemi kendi “fiziksel” adreslerini verir, IOMMU bunları gerçek adreslere çevirir. Üçüncüsü, yalnız 32 bit adresleyebilen eski aygıtların 4 GiB'nin üstündeki belleği kullanabilmesidir. Intel'de bu birimin adı VT-d, AMD'de AMD-Vi, ARM'da SMMU'dur.

11.5.4DMA ile yazıcı örneği

Yazıcı örneğinin üçüncü sürümü DMA kullanır:

/* sistem çağrısı */
copy_from_user(buffer, p, count);
set_up_DMA_controller();
scheduler();     /* süreç bloke */

/* kesme hizmet yordamı */
acknowledge_interrupt();
unblock_user();
return_from_interrupt();

Sistem çağrısı veriyi çekirdek tamponuna kopyalar, DMA denetleyicisini programlar (tampon adresi, sayaç = 3, hedef olarak yazıcı kapısı) ve süreci bloke eder. DMA denetleyicisi karakterleri CPU'suz olarak yazıcıya iletir. Bu sırada CPU'ya hiç kesme gelmez. Bütün karakterler aktarılınca DMA tek bir kesme üretir. Kesme hizmet yordamı yalnız iki iş yapar: kesmeyi onaylar ve kullanıcı sürecini uyandırır. Karakter başına değil, tampon başına bir kesme gelir.

DMA → yazıcıABCsüreç: hazırCPU: ISR
Şekil 11.17: DMA ile yazdırmanın sonu: üç karakter aktarıldı, tek kesmeyle süreç uyandırıldı.

DMA her zaman kazandırmaz. DMA denetleyicisi genellikle CPU'dan yavaştır. Aygıt çok yavaşsa ya da CPU'nun yapacak başka bir işi yoksa, kesme güdümlü G/Ç ya da PIO daha basit ve yeterli olabilir. Tanenbaum bu noktayı özellikle vurgular: DMA'nın kazancı, CPU'nun boşalan zamanda yararlı iş yapabilmesine bağlıdır.

11.5.5Üç yöntemin maliyeti

Üç yöntemi aynı iş üzerinde sayılarla karşılaştıralım.

Örnek 11.4

Yazıcı saniyede 1000 karakter basıyor; 4096 karakter yazdırılacak. Kesme işleme maliyeti 2 µs, DMA kurulumu 5 µs. Her yöntemde CPU bu iş için ne kadar zaman harcar?

  1. Yoklama. CPU her karakteri bekler. Karakter başına 1 ms olduğundan 4096 × 1 ms = 4,096 s boyunca CPU meşguldür. Programlanmış G/Ç'de CPU'nun harcadığı süre aygıtın hızına bağlıdır.
  2. Kesme güdümlü. Karakter başına bir kesme gelir: 4096 × 2 µs = 8192 µs ≈ 8,19 ms. Bu, işin toplam süresinin (4,096 s) %0,2'sidir. Bekleme ortadan kalkar; bedel kesme sayısıdır.
  3. DMA. 5 µs kurulum ve 1 kesme × 2 µs: toplam ≈ 7 µs. Kesme güdümlü yönteme göre 8192 / 7 ≈ 1170 kat daha az CPU zamanı harcanır.
  4. Yorum. Yoklama ile DMA arasında yaklaşık altı basamak (4,096 s / 7 µs ≈ 585 000 kat), kesme güdümlü ile DMA arasında üç basamaktan fazla fark vardır (Şekil 11.18). DMA yine de veri yolundan döngü çalar ve kurulum maliyeti küçük aktarımlarda kendini geri ödemez.
Yoklama (PIO)4,096 sKesme güdümlü8,19 msDMA≈ 7 µsCPU süresi (log): 1 µs … 10 s
Şekil 11.18: 4096 karakterlik yazdırmada üç yöntemin CPU süresi logaritmik eksende: yoklama 4,096 s, kesme güdümlü 8,19 ms, DMA ≈ 7 µs.
Örnek 11.5

Bir disk denetleyicisinden 512 KiB veri okunuyor. Kesme güdümlü yöntemde her 512 baytlık sektör için bir kesme geliyor ve işleyici sektörü belleğe kopyalayarak 5 µs harcıyor. DMA'da kurulum 8 µs, aktarım sonundaki tek kesme 5 µs. CPU yükleri nedir?

  1. Sektör sayısı. 512 KiB = 524 288 B; 524 288 / 512 = 1024 sektör.
  2. Kesme güdümlü. 1024 × 5 µs = 5120 µs ≈ 5,1 ms.
  3. DMA. 8 + 5 = 13 µs. CPU yükü 5120 / 13 ≈ 394 kat azalır.
  4. Kenar durum. Aynı sistemde tek bir 512 baytlık sektör okunursa kesme güdümlü yöntem 5 µs, DMA 13 µs harcar. Sabit kurulum maliyeti küçük aktarımda kazancı siler.

Bu iki örnek kuralı açık eder. Aktarım büyüdükçe DMA'nın sabit maliyeti önemsizleşir ve CPU yükü aktarım boyundan bağımsız hâle gelir. Aktarım küçüldükçe, aygıt yavaşladıkça ya da CPU boşta bekledikçe DMA'nın kazancı kaybolur. Kesme, DMA ve G/Ç Akışı Simülatörü üç yöntemi aynı aktarımla çalıştırır ve CPU kullanımlarını yan yana gösterir.

11.6G/Ç yazılımı

11.6.1G/Ç yazılımının hedefleri

Donanımın bu kadar çeşitli olması, yazılımın düzenli olmasını zorunlu kılar. G/Ç yazılımı tasarlanırken altı hedef gözetilir.

  • Aygıt bağımsızlığı. Program hangi aygıtla çalıştığını bilmeden çalışabilmelidir. sort < girdi komutu, girdi dosyası SSD'de de, USB bellekte de dursa aynı biçimde çalışır.
  • Tek tip adlandırma. Bir dosyanın ya da aygıtın adı aygıt türüne bağlı olmamalıdır. /dev/nvme0n1 ve /mnt/usb/rapor.txt aynı ağacın parçalarıdır; farklı aygıtlar bağlama (mount) ile tek bir ağaca eklenir.
  • Hata ele alma. Hata donanıma olabildiğince yakın çözülmelidir. Denetleyici bit hatasını ECC ile düzeltir, sürücü başarısız işlemi yeniden dener. Üst katman ancak gerektiğinde hatadan haberdar olur.
  • Eş zamanlı ve eş zamansız aktarım. Donanım düzeyinde aktarımların çoğu eş zamansızdır: CPU işi başlatır ve kesmeyi bekler. Kullanıcıya ise bloke eden bir arayüz sunmak programlamayı kolaylaştırır. İşletim sistemi bu ikisi arasında köprü kurar.
  • Tamponlama. Ağdan gelen bir paket, nereye gideceği belli olmadan önce bir yere konmalıdır. Ses gibi gerçek zamanlı aygıtlar da tampona dayanır.
  • Paylaşılan ve adanmış aygıtlar. Bir disk aynı anda birçok kullanıcıya hizmet verebilir. Yazıcı ya da tarayıcı ise belli bir anda tek bir kullanıcıya adanır. Adanmış aygıtlar kilitlenme riski doğurur: iki süreç birbirinin tuttuğu aygıtı bekleyebilir.

11.6.2G/Ç yazılım katmanları

Bu hedeflere ulaşmanın yolu katmanlı bir yapıdır. Her katmanın iyi tanımlı bir görevi ve üst katmana sunduğu bir arayüz vardır.

Kullanıcı süreciread(), printf(), biriktirmeSistem çağrısı + VFSfd → dosya, izinler, adlandırmaSayfa önbelleğitamponlama, önbelleklemeDosya sistemi + blok katmanıofset → LBA, blk-mq, çizelgeleyiciAygıt sürücüsükomut kur, yazmaçlara yazKesme işleyicisitamamlanma, süreci uyandırDonanımdenetleyici + aygıt (DMA)kullanıcıçekirdekdonanımistekyanıt
Şekil 11.19: G/Ç yazılım katmanları: kullanıcı süreci, sistem çağrısı ve VFS, sayfa önbelleği, dosya sistemi ve blok katmanı, aygıt sürücüsü, kesme işleyicisi ve donanım. İstek yukarıdan aşağı, yanıt aşağıdan yukarı akar.

Tanenbaum dört katman sayar: kullanıcı düzeyi G/Ç yazılımı, aygıttan bağımsız yazılım, aygıt sürücüleri ve kesme işleyicileri. Linux'ta “aygıttan bağımsız” kısım kendi içinde birkaç parçaya ayrılır: VFS (sanal dosya sistemi), sayfa önbelleği, dosya sistemi ve blok katmanı (Şekil 11.19). Aşağıdaki örnek, bir isteğin bu katmanlardan nasıl geçtiğini gösterir.

11.6.3Bir read() çağrısının yolu

Kullanıcı süreciread(), printf(), biriktirmeSistem çağrısı + VFSfd → dosya, izinler, adlandırmaSayfa önbelleğitamponlama, önbelleklemeDosya sistemi + blok katmanıofset → LBA, blk-mq, çizelgeleyiciAygıt sürücüsükomut kur, yazmaçlara yazKesme işleyicisitamamlanma, süreci uyandırDonanımdenetleyici + aygıt (DMA)kullanıcıçekirdekdonanımDMA → önbellek sayfası, MSI-X
Şekil 11.20: read() isteğinin katmanlardaki yolu. Bu anda SSD veriyi DMA ile önbellek sayfasına yazmış ve MSI-X kesmesi üretmiştir.
Örnek 11.6

Bir süreç NVMe SSD'deki bir dosyadan read(fd, buf, 4096) ile 4 KiB okuyor ve istenen sayfa bellekte yok. İstek hangi adımlardan geçer?

  1. Sistem çağrısı. libc sarmalayıcısı syscall komutuyla çekirdeğe geçer.
  2. VFS. Dosya tanımlayıcısı açık dosya nesnesine, açık dosya da i-düğüme (inode) çevrilir. Erişim izni dosya açılırken zaten denetlenmiştir.
  3. Sayfa önbelleği. İstenen sayfa önbellekte yoktur. Bir önbellek sayfası ayrılır ve okuma başlatılır.
  4. Dosya sistemi ve blok katmanı. Dosya sistemi dosya ofsetini disk bloğuna (LBA, mantıksal blok adresi) çevirir. Blok katmanı bir istek (bio) oluşturur ve donanım kuyruğuna verir.
  5. Sürücü. NVMe sürücüsü okuma komutunu gönderim kuyruğuna yazar ve kapı zili (doorbell) yazmacına yazarak aygıta haber verir. Süreç bloke olur; Linux'ta durumu D (kesintisiz uyku) görünür.
  6. Aygıt. SSD veriyi DMA ile önbellek sayfasına yazar, tamamlanma kuyruğuna bir kayıt ekler ve MSI-X kesmesi üretir.
  7. Kesme işleyicisi. Tamamlanma kaydı işlenir, sayfa “güncel” olarak işaretlenir ve bekleyen süreç hazır olur.
  8. Dönüş. Süreç yeniden çalışınca VFS veriyi önbellekten kullanıcı tamponuna kopyalar ve çağrı 4096 döndürür.

Aynı veri ikinci kez okunursa üçüncü adımda sayfa önbellekte bulunur. Aygıta hiç gidilmez; işlem mikrosaniyeler içinde biter. Bu fark, işletim sisteminin boş belleği neden önbelleğe ayırdığını da açıklar.

11.6.4Aygıt sürücüleri

Tanım 11.15

Aygıt sürücüsü (device driver), belirli bir aygıta ya da aygıt ailesine özgü kodu kapsülleyen çekirdek bileşenidir. Aygıtın yazmaçlarını, komutlarını ve tuhaflıklarını yalnız sürücü bilir.

Kullanıcı programıçekirdekaygıttan bağımsız katmanNVMesürücüsüUSB/HIDsürücüsüAğsürücüsüPCIeNVMe den.SSDxHCI den.klavyeNICkablo
Şekil 11.21: Aygıt sürücüleri çekirdekte çalışır ve veri yolu üzerinden denetleyicilerle konuşur.

Bir sürücü, üst katmandan gelen her istek için benzer bir iş sırası izler. Önce parametreleri doğrular. Sonra isteği aygıtın anlayacağı bir komuta çevirir; bir disk için bu, LBA ve kuyruk girdisi demektir. Gerekirse aygıtı başlatır, yazmaçlara yazar ve kesmeyi bekler, yani bloke olur. İş bitince hatayı denetler ve sonucu üst katmana döndürür.

Sürücü yazmanın iki zorluğu vardır. Birincisi, sürücü yeniden girilebilir (reentrant) olmalıdır: bir isteği işlerken yeni bir istek ya da bir kesme gelebilir. İkincisi, sıcak takılabilen bir aygıt çalışırken ortadan kaybolabilir; sürücü bunu çökmeden karşılamalıdır.

Linux'ta sürücülerin çoğu çekirdek kaynak ağacının içindedir; bazıları üreticiden ayrıca gelir. Sürücüler genellikle yüklenebilir modüllerdir: modprobe modülü yükler, lsmod yüklü modülleri listeler. Ama çekirdekte çalışan hatalı bir sürücü bütün sistemi çökertebilir. Bu riski azaltmak için sürücüleri kullanıcı alanına taşıyan tasarımlar vardır. MINIX 3'te bütün sürücüler kullanıcı alanındadır. Linux'ta FUSE, VFIO/UIO, DPDK ve SPDK, Windows'ta UMDF bu amaçla kullanılır.

11.6.5Aygıttan bağımsız G/Ç yazılımı

Sürücünün üstündeki katman, bütün sürücüler için ortak olan işleri üstlenir. Bunların başında tek tip arayüz gelir. Her aygıt sınıfı aynı işlevleri sunar ve çekirdek sürücüyü bir işlev işaretçisi tablosu üzerinden çağırır:

struct file_operations nvme_fops = {
  .open           = nvme_open,
  .release        = nvme_release,
  .read_iter      = nvme_read,
  .unlocked_ioctl = nvme_ioctl,
  .poll           = nvme_poll,
};

Bu tablo C'de nesne yönelimli programlamanın karşılığıdır. VFS bir dosyada read istendiğinde hangi aygıt olduğunu sormaz; dosyaya bağlı tablodaki read_iter işaretçisini çağırır. NVMe için bu nvme_read'dir, bir seri port için başka bir işlevdir. Yeni bir aygıt eklemek, çekirdeğin geri kalanına dokunmadan yeni bir tablo yazmak demektir.

(a) arayüz yokİşletim sistemiSATAAğUSB(b) tek tip arayüzİşletim sistemiopen/read/write/ioctlSATAAğUSB
Şekil 11.22: (a) Standart arayüz yoksa çekirdek her sürücüyü farklı biçimde çağırır. (b) Standart arayüzle bütün sürücüler aynı işlevleri sunar.

Aynı katman başka işleri de yapar. Adlandırma: /dev/nvme0n1 gibi bir ad, ana numara (sürücü) ve yan numara (birim) çiftine çevrilir. Koruma: aygıt dosyasının izinleri, kimin aygıta erişebileceğini belirler. Ayrıca aygıttan bağımsız bir blok boyutu sunar, tamponlamayı ve hata raporlamayı yönetir, adanmış aygıtların ayrılmasını ve bırakılmasını düzenler.

11.6.6Ara belleğe alma (tamponlama)

Tanım 11.16

Tampon (buffer), iki aygıt ya da bir aygıt ile bir uygulama arasında aktarılan veriyi geçici olarak tutan bellek alanıdır. Bu alanın kullanılmasına tamponlama ya da ara belleğe alma denir.

Silberschatz tamponlamanın üç nedenini sayar. Birincisi, üretici ile tüketici arasındaki hız farkıdır: modemden gelen yavaş akış diske tek tek yazılmaz, bir tamponda biriktirilir. İkincisi, aktarım boyutu farkıdır: ağdaki küçük paketler alıcı tarafta birleştirilir. Üçüncüsü kopya anlamıdır: write döndükten sonra uygulama kendi tamponunu değiştirebilmelidir; diske yazılacak veri o anki hâliyle çekirdekte kopyalanmış olmalıdır.

Bir modemden ya da seri porttan karakter okuyan bir süreci düşünelim. Tamponlamanın beş farklı düzeyi vardır (Şekil 11.23).

(a) tampon yokkullanıcı süreciçekirdekmodem / aygıther karakterdekesme + süreç uyanır
(d) çift tamponkullanıcı süreciçekirdekmodem / aygıtkullanıcıtampon 1tampon 2dolankopyalanan
(e) ağ üzerinden bir paket: beş kopyakullanıcı (gönderen)çekirdek tamponuağ denetleyicisi tamponukullanıcı (alıcı)çekirdek tamponuağ denetleyicisi tamponu123: kablo45sıfır kopya: sendfile, splice, MSG_ZEROCOPY, io_uring
Şekil 11.23: Tamponlama seçenekleri. Üstte tamponsuz düzen, ortada çift tampon, altta ağ üzerinden bir paketin beş kopyası.
  1. Tamponsuz. Her karakter için bir kesme gelir ve süreç uyandırılır. Çok sayıda bağlam değiştirme olur; verimsizdir.
  2. Kullanıcı tamponu. Kesme işleyicisi karakterleri doğrudan süreçteki bir tampona yazar; süreç ancak tampon dolunca uyandırılır. Ama tampon sayfası o sırada diske atılmışsa işleyici nereye yazacak? Sayfa bellekte kilitlenmelidir. Çok sayıda süreç sayfa kilitlerse kullanılabilir bellek azalır.
  3. Çekirdek tamponu. Karakterler önce çekirdekteki bir tamponda birikir; tampon dolunca tek seferde kullanıcıya kopyalanır. Peki kopyalama sürerken gelen karakter nereye konacak?
  4. Çift tampon. Çekirdekte iki tampon vardır: biri dolarken öteki kullanıcıya kopyalanır, sonra roller değişir. Bu düzenin genel biçimi dairesel tampondur.
  5. Ağda kopyalar. Bir paket ağ üzerinden giderken beş kez kopyalanabilir: gönderen kullanıcıdan çekirdeğe, çekirdekten ağ kartına, kablo üzerinden alıcının ağ kartına, oradan çekirdeğe ve en sonda kullanıcıya. Her kopya bellek bant genişliği ve gecikme demektir. Bu yüzden sendfile, splice, MSG_ZEROCOPY ve io_uring gibi sıfır kopya teknikleri geliştirilmiştir.
Örnek 11.7

Bir aygıttan bir bloğun gelmesi T = 10 ms, sürecin bir bloğu işlemesi C = 6 ms, tampondan kullanıcı alanına kopyalama M = 2 ms sürüyor. Kararlı durumda blok başına süre nedir?

  1. Tamponsuz. Okuma ve işleme sırayla yapılır: T + C = 16 ms.
  2. Tek tampon. Sonraki blok çekirdek tamponuna gelirken önceki blok işlenir. Ama kopyalama sırasında tampon meşguldür ve yeni blok başlayamaz: max(T, C) + M = 10 + 2 = 12 ms.
  3. Çift tampon. Biri dolarken öteki kopyalanır; kopyalama da örtüşür. C + M = 8 ≤ T olduğundan darboğaz aygıttır: max(T, C) = 10 ms.
  4. Yorum. Artık sınır aygıtın hızıdır (T > C). Daha fazla tampon eklemek ortalama hızı artırmaz; yalnız ani patlamaları emer.

11.6.7Dairesel tampon

Tanım 11.17

Dairesel tampon (circular buffer, ring buffer) N yuvalı bir dizinin uçları birleşmiş gibi kullanılmasıdır. Üretici kuyruk indisindeki yuvaya yazar, tüketici baş indisindeki yuvadan okur; iki indis de sona gelince 0'a sarar (mod N). Baş = kuyruk ise tampon boştur; (kuyruk + 1) mod N = baş ise doludur.

i01c2d3e4f5g6h7baş = 2 kuyruk = 1dolu yuva: 7 / 8veri (üretici: ISR / DMA)kuyruk: sonraki yazılacak yuvabaş: sonraki okunacak yuvabir bayt daha: tampon doluboş: baş == kuyruk dolu: (kuyruk + 1) % 8 == baş
Şekil 11.24: Sekiz yuvalı dairesel tampon dolu: baş = 2, kuyruk = 1. Yedi yuvada c, d, e, f, g, h, i durur; bir yuva hep boş kalır.
Örnek 11.8

N = 8 yuvalı tampon boştur (baş = kuyruk = 0). Kesme işleyicisi 3 bayt (a, b, c) yazar, süreç 2 bayt okur, ardından üretici 5 bayt (d … h) ve bir bayt daha (i) yazar. İndisler nasıl değişir?

  1. Boş. Baş ve kuyruk aynı yuvayı gösterir.
  2. Üç yazma. a, b, c yuva 0, 1, 2'ye gider; kuyruk = 3. Tüketici henüz okumamıştır.
  3. İki okuma. a ve b okunur; baş = 2. Yuva 0 ve 1 yeniden kullanılabilir.
  4. Beş yazma. d … h yuva 3–7'ye gider. Kuyruk sona gelince başa sarar: 8 mod 8 = 0.
  5. Son yazma. i yuva 0'a yazılır, kuyruk = 1. Şimdi (1 + 1) mod 8 = 2 = baş: tampon doludur. Tamponda 3 + 5 + 1 − 2 = 7 bayt vardır, yani N − 1.

Dolu tamponda üretici iki şey yapabilir: tüketicinin yer açmasını bekler ya da veriyi düşürür. Ağ kartında ikincisi olur ve paket kaybolur. Bir yuvanın hep boş bırakılmasının nedeni basittir. Bütün yuvalar dolabilseydi dolu tamponda da baş = kuyruk olurdu ve boş ile dolu ayırt edilemezdi. Bunun bedeli, N yuvalı tamponun en çok N − 1 bayt tutmasıdır.

Dairesel tamponlar G/Ç'nin her yerindedir: klavye ve seri port sürücüleri, ağ kartlarının alma ve gönderme halkaları, NVMe'nin gönderim ve tamamlanma kuyrukları, io_uring. Tek üretici ve tek tüketici varsa kilit gerekmez. Üretici yalnız kuyruğu, tüketici yalnız başı değiştirir; doğru sırayı korumak için yalnız bellek bariyerleri gerekir.

11.6.8Kod izleme: dairesel tampon

Aşağıdaki iki işlev dairesel tamponu C ile gerçekler. koy kesme işleyicisinde, al süreçte çalışır.

int koy(char c) {          /* üretici */
  int s = (kuy + 1) % N;
  if (s == bas) return -1; /* dolu */
  buf[kuy] = c;
  kuy = s;
  return 0;
}
int al(char *c) {          /* tüketici */
  if (bas == kuy) return -1; /* boş */
  *c = buf[bas];
  bas = (bas + 1) % N;
  return 0;
}

koy önce kuyruğun bir sonraki değerini s'de hesaplar. s başa eşitse yazmak tamponu “boş” görünür hâle getirirdi; bu yüzden tampon dolu sayılır ve -1 döner. Değilse bayt buf[kuy]'a yazılır ve kuyruk ilerler. Burada sıra önemlidir: önce veri yazılır, sonra indis güncellenir. Tersi olsaydı tüketici henüz yazılmamış bir yuvayı okuyabilirdi. al ise başın kuyruğa eşit olup olmadığına bakar; eşitse tampon boştur. Değilse buf[bas] okunur ve baş ilerler. Her iki işlev O(1) zamanda çalışır. Tamponun alanı N yuvadır ve hiç değişmez.

Tablo 11.6: N = 5 için koy ve al çağrıları. Her satır çağrıdan sonraki durumu gösterir.
ÇağrıAyrıntıbaskuyDönüş
—tampon boş00
koy('a')buf[0] = 'a'010
koy('b')buf[1] = 'b'020
koy('c')buf[2] = 'c'030
albas ≠ kuy; 'a' okunur130
koy('d')buf[3] = 'd'140
koy('e')buf[4] = 'e'; kuyruk başa sarar100
koy('f')s = (0 + 1) mod 5 = 1 = bas: dolu10−1
al'b' okunur200
N = 5 yuva; en çok N − 1 = 4 bayt tutulur·0·1'c'2'd'3'e'4baskuy
Şekil 11.25: İzlemenin sonu: yuva 2, 3, 4'te 'c', 'd', 'e' durur; bas = 2, kuy = 0.

'f' reddedildiğinde tamponda dört bayt vardır ('b', 'c', 'd', 'e'): N − 1 = 4. Bir okuma yapılınca yer açılır ve bir sonraki koy başarılı olur.

Sık yapılan hata

Dolu koşulunu kuy == bas olarak yazmak. Bu koşul boş tampon için de doğrudur; tampon dolunca üretici bütün veriyi “boş” bir tamponun üstüne yazmış olur ve tüketici hiçbir şey okuyamaz. Bir yuvayı boş bırakmak istenmiyorsa ayrı bir sayaç tutmak gerekir; ama o zaman iki taraf aynı değişkeni değiştirir ve kilitsiz çalışma bozulur.

11.6.9Önbellek, biriktirme ve hata ele alma

Önbellek ve tampon farklı şeylerdir. Tampon, verinin tek kopyasını geçici olarak tutar. Önbellek (cache) ise başka bir yerde duran verinin hızlı erişilen bir kopyasıdır. Linux'un sayfa önbelleği ikisini birleştirir: diske yazılacak veri orada tamponlanır, diskten okunan veri orada önbelleklenir. Boşta kalan RAM önbelleğe gider; free -h çıktısındaki buff/cache sütunu bunu gösterir.

Tanım 11.18

Biriktirme (spooling), adanmış bir aygıta gidecek işleri diskte bir kuyruğa alıp tek bir arka plan sürecinin bunları sırayla aygıta göndermesidir.

Yazıcı biriktirmenin klasik örneğidir. Kullanıcı yazdırılacak dosyayı biriktirme dizinine koyar; tek bir arka plan süreci (Linux'ta CUPS, dizin /var/spool/cups) dosyaları sırayla yazdırır. Hiçbir kullanıcı süreci yazıcıyı kilitlemez; bu yüzden yazıcı yüzünden kilitlenme de oluşmaz.

Hata ele alma iki tür hatayı ayırır. Programlama hatalarında, örneğin geçersiz bir dosya tanımlayıcısında, çağrı bir hata kodu döndürür (EBADF, EINVAL). Gerçek bir G/Ç hatasında sürücü işlemi yeniden dener; hata kalıcıysa çağrı EIO döndürür ve olay çekirdek günlüğüne yazılır (dmesg).

Ayırma ve koruma. Adanmış bir aygıt open ile ayrılır. Aygıt meşgulse istek kuyruğa alınır ya da çağrı EBUSY döndürür. G/Ç komutlarının hepsi ayrıcalıklıdır; kullanıcı süreci G/Ç'yi yalnız sistem çağrısıyla yapabilir.

En üstte kullanıcı alanındaki G/Ç yazılımı durur. printf ve scanf gibi kütüphane yordamları biçimlendirme yapar ve kendi tamponlarını tutar. stdio terminale satır satır, dosyaya ise blok blok yazar. Tampon dolunca ya da satır bitince write sistem çağrısı yapılır. Böylece her printf bir sistem çağrısına dönüşmez.

11.7G/Ç arayüzleri

11.7.1Bloke eden, bloke etmeyen ve eş zamansız G/Ç

Bir uygulama G/Ç istediğinde çağrının ne zaman döneceği, programın bütün yapısını belirler. Üç model vardır.

Tanım 11.19

Bloke eden G/Ç (blocking I/O) çağrısı, istenen veri gelene kadar dönmez; süreç bu sürede uyur. Bloke etmeyen G/Ç (nonblocking I/O) çağrısı hemen döner: o an ne kadar veri varsa onu verir, hiç yoksa EAGAIN hatası döndürür. Eş zamansız G/Ç (asynchronous I/O) çağrısı işi başlatıp hemen döner; iş arka planda tamamlanır ve süreç sonradan bilgilendirilir.

Bloke edenread() veri gelene kadar dönmezçalışırbloke: G/Ç bekleniyorveriyle devamBloke etmeyenhemen döner; hazır olanı ya da EAGAINçalışırEAGAINbaşka işEAGAINbaşka işveridevamEş zamansızistek gönderilir, sonuç sonra bildirilirçalışıristekçalışmaya devam edertamamlandıdevamaygıt verisi hazır
Şekil 11.26: Üç modelin zaman çizgileri. Bloke eden çağrıda süreç veri gelene kadar uyur. Bloke etmeyen çağrıda süreç EAGAIN aldıkça başka iş yapar ve yeniden dener. Eş zamansız çağrıda süreç çalışmaya devam eder ve iş bitince haber alır.

Bloke eden model en kolay programlanandır ve çoğu programın kullandığı modeldir. Ama bir süreç aynı anda binlerce ağ bağlantısına hizmet edecekse her bağlantı için bir iş parçacığı açmak pahalıdır. Tek bir iş parçacığının birçok tanımlayıcıyı izleyebilmesi için çekirdek “hangi tanımlayıcı hazır?” sorusuna cevap veren arayüzler sunar (Tablo 11.7).

Tablo 11.7: Çoklu tanımlayıcı arayüzleri.
ArayüzModelNot
select / pollhazır olma bildirimiher çağrıda bütün tanımlayıcı listesi taranır: O(n)
epoll (Linux), kqueue (BSD)hazır olma bildirimiilgi listesi çekirdekte; yalnız hazır olanlar döner
io_uring, Windows IOCPtamamlanma bildirimigerçek eş zamansız G/Ç; disk dahil

11.7.2Olay döngüsü: epoll

int ep = epoll_create1(0);
for (k = 0; k < nsock; k++)   /* O_NONBLOCK */
    epoll_ctl(ep, EPOLL_CTL_ADD, s[k], &ev);
for (;;) {
  int n = epoll_wait(ep, evs, 64, -1);
  for (int i = 0; i < n; i++) {
    int fd = evs[i].data.fd;
    ssize_t r = read(fd, buf, sizeof buf);
    if (r < 0 && errno == EAGAIN) continue;
    handle(fd, buf, r);
  }
}

Kod tipik bir sunucunun olay döngüsüdür. epoll_create1 çekirdekte bir epoll nesnesi oluşturur. Bu nesne iki liste tutar: izlenen tanımlayıcıların ilgi listesi ve o an hazır olanların hazır listesi. Ardından soketler bloke etmeyen kipte (O_NONBLOCK) ilgi listesine eklenir. Bu işlem bir kez yapılır; select'teki gibi her turda listenin yeniden verilmesi gerekmez.

Sonsuz döngüde epoll_wait hiçbir soket hazır değilken süreci bloke eder; CPU boşa dönmez. Diyelim ki soket 7'ye bir paket geldi. Ağ kartı kesme üretir, ağ yığını paketi işler ve fd 7'yi hazır listesine ekler; süreç uyanır ve n = 1 döner. İç döngü yalnız hazır tanımlayıcıyı işler: 512 bayt okunur ve handle çağrılır. Maliyet hazır tanımlayıcı sayısıyla orantılıdır, toplam tanımlayıcı sayısıyla değil. Bir sonraki read EAGAIN döndürürse okunacak veri kalmamıştır ve döngü yeniden epoll_wait'e gider.

ilgi listesi (çekirdekte)fd 5fd 6fd 7hazır listesi(boş)
Şekil 11.27: Veri işlendikten sonra: ilgi listesinde fd 5, 6, 7; hazır listesi boş.

Bu düzenle tek bir iş parçacığı on binlerce bağlantıyı yönetebilir. nginx, Node.js ve Redis böyle çalışır. epoll'un bir sınırı da vardır. epoll yalnız “okuyabilirsin” der; işi yine read yapar ve kopyalama sürecin zamanından gider. Disk dosyaları ise her zaman “hazır” görünür, çünkü çekirdek bir disk okumasını hiçbir zaman EAGAIN ile reddetmez; bloke ederek tamamlar. Bu yüzden disk için gerçek eş zamansızlık io_uring ile gelir.

11.7.3io_uring: paylaşılan halkalar

Tanım 11.20

io_uring (Linux 5.1, 2019), uygulama ile çekirdeğin paylaşımlı bellekte iki dairesel tamponu ortak kullandığı eş zamansız G/Ç arayüzüdür. Uygulama gönderim halkasına (SQ) istek kayıtları (SQE) yazar; çekirdek tamamlanan işlerin sonuçlarını tamamlanma halkasına (CQ) kayıt (CQE) olarak ekler.

Uygulama (kullanıcı alanı)Çekirdek: dosya sistemi, blok katmanı, NVMeSQ (gönderim halkası)CQ (tamamlanma halkası)paylaşımlı bellek (mmap)sistem çağrısız okunur
Şekil 11.28: io_uring: uygulama ile çekirdek arasında paylaşılan gönderim (SQ) ve tamamlanma (CQ) halkaları. Uygulama tamamlanmaları sistem çağrısı yapmadan okur.
  1. Halkalar. Uygulama ile çekirdek iki halkayı mmap ile paylaşımlı bellekte görür. İstekler ve sonuçlar kopyalanmaz.
  2. Gönderim kayıtları. Uygulama üç SQE yazar: iki okuma ve bir yazma. Her kayıt işlem kodunu, dosya tanımlayıcısını, tampon adresini ve ofseti taşır.
  3. Toplu gönderim. Tek bir io_uring_enter çağrısı üç isteği birden gönderir. SQPOLL kipinde bir çekirdek iş parçacığı halkayı sürekli yoklar ve hiç sistem çağrısı gerekmez.
  4. Tamamlanma. Aygıtlar işleri bitirdikçe çekirdek CQE'leri halkaya ekler. Her CQE'nin sonuç alanı aktarılan bayt sayısını ya da negatif hata kodunu (−errno) taşır.
  5. Toplama. Uygulama CQE'leri doğrudan bellekten okur ve halkanın başını ilerletir. Toplu gönderim ve toplu toplama sayesinde bağlam değiştirme çok azalır.

io_uring bir tamamlanma modelidir: çekirdek “işin bitti” der, Windows'taki IOCP gibi. epoll ise bir hazır olma modelidir: “artık okuyabilirsin” der. io_uring ağ, dosya, accept, zamanlayıcı dahil yüzlerce işlem türünü destekler. Karmaşık bir arayüz olduğu için çok sayıda güvenlik açığı çıkmıştır; bu yüzden Android ve bazı konteyner ortamları gibi yerlerde kapalıdır.

Sık yapılan hata

Bloke etmeyen G/Ç ile eş zamansız G/Ç'yi aynı şey saymak. Bloke etmeyen çağrı işi yapmaz, yalnız o an yapılabilecek kadarını yapar ve geri kalanını sürece bırakır; süreç yeniden denemek zorundadır. Eş zamansız çağrı ise işin tamamını çekirdeğe bırakır ve sonucu sonra alır. Bedeli programlamanın karmaşıklaşmasıdır: tamponlar iş bitene kadar geçerli kalmalı ve her isteğin hata yolu ayrıca izlenmelidir.

11.8Saatler ve diğer aygıtlar

11.8.1Saat donanımı

Saatler ne blok ne karakter aygıtıdır, ama işletim sisteminin en çok kullandığı aygıtlardandır. Zaman dilimlerini, günün saatini ve bütün zamanlayıcıları onlar yürütür.

Tanım 11.21

Programlanabilir saat, bir kristal osilatörden gelen her darbede bir sayacı bir azaltan ve sayaç sıfıra inince kesme üreten devredir. Tek atım (one-shot) kipinde saat bir kez kesme üretir ve durur. Kare dalga (periyodik) kipinde sayaç bir tutucu yazmaçtan yeniden yüklenir ve kesmeler düzenli aralıklarla gelir. Her kesmeye bir tik (tick) denir.

Kristal osilatör1,193182 MHzdarbeSayaçher darbede −10 oluncaKesmeIRQ 0 / vektörTutucu yazmaç = 1193yeniden yükle (kare dalga modu)CPUsaat işleyicisimodlar: tek atım (one-shot) · kare dalga (periyodik)
Şekil 11.29: 8254 PIT: 1,193182 MHz'lik osilatör her darbede sayacı bir azaltır; sayaç 0'a inince IRQ 0 üretilir ve sayaç tutucu yazmaçtan (1193) yeniden yüklenir.
Örnek 11.9

8254 PIT saniyede 1 193 182 darbe sayıyor. 1 kHz ve 100 Hz'lik tik için tutucu yazmaca ne yüklenir? Gerçek frekanslar nedir?

  1. 1 kHz. 1 193 182 / 1000 = 1193,18. Sayaç tam sayı olmalıdır: 1193. Gerçek frekans 1 193 182 / 1193 ≈ 1000,15 Hz.
  2. 100 Hz. 1 193 182 / 100 = 11 931,82 → 11 932. Gerçek frekans 1 193 182 / 11 932 ≈ 99,998 Hz.
  3. Sonuç. Yuvarlama yüzünden saat istenen frekanstan biraz sapar. Uzun sürede biriken bu sapma, günün saatinin ara sıra gerçek bir saat kaynağıyla düzeltilmesini gerektirir.

8254 bugün yalnız uyumluluk için durur. Modern x86 işlemcilerinde TSC (zaman damgası sayacı), HPET ve her çekirdekteki yerel APIC zamanlayıcısı kullanılır. APIC zamanlayıcısının TSC-deadline kipinde işletim sistemi doğrudan “TSC şu değere ulaşınca kesme üret” der. ARM işlemcilerinde bu görevi genel zamanlayıcı (generic timer) üstlenir. Pil destekli gerçek zaman saati (RTC) ise bilgisayar kapalıyken tarihi tutar; açılışta günün saati ondan okunur.

Saat yazılımının görevleri şunlardır: günün saatini tutmak, süreçlerin zaman dilimi tükenince çizelgeleyiciyi çağırmak, CPU kullanım muhasebesi yapmak, süreçlerin istediği alarmları ve zamanlayıcıları çalıştırmak, izleme köpeği zamanlayıcılarını beslemek ve profil çıkarmak için örnek almak.

Sık yapılan hata

Günün saatini 32 bitlik bir saniye sayacında tutmak. İşaretli 32 bit time_t, 1 Ocak 1970'ten itibaren en çok 231 − 1 saniye sayabilir. Bu sınıra 19 Ocak 2038, 03:14:07 UTC'de ulaşılır ve sayaç negatife taşar. Günün saati 64 bitlik bir sayaçla tutulmalıdır. Linux 64 bitlik time_t'ye geçmiştir, ama eski gömülü sistemlerde sorun sürüyor.

11.8.2Tik ve tiksiz çekirdek

Klasik çekirdek saati periyodik kipte kullanır. Zamanlayıcı her 1/HZ saniyede bir kesme üretir. Linux'ta HZ yapılandırma sırasında seçilir: 100, 250 ya da 1000; tik aralığı sırasıyla 10, 4 ve 1 ms'dir. Her tikte işleyici zamanı ilerletir, çalışan sürecin kalan zaman dilimini azaltır ve süresi dolan zamanlayıcıları çalıştırır.

(a) periyodik tik: HZ = 250 → 4 ms0481216202428323640zaman (ms)işCPU boşta (derin C durumu isteniyor)zamanlayıcı olayıboşta da 6 gereksiz uyanma: her biri CPU’yu derin uykudan çıkarırhrtimer: ns çözünürlüklü zamanlayıcılar, kırmızı-siyah ağaçta sıralı
(b) tiksiz (NO_HZ): bir sonraki olay için tek atım0481216202428323640zaman (ms)işCPU boşta (derin C durumu isteniyor)zamanlayıcı olayıboşta tik yok: zamanlayıcı 30 ms’de bir kez programlanırhrtimer: ns çözünürlüklü zamanlayıcılar, kırmızı-siyah ağaçta sıralı
Şekil 11.30: (a) Periyodik tik (HZ = 250, 4 ms): CPU boşta olsa bile her tikte uyanır; şekildeki boş aralıkta altı gereksiz uyanma vardır. (b) Tiksiz çekirdek: boşta tik yoktur, saat bir sonraki olaya (30 ms) tek atımla kurulur.

Periyodik tikin sorunu, CPU'nun boştayken de uyanmasıdır. Her uyanma CPU'yu derin uyku durumundan çıkarır ve enerji harcar. Tiksiz çekirdek (Linux'ta NO_HZ_IDLE, 2007) CPU boşken tiki durdurur ve saati bir sonraki olayın zamanına tek atımla kurar. nohz_full seçeneği bir adım daha ileri gider. Tek bir sürecin çalıştığı çekirdekte, süreç çalışırken bile tiki kaldırır. Bu, yüksek başarımlı hesaplama ve gerçek zamanlı iş yükleri için önemlidir. Tiksiz çalışma dizüstü bilgisayarlarda pil, sanal makinelerde ise işlemci zamanı kazandırır. Mikrosaniye düzeyindeki kısa beklemeler için yumuşak zamanlayıcılar ve yoklama birlikte kullanılır. Linux'un yüksek çözünürlüklü zamanlayıcıları (hrtimer) nanosaniye çözünürlüklüdür ve kırmızı-siyah ağaçta sıralı tutulur.

11.8.3Zamanlayıcı listesi

Birçok süreç “şu kadar tik sonra bana haber ver” diye alarm kurabilir. Saat yazılımı bu alarmları her tikte hepsine bakmadan nasıl izleyebilir? Tanenbaum'un cevabı fark listesidir.

Tanım 11.22

Fark listesi (delta list), bekleyen zamanlayıcıları dolma zamanına göre sıralı tutan bağlı listedir. Her düğüm mutlak zamanı değil, bir önceki düğümden kaç tik sonra dolacağını (farkı) saklar. İlk düğümün farkı şimdiki zamana göredir.

(c) zamanlayıcı listesi: şimdiki zaman 4200Şimdi4200Sonraki sinyal3 tik sonra+3t = 4203+4t = 4207+6t = 4213+2t = 4215+1t = 4216fark (delta) listesi: her tikte yalnız baştaki sayaç azaltılır
Şekil 11.31: Şimdiki zaman 4200; bekleyen sinyaller 4203, 4207, 4213, 4215 ve 4216'da. Liste yalnız farkları tutar: 3, 4, 6, 2, 1.

Bu düzenin üstünlüğü tik işleyicisindedir: her tikte yalnız baştaki sayaç bir azaltılır. Baştaki fark 0'a inince olay tetiklenir ve düğüm listeden çıkar. Listede kaç zamanlayıcı olursa olsun tik başına iş O(1)'dir. Bedel eklemededir: yeni zamanlayıcının yeri bulunurken liste gezilir ve bu O(n) sürer.

11.8.4Kod izleme: ekle ve tik

void ekle(Zam *z, int t) {  /* t: tik */
  Zam **p = &bas;
  while (*p && (*p)->fark <= t) {
    t -= (*p)->fark;
    p = &(*p)->sonraki;
  }
  z->fark = t; z->sonraki = *p;
  if (*p) (*p)->fark -= t;
  *p = z;
}
void tik(void) {
  if (bas) bas->fark--;
  while (bas && bas->fark == 0)
    bas = calistir(bas);   /* çıkar */
}

ekle yeni zamanlayıcıyı listeye t tik sonrası için ekler. p bir işaretçinin işaretçisidir: önce liste başını (&bas), sonra her adımda bir önceki düğümün sonraki alanını gösterir. Böylece başa ekleme ile araya ekleme ayrı ayrı yazılmaz. Döngü, geçilen her düğümün farkını t'den çıkarır; t artık “o düğümden sonra kaç tik” anlamına gelir. Farkı t'den büyük bir düğüme gelince döngü durur. Yeni düğüm oraya girer ve arkasındaki düğümün farkı t kadar azaltılır; böylece arkadaki düğümün mutlak zamanı değişmez. tik ise yalnız baştaki farkı azaltır ve farkı 0 olan bütün düğümleri sırayla çalıştırıp çıkarır. Aynı anda dolan iki zamanlayıcıdan ikincisinin farkı 0'dır; while döngüsü bu yüzden vardır.

Örnek 11.10

Liste A 3, B 4, C 6, D 2, E 1 (mutlak zamanlar 4203, 4207, 4213, 4215, 4216). Şimdi 4200'deyken X zamanlayıcısı 10 tik sonrası (4210) için ekleniyor: ekle(X, 10). Ardından üç tik geçiyor.

  1. A. A'nın farkı 3 ≤ 10: X ondan sonra gelir. t = 10 − 3 = 7; *p B'ye geçer.
  2. B. B'nin farkı 4 ≤ 7: X ondan da sonra gelir. t = 7 − 4 = 3; *p C'ye geçer.
  3. C. C'nin farkı 6 > 3: X, C'den önce girmelidir; döngü biter.
  4. Ekleme. X.fark = 3 olarak araya girer; C'nin farkı 6 − 3 = 3 olur. Liste A 3, B 4, X 3, C 3, D 2, E 1. C'nin mutlak zamanı 4203 + 4 + 3 + 3 = 4213, yani değişmedi. Yalnız bir düğüm güncellendi.
  5. Tikler. İlk iki tikte yalnız A'nın farkı azalır: 2, sonra 1. Üçüncü tikte (4203) fark 0 olur; A çalıştırılır ve listeden çıkarılır. Liste B 4, X 3, C 3, D 2, E 1.
şimdi = 4200kutuda: fark (önceki olaydan sonra)A34203B44207X34210C34213D24215E14216*palt satır: olayın mutlak zamanı
şimdi = 4203kutuda: fark (önceki olaydan sonra)B44207X34210C34213D24215E14216alt satır: olayın mutlak zamanı
Şekil 11.32: Solda X eklendikten sonra: A 3, B 4, X 3, C 3, D 2, E 1. Sağda 4203'te A çıktıktan sonra: liste B'den başlar.
Örnek 11.11

Şimdi 8000. tik; alarmlar 8002, 8006, 8006, 8011 ve 8020'de. Fark listesi nedir? 8 tik sonra hangi alarmlar tetiklenmiş olur?

  1. Farklar. 8002 − 8000 = 2, 8006 − 8002 = 4, 8006 − 8006 = 0, 8011 − 8006 = 5, 8020 − 8011 = 9. Liste 2, 4, 0, 5, 9.
  2. İlk alarm. 2 tik sonra baştaki fark 0 olur: 8002'deki alarm tetiklenir.
  3. Eş zamanlı alarmlar. 4 tik daha geçince (8006) baştaki fark 0 olur. Döngü bu düğümü çıkarır; arkasındaki düğümün farkı da 0 olduğu için onu da çıkarır. 8006'daki iki alarm birlikte tetiklenir.
  4. Kalan. 8008'e gelindiğinde iki tik daha geçmiştir: baştaki fark 5 − 2 = 3. Liste 3, 9 (8011 ve 8020).

11.8.5Terminaller, giriş aygıtları ve güç yönetimi

Terminaller. Unix'te terminal sürücüsü (tty) ile uygulama arasında bir satır disiplini bulunur. Kanonik kipte satır disiplini satırı düzenler: geri silme ve satır silme orada işlenir, satır ancak Enter'a basılınca uygulamaya teslim edilir. Ham kipte her tuş hemen uygulamaya gider; metin düzenleyiciler bu kipi kullanır (stty -icanon). Terminal öykünücüleri ve ssh oturumları sözde terminal (pty) kullanır. Ekran denetimi, imleci taşıyan ya da rengi değiştiren ANSI kaçış dizileriyle yapılır.

Klavye ve giriş aygıtları. Klavye karakter değil, tarama kodu gönderir: her tuşun basılması ve bırakılması ayrı bir koddur. Sürücü tuş eşlemesini kullanarak bunları karaktere çevirir. Shift tuşu bu yüzden ayrı bir olaydır. USB klavye ve fareler (HID aygıtları) kesme üretmez; ana makine onları saniyede 125–1000 kez yoklar. Linux'ta giriş olayları input alt sisteminden /dev/input/event* (evdev) dosyalarına, oradan libinput'a ve Wayland birleştiricisine, en sonda pencereye ulaşır.

Güç yönetimi. ACPI sistem için uyku durumları tanımlar: S0 çalışır durumdur (S0ix modern bekleme), S3 RAM'e askıya alma, S4 diske hazırda bekleme, S5 kapalıdır. İşlemcide iki ayrı ayar vardır. C durumları boşta ne kadar derin uyunacağını, P durumları çalışırken hangi frekans ve gerilimin kullanılacağını belirler; Linux'ta P durumlarını schedutil yöneticisi seçer. Aygıtların da D0 (açık) ile D3 (kapalı) arasında durumları vardır. Çalışma zamanı güç yönetimi, kullanılmayan aygıtı sistem çalışırken uyutur: NVMe'de APST, PCIe'de ASPM, Wi-Fi güç tasarrufu ve ekranın kararması buna örnektir. İşletim sistemi aygıtları doğru sırayla uyutup uyandırmalı ve hangi aygıtların (klavye, ağ, saat) sistemi uyandırabileceğini belirlemelidir. Bir dizüstü bilgisayarın pil ömrü, büyük ölçüde boşta geçen sürede ne kadar az güç harcandığına bağlıdır.

11.9Diskler

11.9.1Manyetik disk geometrisi

üstten görünümsektörizyandan görünümsilindirkolmilkafabölgeli kayıt (ZBR)32 sektör/iz24 sektör/iz16 sektör/izsanal geometri: düz LBA 0 … N−1
Şekil 11.33: Sabit disk: plakalar, izler, sektörler, silindir, okuma-yazma kafaları ve kol. Sağda bölgeli kayıt (ZBR): dış bölgelerde iz başına daha çok sektör vardır; işletim sistemi bunu düz bir LBA dizisi olarak görür.
Tanım 11.23

Sabit disk (HDD) aynı mil üzerinde dönen bir ya da daha çok manyetik plakadan oluşur. Her yüzeyin bir okuma-yazma kafası vardır; kafalar ortak bir kolla birlikte hareket eder. Bir yüzeydeki eş merkezli halkalara iz, izlerin parçalarına sektör, bütün yüzeylerde aynı yarıçaptaki izlerin oluşturduğu kümeye silindir denir.

Plakalar masaüstü disklerde 5400–7200 rpm, sunucu disklerinde 10 000–15 000 rpm hızla döner. Dış izler daha uzun olduğu için dış bölgelerde iz başına daha çok sektör vardır. Buna bölgeli kayıt (zoned bit recording) denir; disk dış izlerde daha hızlı aktarım yapar.

İşletim sistemi bu geometriyi bugün görmez. Eskiden diskler silindir-kafa-sektör (CHS) üçlüsüyle adreslenirdi. Bugün işletim sistemi yalnız LBA (logical block address), yani 0'dan N − 1'e kadar numaralı bloklar görür. Disk içindeki denetleyici LBA'yı gerçek geometriye, yedek sektörlere ve kendi önbelleğine kendisi çevirir. Eski CHS arayüzünün sınırı 1024 silindir × 255 kafa × 63 sektör × 512 B ≈ 8,4 GB idi.

11.9.2Erişim süresi

Tanım 11.24

Bir disk isteğinin erişim süresi üç bileşenin toplamıdır. Arama süresi (seek time) kolun doğru silindire gitmesi için geçen süredir. Dönme gecikmesi (rotational latency) istenen sektörün kafanın altına gelmesini beklemektir; ortalaması yarım turdur. Aktarım süresi verinin kafanın altından geçme süresidir.

arama: kol silindir değiştirirdönmekırmızı: kafanın silindiri
Şekil 11.34: Arama, kolun silindir değiştirmesidir; dönme gecikmesi, plakanın istenen sektörü kafanın altına getirmesidir.
Örnek 11.12

Disk 7200 rpm dönüyor; ortalama arama süresi 8 ms, ortam aktarım hızı 200 MB/s. 4 KiB'lık rastgele bir blok okumak ne kadar sürer?

  1. Bir tur. 60 000 ms / 7200 = 8,33 ms.
  2. Dönme gecikmesi. İstenen sektör ortalamada yarım tur uzaktadır: 8,33 / 2 = 4,17 ms.
  3. Aktarım. 4096 B / (200 × 106 B/s) = 0,020 ms.
  4. Toplam. 8 + 4,17 + 0,02 = 12,19 ms. Aktarım süresi, küçük de olsa, üç bileşenden biridir ve sayılmalıdır.
arama 8 msdönme 4,17 msaktarım 0,02 ms ← (%0,2)toplam 12,19 ms
Şekil 11.35: 4 KiB rastgele okumada arama (8 ms), dönme gecikmesi (4,17 ms) ve aktarım (0,02 ms; toplamın %0,2'si).

Sonuç açıktır: süre neredeyse tamamen mekanik hareketlerde geçer. CPU bu 12 ms'de on milyonlarca komut çalıştırabilir; işletim sistemi beklemez, başka bir süreç çalıştırır. Aynı disk erişim düzenine göre çok farklı hızlar verir:

  • Rastgele 4 KiB. Saniyede 1000 / 12,19 ≈ 82 istek (IOPS) yapılabilir. Bu, 82 × 4096 B ≈ 0,34 MB/s eder.
  • Ardışık 1 MiB. Aktarım 1 048 576 / (200 × 106) = 5,24 ms; toplam 8 + 4,17 + 5,24 = 17,41 ms. Hız 1 048 576 B / 17,41 ms ≈ 60 MB/s olur.

Aynı disk, büyük ardışık okumada küçük rastgele okumaya göre yaklaşık 180 kat hızlıdır. Dosya sistemlerinin blokları yakın yerleştirmeye çalışması ve disk kolu çizelgelemenin önemi bu farktan gelir. Karşılaştırma için: bir NVMe SSD'de 4 KiB rastgele okuma 50–100 µs sürer ve paralel kuyruklarla saniyede bir milyonun üzerinde istek yapılabilir.

Örnek 11.13

15 000 rpm dönen bir sunucu diskinin ortalama arama süresi 3,5 ms, ortam hızı 250 MB/s (1 MB = 106 B). 64 KiB ardışık okuma ve diske dağılmış 16 adet 4 KiB blok okuma ne kadar sürer?

  1. Tur ve dönme. 60 000 / 15 000 = 4 ms; ortalama dönme gecikmesi 2 ms.
  2. 64 KiB ardışık. Aktarım 65 536 / (250 × 106) s = 0,262 ms; toplam 3,5 + 2 + 0,262 ≈ 5,76 ms.
  3. 16 × 4 KiB dağınık. Blok başına 3,5 + 2 + 0,016 = 5,516 ms; 16 blok 16 × 5,516 ≈ 88,3 ms. Aynı veri yaklaşık 15 kat yavaş okunur.
  4. Hızlı ortam işe yarar mı? Ortam hızı iki katına çıksa dağınık okumadaki aktarım payı 16 × 0,016 = 0,26 ms'den 0,13 ms'ye iner: toplamın yalnız %0,15'i kadar kazanç. Küçük rastgele okumada süreye arama ve dönme hâkimdir.

11.9.3Biçimlendirme ve silindir eğriliği

Bir diskin kullanılabilmesi için iki düzeyde biçimlendirilmesi gerekir. Düşük düzey format fabrikada yapılır. İzler ve sektörler yüzeye yazılır; her sektöre öncül, veri alanı ve ECC konur, yedek sektörler ayrılır. Bu yüzden biçimlendirilmiş kapasite ham kapasiteden azdır. Üst düzey format ise bir bölüme boş bir dosya sistemi yazar: önyükleme bloğu, boş alan yönetimi yapıları ve kök dizin.

sektör biçimi (düşük düzey format)öncülveri 512 B / 4 KiBECCeğrilik (skew): izler arası geçişte sektör 0 kaçırılmasıniz 00123456789101112131415iz 11314150123456789101112iz 21011121314150123456789
Şekil 11.36: Sektör biçimi ve silindir eğriliği. Her izin sektör 0'ı bir öncekine göre kaydırılmıştır; kafa iz değiştirirken sektör 0 kaçırılmaz.
Tanım 11.25

Silindir eğriliği (cylinder skew), ardışık izlerin sektör 0 konumlarının birbirine göre kaydırılmasıdır. Kafa bir izi bitirip sonrakine geçerken disk dönmeye devam eder; kaydırma, kafa yeni ize vardığında sektör 0'ın tam gelmek üzere olmasını sağlar.

Örnek 11.14

Disk 10 000 rpm dönüyor ve izde 300 sektör var. İz değiştirme 800 µs sürüyor. Eğrilik kaç sektör olmalıdır?

  1. Tur. 60 000 / 10 000 = 6 ms.
  2. Sektör süresi. 6 ms / 300 = 20 µs.
  3. Eğrilik. İz değiştirme sırasında 800 / 20 = 40 sektör kafanın altından geçer. Sonraki izin sektör 0'ı 40 sektör kaydırılmalıdır.

Eskiden yavaş denetleyiciler için bir de serpiştirme (interleaving) yapılırdı: denetleyici bir sektörü belleğe aktarırken sonraki sektör kafanın altından geçip kaçmasın diye ardışık sektörler izde aralıklı dizilirdi. Bugün denetleyici bütün izi kendi tamponuna okuduğu için serpiştirme gereksizdir.

11.9.4Optik diskler

Optik disklerde veri, manyetik diskteki eş merkezli izler yerine içten dışa uzanan tek bir sarmal iz boyunca durur. İzde çukurlar (pit) ve düzlükler (land) vardır; çukur ile düzlük arasındaki her geçiş 1 bitini, geçiş olmaması 0'ı gösterir. Basılı disklerde çukurlar polikarbonata kalıpla basılır, lazerle yakılmaz. Kaydedilebilir CD-R ve DVD-R disklerinde ise lazer organik bir boya katmanını ısıtarak yansıtıcılığını değiştirir.

optik: tek sarmal izmanyetik: eş merkezli
Şekil 11.37: Optik diskte veri tek bir sarmal iz boyunca, manyetik diskte eş merkezli izlerde durur.
Tablo 11.8: Optik disk kuşakları.
CD (1982)DVD (1996)Blu-ray (2006)
Kapasite650–700 MB4,7 / 8,5 / 9,4 / 17 GB25 / 50 GB; BDXL 100–128 GB
Lazer780 nm (kızılötesi)650 nm (kırmızı)405 nm (mavi-mor)
En küçük çukur0,83 µm0,40 µm0,15 µm
İz aralığı1,6 µm0,74 µm0,32 µm

Kuşaklar arasındaki kapasite artışı, Tablo 11.8'deki gibi lazer dalga boyunun kısalmasından gelir: daha kısa dalga boyu daha küçük bir noktaya odaklanır, çukurlar ve iz aralığı küçülür. CD-ROM'un kodlaması ayrıca ilginçtir. Her bayt, EFM (eight-to-fourteen modulation) ile 14 bitlik bir kanal sembolüne çevrilir. Bu bir modülasyon kodudur, hata düzeltme kodu değildir; hata düzeltme CIRC ile yapılır. 98 çerçevenin her biri 24 veri baytı taşır ve 98 × 24 = 2352 B'lık bir sektör oluşur. Mod 1'de bunun 2048 B'ı kullanıcı verisidir; geri kalanı eşzamanlama, başlık ve ek ECC/EDC alanıdır.

DVD'nin iki katmanlı türlerinde üst katman yarı saydamdır; lazer odağını değiştirerek alttaki katmanı okur. Optik ortam bugün çoğunlukla arşiv ve dağıtım için kullanılır; yoğunlukta manyetik ve flaş ortamın gerisindedir.

11.10Disk kolu çizelgeleme

11.10.1Amaç ve ölçüt

Erişim süresi örneği, bir disk isteğinin süresinin büyük kısmının arama ve dönmede geçtiğini gösterdi. Diskte aynı anda birçok istek bekliyorsa bunların hangi sırayla karşılanacağı, kolun ne kadar yol alacağını belirler.

Tanım 11.26

Disk kolu çizelgeleme (disk scheduling), bekleyen disk isteklerinin karşılanma sırasını seçerek kol hareketini (dolayısıyla arama süresini) azaltmayı ve verimi artırmayı amaçlar. Sürücü her istek için silindir numarasını ve geliş zamanını tutar. Algoritmaları karşılaştırmak için kullanılan ölçüt, kafanın silindir cinsinden toplam hareketidir.

Bu modelin bir basitleştirme olduğunu baştan söylemek gerekir. Bugün LBA'nın fiziksel konuma eşlenmesi diskin içindedir; işletim sistemi silindirleri bilmez. Disk ayrıca NCQ (native command queuing) ile 32 isteği dönme konumuna göre kendisi yeniden sıralar. İşletim sistemi bu yüzden yaklaşık olarak “LBA yakınlığı” ile çizelgeler: numaraları yakın blokların diskte de yakın olduğunu varsayar.

0143753656798122124183199toplam: 0 silindirhizmet verildibekliyor
Şekil 11.38: Ortak kuyruk: kafa 53. silindirde; bekleyen istekler 98, 183, 37, 122, 14, 124, 65, 67. Silindirler 0–199.

Algoritmaları karşılaştırmak için hepsine aynı kuyruğu vereceğiz. Silberschatz'ın klasik örneğinde silindirler 0–199 arasıdır, kafa 53'tedir ve istekler geliş sırasıyla 98, 183, 37, 122, 14, 124, 65, 67'dir (Şekil 11.38). Tablo 11.9 algoritmaların özünü önceden özetler.

Tablo 11.9: Disk kolu çizelgeleme algoritmaları.
AlgoritmaFikirSorun
FCFSGeliş sırasıAdil ama kol çok gezer
SSTFKafaya en yakın istek (açgözlü)Uçtaki istekler açlık çekebilir
SCAN (asansör)Bir yönde uca kadar git, dönUçlar ve orta eşit beklemez
C-SCANTek yönde tara, uçtan başa sıçraDönüş hareketi boşa
LOOK / C-LOOKSCAN / C-SCAN, ama son isteğe kadarUygulamada kullanılan biçimler

11.10.2FCFS

Tanım 11.27

FCFS (first-come, first-served, ilk gelen ilk hizmet alır) istekleri geliş sırasıyla karşılar.

Örnek 11.15

Ortak kuyrukta FCFS'nin toplam kafa hareketi nedir?

  1. 53 → 98: 45 silindir; toplam 45.
  2. 98 → 183: 85; toplam 130.
  3. 183 → 37: 146; toplam 276.
  4. 37 → 122: 85; toplam 361.
  5. 122 → 14: 108; toplam 469.
  6. 14 → 124: 110; toplam 579.
  7. 124 → 65: 59; toplam 638.
  8. 65 → 67: 2; toplam 640 silindir.
0143753656798122124183199toplam: 640 silindirhizmet verildibekliyor
Şekil 11.39: FCFS'de kafanın yolu: 53, 98, 183, 37, 122, 14, 124, 65, 67. Toplam 640 silindir.

FCFS adildir ve açlık yaratmaz: her istek, önündekiler bitince mutlaka karşılanır. Ama 122 → 14 → 124 gibi gidip gelmeler çok pahalıdır. Kol diskin bir ucundan öbürüne iki kez gidip gelir; 14 ile 124'e giden iki istek yalnız 218 silindir tüketir.

11.10.3SSTF

Tanım 11.28

SSTF (shortest seek time first, en kısa arama süresi önce) her adımda kafaya en yakın bekleyen isteği seçer.

Örnek 11.16

Ortak kuyrukta SSTF hangi sırayı izler, toplam hareket nedir?

  1. Kafa 53: adaylar 65 (12 uzakta) ve 37 (16 uzakta). 53 → 65: 12; toplam 12.
  2. 65 → 67: 2; toplam 14.
  3. Kafa 67: 37 (30 uzakta), 98'den (31 uzakta) yakındır. 67 → 37: 30; toplam 44.
  4. 37 → 14: 23; toplam 67.
  5. Alt bölge bitti. 14 → 98: 84; toplam 151.
  6. 98 → 122: 24; toplam 175.
  7. 122 → 124: 2; toplam 177.
  8. 124 → 183: 59; toplam 236.
0143753656798122124183199toplam: 236 silindirhizmet verildibekliyor
Şekil 11.40: SSTF'de kafanın yolu: 53, 65, 67, 37, 14, 98, 122, 124, 183. Toplam 236 silindir.

236, FCFS'nin 640'ının yaklaşık %37'sidir. Ama SSTF açgözlüdür ve en iyi sonucu garanti etmez. Kafa önce aşağı inseydi sıra 53 → 37 → 14 → 65 → 67 → 98 → 122 → 124 → 183 olurdu. Toplam 16 + 23 + 51 + 2 + 31 + 24 + 2 + 59 = 208 çıkar. SSTF'de üçüncü adımdaki 30 silindirlik kısa adım, sonra 84 silindirlik bir geri dönüşe yol açmıştır.

Sık yapılan hata

SSTF'yi en iyi algoritma sanmak. SSTF her adımda en kısa hareketi seçer, ama toplam hareketi en aza indirmez; ortak kuyrukta 236 yapar, en iyi sıra ise 208'dir. Daha önemli sorun adalettir. Yoğun yükte isteklerin sürekli diskin orta bölgesine geldiğini düşünelim: kafa hep ortada kalır ve 190. silindirdeki bir istek dakikalarca bekleyebilir. Bu bir açlık durumudur. FCFS'de ve tarama algoritmalarında açlık yoktur.

11.10.4Kod izleme: sstf()

SSTF'nin C ile yazılmış bir sürümü aşağıdadır. bitti[] dizisi hangi isteklerin karşılandığını tutar.

int sstf(int bas, int q[], int n) {
  int top = 0;
  for (int k = 0; k < n; k++) {
    int en = -1;
    for (int i = 0; i < n; i++)
      if (!bitti[i] && (en < 0 ||
        abs(q[i]-bas) < abs(q[en]-bas)))
        en = i;            /* en yakın */
    top += abs(q[en] - bas);
    bas = q[en];
    bitti[en] = 1;
  }
  return top;
}

Dış döngü her turda bir istek karşılar ve n tur sürer. İç döngü bekleyen isteklerin hepsine bakar; en, şu ana kadar bulunan en yakın isteğin indisidir. Koşuldaki en < 0 || parçası ilk bekleyen isteği aday yapar; sonraki istekler ancak kesin olarak daha yakınsa adayın yerini alır. Bu yüzden eşit uzaklıkta iki istek varsa geliş sırasında önce olan seçilir. Tur sonunda uzaklık top'a eklenir, kafa o isteğe taşınır ve istek bitti işaretlenir. Her turda n istek tarandığı için algoritma O(n2) zaman ve bitti[] için O(n) ek alan kullanır.

Tablo 11.10: sstf() izlemesi. Kafa 50; kuyruk (geliş sırası) 82, 170, 43, 140, 24, 16, 190.
kKafaEn yakın (uzaklık)Sonraki aday (uzaklık)top
05043 (7)24 (26)7
14324 (19)16 (27)26
22416 (8)82 (58)34
31682 (66)140 (124)100
482140 (58)170 (88)158
5140170 (30)190 (50)188
6170190 (20)—208
0501001501995043241682140170190toplam = 208 silindir
Şekil 11.41: sstf() izlemesinin sonu: bütün istekler karşılandı, top = 208.

Bu kuyrukta FCFS 642 silindir harcardı; SSTF 208 ile üçte birin altına iner. k = 3 satırı SSTF'nin zayıf noktasını gösterir: aşağıdaki istekler bitince kol 66 silindirlik uzun bir yolu geri döner. Ortadaki istekler sürekli gelseydi uçtakiler bekleyebilirdi.

11.10.5SCAN (asansör)

Tanım 11.29

SCAN algoritmasında kafa bir yönde diskin ucuna kadar gider ve yolu üzerindeki istekleri karşılar; uca varınca yön değiştirir ve öbür yöndeki istekleri karşılar. Bir binanın katları arasında gidip gelen asansöre benzediği için asansör algoritması da denir.

Örnek 11.17

Ortak kuyrukta kafa 0'a doğru (aşağı) hareket ediyor. SCAN'in toplam hareketi nedir?

  1. 53 → 37: 16; toplam 16.
  2. 37 → 14: 23; toplam 39.
  3. 14 → 0: 14; toplam 53. Aşağıda istek kalmadığı hâlde kafa diskin ucuna kadar gider.
  4. Yön değişir. 0 → 65: 65; toplam 118.
  5. 65 → 67: 2; toplam 120.
  6. 67 → 98: 31; toplam 151.
  7. 98 → 122: 24; toplam 175.
  8. 122 → 124: 2; toplam 177.
  9. 124 → 183: 59; toplam 236.
0143753656798122124183199toplam: 236 silindirhizmet verildibekliyor
Şekil 11.42: SCAN (aşağı yönde): 53, 37, 14, 0, 65, 67, 98, 122, 124, 183. Toplam 236 silindir.

SCAN bu kuyrukta SSTF ile aynı toplamı verir, ama açlığı önler: kafa yolundaki her isteği bir tarama içinde karşılar. Yeni gelen istekler kafanın önündeyse aynı taramada, arkasındaysa dönüşte karşılanır. Uca kadar gitmenin (14 → 0) bedeli boşa harcanan 14 silindirdir. Yön seçimi de sonucu değiştirir: aynı kuyrukta kafa yukarı yönde başlasaydı 53 → 199 (146) ve 199 → 14 (185) ile toplam 331 olurdu.

SCAN'in bir adalet kusuru vardır. Kafa bir uca varıp döndüğünde, hemen arkasındaki bölgeye yeni hizmet vermiştir; orada bekleyen istek azdır. İsteklerin en yoğun olduğu yer diskin öbür ucudur ve o istekler en uzun bekleyenlerdir. Kafa ortadaki silindirlerden her gidiş-dönüşte eşit aralıklarla iki kez geçer. Uçtaki silindirlerden ise kısa arayla iki kez geçer, ardından uzun bir boşluk gelir. Bu yüzden uçlara düşen istekler ortalamada daha uzun bekler.

11.10.6C-SCAN

Tanım 11.30

C-SCAN (dairesel SCAN) yalnız bir yönde hizmet verir. Kafa yukarı yönde uca kadar gider; uca varınca hizmet vermeden diskin başına (0'a) döner ve yeniden yukarı taramaya başlar. Disk bir halka gibi görülür.

Örnek 11.18

Ortak kuyrukta C-SCAN (yukarı yönde) hangi yolu izler? Dönüş hareketi de sayılırsa toplam nedir?

  1. 53 → 65 → 67 → 98 → 122 → 124 → 183: 12 + 2 + 31 + 24 + 2 + 59 = 130.
  2. 183 → 199: 16; toplam 146. Yukarıda istek yoktur ama kafa uca kadar gider.
  3. 199 → 0: geri dönüş, 199 silindir, hizmet yok; toplam 345.
  4. 0 → 14: 14; toplam 359.
  5. 14 → 37: 23; toplam 382.
0143753656798122124183199toplam: 382 silindirhizmet verildibekliyor
Şekil 11.43: C-SCAN (yukarı yönde): 53 … 183, 199, geri dönüş 0, 14, 37. Dönüş sayılırsa toplam 382, sayılmazsa 183 silindir.

Dönüş hareketi sayılmazsa toplam 382 − 199 = 183 olur. Kaynaklar bu konuda farklı davranır; bir sınav sorusunda dönüşün sayılıp sayılmadığı mutlaka belirtilmelidir. Dönüş gerçekte de hızlıdır: kol hiçbir sektörde durmadan tek hamlede geri gelir. C-SCAN'in kazancı toplam hareketten değil, adaletten gelir. Her istek, kafanın yukarı taramasında bir kez karşılanır; uçtaki ve ortadaki istekler yaklaşık eşit bekler.

11.10.7LOOK ve C-LOOK

Tanım 11.31

LOOK, SCAN gibi çalışır ama diskin ucuna gitmez: o yöndeki son istekte döner. C-LOOK, C-SCAN gibi tek yönde hizmet verir ama uca gitmez: yukarıdaki son istekten sonra doğrudan en küçük bekleyen isteğe sıçrar.

Örnek 11.19

Ortak kuyrukta LOOK (aşağı yönde) ve C-LOOK (yukarı yönde) için toplam hareket nedir?

  1. LOOK, aşağı. 53 → 37 → 14: 16 + 23 = 39. Aşağıda istek kalmadı; 0'a gidilmez.
  2. LOOK, yukarı. Yön değişir: 14 → 65 → 67 → 98 → 122 → 124 → 183: 51 + 2 + 31 + 24 + 2 + 59 = 169. Toplam 39 + 169 = 208; SCAN'e göre 28 silindir az, çünkü 14 → 0 → 14 yolu (2 × 14) yapılmaz.
  3. C-LOOK, yukarı. 53 → 65 … 183: 130.
  4. Sıçrama. 183 → 14: en küçük bekleyen isteğe 169 silindir; 199 ve 0'a gidilmez. Toplam 299.
  5. Son. 14 → 37: 23; toplam 322. Sıçrama sayılmazsa 322 − 169 = 153.
0143753656798122124183199toplam: 208 silindirhizmet verildibekliyor
0143753656798122124183199toplam: 322 silindirhizmet verildibekliyor
Şekil 11.44: Üstte LOOK (aşağı yönde): 53, 37, 14, 65 … 183; toplam 208. Altta C-LOOK (yukarı yönde): 53 … 183, sıçrama 14, 37; toplam 322 (sıçramasız 153).

Uygulamada “asansör” denen davranış çoğunlukla LOOK'tur; hiçbir gerçek sürücü istek yokken kolu diskin ucuna götürmez. Linux'un eski deadline ve CFQ çizelgeleyicileri ile bugünkü mq-deadline istekleri LBA'ya göre artan sırada gönderir ve en yüksek LBA'dan sonra baştan başlar. Bu, C-LOOK'a benzer bir davranıştır.

11.10.8Kod izleme: look()

LOOK'un yukarı yönde başlayan sürümü, sıralı bir dizi üzerinde iki döngüyle yazılabilir:

/* s[] sıralı; önce yukarı, sonra aşağı */
int look(int bas, int s[], int n) {
  int i = 0, top = 0;
  while (i < n && s[i] < bas) i++;
  for (int j = i; j < n; j++) {
    top += s[j] - bas; bas = s[j];
  }
  for (int j = i - 1; j >= 0; j--) {
    top += bas - s[j]; bas = s[j];
  }
  return top;
}

İlk while döngüsü kafadan büyük ya da kafaya eşit ilk isteğin indisini (i) bulur. Birinci for döngüsü i'den sona kadar yukarı yönde ilerler ve her adımın uzaklığını (s[j] - bas) toplar. İkinci for döngüsü i - 1'den 0'a kadar aşağı iner; bu kez uzaklık bas - s[j]'dir. Kafa yukarıdaki son istekten sonra uca gitmeden döndüğü için kod LOOK'tur, SCAN değildir. Fonksiyon dizi sıralı verildiğinde O(n) çalışır; sıralama O(n log n) sürer.

Tablo 11.11: look() izlemesi. s = 16, 24, 43, 82, 140, 170, 190; kafa 50, yukarı yönde.
AdımjHedefHarekettop
whilei = 3: 50'den büyük ilk istek s[3] = 82. Altındaki 16, 24, 43 dönüşte alınacak.
yukarı382+3232
yukarı4140+5890
yukarı5170+30120
yukarı6190+20140
aşağı243+147287
aşağı124+19306
aşağı016+8314
0501001501995082140170190432416toplam = 314 silindir
Şekil 11.45: look() izlemesinin sonu: toplam 314 silindir.

Aynı kuyrukta SSTF 208 yapmıştı; LOOK 314 yaptı. Fark yön seçiminden gelir. Kafa aşağı yönde başlasaydı LOOK önce 50 → 16 (34 silindir), sonra 16 → 190 (174 silindir) giderdi: 34 + 174 = 208. Yön seçimi sonucu belirler, ama yön ne olursa olsun hiçbir istek aç kalmaz.

11.10.9Algoritmaların karşılaştırması

FCFS640SSTF236SCAN236C-SCAN382 (sıçramasız 183)LOOK208C-LOOK322 (sıçramasız 153)
Şekil 11.46: Ortak kuyrukta altı algoritmanın toplam kafa hareketi: FCFS 640, SSTF 236, SCAN 236, C-SCAN 382 (sıçramasız 183), LOOK 208, C-LOOK 322 (sıçramasız 153).

Şekil 11.46 ortak kuyruktaki sonuçları bir arada gösterir. Toplam hareket tek başına bir algoritmayı seçmeye yetmez. SSTF ve LOOK en az hareketi verir, ama SSTF açlık yaratabilir. C-SCAN ve C-LOOK daha uzun yol alır, buna karşılık bekleme sürelerini daha eşit dağıtır. Yük hafifken bütün algoritmalar FCFS'ye benzer davranır, çünkü kuyrukta aynı anda genellikle tek istek vardır.

Örnek 11.20

Tanenbaum'un örneği: kafa 11. silindirde, istekler geliş sırasıyla 1, 36, 16, 34, 9, 12. FCFS, SSTF ve yukarı yönde asansör kaç silindir hareket eder?

  1. FCFS. 11 → 1 → 36 → 16 → 34 → 9 → 12: 10 + 35 + 20 + 18 + 25 + 3 = 111.
  2. SSTF. Sıra 12, 9, 16, 1, 34, 36: 1 + 3 + 7 + 15 + 33 + 2 = 61. Kafa 9'dayken 16 (7 uzakta), 1'den (8 uzakta) yakındır.
  3. Asansör, yukarı. Sıra 12, 16, 34, 36, 9, 1: 1 + 4 + 18 + 2 + 27 + 8 = 60.
  4. Yorum. Tanenbaum'un asansörü yukarıdaki son istekte (36) döner, uca gitmez; yani aslında LOOK'tur.
Örnek 11.21

Silindirler 0–199; kafa 84'te ve son olarak 70'e hizmet verdiği için yukarı yönde gidiyor. İstekler geliş sırasıyla 184, 142, 170, 8, 60, 155, 116, 37. Altı algoritmanın toplam hareketi nedir? C-SCAN ve C-LOOK'ta geri dönüş sayılsın.

  1. FCFS. 100 + 42 + 28 + 162 + 52 + 95 + 39 + 79 = 597.
  2. SSTF. 84 → 60 (24) → 37 (23) → 8 (29) → 116 (108) → 142 (26) → 155 (13) → 170 (15) → 184 (14): 252. Başta 60 (24 uzakta), 116'dan (32 uzakta) yakındır.
  3. SCAN. 84 → 116 … 184 → 199, sonra 60, 37, 8: (199 − 84) + (199 − 8) = 115 + 191 = 306.
  4. C-SCAN. 84 → 199 (115), 199 → 0 (199), 0 → 8 → 37 → 60 (60): 374. Dönüş sayılmazsa 175.
  5. LOOK. 84 → 184 (100), 184 → 8 (176): 276.
  6. C-LOOK. 84 → 184 (100), sıçrama 184 → 8 (176), 8 → 60 (52): 328. Sıçrama sayılmazsa 152.
  7. Sıralama. SSTF 252 < LOOK 276 < SCAN 306 < C-LOOK 328 < C-SCAN 374 < FCFS 597. LOOK, SCAN'den yalnız uca (199) boşuna gitmemesiyle ayrılır.

Disk Kolu Çizelgeleme Simülatörü, farklı istek kuyrukları ve başlangıç yönleriyle bu altı algoritmayı yan yana çalıştırır ve kafanın yolunu canlandırır.

11.10.10SPTF: dönme gecikmesini de saymak

Buraya kadarki bütün algoritmalar yalnız silindir uzaklığına baktı. Oysa bir isteğin süresini arama ve dönme birlikte belirler. Yakındaki bir sektör kafanın altından yeni geçmişse, onun için neredeyse bir tur beklemek gerekir.

Tanım 11.32

SPTF (shortest positioning time first, en kısa konumlanma süresi önce) bekleyen isteklerden arama süresi ile dönme gecikmesinin toplamı en küçük olanı seçer.

dönüşkafa: iz 10A (iz 11)B (iz 30)7200 rpm: 1 tur = 8,33 ms, 1 ms’de disk 43,2° dönerA: arama 1 ms, sektör kafaya 30° sonra gelirB: arama 3 ms, sektör kafaya 150° sonra gelir0246810msSSTF → A9,03 msSPTF → B3,47 msaramadönme bekleme
Şekil 11.47: Kafa iz 10'da. A (iz 11) yakın ama sektörü 30° ötede; B (iz 30) uzak ama sektörü 150° ötede. SSTF A'yı seçer ve 9,03 ms harcar; SPTF B'yi seçer ve 3,47 ms harcar.
Örnek 11.22

Disk 7200 rpm dönüyor. Kafa iz 10'da. A isteği iz 11'de: arama 1 ms, sektörü kafaya göre dönme yönünde 30° ötede. B isteği iz 30'da: arama 3 ms, sektörü 150° ötede. SSTF ve SPTF hangisini seçer, ne kadar sürer?

  1. Dönme hızı. Bir tur 60 000 / 7200 = 8,33 ms; disk 1 ms'de 360 / 8,33 = 43,2° döner.
  2. SSTF → A. 1 ms'lik aramada disk 43,2° döner. Sektör 30° ötedeydi; arama bitince kafayı 13,2° geçmiştir. Sektörün yeniden gelmesi için 360 − 13,2 = 346,8° beklenir: 346,8 / 43,2 = 8,03 ms. Toplam 1 + 8,03 = 9,03 ms.
  3. SPTF → B. 3 ms'lik aramada disk 129,6° döner; B'ye 150 − 129,6 = 20,4° kalır. Bekleme 20,4 / 43,2 = 0,47 ms. Toplam 3 + 0,47 = 3,47 ms.
  4. Sonuç. Uzak iz, yakın izden 9,03 / 3,47 ≈ 2,6 kat çabuk sonuç verdi.

SPTF konumlanma süresini en aza indirir, ama işletim sistemi sektörlerin açısal konumunu bilmez. Bu yüzden SPTF diskin içinde, NCQ ile uygulanır. SPTF de SSTF gibi açlığa açıktır. Açlığa karşı kullanılan bir yöntem F-SCAN'dir: tarama başlarken kuyruk dondurulur; tarama sırasında gelen yeni istekler bir sonraki taramayı bekler. Böylece kafanın önüne sürekli yeni istek dökülerek eski istekler geride bırakılamaz.

11.10.11Denetleyici önbelleği ve bozuk sektörler

Denetleyici önbelleği. Disk kendi DRAM'inde (yaklaşık 256 MB) bir iz önbelleği tutar. Kafanın altından geçen ve henüz istenmemiş blokları da okur; sonraki ardışık istek doğrudan önbellekten karşılanır. Bu önbellek işletim sisteminin sayfa önbelleğinden ayrıdır.

Yazma önbelleği ve NCQ. Disk bir yazmayı önbelleğine alıp hemen “tamam” diyebilir. Veri henüz plakaya ulaşmamıştır; güç kesilirse kaybolur. Kalıcılık gerektiğinde işletim sistemi FLUSH komutu ya da FUA (force unit access) bayraklı yazma kullanır. NCQ ise kuyruktaki 32 isteği dönme konumuna göre sıralar; bu, donanımın içindeki bir SPTF'dir.

Bozuk sektörler. Üretimde ya da kullanımda bozulan bir sektöre yazılan veri geri okunamaz. Denetleyici fabrikada bulunan bozuk sektörlerin listesini ve kullanımda bulduklarını izler. SMART verilerindeki “Reallocated Sectors” sayısı bu listenin büyüklüğünü gösterir. Bozuk bir sektörün iki çözümü vardır (Şekil 11.48).

(b) bozuk sektör yedekle eşlenir01234567 ✗891011121314157Y2denetleyici yönlendirir
(c) sektörler kaydırılır, bozuk atlanır0123456✗789101112131415Y28 … 15 numaralı sektörler bir kaydı, bir yedek kullanıldı; sıralı okuma bozulmaz
Şekil 11.48: Bozuk sektör 7'yi ele almanın iki yolu. Üstte sektör 7 bir yedekle eşlenir. Altta 8–15 numaralı sektörler bir kaydırılır ve bir yedek kullanılır; sıralı okuma bozulmaz.
  1. Yedekle eşleme. Bozuk sektör izin sonundaki bir yedek sektörle eşlenir. Basittir, ama sektör 6, 7, 8'i okuyan ardışık bir istek 7 için izin sonuna gidip gelmek zorunda kalır ve bir tur daha ödeyebilir.
  2. Kaydırma. Bozuk sektörden sonraki bütün sektörler bir kaydırılır. İzin tamamı yeniden yazılmalıdır, ama sıra korunur.

İşletim sistemi de bozuk blok listesi tutabilir: Linux'ta badblocks aracı ve dosya sistemlerinin bozuk blok listeleri bu amaçla vardır. Bugünkü disklerde yeniden eşleme tamamen disk içinde yapıldığı için bu liste çoğu zaman boş kalır.

11.11SSD ve NVMe

11.11.1NAND flaş

Tanım 11.33

SSD (solid-state drive), veriyi NAND flaş bellek yongalarında tutan, hareketli parçası olmayan depolama aygıtıdır. Flaş belleğin okuma ve yazma birimi sayfa (yaklaşık 16 KiB), silme birimi ise çok sayıda sayfadan oluşan bloktur (yaklaşık 10–40 MiB).

SSD denetleyicisiFTLECC (LDPC)DRAM önbelleğikalıpkalıpkalıpkalıpkalıpkalıpkalıpkalıpkalıpkalıpkalıpkalıp4–8 kanal × kalıp: paralellikblok (≈ 10–40 MiB) = silme birimisayfa (≈ 16 KiB) = okuma/yazma birimi
Şekil 11.49: SSD iç yapısı: denetleyici (FTL, LDPC hata düzeltme, DRAM önbelleği) 4–8 kanal üzerinden NAND kalıplarına bağlanır. Kanallar ve kalıplar paralel çalışır.

SSD'nin sabit diskten iki temel farkı vardır. Birincisi, hareketli parça olmadığı için arama ve dönme gecikmesi yoktur. Rastgele erişim ardışık erişimle hemen hemen aynı hızdadır. İkincisi, flaşta yerinde güncelleme yoktur. Bir sayfa ancak silinmiş durumdayken yazılabilir ve silme yalnız blok düzeyinde yapılır. Ayrıca her hücrenin dayanabileceği silme sayısı sınırlıdır. Bu sayıya P/E (program/erase) döngüsü denir ve bir hücrede kaç bit tutulduğuna bağlıdır (Tablo 11.12).

Tablo 11.12: NAND hücre türleri.
HücreBit/hücreTipik P/E döngüsüKullanım
SLC1≈ 50–100 binönbellek bölgesi, endüstriyel
MLC2≈ 3–10 bineski kurumsal
TLC3≈ 1–3 binçoğu tüketici ve sunucu SSD'si
QLC4≈ 0,1–1 binyüksek kapasite, okuma ağırlıklı

Hücre başına bit arttıkça hücrenin ayırt etmesi gereken gerilim düzeyi sayısı 2, 4, 8, 16 diye artar. Düzeyler sıklaştıkça okuma yavaşlar ve hücre daha çabuk yıpranır. Kapasiteyi artırmanın öteki yolu dikey yığmadır: bugünkü 3D NAND yongalarında 200–300'ün üzerinde katman bulunur. SSD ile HDD arasındaki GB başına fiyat farkı azalmıştır; yine de büyük kapasite ve arşiv için HDD hâlâ daha ucuzdur.

11.11.2FTL, çöp toplama, TRIM ve aşınma dengeleme

Yerinde güncelleme yapılamıyorsa işletim sisteminin “LBA 1'in üzerine yaz” isteği nasıl karşılanır? Cevap, SSD denetleyicisindeki bir çeviri katmanıdır.

Tanım 11.34

Flaş çeviri katmanı (FTL, flash translation layer), mantıksal blok adreslerini (LBA) fiziksel flaş sayfalarına eşleyen denetleyici yazılımıdır. Güncellenen veri yeni bir boş sayfaya yazılır, eşleme tablosu değiştirilir ve eski sayfa geçersiz sayılır. Buna yer dışı güncelleme denir.

Örnek 11.23

Her biri dört sayfalık iki blok (B0, B1) olan küçük bir SSD'de şu işlemler yapılıyor: LBA 0, 1, 2 yazılır; LBA 1 yeniden yazılır; LBA 2'nin bulunduğu dosya silinir; LBA 3 yazılır. Sonra B0 temizlenir.

  1. İlk yazmalar. FTL üç bloğu B0'ın boş sayfalarına (s0, s1, s2) koyar ve tabloya kaydeder.
  2. Güncelleme. LBA 1 yeniden yazılır. Eski sayfanın üzerine yazılamaz; yeni veri B0.s3'e gider, B0.s1 geçersiz olur.
  3. TRIM. Dosya silinince dosya sistemi TRIM (NVMe'de Deallocate) komutu gönderir. SSD, LBA 2'nin sayfasının artık gereksiz olduğunu öğrenir ve onu geçersiz sayar.
  4. Blok doldu. B0'da boş sayfa kalmamıştır; LBA 3 B1'e yazılır. B0'ın yarısı geçersizdir.
  5. Çöp toplama. B0'daki geçerli sayfalar (LBA 0 ve LBA 1) B1'e kopyalanır, B0 silinir. Kullanıcının istemediği iki ek sayfa yazması yapılmıştır; buna yazma büyütmesi denir.
eşleme tablosu (FTL)LBA 0B0.s0LBA 1B0.s3LBA 2—LBA 3B1.s0blok B0 (silme: 0)s0geçerli: LBA 0s1geçersiz (eski LBA 1)s2geçersiz (eski LBA 2)s3geçerli: LBA 1blok B1 (silme: 0)s0geçerli: LBA 3s1boşs2boşs3boşB0 doldu, LBA 3 B1’eyalnız silinmiş (boş) sayfaya yazılabilir; silme blok düzeyinde
eşleme tablosu (FTL)LBA 0B1.s1LBA 1B1.s2LBA 2—LBA 3B1.s0blok B0 (silme: 1)s0boşs1boşs2boşs3boşblok B1 (silme: 0)s0geçerli: LBA 3s1geçerli: LBA 0s2geçerli: LBA 1s3boşaşınma dengeleme: az silinen bloklar da kullanılıryalnız silinmiş (boş) sayfaya yazılabilir; silme blok düzeyinde
Şekil 11.50: FTL'nin iki anı. Üstte B0 dolmuş ve yarısı geçersizdir; LBA 3 B1'e yazılmıştır. Altta çöp toplamadan sonra: geçerli sayfalar B1'e taşınmış, B0 silinmiş (silme sayısı 1).

TRIM olmasaydı SSD, silinmiş dosyanın sayfalarını hâlâ geçerli sanır ve çöp toplamada onları da boşuna kopyalardı. Yazma büyütmesi artar, hem performans hem ömür düşerdi.

Tanım 11.35

Aşınma dengeleme (wear leveling), bütün flaş bloklarının yaklaşık eşit sayıda silinmesini sağlayan FTL politikasıdır. Yeni yazmalar az silinmiş bloklara yönlendirilir; uzun süre değişmeyen (soğuk) veri de ara sıra taşınır ki onun tuttuğu bloklar da kullanılabilsin.

Aşınma dengeleme olmasaydı sık güncellenen birkaç blok kısa sürede ömrünü doldurur, geri kalanlar ise neredeyse hiç kullanılmamış kalırdı. SSD'ler ayrıca kullanıcıya gösterilmeyen bir yedek alan (over-provisioning) ayırır. Yedek alan, çöp toplamanın her zaman boş blok bulmasını kolaylaştırır ve yazma büyütmesini azaltır.

Sık yapılan hata

SSD'yi sabit disk gibi birleştirmek (defragmente etmek). SSD'de dosyanın parçalarının yan yana olması hız kazandırmaz, çünkü arama yoktur; birleştirme yalnız gereksiz yazma ve aşınma ekler. Windows'taki “İyileştir” komutu SSD'de birleştirme yapmaz, TRIM gönderir. Bunun yerine bölümleri 1 MiB sınırına hizalamak önemlidir: mantıksal bloklar flaş sayfalarına bölünmeden oturur ve tek bir yazma iki sayfaya taşmaz.

11.11.3NVMe ve çoklu kuyruk

Tanım 11.36

NVMe (NVM Express), PCIe üzerine bağlanan flaş depolama için tasarlanmış komut protokolüdür. Her çekirdeğe kendi gönderim (SQ) ve tamamlanma (CQ) kuyruğu çifti verilebilir; kuyruklar bellekte duran dairesel tamponlardır.

Çekirdek 0SQCQÇekirdek 1SQCQÇekirdek 2SQCQÇekirdek 3SQCQNVMe denetleyicisi (PCIe x4)AHCI (SATA)tek kuyruk · 32 komut · kilitNVMe65 535 kuyruk × 65 536 komutçekirdek başına kuyruk: kilitsiz gönderim
Şekil 11.51: NVMe'de her çekirdeğin kendi SQ/CQ çifti vardır ve gönderim kilitsizdir. AHCI'da (SATA) tek bir kuyruk ve 32 komut vardır; bütün çekirdekler aynı kilidi paylaşır.

SATA ve onun AHCI arayüzü dönen diskler için tasarlanmıştı. Tek bir kuyruğu ve en çok 32 komutu vardır; bu sınır bir sabit disk için yeterliydi, çünkü disk zaten aynı anda tek bir isteği yerine getirir. Flaş ise kanallar ve kalıplar boyunca yüzlerce işi paralel yürütebilir. NVMe bu paralelliği açığa çıkarır: 64 K kuyruk ve kuyruk başına 64 K komut tanımlar. Her çekirdek kendi kuyruğuna yazdığı için çekirdekler arasında kilit çekişmesi olmaz; tamamlanma kesmesi de MSI-X ile aynı çekirdeğe gelir.

Birkaç terim sık karıştırılır. NVMe bir protokoldür; M.2 ise bir kart biçimidir (form faktörü). M.2 bir kart SATA da olabilir. U.2/U.3 ve E1.S/E3.S sunucularda kullanılan kablolu ya da yuvalı biçimlerdir. NVMe sürücü desteği 2013'ten beri bütün yaygın işletim sistemlerinde yerleşiktir; AHCI ile uyumsuzluk yalnız protokol düzeyindedir. NVMe disklerde gecikme 10–100 µs arasındadır; PCIe 5.0 x4 diskler yaklaşık 14 GB/s ve saniyede 2 milyonun üzerinde istek sunar. NVMe-oF aynı protokolü ağ üzerinden taşır; ZNS (bölgeli ad alanları) ise flaşın blok yapısını doğrudan yazılıma açar.

11.11.4Linux blk-mq ve G/Ç çizelgeleyicileri

CPU 0yazılım k.CPU 1yazılım k.CPU 2yazılım k.CPU 3yazılım k.çizelgeleyici (isteğe bağlı)dk 0dk 1dk 2dk 3donanım gönderim kuyruklarıNVMe
Şekil 11.52: Linux blk-mq: CPU başına yazılım kuyrukları, isteğe bağlı bir çizelgeleyici ve aygıtın donanım gönderim kuyrukları.

Linux'un blok katmanı NVMe'nin çoklu kuyruk modelini izler. blk-mq (multi-queue block layer) her CPU'ya bir yazılım kuyruğu verir ve bunları aygıtın donanım kuyruklarına bağlar. Tek kuyruklu eski tasarımda milyonlarca IOPS'de bütün çekirdekler aynı kuyruk kilidini beklerdi; blk-mq bu çekişmeyi ortadan kaldırır. Linux 5.0'da tek kuyruklu eski blok katmanı ve onun çizelgeleyicileri (noop, deadline, CFQ) tamamen kaldırıldı. Bugünkü G/Ç çizelgeleyicileri (I/O scheduler) Tablo 11.13'te özetlenmiştir.

Tablo 11.13: Linux blk-mq G/Ç çizelgeleyicileri.
ÇizelgeleyiciFikirUygun
noneSıralama yok, doğrudan gönderNVMe (varsayılan), hızlı SSD
mq-deadlineLBA'ya göre sıralı + okuma/yazma son tarihiSATA HDD/SSD (çoğu dağıtımda varsayılan)
bfqSüreç başına bütçe, adil paylaşımMasaüstü, yavaş aygıtlarda etkileşim
kyberOkuma/yazma gecikme hedefleriyle sınırlamaHızlı çok kuyruklu aygıtlar

Bir aygıtın çizelgeleyicisi sysfs üzerinden okunur ve değiştirilir:

$ cat /sys/block/nvme0n1/queue/scheduler
[none] mq-deadline kyber bfq
$ echo bfq | sudo tee /sys/block/sda/queue/scheduler

Köşeli parantez içindeki ad etkin çizelgeleyicidir; NVMe diskte bu none'dır. İkinci komut bir SATA diskin çizelgeleyicisini bfq yapar. sudo tee kullanılır, çünkü yönlendirme (>) sudo'nun dışında, kullanıcının yetkisiyle yapılırdı.

NVMe'de neden hiç sıralama yapılmaz? Kol çizelgelemenin varlık nedeni arama süresini azaltmaktır. SSD'de arama yoktur ve erişim süresi konumdan neredeyse bağımsızdır. Çoklu donanım kuyrukları zaten her çekirdeğe paralel bir yol verir. Sıralama yalnız CPU harcar ve gecikme ekler. SSD'de çizelgeleyicinin işi sıralamak değil, gerektiğinde adaleti ve gecikme hedeflerini korumaktır; bunun için mq-deadline, bfq ya da kyber seçilebilir.

11.12RAID ve kararlı depolama

11.12.1RAID: diskleri birleştirmek

Tanım 11.37

RAID (Redundant Array of Independent Disks), birçok diski işletim sistemine tek bir disk gibi gösteren düzendir. Şeritleme (striping) ardışık blokları farklı disklere dağıtarak paralellik sağlar. Aynalama (mirroring) her bloğun kopyasını başka bir diskte tutar. Eşlik (parity) ise birkaç bloğun XOR'unu ayrı bir blokta saklayarak kaybolan bir bloğun yeniden hesaplanmasını sağlar.

RAID fikri Patterson, Gibson ve Katz'ın 1988 tarihli makalesine dayanır. Makale, pahalı tek büyük disk (SLED, single large expensive disk) yerine ucuz disklerden bir dizi kurmayı önerdi. Açılımdaki “I” bu yüzden ilk başta inexpensive idi. Bugün “bağımsız” (independent) olarak okunur. Standart düzeyler 0–6 arasıdır. RAID bir donanım kartıyla ya da yazılımla kurulabilir: Linux'ta mdadm, ZFS'te RAID-Z, Btrfs ve Windows Depolama Alanları yazılım RAID örnekleridir.

RAID 0: şeritlemedisk 0A0A2A4A6disk 1A1A3A5A7RAID 1: aynalamadisk 0A0A1A2A3disk 1A0A1A2A3paralel, artıklık yokher blok iki kez
Şekil 11.53: RAID 0 ardışık blokları (A0, A1, …) disklere sırayla dağıtır: paralel ama artıklık yok. RAID 1 her bloğu iki diske birden yazar.
Tablo 11.14: Dört adet 4 TB diskle RAID düzenleri.
DüzenKullanılabilirDayanırKüçük rastgele yazma maliyeti
RAID 0 (şeritleme)16 TB0 disk1 G/Ç
RAID 1 (4 kopya ayna)4 TB3 disk4 G/Ç (her kopyaya)
RAID 10 (ayna + şerit)8 TB1 (çiftler farklıysa 2)2 G/Ç
RAID 5 (dağıtılmış eşlik)12 TB1 disk4 G/Ç (2 oku + 2 yaz)
RAID 6 (çift eşlik)8 TB2 disk6 G/Ç (3 oku + 3 yaz)
Sık yapılan hata

RAID'i yedek saymak. RAID disk arızasına karşı korur, ama kullanıcı hatasına, yazılım hatasına ya da fidye yazılımına karşı korumaz. Silinen bir dosya bütün disklerde aynı anda silinir; bozulan bir dosya her kopyada bozulur. Yedek, ayrı bir ortamda ve zamanda alınmış bir kopyadır.

11.12.2RAID düzenleri

RAID 0: şeritleme, artıklık yokdisk 0ş0ş4ş8ş12disk 1ş1ş5ş9ş13disk 2ş2ş6ş10ş14disk 3ş3ş7ş11ş15
RAID 5: eşlik disklere dağıtılırdisk 0ş0ş4ş8ş12disk 1ş1ş5ş9P3disk 2ş2ş6P2ş13disk 3ş3P1ş10ş14disk 4P0ş7ş11ş15
RAID 6: P ve Q eşliği dağıtılırdisk 0Q0ş3ş6ş9disk 1ş0ş4ş7P3disk 2ş1ş5P2Q3disk 3ş2P1Q2ş10disk 4P0Q1ş8ş11
Şekil 11.54: Üstte RAID 0: şeritler (ş0, ş1, …) dört diske sırayla dağılır. Ortada RAID 5: eşlik blokları (P) her şeritte başka bir diskte durur. Altta RAID 6: P ve Q eşlikleri beş diske dağıtılır.
  • RAID 0. Ardışık şeritler farklı disklere gider. Büyük isteklerde n disk n kat bant genişliği verir. Artıklık yoktur: bir disk bozulursa dizideki her şey kaybolur.
  • RAID 1. Her yazma iki kez yapılır. Okuma iki diskten birinden yapılabilir; yük diskler arasında dağıtılır. Kapasitenin yarısı artıklığa gider.
  • RAID 10. Önce aynalanır, sonra şeritlenir. Yazma hızlıdır ve bozulan disk yalnız aynasından kopyalanarak hızla yeniden kurulur. Veritabanlarında yaygındır.
  • RAID 2 ve 3. RAID 2 sözcük düzeyinde Hamming kodu, RAID 3 bayt düzeyinde tek bir eşlik diski kullanır. Her iki düzende de bütün kolların eş zamanlı dönmesi gerekir. Diskler zaten kendi içlerinde ECC yaptığı için bugün kullanılmazlar.
  • RAID 4. Blok düzeyinde şeritleme ve ayrı bir eşlik diski. Her küçük yazma eşlik diskini de günceller; eşlik diski darboğaz olur.
  • RAID 5. Eşlik blokları disklere sırayla dağıtılır ve darboğaz kalkar. n diskin n − 1'inin kapasitesi kullanılır; bir disk kaybına dayanır.
  • RAID 6. İki eşlik tutar: P (XOR) ve Q (GF(28) cisminde Reed–Solomon kodu). İki disk kaybına dayanır. Büyük disklerde yeniden kurma saatler sürdüğü için, bu sürede ikinci bir arıza riskine karşı tercih edilir.

11.12.3Eşlik ve XOR

Eşliğin temeli XOR (⊕) işlemidir. İki bit farklıysa XOR 1, aynıysa 0 verir. Bit dizileri üzerinde XOR, her bit konumunda ayrı ayrı uygulanır. İşlemin üç özelliği RAID'in bütün hesaplarını taşır: x ⊕ x = 0, x ⊕ 0 = x, ve işlem değişme ve birleşme özelliklidir.

Teorem 11.1 (Eşlikle kurtarma)

P = D0 ⊕ D1 ⊕ … ⊕ Dn−1 olsun. Hangisinin kaybolduğu bilinen herhangi bir Dk bloğu, geri kalan veri bloklarının ve P'nin XOR'una eşittir: Dk = P ⊕ (k dışındaki bütün Di'lerin XOR'u).

Kanıt kısadır. P'nin tanımında Dk dışındaki blokları her iki yana XOR'layalım. Her Di (i ≠ k) sağ tarafta iki kez geçer ve x ⊕ x = 0 olduğu için yok olur; geriye Dk kalır. Tek bir eşliğin yetmesinin nedeni, hangi diskin bozulduğunun bilinmesidir: disk ya hiç yanıt vermez ya da ECC hatası bildirir. Eşlik bozuk bloğu bulmaz, yalnız yerini bildiğimiz bloğu yeniden hesaplar.

Örnek 11.24

Bir RAID 5 şeridinde D0 = 1011 0010, D1 = 0101 1100, D2 = 1110 0111 (8 bitlik örnek bloklar). Eşlik nedir? Disk 1 bozulursa D1 nasıl bulunur? D1 yerine 0000 1111 yazılırsa yeni eşlik nedir?

  1. Eşlik. D0 ⊕ D1 = 1110 1110; ⊕ D2 = 0000 1001. P = 0000 1001. Her bit konumunda 1'lerin sayısı artık çifttir.
  2. Kurtarma. D1 = D0 ⊕ D2 ⊕ P = 1011 0010 ⊕ 1110 0111 ⊕ 0000 1001. İlk ikisi 0101 0101, P ile 0101 1100 verir: özgün D1. Yeni diske bütün şeritler böyle yeniden yazılır; buna yeniden kurma denir.
  3. Küçük yazma. D1' = 0000 1111. Eski D1 ve eski P okunur. P' = P ⊕ D1 ⊕ D1' = 0000 1001 ⊕ 0101 1100 ⊕ 0000 1111 = 0101 1010. Sonra D1' ve P' yazılır: 4 G/Ç (oku-değiştir-yaz).
  4. Denetim. D0 ⊕ D1' ⊕ D2 = 1011 0010 ⊕ 0000 1111 ⊕ 1110 0111 = 0101 1010 = P'. Şeridin tamamı okunmadan doğru eşlik bulunmuştur.
D0 (disk 0)1011 0010D1 (disk 1)0101 1100D2 (disk 2)1110 0111P (disk 3)0000 1001D1 = 1011 0010 ⊕ 1110 0111 ⊕ 0000 1001 = 0101 1100
D0 (disk 0)1011 0010D1 (disk 1)0000 1111D2 (disk 2)1110 0111P (disk 3)0101 1010P' = P ⊕ D1 ⊕ D1' = 0101 1010
Şekil 11.55: Üstte kurtarma: D1 = D0 ⊕ D2 ⊕ P = 0101 1100. Altta küçük yazma: D1' = 0000 1111 yazılınca P' = P ⊕ D1 ⊕ D1' = 0101 1010.

Yeniden kurmanın bir riski de vardır. Tüketici sınıfı disklerde düzeltilemeyen okuma hatası (URE, unrecoverable read error) oranı yaklaşık 1014 bitte 1'dir. Bozulan diski kurmak için kalan disklerin tamamı okunmalıdır.

Örnek 11.25

RAID 5 dizisinde bir disk bozuldu; yeniden kurma için kalan disklerden toplam 12 TB okunacak. URE oranı 1014 bitte 1. Okuma sırasında beklenen hata sayısı ve en az bir hata olasılığı nedir?

  1. Bit sayısı. 12 × 1012 B × 8 = 9,6 × 1013 bit.
  2. Beklenen hata. 9,6 × 1013 / 1014 = 0,96.
  3. En az bir hata. Hatalar bağımsız ve seyrekse sayıları yaklaşık Poisson dağılır: 1 − e−0,96 ≈ 0,62. Yeniden kurmanın en az bir okunamayan sektöre takılma olasılığı yaklaşık %62'dir.
  4. Yorum. RAID 5'te bu anda artıklık kalmamıştır; takılan sektörün şeridi kurtarılamaz. Büyük dizilerde RAID 6 bu yüzden seçilir: ikinci eşlik, yeniden kurma sırasındaki bu hatayı da düzeltir.

11.12.4Kod izleme: küçük yazma ve kurtarma

void kucuk_yaz(int d, uint8_t yeni) {
  uint8_t eski = oku(d);   /* G/Ç 1 */
  uint8_t p = oku(P);      /* G/Ç 2 */
  p = p ^ eski ^ yeni;
  yaz(d, yeni);            /* G/Ç 3 */
  yaz(P, p);               /* G/Ç 4 */
}
uint8_t kur(int bozuk) {
  uint8_t x = 0;
  for (int d = 0; d <= P; d++)
    if (d != bozuk)
      x ^= oku(d);         /* P dahil */
  return x;
}

Kodda diskler 0'dan numaralanır; P, eşlik diskinin numarasıdır (burada 3). oku ve yaz birer disk G/Ç'sidir. kucuk_yaz d diskindeki bloğu yeni ile değiştirir. Önce eski veri ve eski eşlik okunur (2 G/Ç). p ^ eski eski verinin etkisini eşlikten çıkarır, çünkü x ⊕ x = 0'dır; ^ yeni yeni değeri ekler. Sonra yeni veri ve yeni eşlik yazılır (2 G/Ç). Şeridin öteki veri diskleri hiç okunmaz. kur ise bozuk disk dışındaki bütün diskleri, eşlik dahil, XOR'layarak kaybolan bloğu hesaplar.

Tablo 11.15: kucuk_yaz(1, 0011 0011) ve ardından kur(2). Başta D0 = 1011 0010, D1 = 0110 1001, D2 = 1100 0101, P = 0001 1110.
AdımİşlemDeğerG/Ç
1eski = oku(1)0110 10011
2p = oku(P)0001 11102
3p = p ^ eski ^ yeni0001 1110 ⊕ 0110 1001 ⊕ 0011 0011 = 0100 01002
4yaz(1, yeni)D1 = 0011 00113
5yaz(P, p)P = 0100 01004
6kur(2), d = 0x = 1011 0010
7d = 1x = 1011 0010 ⊕ 0011 0011 = 1000 0001
8d = 2 atlanır; d = 3 (P)x = 1000 0001 ⊕ 0100 0100 = 1100 01013 okuma
disk 0D01011 0010disk 1D10011 0011disk 2D2????disk 3P0100 0100D2 = 1100 0101disk G/Ç: 3
Şekil 11.56: kur(2)'nin sonucu: x = 1100 0101, kaybolan D2 ile aynı.

Kurtarılan değer özgün D2 ile aynıdır. Yeni diske her şerit için 3 okuma yapılarak yeniden kurulur ve bu süre boyunca dizi korumasızdır. Küçük yazmanın da bir zayıf noktası vardır: 4. ve 5. adımdaki iki yazma arasında sistem çökerse veri yeni, eşlik eski kalır ve şerit tutarsız olur. Buna yazma deliği (write hole) denir. Ardından bir disk bozulursa yanlış eşlikten yanlış veri hesaplanır. Donanım RAID kartları bunu pil destekli önbellekle, ZFS ise şeritleri hiçbir zaman yerinde güncellemeyerek önler.

Örnek 11.26

Dört veri diskli bir şeritte D0 = 1100 1010, D1 = 0011 0110, D2 = 1010 0001, D3 = 0101 1100. P'yi bulun; D3'ün diski bozulursa D3'ü kurtarın; D0 yerine 1111 0000 yazılırsa yeni P nedir?

  1. P. D0 ⊕ D1 = 1111 1100; ⊕ D2 = 0101 1101; ⊕ D3 = 0000 0001. P = 0000 0001.
  2. Kurtarma. D0 ⊕ D1 ⊕ D2 ⊕ P = 0101 1101 ⊕ 0000 0001 = 0101 1100: özgün D3.
  3. Küçük yazma. Değişen bitler D0 ⊕ Y = 1100 1010 ⊕ 1111 0000 = 0011 1010. P' = P ⊕ 0011 1010 = 0011 1011.
  4. Denetim. Y ⊕ D1 ⊕ D2 ⊕ D3 = 1111 0000 ⊕ 0011 0110 ⊕ 1010 0001 ⊕ 0101 1100 = 0011 1011. Sonuç aynıdır.

11.12.5RAID performansı

Bir RAID düzeninin hızı, tek bir diskin iki ölçüsüyle kolayca ifade edilir. Bunlar rastgele küçük isteklerdeki hız R (IOPS) ve büyük ardışık aktarımdaki hız S (MB/s)'dir. Arpaci-Dusseau'ların modeli bu iki sayıdan yola çıkar.

Örnek 11.27

Tek disk 7200 rpm, 8 ms ortalama arama, 200 MB/s ortam hızına sahip. Dizi N = 4 diskten oluşuyor. R ve S nedir? RAID 0, 1, 4 ve 5'in hızları nedir?

  1. R. Rastgele 4 KiB okuma 12,19 ms sürer; R = 1000 / 12,19 ≈ 82 IOPS (0,34 MB/s).
  2. S. Ardışık 10 MiB parça: aktarım 10 485 760 / (200 × 106) = 52,43 ms; toplam 8 + 4,17 + 52,43 = 64,6 ms. S = 10,49 MB / 64,6 ms ≈ 162 MB/s.
  3. RAID 0. Her istek tek diske gider ve dört disk paralel çalışır: ardışık 4 · S ≈ 649 MB/s, rastgele okuma ve yazma 4 · R ≈ 328 IOPS.
  4. RAID 1 (iki ayna çifti). Kapasite 2 disk. Rastgele okuma her iki kopyadan yapılabilir: 4 · R ≈ 328. Her yazma iki diske gider: 2 · R ≈ 164. Ardışık okuma ise yalnız 2 · S ≈ 325 MB/s'dir.
  5. RAID 4. Kapasite 3 disk. Ardışık 3 · S ≈ 487 MB/s; rastgele okuma 3 · R ≈ 246. Küçük yazmada eşlik diski her yazmada 1 okuma ve 1 yazma yapar: R / 2 ≈ 41 IOPS.
  6. RAID 5. Kapasite 3 disk. Ardışık 3 · S ≈ 487 MB/s; rastgele okuma bütün diskleri kullanır: 4 · R ≈ 328. Küçük yazmanın 4 G/Ç'si disklere yayılır: (4 / 4) · R ≈ 82 IOPS.
rastgele 4 KiB yazma (IOPS), N = 4RAID 0328RAID 1164RAID 441RAID 582
Şekil 11.57: Dört diskte rastgele küçük yazma hızı: RAID 0 328, RAID 1 164, RAID 4 41, RAID 5 82 IOPS.
Tablo 11.16: N diskli dizilerin hızları (R: tek diskin rastgele IOPS'si, S: ardışık hızı, D: tek isteğin gecikmesi). Sayılar N = 4, R ≈ 82, S ≈ 162 MB/s içindir.
DüzenKapasiteArdışık okuma/yazmaRastgele okumaRastgele yazmaYazma gecikmesi
RAID 0N = 4N·S ≈ 649 MB/sN·R ≈ 328N·R ≈ 328D
RAID 1 (ikili ayna)N/2 = 2(N/2)·S ≈ 325 MB/sN·R ≈ 328(N/2)·R ≈ 164D (yavaş kopya)
RAID 4N − 1 = 3(N−1)·S ≈ 487 MB/s(N−1)·R ≈ 246R/2 ≈ 412D
RAID 5N − 1 = 3(N−1)·S ≈ 487 MB/sN·R ≈ 328(N/4)·R ≈ 822D

Tablonun üç satırı açıklama ister. Aynada ardışık okuma neden yalnız (N/2)·S'dir? İki kopya blokları paylaşabilir: bir disk çift numaralı blokları, öteki tek numaralıları okur. Ama her disk atladığı blokların da altından dönerek geçmek zorundadır; atlanan bloklar zaman kazandırmaz. RAID 4'te küçük yazma her seferinde eşlik diskinde bir okuma ve bir yazma ister. Dizinin hızı tek diskin yarısına iner ve disk eklemek hiç işe yaramaz. RAID 5 aynı 4 G/Ç'yi bütün disklere yaydığı için küçük yazma hızı N·R/4 olur ve disk sayısıyla büyür. Tam bir şerit yazıldığında ise eski veri okunmaz; eşlik yeni verilerden hesaplanır ve hız (N − 1)·S olur.

Örnek 11.28

Tek disk 150 rastgele IOPS veriyor. 8 diskli dizide küçük rastgele yazma hızı nedir: RAID 0, RAID 10 (4 aynalı çift), RAID 4, RAID 5, RAID 6? Disk sayısı 16'ya çıkarsa hangisi hiç hızlanmaz?

  1. RAID 0. Yazma başına 1 G/Ç: 8 × 150 = 1200 IOPS.
  2. RAID 10. İki kopya, 2 G/Ç: 1200 / 2 = 600.
  3. RAID 4. 4 G/Ç, ama ikisi hep eşlik diskinde: 150 / 2 = 75.
  4. RAID 5. 4 G/Ç, disklere yayılır: 1200 / 4 = 300.
  5. RAID 6. 6 G/Ç (3 oku + 3 yaz): 1200 / 6 = 200.
  6. 16 disk. RAID 4 yine 75'te kalır; darboğaz tek eşlik diskidir. RAID 5 600'e çıkar.

11.12.6Kararlı depolama

RAID bozulan sektörlere ve disklere karşı korur, ama yazma sırasında bir çökme olursa ne olacağına cevap vermez. Aynalı iki diske aynı anda yazılırken güç kesilirse iki kopya da yarım kalabilir.

Tanım 11.38

Kararlı depolama (stable storage), bir yazmanın ya doğru biçimde tamamlandığını ya da eski verinin yerinde kaldığını garanti eden depolama düzenidir. Tanenbaum'un modeli üç varsayıma dayanır: iki diskte aynı bloğun aynı anda bozulma olasılığı ihmal edilir; hatalı veri ECC ile fark edilir; CPU çökerse süren yazma durur.

Düzen iki diskle ve üç işlemle kurulur. Kararlı yazma: blok önce disk 1'e yazılır, geri okunur ve doğrulanır; gerekirse n kez yeniden denenir, olmazsa yedek sektöre yazılır. Disk 1 başarıyla yazılınca aynı işlem disk 2'ye yapılır. Kararlı okuma: blok disk 1'den okunur; geçerli ECC ile okunamazsa disk 2'den okunur. Kurtarma: çökmeden sonra iki diskteki bloklar karşılaştırılır ve tutarsızlık giderilir.

disk 1ECC hatasıdisk 2eskikurtarma:disk 2 → disk 1 kopyala
disk 1yenidisk 2eskikurtarma:disk 1 → disk 2 kopyala
Şekil 11.58: Çökmeden sonra kurtarma. Üstte çökme disk 1'e yazılırken oldu: disk 1 bozuk (ECC), eski değer disk 2'den geri getirilir. Altta disk 1 yazıldı, disk 2 yazılmadan çöktü: yeni değer disk 2'ye kopyalanır.
Örnek 11.29

Kararlı yazmanın beş anında çökme olursa kurtarma ne yapar?

  1. Disk 1'e yazmadan önce. İki kopya da eskidir. Bir şey yapılmaz; yazma hiç olmamış sayılır.
  2. Disk 1'e yazarken. Disk 1'deki blok yarım kalmıştır ve ECC hatası verir. Eski değer disk 2'den disk 1'e kopyalanır.
  3. Disk 1 yazıldı, disk 2'ye başlanmadı. İki kopya da geçerli ama farklıdır. Kural: disk 1 kazanır. Yeni değer disk 2'ye yazılır ve yazma tamamlanır.
  4. Disk 2'ye yazarken. Disk 2 bozuktur (ECC); yeni değer disk 1'den kopyalanır.
  5. İki kopya da yeni. Yazma tamamlanmıştır; bir şey yapılmaz.

Her durumda sonuç ya tamamen eski ya tamamen yenidir. Bunu sağlayan yazma sırasının sabit olmasıdır. Herhangi bir anda en az bir kopya ya tamamen eski ya tamamen yeni ve geçerlidir; kurtarma o kopyayı ötekine yayar. RAID 1 bunu tek başına sağlamaz, çünkü iki diske aynı anda yazar ve hangisinin “doğru” olduğuna dair bir sıra tutmaz.

Aynı fikir bugün başka biçimlerde yaşar. Dosya sistemleri değişiklikleri önce bir günlüğe yazar (journaling) ve ancak günlük kalıcı olunca asıl yerlerine işler. Yazma bariyerleri ve FUA, belirli yazmaların diskte kalıcı olduğundan emin olmayı sağlar. Kurumsal SSD'lerde güç kaybı koruma kapasitörleri, elektrik kesildiğinde önbellekteki veriyi flaşa yazmaya yetecek enerjiyi saklar.

11.13Özet

  • İşletim sistemi aygıtla değil, aygıt denetleyicisiyle konuşur. Denetleyicinin durum, komut, veri giriş ve veri çıkış yazmaçları vardır: yazmaca yazmak komut vermek, okumak durum öğrenmektir.
  • Yazmaçlara kapı eşlemeli G/Ç'de özel IN/OUT komutlarıyla, bellek eşlemeli G/Ç'de sıradan yükleme ve saklama komutlarıyla erişilir. MMIO'da yazmaç sayfası önbelleklenemez eşlenmeli, işaretçi volatile olmalıdır. Bugün PCIe aygıtları BAR'larla MMIO kullanır.
  • Üç aktarım yöntemi vardır. Yoklamada CPU durum yazmacını döngüyle bekler; kesme güdümlü G/Ç'de her birim için bir kesme gelir; DMA'da CPU yalnız kurulumu ve bitiş kesmesini işler. 4096 karakterlik örnekte CPU maliyetleri 4,096 s, 8,19 ms ve yaklaşık 7 µs'dir.
  • Kesme geldiğinde CPU komut sınırında durur, donanım PC ve PSW'yi kaydeder, vektör IDT'de indis olur, işleyici aygıtı onaylar ve bekleyeni uyandırır, çizelgeleyici seçer, iret ile dönülür. Modern aygıtlar kesmeyi MSI-X ile bellek yazması olarak gönderir. İşleyici üst yarı ve alt yarı olarak ikiye bölünür.
  • Dağıt-topla bitişik olmayan sayfalara tek DMA işlemiyle yazar; IOMMU aygıt adreslerini çevirir ve aygıtları birbirinden yalıtır.
  • G/Ç yazılımı katmanlıdır: kullanıcı kütüphanesi, aygıttan bağımsız yazılım (Linux'ta VFS, sayfa önbelleği, dosya sistemi, blok katmanı), aygıt sürücüsü ve kesme işleyicisi. Sürücüler ortak bir işlev tablosu (file_operations) sunar.
  • Tamponlama hız farkını, boyut farkını ve kopya anlamını karşılar. Dairesel tampon N yuvada en çok N − 1 bayt tutar: baş = kuyruk boş, (kuyruk + 1) mod N = baş dolu demektir. Tek üretici ve tek tüketicide kilit gerekmez.
  • Bloke eden çağrı veri gelene kadar dönmez; bloke etmeyen çağrı ne varsa onu ya da EAGAIN döndürür; eş zamansız çağrı işi başlatır ve sonra haber verir. epoll hazır olmayı, io_uring tamamlanmayı bildirir.
  • Saat donanımı her darbede bir sayacı azaltır ve sıfırda kesme üretir. Tiksiz çekirdek boşta tiki durdurur. Bekleyen zamanlayıcılar fark listesinde tutulur: tik başına iş O(1), ekleme O(n)'dir.
  • Disk erişim süresi = arama + dönme gecikmesi + aktarım. 7200 rpm, 8 ms arama ve 200 MB/s ile rastgele 4 KiB okuma 12,19 ms (≈ 82 IOPS), ardışık 1 MiB okuma 17,41 ms (≈ 60 MB/s) sürer.
  • Disk kolu çizelgelemede ortak kuyruğun (kafa 53) toplam hareketleri: FCFS 640, SSTF 236, SCAN 236, C-SCAN 382 (dönüşsüz 183), LOOK 208, C-LOOK 322 (sıçramasız 153). SSTF açlık yaratabilir; tarama algoritmaları yaratmaz. SPTF dönme gecikmesini de hesaba katar ve diskin içinde NCQ ile uygulanır.
  • SSD'de arama yoktur ve yerinde güncelleme yapılamaz. FTL yer dışı güncelleme, çöp toplama ve aşınma dengeleme yapar; TRIM yazma büyütmesini azaltır. NVMe çekirdek başına kuyruk sunar; Linux blk-mq bu modeli izler ve NVMe'de varsayılan çizelgeleyici none'dır.
  • RAID şeritleme, aynalama ve eşlikle diskleri birleştirir. Eşlik XOR'dur; kaybolan blok kalanların XOR'udur. RAID 5'te küçük yazma 4 G/Ç, RAID 6'da 6 G/Ç ister. RAID 4'te eşlik diski darboğazdır. RAID yedek değildir.
  • Kararlı depolama iki diske sırayla yazarak her çökmeden sonra ya tamamen eski ya tamamen yeni veriyi garanti eder. Bugün aynı fikri günlükleme, FUA ve güç kaybı koruması sürdürür.

11.14Kavram listesi

11.15Kendini Dene

  1. Silindirler 0–199; kafa 100'de ve yukarı yönde gidiyor. İstekler geliş sırasıyla 55, 58, 39, 18, 90, 160, 150, 38, 184. FCFS, SSTF, LOOK ve C-LOOK (sıçrama sayılsın) toplam hareketi nedir?

    Cevap

    FCFS: 45 + 3 + 19 + 21 + 72 + 70 + 10 + 112 + 146 = 498. SSTF: 90, 58, 55, 39, 38, 18, 150, 160, 184 sırasıyla 10 + 32 + 3 + 16 + 1 + 20 + 132 + 10 + 24 = 248. LOOK: 100 → 184 (84), 184 → 18 (166): 250. C-LOOK: 100 → 184 (84), sıçrama 184 → 18 (166), 18 → 90 (72): 322.

  2. 5400 rpm dönen bir diskin ortalama arama süresi 10 ms, ortam hızı 100 MB/s (1 MB = 106 B). 8 KiB'lık rastgele bir okuma ortalama ne kadar sürer? Disk saniyede yaklaşık kaç rastgele okuma yapabilir?

    Cevap

    Bir tur 60 000 / 5400 ≈ 11,11 ms; ortalama dönme gecikmesi 5,56 ms. Aktarım 8192 / 108 s ≈ 0,08 ms. Toplam 10 + 5,56 + 0,08 ≈ 15,64 ms; saniyede 1000 / 15,64 ≈ 64 okuma.

  3. N = 4 yuvalı dairesel tampon boş. Çağrılar sırayla: koy x, koy y, al, koy z, koy w, koy v, al, koy u. Hangi çağrı başarısız olur? Sonunda baş ve kuyruk nedir?

    Cevap

    x yuva 0'a, y yuva 1'e yazılır (kuyruk 2). al x'i okur (baş 1). z yuva 2'ye, w yuva 3'e yazılır; kuyruk başa sarar (0). koy v'de (0 + 1) mod 4 = 1 = baş: tampon dolu, −1 döner. al y'yi okur (baş 2). u yuva 0'a yazılır (kuyruk 1). Sonuç: baş = 2, kuyruk = 1; tamponda z, w, u vardır.

  4. Fark listesi A 5, B 2, C 6. 9 tik sonra dolacak X eklenirse liste ne olur? ekle kaç düğüm geçer?

    Cevap

    A'nın farkı 5 ≤ 9 → t = 4; B'nin farkı 2 ≤ 4 → t = 2; C'nin farkı 6 > 2, döngü durur. X 2 farkıyla C'den önce girer, C'nin farkı 6 − 2 = 4 olur. Liste A 5, B 2, X 2, C 4. İki düğüm geçilir. C'nin mutlak zamanı 13 tik sonrası olarak kalır.

  5. RAID 5 şeridinde D0 = 0110 1101, D2 = 1001 0011, P = 1010 0110. D1'in diski bozuldu. D1 nedir?

    Cevap

    D1 = D0 ⊕ D2 ⊕ P. D0 ⊕ D2 = 1111 1110; ⊕ P = 0101 1000. D1 = 0101 1000.

  6. Bir ağ kartı 2000 paketlik bir patlama alıyor. Kesme güdümlü yöntemde her paket için 3 µs'lik bir kesme, DMA'lı halkada ise 10 µs kurulum ve patlama sonunda tek bir 3 µs'lik kesme gerekiyor. CPU maliyetleri nedir? Tek paketlik trafikte hangisi daha ucuzdur?

    Cevap

    Kesme güdümlü: 2000 × 3 = 6000 µs = 6 ms. DMA: 10 + 3 = 13 µs; yaklaşık 460 kat az. Tek pakette kesme güdümlü 3 µs, DMA 13 µs harcar: sabit kurulum maliyeti küçük aktarımda kendini geri ödemez.

  7. Bir program diskteki bir dosyayı epoll ile izleyip “hazır” olduğunda okumak istiyor. Bu neden eş zamansız okuma sağlamaz? Hangi arayüz sağlar?

    Cevap

    epoll yalnız hazır olmayı bildirir. Disk dosyaları her zaman hazır görünür, çünkü çekirdek bir disk okumasını EAGAIN ile reddetmez, bloke ederek tamamlar. Okuma yine read içinde beklenir. İşi başlatıp tamamlanmayı sonra bildiren io_uring (ya da Windows'ta IOCP) gerçek eş zamansızlık sağlar.

  8. Bir SSD'de dosya sistemi TRIM göndermiyor. Kullanıcı büyük bir dosyayı sildikten sonra disk yeniden doldukça neden yavaşlar ve neden daha çabuk yıpranır?

    Cevap

    SSD silinen dosyanın sayfalarını hâlâ geçerli sanır. Çöp toplama bir bloğu temizlerken bu sayfaları da başka bloğa kopyalar. Kopyalanan her sayfa ek bir yazmadır: yazma büyütmesi artar, yazmalar çöp toplamayı bekler ve hücreler gereksiz yere silinip yazılır.

  9. Bir sürücü durum yazmacını int *durum işaretçisiyle okuyan bir bekleme döngüsü içeriyor. Optimizasyon açılınca döngü hiç bitmiyor. Neden? İki düzeltme nedir?

    Cevap

    Derleyici *durum'un döngüde değişmediğini varsayar ve okumayı bir kez yapar. İşaretçi volatile int * yapılmalıdır. Ayrıca yazmaç sayfası önbelleklenemez eşlenmelidir (Linux'ta ioremap); yoksa CPU önbellekteki eski değeri okur.

  10. Kararlı depolamada iki kopyanın ikisi de geçerli ECC veriyor ama değerleri farklı. Kurtarma ne yapar? Bu durum hangi anda çökme olduğunu gösterir?

    Cevap

    Disk 1 kazanır; değeri disk 2'ye kopyalanır ve yazma tamamlanır. Bu durum, disk 1'e yazmanın bitip doğrulandığını, disk 2'ye yazmanın ise henüz başlamadığını gösterir. Disk 2'ye yazarken çökülseydi disk 2'deki blok ECC hatası verirdi.

11.16İleri okuma

  • R. H. Arpaci-Dusseau ve A. C. Arpaci-Dusseau, Operating Systems: Three Easy Pieces, “I/O Devices”, “Hard Disk Drives”, “Redundant Arrays of Inexpensive Disks” ve “Flash-based SSDs” bölümleri: disk zamanlamasını, RAID başarım modelini ve FTL'yi kısa ve hesaplı anlatır; ostep.org'da ücretsizdir.
  • D. A. Patterson, G. Gibson ve R. H. Katz, “A Case for Redundant Arrays of Inexpensive Disks (RAID)”, SIGMOD 1988: RAID düzeylerini ilk kez tanımlayan, bugün de okunabilir kısa makaledir.
  • J. Axboe, “Efficient IO with io_uring” (kernel.dk): io_uring'in tasarımcısının arayüzü, halkaları ve SQPOLL kipini anlattığı belgedir.
  • J. Corbet, A. Rubini ve G. Kroah-Hartman, Linux Device Drivers, 3. baskı, O'Reilly, 2005: karakter sürücüleri, kesme işleme ve DMA üzerine örnekli klasik kaynaktır; çekirdek arayüzlerinin bir kısmı eskimiştir ama kavramlar geçerlidir.

11.17Kaynaklar

  1. A. Silberschatz, P. B. Galvin, G. Gagne, Operating System Concepts, 10. baskı, Wiley, 2018 — Bölüm 11: Mass-Storage Structure; Bölüm 12: I/O Systems.
  2. A. S. Tanenbaum, H. Bos, Modern Operating Systems, 4. baskı (2014) / 5. baskı (2023), Pearson — Bölüm 5: Input/Output.
  3. R. H. Arpaci-Dusseau, A. C. Arpaci-Dusseau, Operating Systems: Three Easy Pieces (OSTEP), ostep.org — “I/O Devices”, “Hard Disk Drives”, “RAID”, “Flash-based SSDs”.
  4. D. A. Patterson, G. Gibson, R. H. Katz, “A Case for Redundant Arrays of Inexpensive Disks (RAID)”, SIGMOD 1988.
  5. NVM Express Base Specification 2.x; PCI-SIG PCIe 5.0/6.0; USB4 Specification; UEFI Specification (GPT).
  6. Linux çekirdek belgeleri: “Multi-Queue Block IO Queueing Mechanism (blk-mq)”, “NO_HZ: Reducing Scheduling-Clock Ticks”, io_uring (J. Axboe, “Efficient IO with io_uring”); J. Corbet, A. Rubini, G. Kroah-Hartman, Linux Device Drivers, 3. baskı.