İş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.
Ş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
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.
| Bağlantı | Ham hız | Etkin veri hızı | Not |
|---|---|---|---|
| PCIe 4.0 | 16 GT/s / hat | ≈ 1,97 GB/s / hat; x4 ≈ 7,9 GB/s | 128b/130b kodlama; NVMe SSD'ler |
| PCIe 5.0 | 32 GT/s / hat | ≈ 3,94 GB/s / hat; x16 ≈ 63 GB/s | GPU, 14 GB/s sınıfı SSD |
| PCIe 6.0 | 64 GT/s (PAM4) | ≈ 7,6 GB/s / hat; x16 ≈ 121 GB/s | FLIT + FEC; sunucularda yaygınlaşıyor |
| USB 2.0 / 3.2 | 480 Mb/s / 5–20 Gb/s | ≤ 60 MB/s / ≤ 2,5 GB/s | Ana makine yoklamalı protokol |
| USB4 v1 / v2 | 40 / 80 Gb/s | PCIe, DisplayPort tünelleme | Thunderbolt 4/5 ile uyumlu |
| SATA III | 6 Gb/s | ≈ 600 MB/s (8b/10b) | AHCI: tek kuyruk, 32 komut |
| Ethernet | 1 / 10 / 100 / 400 Gb/s | 125 MB/s … 50 GB/s | Veri merkezi: 400–800 GbE |
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?
- 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.
- Bayta çevirme. 15,75 / 8 ≈ 1,97 GB/s. Bu, bir hattın tek yöndeki hızıdır.
- 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.
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.
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.
| Sınıf | Örnek | Özellik | Tipik arayüz |
|---|---|---|---|
| Blok | HDD, SSD, USB bellek, SD kart | Sabit boyutlu, adreslenebilir bloklar (512 B / 4 KiB); her blok bağımsız okunur ve yazılır | read, write, lseek; dosya sistemi, mmap |
| Karakter | Klavye, fare, seri port, yazıcı, ses | Bayt akışı; adreslenemez, konum yok | read/write, ioctl; /dev/ttyS0 |
| Ağ | Ethernet, Wi-Fi | Paket tabanlı; ne blok ne karakter | Soket API'si (send, recv); /dev'de dosyası yok |
| Diğer | Saatler, zamanlayıcılar, GPU | Ne akış ne blok: kesme üretir ya da bellek eşlemelidir | timerfd, 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 = karakterb 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.
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
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.
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 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.
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.
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.
| Kapı (x86) | Aygıt |
|---|---|
| 0x20–0x21 | 8259 kesme denetleyicisi |
| 0x40–0x43 | 8254 zamanlayıcı (PIT) |
| 0x60, 0x64 | 8042 klavye denetleyicisi |
| 0x3F8–0x3FF | COM1 seri port |
| 0xCF8–0xCFF | PCI 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
ioremapbunu 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
volatileolmalı, sıranın önemli olduğu yerlere bellek bariyerleri konmalıdır.
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.
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:
- Bekleme. Sürücü durum yazmacını okur. Denetleyici önceki işi bitirmediği için BUSY = 1'dir ve
whiledöngüsü döner. CPU bu sırada başka iş yapamaz. - Çı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.
- Komut. Komut yazmacına WRITE biti yazılır: yapılacak iş yazmadır.
- Veri. Gönderilecek bayt (0x41) veri çıkış yazmacına konur.
- 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.
- Denetleyici meşgul. Denetleyici CMDRDY'yi görür ve BUSY = 1 yapar. Böylece ana makineye meşgul olduğunu bildirir.
- İş. Denetleyici komut yazmacını okur (WRITE), veri çıkış yazmacındaki baytı alır ve aygıta gönderir.
- 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.
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.
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?
- Karakter süresi. 1 s / 100 = 10 ms. CPU her karakterden sonra yaklaşık 10 ms durum yazmacını yoklar.
- Boşa giden tur. 10 ms × 2 × 109 tur/s = 2 × 107 tur. CPU bu sürede on milyonlarca komut çalıştırabilirdi.
- 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.
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?
- Oran. CPU payı = (yoklama/s × tur/yoklama) / (tur/s).
- Klavye. 100 × 600 = 60 000 tur/s; 60 000 / (2 × 109) = %0,003. Önemsizdir.
- 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.
- 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.
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.
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:
- Sinyal. Disk denetleyicisi işini bitirince kendi kesme hattına sinyal verir.
- İ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.
- 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.
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
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ö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).
| Vektör | Olay | Tür |
|---|---|---|
| 0 | Bölme hatası | hata (fault) |
| 1 / 3 | Hata ayıklama / kesme noktası | tuzak (trap) |
| 2 | NMI (maskelenemez kesme) | kesme |
| 6 | Geçersiz işlem kodu | hata |
| 13 | Genel koruma hatası | hata |
| 14 | Sayfa hatası | hata |
| 18 | Makine denetimi | iptal (abort) |
| 32–255 | Aygıt kesmeleri, IPI, APIC zamanlayıcısı | maskelenebilir |
İ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.
iret ile seçilen sürece dönülüyor.- Kabul. CPU o anki komutu bitirir, sonra kesmeyi kabul eder. Kesme ancak komut sınırında alınır.
- 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.
- İş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.
- 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.
- Uyandırma. Bekleyen süreç B, bloke durumundan hazır kuyruğuna alınır.
- Ç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.
- Dönüş.
iretkomutu 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.
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.
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.
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.
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.
| Olay | Yazılan | i | count | Süreç |
|---|---|---|---|---|
| Sistem çağrısı | p[0] = 'A' | 1 | 2 | bloke |
| 1. kesme ('A' basıldı) | p[1] = 'B' | 2 | 1 | bloke |
| 2. kesme ('B' basıldı) | p[2] = 'C' | 3 | 0 | bloke |
| 3. kesme ('C' basıldı) | — | 3 | 0 | hazır (unblock_user) |
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.
Ü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.
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.
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.
- 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.
- Tampon. Disk denetleyicisi sektörü kendi tamponuna okur ve ECC ile doğrular. Tampon sayesinde yol o an meşgulse veri kaybolmaz.
- İstek. DMA denetleyicisi yolu ister, bellek adresini yola koyar ve disk denetleyicisine okuma isteği gönderir.
- Yazma. Disk denetleyicisi sözcüğü doğrudan belleğe yazar. CPU bu aktarımın içinde değildir.
- 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.
- 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) 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 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.
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?
- 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.
- 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.
- 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.
- 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.
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?
- Sektör sayısı. 512 KiB = 524 288 B; 524 288 / 512 = 1024 sektör.
- Kesme güdümlü. 1024 × 5 µs = 5120 µs ≈ 5,1 ms.
- DMA. 8 + 5 = 13 µs. CPU yükü 5120 / 13 ≈ 394 kat azalır.
- 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 < girdikomutu,girdidosyası 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/nvme0n1ve/mnt/usb/rapor.txtaynı 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.
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
read() isteğinin katmanlardaki yolu. Bu anda SSD veriyi DMA ile önbellek sayfasına yazmış ve MSI-X kesmesi üretmiştir.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?
- Sistem çağrısı. libc sarmalayıcısı
syscallkomutuyla çekirdeğe geçer. - 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.
- Sayfa önbelleği. İstenen sayfa önbellekte yoktur. Bir önbellek sayfası ayrılır ve okuma başlatılır.
- 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.
- 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.
- Aygıt. SSD veriyi DMA ile önbellek sayfasına yazar, tamamlanma kuyruğuna bir kayıt ekler ve MSI-X kesmesi üretir.
- Kesme işleyicisi. Tamamlanma kaydı işlenir, sayfa “güncel” olarak işaretlenir ve bekleyen süreç hazır olur.
- 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
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.
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.
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)
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).
- 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.
- 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.
- Ç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?
- Ç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.
- 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_ZEROCOPYve io_uring gibi sıfır kopya teknikleri geliştirilmiştir.
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?
- Tamponsuz. Okuma ve işleme sırayla yapılır: T + C = 16 ms.
- 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.
- Ç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.
- 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
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.
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?
- Boş. Baş ve kuyruk aynı yuvayı gösterir.
- Üç yazma. a, b, c yuva 0, 1, 2'ye gider; kuyruk = 3. Tüketici henüz okumamıştır.
- İki okuma. a ve b okunur; baş = 2. Yuva 0 ve 1 yeniden kullanılabilir.
- Beş yazma. d … h yuva 3–7'ye gider. Kuyruk sona gelince başa sarar: 8 mod 8 = 0.
- 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.
| Çağrı | Ayrıntı | bas | kuy | Dönüş |
|---|---|---|---|---|
| — | tampon boş | 0 | 0 | |
koy('a') | buf[0] = 'a' | 0 | 1 | 0 |
koy('b') | buf[1] = 'b' | 0 | 2 | 0 |
koy('c') | buf[2] = 'c' | 0 | 3 | 0 |
al | bas ≠ kuy; 'a' okunur | 1 | 3 | 0 |
koy('d') | buf[3] = 'd' | 1 | 4 | 0 |
koy('e') | buf[4] = 'e'; kuyruk başa sarar | 1 | 0 | 0 |
koy('f') | s = (0 + 1) mod 5 = 1 = bas: dolu | 1 | 0 | −1 |
al | 'b' okunur | 2 | 0 | 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.
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.
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.
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.
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).
| Arayüz | Model | Not |
|---|---|---|
select / poll | hazır olma bildirimi | her çağrıda bütün tanımlayıcı listesi taranır: O(n) |
epoll (Linux), kqueue (BSD) | hazır olma bildirimi | ilgi listesi çekirdekte; yalnız hazır olanlar döner |
| io_uring, Windows IOCP | tamamlanma bildirimi | gerç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.
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
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.
- Halkalar. Uygulama ile çekirdek iki halkayı
mmapile paylaşımlı bellekte görür. İstekler ve sonuçlar kopyalanmaz. - 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.
- 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. - 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.
- 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.
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.
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.
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 kHz. 1 193 182 / 1000 = 1193,18. Sayaç tam sayı olmalıdır: 1193. Gerçek frekans 1 193 182 / 1193 ≈ 1000,15 Hz.
- 100 Hz. 1 193 182 / 100 = 11 931,82 → 11 932. Gerçek frekans 1 193 182 / 11 932 ≈ 99,998 Hz.
- 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.
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.
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.
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.
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.
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.
- A. A'nın farkı 3 ≤ 10: X ondan sonra gelir. t = 10 − 3 = 7;
*pB'ye geçer. - B. B'nin farkı 4 ≤ 7: X ondan da sonra gelir. t = 7 − 4 = 3;
*pC'ye geçer. - C. C'nin farkı 6 > 3: X, C'den önce girmelidir; döngü biter.
- 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.
- 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 8000. tik; alarmlar 8002, 8006, 8006, 8011 ve 8020'de. Fark listesi nedir? 8 tik sonra hangi alarmlar tetiklenmiş olur?
- Farklar. 8002 − 8000 = 2, 8006 − 8002 = 4, 8006 − 8006 = 0, 8011 − 8006 = 5, 8020 − 8011 = 9. Liste 2, 4, 0, 5, 9.
- İlk alarm. 2 tik sonra baştaki fark 0 olur: 8002'deki alarm tetiklenir.
- 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.
- 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
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
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.
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?
- Bir tur. 60 000 ms / 7200 = 8,33 ms.
- Dönme gecikmesi. İstenen sektör ortalamada yarım tur uzaktadır: 8,33 / 2 = 4,17 ms.
- Aktarım. 4096 B / (200 × 106 B/s) = 0,020 ms.
- 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.
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.
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?
- Tur ve dönme. 60 000 / 15 000 = 4 ms; ortalama dönme gecikmesi 2 ms.
- 64 KiB ardışık. Aktarım 65 536 / (250 × 106) s = 0,262 ms; toplam 3,5 + 2 + 0,262 ≈ 5,76 ms.
- 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.
- 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.
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.
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?
- Tur. 60 000 / 10 000 = 6 ms.
- Sektör süresi. 6 ms / 300 = 20 µs.
- 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.
| CD (1982) | DVD (1996) | Blu-ray (2006) | |
|---|---|---|---|
| Kapasite | 650–700 MB | 4,7 / 8,5 / 9,4 / 17 GB | 25 / 50 GB; BDXL 100–128 GB |
| Lazer | 780 nm (kızılötesi) | 650 nm (kırmızı) | 405 nm (mavi-mor) |
| En küçük çukur | 0,83 µm | 0,40 µm | 0,15 µm |
| İz aralığı | 1,6 µm | 0,74 µm | 0,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.
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.
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.
| Algoritma | Fikir | Sorun |
|---|---|---|
| FCFS | Geliş sırası | Adil ama kol çok gezer |
| SSTF | Kafaya en yakın istek (açgözlü) | Uçtaki istekler açlık çekebilir |
| SCAN (asansör) | Bir yönde uca kadar git, dön | Uçlar ve orta eşit beklemez |
| C-SCAN | Tek yönde tara, uçtan başa sıçra | Dönüş hareketi boşa |
| LOOK / C-LOOK | SCAN / C-SCAN, ama son isteğe kadar | Uygulamada kullanılan biçimler |
11.10.2FCFS
FCFS (first-come, first-served, ilk gelen ilk hizmet alır) istekleri geliş sırasıyla karşılar.
Ortak kuyrukta FCFS'nin toplam kafa hareketi nedir?
- 53 → 98: 45 silindir; toplam 45.
- 98 → 183: 85; toplam 130.
- 183 → 37: 146; toplam 276.
- 37 → 122: 85; toplam 361.
- 122 → 14: 108; toplam 469.
- 14 → 124: 110; toplam 579.
- 124 → 65: 59; toplam 638.
- 65 → 67: 2; 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
SSTF (shortest seek time first, en kısa arama süresi önce) her adımda kafaya en yakın bekleyen isteği seçer.
Ortak kuyrukta SSTF hangi sırayı izler, toplam hareket nedir?
- Kafa 53: adaylar 65 (12 uzakta) ve 37 (16 uzakta). 53 → 65: 12; toplam 12.
- 65 → 67: 2; toplam 14.
- Kafa 67: 37 (30 uzakta), 98'den (31 uzakta) yakındır. 67 → 37: 30; toplam 44.
- 37 → 14: 23; toplam 67.
- Alt bölge bitti. 14 → 98: 84; toplam 151.
- 98 → 122: 24; toplam 175.
- 122 → 124: 2; toplam 177.
- 124 → 183: 59; toplam 236.
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.
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.
k | Kafa | En yakın (uzaklık) | Sonraki aday (uzaklık) | top |
|---|---|---|---|---|
| 0 | 50 | 43 (7) | 24 (26) | 7 |
| 1 | 43 | 24 (19) | 16 (27) | 26 |
| 2 | 24 | 16 (8) | 82 (58) | 34 |
| 3 | 16 | 82 (66) | 140 (124) | 100 |
| 4 | 82 | 140 (58) | 170 (88) | 158 |
| 5 | 140 | 170 (30) | 190 (50) | 188 |
| 6 | 170 | 190 (20) | — | 208 |
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)
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.
Ortak kuyrukta kafa 0'a doğru (aşağı) hareket ediyor. SCAN'in toplam hareketi nedir?
- 53 → 37: 16; toplam 16.
- 37 → 14: 23; toplam 39.
- 14 → 0: 14; toplam 53. Aşağıda istek kalmadığı hâlde kafa diskin ucuna kadar gider.
- Yön değişir. 0 → 65: 65; toplam 118.
- 65 → 67: 2; toplam 120.
- 67 → 98: 31; toplam 151.
- 98 → 122: 24; toplam 175.
- 122 → 124: 2; toplam 177.
- 124 → 183: 59; toplam 236.
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
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.
Ortak kuyrukta C-SCAN (yukarı yönde) hangi yolu izler? Dönüş hareketi de sayılırsa toplam nedir?
- 53 → 65 → 67 → 98 → 122 → 124 → 183: 12 + 2 + 31 + 24 + 2 + 59 = 130.
- 183 → 199: 16; toplam 146. Yukarıda istek yoktur ama kafa uca kadar gider.
- 199 → 0: geri dönüş, 199 silindir, hizmet yok; toplam 345.
- 0 → 14: 14; toplam 359.
- 14 → 37: 23; toplam 382.
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
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.
Ortak kuyrukta LOOK (aşağı yönde) ve C-LOOK (yukarı yönde) için toplam hareket nedir?
- LOOK, aşağı. 53 → 37 → 14: 16 + 23 = 39. Aşağıda istek kalmadı; 0'a gidilmez.
- 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.
- C-LOOK, yukarı. 53 → 65 … 183: 130.
- Sıçrama. 183 → 14: en küçük bekleyen isteğe 169 silindir; 199 ve 0'a gidilmez. Toplam 299.
- Son. 14 → 37: 23; toplam 322. Sıçrama sayılmazsa 322 − 169 = 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.
| Adım | j | Hedef | Hareket | top |
|---|---|---|---|---|
while | i = 3: 50'den büyük ilk istek s[3] = 82. Altındaki 16, 24, 43 dönüşte alınacak. | |||
| yukarı | 3 | 82 | +32 | 32 |
| yukarı | 4 | 140 | +58 | 90 |
| yukarı | 5 | 170 | +30 | 120 |
| yukarı | 6 | 190 | +20 | 140 |
| aşağı | 2 | 43 | +147 | 287 |
| aşağı | 1 | 24 | +19 | 306 |
| aşağı | 0 | 16 | +8 | 314 |
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ı
Ş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.
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?
- FCFS. 11 → 1 → 36 → 16 → 34 → 9 → 12: 10 + 35 + 20 + 18 + 25 + 3 = 111.
- 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.
- Asansör, yukarı. Sıra 12, 16, 34, 36, 9, 1: 1 + 4 + 18 + 2 + 27 + 8 = 60.
- Yorum. Tanenbaum'un asansörü yukarıdaki son istekte (36) döner, uca gitmez; yani aslında LOOK'tur.
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.
- FCFS. 100 + 42 + 28 + 162 + 52 + 95 + 39 + 79 = 597.
- 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.
- SCAN. 84 → 116 … 184 → 199, sonra 60, 37, 8: (199 − 84) + (199 − 8) = 115 + 191 = 306.
- C-SCAN. 84 → 199 (115), 199 → 0 (199), 0 → 8 → 37 → 60 (60): 374. Dönüş sayılmazsa 175.
- LOOK. 84 → 184 (100), 184 → 8 (176): 276.
- C-LOOK. 84 → 184 (100), sıçrama 184 → 8 (176), 8 → 60 (52): 328. Sıçrama sayılmazsa 152.
- 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.
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.
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?
- Dönme hızı. Bir tur 60 000 / 7200 = 8,33 ms; disk 1 ms'de 360 / 8,33 = 43,2° döner.
- 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.
- 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.
- 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).
- 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.
- 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ş
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'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).
| Hücre | Bit/hücre | Tipik P/E döngüsü | Kullanım |
|---|---|---|---|
| SLC | 1 | ≈ 50–100 bin | önbellek bölgesi, endüstriyel |
| MLC | 2 | ≈ 3–10 bin | eski kurumsal |
| TLC | 3 | ≈ 1–3 bin | çoğu tüketici ve sunucu SSD'si |
| QLC | 4 | ≈ 0,1–1 bin | yü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.
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.
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.
- İlk yazmalar. FTL üç bloğu B0'ın boş sayfalarına (s0, s1, s2) koyar ve tabloya kaydeder.
- 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.
- 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.
- Blok doldu. B0'da boş sayfa kalmamıştır; LBA 3 B1'e yazılır. B0'ın yarısı geçersizdir.
- Çö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.
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.
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.
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
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.
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
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.
| Çizelgeleyici | Fikir | Uygun |
|---|---|---|
none | Sıralama yok, doğrudan gönder | NVMe (varsayılan), hızlı SSD |
mq-deadline | LBA'ya göre sıralı + okuma/yazma son tarihi | SATA HDD/SSD (çoğu dağıtımda varsayılan) |
bfq | Süreç başına bütçe, adil paylaşım | Masaüstü, yavaş aygıtlarda etkileşim |
kyber | Okuma/yazma gecikme hedefleriyle sınırlama | Hı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/schedulerKöş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
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.
| Düzen | Kullanılabilir | Dayanır | Küçük rastgele yazma maliyeti |
|---|---|---|---|
| RAID 0 (şeritleme) | 16 TB | 0 disk | 1 G/Ç |
| RAID 1 (4 kopya ayna) | 4 TB | 3 disk | 4 G/Ç (her kopyaya) |
| RAID 10 (ayna + şerit) | 8 TB | 1 (çiftler farklıysa 2) | 2 G/Ç |
| RAID 5 (dağıtılmış eşlik) | 12 TB | 1 disk | 4 G/Ç (2 oku + 2 yaz) |
| RAID 6 (çift eşlik) | 8 TB | 2 disk | 6 G/Ç (3 oku + 3 yaz) |
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. 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.
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.
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?
- Eşlik. D0 ⊕ D1 = 1110 1110; ⊕ D2 = 0000 1001. P = 0000 1001. Her bit konumunda 1'lerin sayısı artık çifttir.
- 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.
- 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).
- Denetim. D0 ⊕ D1' ⊕ D2 = 1011 0010 ⊕ 0000 1111 ⊕ 1110 0111 = 0101 1010 = P'. Şeridin tamamı okunmadan doğru eşlik bulunmuştur.
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.
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?
- Bit sayısı. 12 × 1012 B × 8 = 9,6 × 1013 bit.
- Beklenen hata. 9,6 × 1013 / 1014 = 0,96.
- 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.
- 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.
| Adım | İşlem | Değer | G/Ç |
|---|---|---|---|
| 1 | eski = oku(1) | 0110 1001 | 1 |
| 2 | p = oku(P) | 0001 1110 | 2 |
| 3 | p = p ^ eski ^ yeni | 0001 1110 ⊕ 0110 1001 ⊕ 0011 0011 = 0100 0100 | 2 |
| 4 | yaz(1, yeni) | D1 = 0011 0011 | 3 |
| 5 | yaz(P, p) | P = 0100 0100 | 4 |
| 6 | kur(2), d = 0 | x = 1011 0010 | |
| 7 | d = 1 | x = 1011 0010 ⊕ 0011 0011 = 1000 0001 | |
| 8 | d = 2 atlanır; d = 3 (P) | x = 1000 0001 ⊕ 0100 0100 = 1100 0101 | 3 okuma |
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.
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?
- P. D0 ⊕ D1 = 1111 1100; ⊕ D2 = 0101 1101; ⊕ D3 = 0000 0001. P = 0000 0001.
- Kurtarma. D0 ⊕ D1 ⊕ D2 ⊕ P = 0101 1101 ⊕ 0000 0001 = 0101 1100: özgün D3.
- Küçük yazma. Değişen bitler D0 ⊕ Y = 1100 1010 ⊕ 1111 0000 = 0011 1010. P' = P ⊕ 0011 1010 = 0011 1011.
- 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.
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?
- R. Rastgele 4 KiB okuma 12,19 ms sürer; R = 1000 / 12,19 ≈ 82 IOPS (0,34 MB/s).
- 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.
- 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.
- 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.
- 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.
- 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.
| Düzen | Kapasite | Ardışık okuma/yazma | Rastgele okuma | Rastgele yazma | Yazma gecikmesi |
|---|---|---|---|---|---|
| RAID 0 | N = 4 | N·S ≈ 649 MB/s | N·R ≈ 328 | N·R ≈ 328 | D |
| RAID 1 (ikili ayna) | N/2 = 2 | (N/2)·S ≈ 325 MB/s | N·R ≈ 328 | (N/2)·R ≈ 164 | D (yavaş kopya) |
| RAID 4 | N − 1 = 3 | (N−1)·S ≈ 487 MB/s | (N−1)·R ≈ 246 | R/2 ≈ 41 | 2D |
| RAID 5 | N − 1 = 3 | (N−1)·S ≈ 487 MB/s | N·R ≈ 328 | (N/4)·R ≈ 82 | 2D |
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.
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?
- RAID 0. Yazma başına 1 G/Ç: 8 × 150 = 1200 IOPS.
- RAID 10. İki kopya, 2 G/Ç: 1200 / 2 = 600.
- RAID 4. 4 G/Ç, ama ikisi hep eşlik diskinde: 150 / 2 = 75.
- RAID 5. 4 G/Ç, disklere yayılır: 1200 / 4 = 300.
- RAID 6. 6 G/Ç (3 oku + 3 yaz): 1200 / 6 = 200.
- 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.
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.
Kararlı yazmanın beş anında çökme olursa kurtarma ne yapar?
- Disk 1'e yazmadan önce. İki kopya da eskidir. Bir şey yapılmaz; yazma hiç olmamış sayılır.
- 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.
- 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.
- Disk 2'ye yazarken. Disk 2 bozuktur (ECC); yeni değer disk 1'den kopyalanır.
- İ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/OUTkomutları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çivolatileolmalı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,
iretile 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
EAGAINdö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
- veri yolu bus
- aygıt denetleyicisi device controller
- blok / karakter aygıtı block / character device
- bellek eşlemeli G/Ç memory-mapped I/O
- yoklama polling
- meşgul bekleme busy waiting
- programlanmış G/Ç programmed I/O
- kesme interrupt
- kesme vektörü interrupt vector
- kesme işleyicisi interrupt handler
- istisna exception
- tuzak trap
- boru hattı pipeline
- kesme güdümlü G/Ç interrupt-driven I/O
- doğrudan bellek erişimi (DMA) direct memory access
- aygıt sürücüsü device driver
- tampon / tamponlama buffer / buffering
- sayfa önbelleği page cache
- biriktirme spooling
- bloke eden / bloke etmeyen / eş zamansız G/Ç blocking / nonblocking / asynchronous I/O
- zamanlayıcı timer
- sabit disk (HDD) hard disk drive
- arama süresi / dönme gecikmesi seek time / rotational latency
- disk kolu çizelgeleme disk scheduling
- SSD solid-state drive
- aşınma dengeleme wear leveling
- G/Ç çizelgeleyicisi I/O scheduler
- RAID
- eşlik parity
- günlükleme journaling
11.15Kendini Dene
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.
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.
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.
Fark listesi A 5, B 2, C 6. 9 tik sonra dolacak X eklenirse liste ne olur?
eklekaç 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.
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.
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.
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ı
EAGAINile reddetmez, bloke ederek tamamlar. Okuma yinereadiçinde beklenir. İşi başlatıp tamamlanmayı sonra bildiren io_uring (ya da Windows'ta IOCP) gerçek eş zamansızlık sağlar.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.
Bir sürücü durum yazmacını
int *durumiş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çivolatile int *yapılmalıdır. Ayrıca yazmaç sayfası önbelleklenemez eşlenmelidir (Linux'taioremap); yoksa CPU önbellekteki eski değeri okur.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
- 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.
- A. S. Tanenbaum, H. Bos, Modern Operating Systems, 4. baskı (2014) / 5. baskı (2023), Pearson — Bölüm 5: Input/Output.
- 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”.
- D. A. Patterson, G. Gibson, R. H. Katz, “A Case for Redundant Arrays of Inexpensive Disks (RAID)”, SIGMOD 1988.
- NVM Express Base Specification 2.x; PCI-SIG PCIe 5.0/6.0; USB4 Specification; UEFI Specification (GPT).
- 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ı.