İşletim Sistemleri · Ders Notu

Bölüm 1: Giriş

Bilgisayarda çalışan her program, kendisinden daha alt düzeydeki bir yazılıma güvenir: işletim sistemine. Tarayıcı bir dosyayı diske yazarken diskin markasını bilmez. Oyun ekrana çizerken ekran kartının yazmaçlarına dokunmaz. Derleyici çalışırken başka programların belleğini bozamaz. Bu kolaylığın ve korumanın hepsini işletim sistemi sağlar. Not, işletim sisteminin donanım ile uygulamalar arasındaki yerini ve iki temel rolünü anlatarak başlar: donanımı kullanışlı soyutlamalarla sunan genişletilmiş makine ve kaynakları paylaştıran yönetici. Ardından çekirdek ve kullanıcı modunu, bu iki mod arasındaki denetimli geçişi, işletim sistemlerinin tarihsel gelişimini ve bu gelişimi yönlendiren donanım özelliklerini işler. Son kesimler G/Ç yöntemlerine, işletim sistemi çeşitlerine, politika ile mekanizma ayrımına ve bir C programının çalışan bir sürece dönüşmesine ayrılmıştır.

Öğrenme çıktıları

Tanım ve konum
İşletim sisteminin donanım ile uygulamalar arasındaki yerini ve iki temel rolünü (genişletilmiş makine, kaynak yöneticisi) açıklamak.
Koruma
Çekirdek modu ile kullanıcı modunu ve aralarındaki denetimli geçişi (sistem çağrısı, kesme, istisna) açıklamak.
Tarihsel gelişim
Toplu iş, çoklu programlama ve zaman paylaşımı fikirlerinin hangi sorunları çözdüğünü anlatmak; iki işin zaman çizelgesini çizip CPU kullanımını hesaplamak.
Donanım temelleri
İşlemci ve yazmaçların, bellek hiyerarşisinin ve çok çekirdekli yongaların işletim sistemini nasıl etkilediğini yorumlamak.
G/Ç yöntemleri
Yoklama, kesme ve DMA arasındaki temel farkı söylemek, CPU yükünü hesaplamak; kesme ile tuzağı ayırmak.
Kavramlar
İşletim sistemi çeşitlerini, politika–mekanizma ayrımını ve programın çalıştırılabilir dosyaya dönüşmesini örneklerle açıklamak.

1.1İşletim sistemi nedir?

“İşletim sistemi” sözü günlük dilde çoğu zaman ekranda görülen masaüstüyle karışır. Başlat menüsü, dosya gezgini, duvar kâğıdı ve simgeler akla gelir. Oysa bunlar işletim sisteminin yalnız görünen yüzüdür, hatta çoğu sıradan birer programdır. Asıl iş görünmeyen yerde yapılır: hangi programın ne zaman çalışacağına, belleğin hangi parçasını kimin kullanacağına, diske giden isteklerin hangi sırayla işleneceğine karar veren yazılım katmanında. Bu kesim, o katmanın ne olduğunu ve neden var olduğunu anlatır.

1.1.1Neden bir ara katman gerekir?

Bugünkü sıradan bir dizüstü bilgisayarın içine bakalım. Çok çekirdekli bir işlemci, gigabaytlarca bellek, bir SSD, kablosuz ve kablolu ağ kartları, bir grafik işlemcisi (GPU), klavye, dokunmatik yüzey, kamera, mikrofon, hoparlör ve birkaç USB bağlantısı vardır. Bu aygıtların her biri başka bir üreticiden gelir ve her birinin kendine özgü bir denetim yöntemi vardır. Bir SSD'ye veri yazmak için komut kuyrukları hazırlanır, blok adresleri hesaplanır, hata kodları denetlenir. Bir ağ kartından paket almak için halka biçimli tamponlar kurulur ve kesmeler ayarlanır.

Her uygulama geliştiricisinin bu ayrıntıların hepsini öğrenmesi ve kendi programında doğrudan yazması düşünülemez. Böyle olsaydı bir metin düzenleyicinin, piyasadaki bütün disk modelleri için ayrı kod içermesi gerekirdi. Üstelik aynı anda çalışan iki program aynı diske kendi bildiği gibi yazmaya kalkışsa diskteki veri birbirine karışırdı. Demek ki iki ayrı ihtiyaç vardır: donanımın karmaşıklığını gizleyen ortak bir arayüz ve aynı donanımı kullanan programlar arasında düzen ve koruma. İşletim sistemi bu iki ihtiyaca verilen yanıttır.

Tanım 1.1

İşletim sistemi (operating system, kısaca İS), bilgisayarın donanımını yöneten ve uygulamalara basit, güvenli ve ortak bir arayüz sunan yazılım katmanıdır.

Tanımdaki üç sıfatın her biri ayrı bir işe karşılık gelir. Basit arayüz, uygulamanın “şu dosyaya şu baytları yaz” demesinin yetmesidir; diskin içyapısı İS'nin sorunudur. Güvenli arayüz, bir programın hatasının ya da kötü niyetinin başka programlara ve sistemin bütününe zarar verememesidir. Ortak arayüz ise aynı programın farklı donanımlarda değişmeden çalışabilmesidir: bir C programı hangi SSD takılı olursa olsun aynı write çağrısıyla dosyaya yazar.

Bugün çok farklı ortamlarda çok farklı işletim sistemleri çalışır. Masaüstünde Windows 11, macOS, Ubuntu ya da Fedora gibi Linux dağıtımları ve ChromeOS yaygındır. Telefon ve tabletlerde Android, iOS/iPadOS ve HarmonyOS kullanılır. Sunucu ve bulut tarafında Linux açık farkla öndedir; Windows Server ve FreeBSD de yer bulur. Otomobillerin, ev aletlerinin ve nesnelerin interneti (Internet of Things, IoT) aygıtlarının içinde ise Zephyr, FreeRTOS, Contiki-NG, VxWorks ve QNX gibi küçük, özel amaçlı sistemler çalışır. Bu sistemlerin boyutları ve hedefleri birbirinden çok farklıdır, ama hepsi aynı iki temel sorunu çözer.

1.1.2Çekirdek ve geri kalan yazılım

Bir işletim sistemi kurulduğunda diske binlerce dosya yazılır: sürücüler, kütüphaneler, yardımcı programlar, yazı tipleri, ayar araçları, masaüstü ortamı. Bunların hepsini “işletim sistemi” saymak, terimi neredeyse anlamsız kılar. Bu yüzden ders kitapları dar ve geniş iki anlam ayırır.

Tanım 1.2

Çekirdek (kernel), işletim sisteminin bilgisayar açık kaldığı sürece bellekte duran ve işlemcinin ayrıcalıklı modunda çalışan kısmıdır. Dar anlamda “işletim sistemi” denince çekirdek kastedilir.

Çekirdek; süreçleri yaratır ve çizelgeler, belleği dağıtır, dosya sistemlerini ve aygıt sürücülerini barındırır, kesmeleri karşılar. Geri kalan yazılımın büyük kısmı ise çekirdeğin üstünde, sıradan programlar gibi çalışır. Kullanıcının bilgisayarla konuştuğu iki arayüz de bu ikinci gruptadır: kabuk (shell), yani komut satırı, ve GUI (graphical user interface: grafik kullanıcı arayüzü). İkisinin ayrıntısına aşağıda dönülecek; burada önemli olan, ikisinin de çekirdeğin parçası olmamasıdır.

1.1.3Bilgisayar sisteminin bileşenleri

Bir bilgisayar sistemini katmanlar hâlinde düşünmek, kimin neyi yaptığını görmenin en kolay yoludur. Şekil 1.1 bu katmanları alttan üste gösterir.

KullanıcımoduÇekirdekmoduDonanımWeb tarayıcıE-posta istemcisiMüzik çalarKabuk / GUI (kullanıcı arayüzü programı)İşletim sistemi (çekirdek)İşlemciBellekSSD/DiskAğ kartıKlavye/FareEkranYazıcı
Şekil 1.1: Bilgisayar sisteminin katmanları. En altta donanım (işlemci, bellek, SSD/disk, ağ kartı, klavye/fare, ekran, yazıcı) bulunur. Üstünde çekirdek modunda çalışan işletim sistemi yer alır. En üstte, kesikli çerçevenin içinde, kullanıcı modunda çalışan programlar vardır: web tarayıcı, e-posta istemcisi, müzik çalar ve onlarla aynı düzeyde kabuk ya da GUI.

En alttaki donanım katmanı işlemciyi, belleği, depolama aygıtlarını ve giriş/çıkış (G/Ç) aygıtlarını içerir. Bunlar fiziksel bileşenlerdir; kendi başlarına yalnız çok basit komutları anlarlar. Hemen üstteki işletim sistemi, donanımı doğrudan denetleyen tek yazılımdır ve işlemcinin ayrıcalıklı modunda çalışır. En üstteki katmanda ise geri kalan her şey bulunur: kullanıcının çalıştırdığı uygulamalar ve onlarla aynı düzeyde duran kabuk ile GUI.

Şekildeki ayrım yazılım dünyasını ikiye böler. Çekirdek modunda çalışan yazılım işletim sistemidir. Kullanıcı modunda çalışan her şey, kabuk ve GUI bile, sıradan bir kullanıcı programıdır. Bu ayrımın neden bu kadar önemli olduğu, modları anlatan 1.2.2'de açıklanır.

Sık yapılan hata

Masaüstünü, pencereleri ya da komut satırını işletim sisteminin kendisi sanmak. Bir Linux sunucusunda hiç GUI yoktur, ama işletim sistemi tam olarak çalışır. Aynı Linux çekirdeğinin üstünde GNOME, KDE Plasma ya da yalnız bir terminal bulunabilir. Görünen arayüz değişir, çekirdek aynı kalır.

1.1.4Kabuk ve GUI

Kullanıcı bir işi yaptırmak istediğinde bunu ya bir komut yazarak ya da fareyle bir simgeye tıklayarak bildirir. Bu iki yolun her birini ayrı bir program karşılar.

Tanım 1.3

Kabuk, kullanıcının yazdığı komutları okuyan, yorumlayan ve ilgili programları başlatan komut satırı arayüzüdür (command-line interface, CLI). GUI ise pencere, simge, menü ve işaretçiden (windows, icons, menus, pointer: WIMP) oluşan görsel etkileşim ortamıdır.

● ● ●Terminal: bash$ ls ~/notlarbolum1.md bolum2.md$ ps aux | grep firefoxsercan 4211 2.1 firefox$ uname -srLinux 6.12.8$ _Masaüstü (GUI)BelgelerAyarlarTarayıcıDosya Gezginipencere · simge · menü · işaretçi
Şekil 1.2: Solda bir terminal penceresinde bash kabuğu: ls ~/notlar iki dosya listeler, ps aux | grep firefox çalışan Firefox sürecini bulur, uname -sr çekirdek sürümünü (Linux 6.12.8) yazar. Sağda pencere, simge, menü ve işaretçiden oluşan bir masaüstü (GUI).

Yaygın kabuklar arasında Linux ve macOS'ta bash, zsh ve fish; Windows'ta PowerShell ve eski cmd.exe sayılabilir. GUI tarafında Windows Gezgini, macOS Finder ve Linux'taki GNOME ile KDE Plasma ortamları bilinir. Şekil 1.2, aynı bilgisayarda iki arayüzün yan yana çalışabildiğini gösterir.

Kabuk da GUI de çekirdeğin parçası değildir. İkisi de kullanıcı modunda çalışan programlardır ve çekirdekten bir hizmet istediklerinde, örneğin bir dosyayı açmak ya da yeni bir program başlatmak için, sistem çağrısı yaparlar. Bunun pratik sonuçları vardır. Kabuk değiştirilebilir: bash'ten zsh'e geçmek için çekirdeğe dokunmak gerekmez. Aynı anda birden fazla kabuk açılabilir. Bir kabuk çökerse yalnız o pencere kapanır, sistem çalışmaya devam eder. Kullanıcılar arayüzlerini kişiselleştirebilir, çünkü değiştirdikleri şey yalnız bir programdır.

1.1.5Kabuk mu, GUI mi?

İki arayüz arasında bir yarış yoktur; her biri farklı işlerde öne çıkar. Kabuğun en büyük gücü otomasyondur. Komutlar bir betik (script) dosyasına yazılınca aynı iş binlerce kez, hiç tıklama yapmadan tekrarlanabilir. İkinci gücü, küçük araçları birleştirebilmesidir. Bir komutun çıktısı boru hattıyla (pipeline) bir sonrakinin girdisine bağlanır:

$ ps aux | grep ssh | wc -l

Bu satırda ps aux çalışan bütün süreçleri listeler. grep ssh bu listeden içinde “ssh” geçen satırları seçer. wc -l ise kalan satırları sayar. Üç basit aracın birleşimi “içinde ssh geçen kaç süreç satırı var?” sorusunu tek satırda yanıtlar. Kabuk az kaynak harcar ve SSH bağlantısıyla uzaktaki bir sunucuyu yönetmenin doğal yoludur. Belirli görevleri hızlı ve kesin biçimde yapar.

GUI'nin gücü ise keşfedilebilirliktir. Kullanıcı komut ezberlemek zorunda kalmaz; menülere bakarak neler yapılabileceğini görür. Fotoğraf, harita ya da tasarım gibi görsel içerikle çalışmak için doğal ortamdır. Geniş bir kullanıcı kitlesine ulaşır ve ekran okuyucu gibi erişilebilirlik araçlarıyla birlikte çalışır. Dokunmatik ve sesli arayüzler de bu ailenin üyeleridir.

Pratikte ikisi birbirini tamamlar: günlük işler için GUI, tekrarlı ve uzaktan yapılan işler için kabuk. Her ikisi de aynı çekirdek hizmetlerini kullanır. Çekirdek açısından bir dosyanın kabuktaki cp komutuyla mı, yoksa gezginde sürükle-bırak ile mi kopyalandığı fark etmez. Her iki durumda da çekirdeğe ulaşan şey, aynı türden sistem çağrılarıdır.

1.1.6Aygıt Yöneticisi

İşletim sisteminin donanımı nasıl gördüğünü izlemenin somut bir yolu, Windows'taki Aygıt Yöneticisi aracıdır. Bu araç, bilgisayara bağlı donanım aygıtlarını ve onları süren yazılımları kategoriler hâlinde bir ağaçta listeler. Komut satırından devmgmt.msc yazılarak açılır; Microsoft Yönetim Konsolu (MMC) için yazılmış bir eklentidir.

BILGISAYAR-01Disk sürücüleriNVMe SSD 1 TBEkran bağdaştırıcılarıGPU (ayrık)Ağ bağdaştırıcılarıWi-Fi 7Ethernet 2.5 GbEUSB denetleyicileriUSB kök hub (USB 3.2)Diğer aygıtlarBilinmeyen aygıt!
Şekil 1.3: Aygıt Yöneticisi'nin ağaç görünümü. BILGISAYAR-01 altında disk sürücüleri (NVMe SSD 1 TB), ekran bağdaştırıcıları (ayrık GPU), ağ bağdaştırıcıları (Wi-Fi 7, Ethernet 2.5 GbE) ve USB denetleyicileri listelenir. “Diğer aygıtlar” altındaki bilinmeyen aygıt, sürücüsü bulunamadığı için uyarı simgesiyle işaretlenmiştir.

Aygıt Yöneticisi her aygıtın türünü, üreticisini, sürücü sürümünü ve kullandığı kaynakları (hangi kesme hattını, hangi bellek aralığını) gösterir. Buradan sürücü güncellenebilir ya da önceki sürüme geri alınabilir, bir aygıt devre dışı bırakılabilir. Sorunlu aygıtlar Şekil 1.3'teki gibi uyarı simgesiyle işaretlenir.

Burada dikkat edilmesi gereken nokta, aracın kendisinin işi yapmadığıdır. Aygıtları algılayan, onlara kaynak atayan ve uygun sürücüyü yükleyen, çekirdekteki Tak-Çalıştır (Plug and Play, PnP) yöneticisi ve aygıt sürücüleridir. Aygıt Yöneticisi yalnız onların tuttuğu bilgiye açılan bir penceredir. Linux'ta aynı bilgi lspci (PCI aygıtları), lsusb (USB aygıtları), lsblk (blok aygıtları) ve lsmod (yüklü çekirdek modülleri) komutlarıyla okunur. /sys dizinindeki dosyalar ve aygıt olaylarını yöneten udev hizmeti de aynı bilgiye açılır.

1.2Konum ve modlar

İşletim sisteminin “donanım ile uygulamalar arasında” durduğunu söylemek kolaydır. Asıl soru, bu aradalığın nasıl korunduğudur. Bir uygulama neden diske kendisi yazamaz? Kötü niyetli bir program işletim sistemini neden atlayamaz? Yanıt yazılımda değil, işlemcinin kendisindedir. Bu kesim önce işletim sisteminin konumunu, sonra bu konumu güvence altına alan donanım özelliğini, yani işlemci modlarını anlatır.

1.2.1İşletim sisteminin konumu

Donanım fiziksel bileşenlerin, yani CPU'nun, RAM'in ve diskin adıdır. Yazılım ise bilgisayarın yapacağı işleri tarif eden koddur. İşletim sistemi bu ikisi arasındaki arayüzdür. Uygulamalar donanıma doğrudan erişmez; İS'nin sunduğu sistem çağrıları üzerinden erişir.

Tanım 1.4

Sistem çağrısı (system call), bir kullanıcı programının çekirdekten hizmet istemek için kullandığı denetimli giriş noktasıdır. Dosya açmak, okumak, yazmak, yeni süreç yaratmak ve bellek istemek gibi ayrıcalık gerektiren işlerin hepsi sistem çağrısıyla yapılır.

KullanıcılarUygulamalar (tarayıcı, ofis, oyun, derleyici)Sistem çağrısı arayüzüİşletim sistemi (çekirdek)Donanım (CPU, RAM, SSD, aygıtlar)• yazılımın donanımı kullanmasını kontrol eder• donanım kullanımını optimize eder• sistem güvenliğini ve yalıtımı sağlar
Şekil 1.4: İşletim sisteminin konumu. Kullanıcılar uygulamaları (tarayıcı, ofis, oyun, derleyici) kullanır; uygulamalar sistem çağrısı arayüzünden geçerek işletim sistemine ulaşır; işletim sistemi donanımı (CPU, RAM, SSD, aygıtlar) denetler. Sağdaki notlar İS'nin üç görevini sıralar: yazılımın donanımı kullanmasını kontrol eder, donanım kullanımını en iyi duruma getirir, sistem güvenliğini ve yalıtımı sağlar.

Şekil 1.4 beş katmanı üst üste gösterir. Dikkat çeken katman, uygulamalar ile işletim sistemi arasındaki ince “sistem çağrısı arayüzü”dür. Bu arayüz, iki dünya arasındaki tek kapıdır. Uygulamanın İS'den isteyebileceği her şey bu kapının listesinde yazılıdır; listede olmayan bir işi uygulama yaptıramaz. Linux'ta bu listede birkaç yüz çağrı bulunur ve her birinin bir numarası vardır.

Şeklin sağındaki üç madde, işletim sisteminin bu konumdan yaptığı işleri özetler. Kontrol: hangi programın hangi donanıma ne zaman ve ne kadar erişeceğine İS karar verir. En iyileme: donanımın boşta kalmamasını, işlerin makul sürede bitmesini sağlar. Güvenlik ve yalıtım: programların birbirine ve sisteme zarar vermesini engeller.

1.2.2Çekirdek modu ve kullanıcı modu

Bir programın donanıma doğrudan erişmesini yalnız “erişmemesi gerektiğini söyleyerek” engellemek mümkün değildir. Program ne de olsa işlemcinin üzerinde çalışır ve işlemci kendisine verilen her komutu yürütür. Engel, işlemcinin içine yerleştirilmiş bir donanım özelliğiyle kurulur.

Tanım 1.5

İşlemcide o anki ayrıcalık düzeyini gösteren bir mod biti (ya da birkaç bitlik ayrıcalık seviyesi) bulunur. Çekirdek modunda (kernel mode) bütün komutlar ve bütün bellek kullanılabilir. Kullanıcı modunda (user mode) ayrıcalıklı komutlar yasaktır; böyle bir komut denendiğinde işlemci komutu yürütmez, bir istisna üretir ve denetimi çekirdeğe verir.

Ayrıcalıklı komutlar, sistemin bütününü etkileyebilecek komutlardır. G/Ç aygıtlarına doğrudan erişmek, kesmeleri kapatmak, sayfa tablosunu (yani hangi sanal adresin hangi fiziksel belleğe karşılık geldiğini) değiştirmek bu gruptadır. Bunlardan biri kullanıcı modunda denendiğinde işlemci bir tuzak (trap) üretir: o anki komutu bırakır, çekirdek moduna geçer ve çekirdeğin önceden belirlediği işleyiciye atlar. Çekirdek de çoğunlukla bu programı sonlandırır. Sistemin geri kalanı bundan etkilenmez.

Farklı işlemci ailelerinde bu düzeyler farklı adlar taşır. x86 mimarisinde dört halka (ring) vardır: ring 0 en ayrıcalıklı, ring 3 en kısıtlı düzeydir. ARMv8 mimarisinde istisna düzeyleri EL0'dan EL3'e kadar sıralanır; uygulamalar EL0'da, çekirdek EL1'de çalışır. RISC-V'de ise U (kullanıcı), S (denetçi) ve M (makine) modları bulunur.

Ring 0çekirdekRing 1Ring 2Ring 3 · uygulamalarx86: ring 1–2 pratikte kullanılmaz
Şekil 1.5: x86 ayrıcalık halkaları. Merkezdeki ring 0'da çekirdek, en dıştaki ring 3'te uygulamalar çalışır. Ring 1 ve ring 2 pratikte kullanılmaz; bugünkü işletim sistemleri yalnız iki düzeyle yetinir.

Şekil 1.5, işletim sistemlerinin dört düzeyin yalnız ikisini kullandığını gösterir. Bunun nedeni taşınabilirliktir: Linux ve Windows pek çok işlemci ailesinde çalışır ve bunların ortak paydası iki düzeydir. Kabuk ve GUI, kullanıcı modundaki yazılımın en alt basamağında durur; onların bile ring 0'a erişimi yoktur.

Kullanıcı modundan çekirdek moduna geçmenin yalnız üç denetimli yolu vardır:

  • Sistem çağrısı: program bilerek, özel bir komutla (x86-64'te syscall) çekirdekten hizmet ister.
  • Kesme (interrupt): bir aygıt, örneğin zamanlayıcı ya da ağ kartı, işlemciye dışarıdan sinyal gönderir.
  • İstisna (exception): yürütülen komutun kendisi bir sorun çıkarır; sıfıra bölme, geçersiz adrese erişim ya da kullanıcı modunda ayrıcalıklı komut gibi.

Üç yolun ortak özelliği, programın nereye atlanacağını seçememesidir. Atlanacak adresler, çekirdeğin açılışta doldurduğu bir tabloda yazılıdır. Program yalnız “çekirdeğe gitmek istiyorum” diyebilir; çekirdeğin hangi noktasından içeri gireceğini belirleyemez. Koruma tam olarak buna dayanır.

Sık yapılan hata

Çekirdek modunu “yönetici (root ya da Administrator) hesabıyla çalışmak” ile karıştırmak. Yönetici hesabıyla açılan bir program da kullanıcı modunda çalışır; yalnız çekirdek ona daha çok izin verir. Örneğin her dosyayı açabilir. Ama bu programın her işi yine sistem çağrısıyla yapılır ve programın kendisi asla ring 0'da çalışmaz.

1.2.3Mod geçişi adım adım: read()

Mod geçişini somutlaştırmak için bir C programının dosyadan okuma yaptığı tek satırı izleyelim:

char buf[100];
int n = read(fd, buf, 100);   /* n: okunan bayt sayısı */

Programcı açısından bu, sıradan bir fonksiyon çağrısıdır. Gerçekte ise bu satır, kullanıcı modundan çekirdek moduna gidip geri dönen beş adımlık bir yolculuk başlatır. Aşağıdaki değerler x86-64 mimarisinde çalışan Linux içindir.

Kullanıcı moduÇekirdek moduKullanıcı sürecin = read(fd, buf, 100);libc: read() sarmalayıcısırax=0, rdi=fd, …; syscallSistem çağrısı tablosu[0] → sys_read()sys_read(): argümanlarıdenetle, veriyi kopyalatuzak (syscall)sysret (dönüş)mod biti1kullanıcı Kullanıcı moduÇekirdek moduKullanıcı sürecin = read(fd, buf, 100);libc: read() sarmalayıcısırax=0, rdi=fd, …; syscallSistem çağrısı tablosu[0] → sys_read()sys_read(): argümanlarıdenetle, veriyi kopyalatuzak (syscall)sysret (dönüş)mod biti0çekirdek
Kullanıcı moduÇekirdek moduKullanıcı sürecin = read(fd, buf, 100);libc: read() sarmalayıcısırax=0, rdi=fd, …; syscallSistem çağrısı tablosu[0] → sys_read()sys_read(): argümanlarıdenetle, veriyi kopyalatuzak (syscall)sysret (dönüş)mod biti0çekirdek Kullanıcı moduÇekirdek moduKullanıcı sürecin = read(fd, buf, 100);libc: read() sarmalayıcısırax=0, rdi=fd, …; syscallSistem çağrısı tablosu[0] → sys_read()sys_read(): argümanlarıdenetle, veriyi kopyalatuzak (syscall)sysret (dönüş)mod biti1kullanıcı
Şekil 1.6: read() sistem çağrısında mod geçişi (soldan sağa, yukarıdan aşağı). (a) libc sarmalayıcısı çağrı numarasını ve argümanları yazmaçlara koyar; mod biti 1, yani kullanıcı modu. (b) syscall komutu tuzak üretir; mod biti 0 olur. (c) Çekirdek, sistem çağrısı tablosunun 0. girdisinden sys_read()'i bulur, argümanları denetler ve veriyi kopyalar. (d) sysret mod bitini yeniden 1 yapar ve süreç kaldığı yerden devam eder.
  1. Başlangıç. Süreç kullanıcı modunda çalışıyor (mod biti = 1). Bir dosyadan okumak ister, ama diske kendisi erişemez; disk denetleyicisinin yazmaçlarına dokunan komutlar ayrıcalıklıdır.
  2. Sarmalayıcı. Programın çağırdığı read() aslında C kütüphanesindeki (libc) küçük bir sarmalayıcı fonksiyondur. Bu fonksiyon çağrı numarasını rax yazmacına koyar (x86-64 Linux'ta read'in numarası 0'dır), argümanları da sırayla rdi, rsi, rdx yazmaçlarına yerleştirir: fd, buf, 100. Ardından syscall komutunu yürütür.
  3. Tuzak. syscall komutuyla donanım üç şey yapar: mod bitini 0 yapar, dönüş adresini kaydeder ve çekirdeğin sabit giriş noktasına atlar. Süreç nereye atlanacağını seçemez; giriş noktasını çekirdek açılışta bir yazmaca yazmıştır.
  4. Çekirdekte iş. Çekirdek, rax'taki numarayla sistem çağrısı tablosuna bakar ve 0. girdide sys_read()'i bulur. Bu fonksiyon önce argümanları doğrular: fd bu sürecin açık bir dosyası mı, buf adresi gerçekten sürecin kendi belleğinde mi? Denetimler geçerse veriyi dosyadan sürecin tamponuna kopyalar.
  5. Dönüş. Sonuç, yani okunan bayt sayısı, rax'a yazılır. sysret komutu mod bitini yeniden 1 yapar ve süreç read()'ten sonraki komuttan devam eder. Programın gözünde n değişkeni okunan bayt sayısını almıştır.

Dördüncü adımdaki doğrulama, korumanın kalbidir. Kötü niyetli bir program buf olarak çekirdeğin kendi belleğinin adresini verebilir. Çekirdek bu adresi denetlemeden kullansaydı, dosyadaki veriyi kendi yapılarının üstüne yazar ve sistemi ele geçirmeye açık hâle gelirdi. Bu yüzden çekirdek, kullanıcıdan gelen hiçbir değere güvenmez.

Örnek 1.1

Aşağıdaki işlemlerden hangileri kullanıcı modundan çekirdek moduna geçişe yol açar? (a) int x = a + b; (b) read(fd, buf, 100); (c) bir komutun geçersiz bir adrese erişmesi (d) strlen(s); (e) zamanlayıcının kesme üretmesi.

Çözüm. (a) Toplama, yazmaçlar ve ALU üzerinde yapılan sıradan bir işlemdir; mod değişmez. (b) Sistem çağrısıdır; libc sarmalayıcısı syscall komutuyla bilerek tuzak üretir, geçiş olur. (c) Komutun kendisinden doğan bir istisnadır; işlemci çekirdeğin işleyicisine atlar, geçiş olur. (d) strlen, bellekteki karakterleri sıfır baytına kadar sayan bir kütüphane fonksiyonudur; tamamen kullanıcı modunda kalır. (e) Dışarıdan gelen bir donanım kesmesidir; geçiş olur. Sonuç: (b), (c) ve (e) çekirdeğe geçer; bunlar sırasıyla sistem çağrısı, istisna ve kesmedir.

Sık yapılan hata

Her kütüphane fonksiyonunun çekirdeğe gittiğini sanmak. strlen, memcpy, sqrt gibi fonksiyonlar libc'de olsalar da yalnız kullanıcı belleği ve yazmaçlarla çalışır. Çekirdeğe yalnız read, write, open, fork gibi sistem çağrısı sarmalayıcıları gider. printf ise ikisinin karışımıdır: biçimlendirmeyi kullanıcı modunda yapar, sonunda write sistem çağrısıyla çıktıyı gönderir.

1.2.4Fiziksel aygıtlar, mikro mimari, makine dili

İşletim sisteminin altında da katmanlar vardır. Bunları ayırmak, İS'nin işini sınırlamak için yararlıdır.

BankaRezervasyonTarayıcıDerleyiciEditörKabukİşletim sistemiMakine dili (ISA)Mikro mimariFiziksel aygıtlarUygulamaSistemDonanımYazılım
Şekil 1.7: Altı katmanlı görünüm. Donanım tarafında alttan üste fiziksel aygıtlar, mikro mimari ve makine dili (ISA); yazılım tarafında işletim sistemi, sistem programları (derleyici, editör, kabuk) ve uygulama programları (banka, rezervasyon, tarayıcı).
  • Fiziksel aygıtlar: yongalar, bellek modülleri, SSD ve G/Ç aygıtları; elektronik bileşenlerin kendisi.
  • Mikro mimari: işlemcinin iç tasarımı. Veri yolu, yazmaçlar, aritmetik-mantık birimi (ALU), önbellekler ve denetim birimi bu düzeydedir.
  • Makine dili: donanımın doğrudan yürüttüğü ikili komutlar. Bir işlemci ailesinin komut kümesine komut kümesi mimarisi (instruction set architecture, ISA) denir. x86-64, ARM64 ve RISC-V bugünün başlıca ISA'larıdır.

İşletim sisteminin bu katmanlardaki işi, sürücüler aracılığıyla üst katmanın isteklerini aygıtın anlayacağı düşük düzeyli komutlara çevirmektir. Bir sınırı da iyi bilmek gerekir: programı makine diline derleyici çevirir, işletim sistemi bunu yapmaz. Derleyici, Şekil 1.7'deki gibi sistem programları arasında yer alan sıradan bir kullanıcı programıdır. İşletim sistemi derlenmiş programı belleğe yükler, çalıştırır ve donanıma erişimine aracılık eder.

1.3İşletim sisteminin rolleri

İşletim sistemine iki ayrı açıdan bakılabilir. Yukarıdan, yani uygulamanın gözünden bakıldığında İS, donanımın çirkin ayrıntılarını gizleyen ve yerine kullanışlı kavramlar koyan bir katmandır. Aşağıdan, yani donanımın gözünden bakıldığında ise aynı işlemciyi, aynı belleği ve aynı aygıtları isteyen pek çok program arasında hakemlik yapan bir yöneticidir. Ders kitapları bu iki bakışı genişletilmiş makine ve kaynak yöneticisi diye adlandırır.

1.3.1Genişletilmiş makine

Çıplak donanımın sunduğu arayüz, programcı için hem karmaşık hem de kırılgandır. Bir SSD'ye tek bir blok yazmak için önce NVMe komut kuyrukları hazırlanır ve mantıksal blok adresi (logical block address, LBA) hesaplanır. Komut kuyruğa konur ve denetleyici uyarılır. Ardından tamamlanma kuyruğu izlenir ve olası hata kodları yorumlanır. Uygulamanın bunların hiçbirini bilmesi gerekmez; yalnız şunu der:

write(fd, "Merhaba", 7);

Aradaki farkı kapatan işletim sistemidir. Tanenbaum bu rolü özlü bir cümleyle anlatır: işletim sistemi çirkin donanımı güzel soyutlamalara dönüştürür.

Tanım 1.6

Soyutlama (abstraction), bir şeyin gereksiz ayrıntılarını gizleyip yalnız işe yarayan özelliklerini gösteren basitleştirilmiş modeldir. İşletim sistemi, donanım üzerine soyutlamalar kurduğu için genişletilmiş makine (extended machine) olarak görülür: programcıya çıplak donanımdan daha kullanışlı, “genişletilmiş” bir bilgisayar sunar.

Uygulama programlarıGüzel arayüzopen, read, write, fork …İşletim sistemiÇirkin arayüzyazmaçlar, kesmeler, zamanlamaDonanım
Şekil 1.8: Genişletilmiş makine. Uygulama programları işletim sisteminin sunduğu güzel arayüzü (open, read, write, fork …) görür. İşletim sistemi ise donanımın çirkin arayüzüyle (yazmaçlar, kesmeler, zamanlama) uğraşır.

Genişletilmiş makine rolünün üç sonucu vardır. Birincisi kolaylıktır: donanımın yetenekleri kullanılabilir hâle gelir, karmaşık ayrıntıları gizlenir. İkincisi korumadır: uygulamalar donanıma doğrudan erişemediği için birbirinin işini bozamaz. Üçüncüsü taşınabilirliktir: SSD değişse de, hatta disk yerine ağ üzerinden bir depolama kullanılsa da write çağrısı aynı kalır.

1.3.2Soyutlamalar ve roller

İşletim sisteminin kurduğu soyutlamaların her biri belirli bir donanım parçasına karşılık gelir. Tablo 1.1 başlıca eşleşmeleri verir.

Tablo 1.1: Donanım bileşenleri ve işletim sisteminin onlar için kurduğu soyutlamalar.
DonanımİS soyutlaması
İşlemci (CPU)Süreç ve iş parçacığı
Bellek (RAM)Adres alanı (sanal bellek)
Depolama (SSD/disk)Dosya ve dizin
Ağ kartıSoket
Ekran, klavyePencere, terminal

Tablodaki her soyutlama, donanımın sınırlı ve paylaşılan bir kaynağını her programa “kendine ait” gibi gösterir. Tek bir işlemci vardır, ama her süreç işlemciyi yalnız kendisi kullanıyormuş gibi yazılır. Tek bir fiziksel bellek vardır, ama her süreç 0 adresinden başlayan kendi adres alanını görür. Diskte milyonlarca blok vardır, ama program yalnız bir dosya adı bilir. Ağ kartı tektir, ama her bağlantının kendi soketi vardır.

Bu soyutlamaların çevresinde dört ayrı rol ayırt edilir. Donanım tasarımcısı ISA'yı, aygıtları ve aygıtların arayüzlerini tasarlar. Çekirdek geliştiricisi soyutlamaları bu donanımın üzerinde gerçekler; süreç, sanal bellek ve dosya sistemi onun ürünüdür. Uygulama geliştiricisi soyutlamaları sistem çağrıları ve uygulama programlama arayüzleri (API) üzerinden kullanır. Son kullanıcı ise yalnız uygulamaları ve arayüzü görür. Bu dersin büyük kısmı, ikinci rolün işini anlamaya ayrılmıştır.

1.3.3Bir write() çağrısının yolculuğu

Soyutlamanın neyi gizlediğini görmek için write(fd, "Merhaba", 7) çağrısını uygulamadan SSD'ye kadar izleyelim. Bu çağrı, programın açtığı dosyaya 7 baytlık “Merhaba” dizgesini yazmak ister.

Uygulamawrite(fd, "Merhaba", 7)C kütüphanesi (libc)write() sarmalayıcısı → syscallSistem çağrısı arayüzükullanıcı → çekirdek moduVFS (sanal dosya sistemi)fd → açık dosya → inodeDosya sistemi (ext4, NTFS, APFS)dosya ofseti → blok adresiSayfa önbelleği + blok katmanıkirli sayfa, G/Ç kuyruğuAygıt sürücüsü (NVMe)NVMe yazma komutu, DMASSD denetleyicisiflash sayfasına yazarkullanıcı modu ↑çekirdek modu ↓
Şekil 1.9: Bir write() çağrısının katmanları. Kullanıcı modunda uygulama ve C kütüphanesi; çekirdek modunda sistem çağrısı arayüzü, sanal dosya sistemi (VFS), dosya sistemi (ext4, NTFS, APFS), sayfa önbelleği ve blok katmanı, aygıt sürücüsü (NVMe); en altta SSD denetleyicisi. Son adımda sürücü ile SSD denetleyicisi vurgulanmıştır.
  1. Uygulama yalnız üç şey verir: bir dosya tanımlayıcısı (fd), bir tampon ve bir uzunluk. Diskin türünü, hatta verinin yerel diske mi ağa mı gideceğini bilmez.
  2. libc sarmalayıcısı argümanları yazmaçlara koyar ve syscall ile çekirdeğe geçer; tıpkı 1.2.3'teki read() gibi.
  3. Sanal dosya sistemi (virtual file system, VFS), fd'yi sürecin açık dosya tablosundan bir dosya nesnesine ve oradan dosyanın i-düğümüne (inode) çözer. VFS, bütün dosya sistemleri için ortak olan katmandır.
  4. Dosya sistemi (ext4, NTFS, APFS …), dosyadaki konumu, yani ofseti, diskteki blok adresine çevirir. Dosya büyüyorsa yeni blok ayırır.
  5. Sayfa önbelleği: veri çoğunlukla önce bellekteki sayfa önbelleğine yazılır ve write() hemen döner. Sayfa “kirli” olarak işaretlenir; diske yazma daha sonra yapılır. Verinin hemen kalıcı olması isteniyorsa program fsync() çağırır.
  6. Aygıt sürücüsü bir NVMe yazma komutu hazırlar. SSD denetleyicisi veriyi doğrudan bellek erişimiyle (DMA) bellekten alır, flash sayfasına yazar ve iş bitince bir kesme üretir.

Yedi baytlık bir yazma için altı katman çalışmıştır. Bunların yalnız ilki uygulamanın kodudur. Beşinci adımdaki ayrıntı da önemlidir: write() döndüğünde veri büyük olasılıkla henüz diskte değildir. Elektrik o anda kesilirse son yazılanlar kaybolabilir. Bu yüzden veritabanları gibi güvenilirlik isteyen programlar fsync()'i bilinçli olarak kullanır.

1.3.4Kaynak yöneticisi

Aşağıdan bakıldığında işletim sisteminin görevi farklı görünür. Birden çok program aynı anda çalışmak ister; hepsinin işlemciye, belleğe, diske ve ağa ihtiyacı vardır. İşletim sistemi bu kaynakları programlar arasında dağıtır ve her programın payını diğerlerine karşı korur.

Tanım 1.7

Kaynak yöneticisi olarak işletim sistemi, CPU, bellek ve G/Ç aygıtları gibi kaynakları birden çok programa dağıtır ve korur. Hedefi, kaynakları verimli kullanmak ve programlar arasında adil paylaştırmaktır.

Kaynaklar iki biçimde paylaştırılır. Zamanda paylaştırmada (time multiplexing) programlar kaynağı sırayla kullanır: önce biri, sonra öteki. Alanda paylaştırmada (space multiplexing) kaynak parçalara bölünür ve her program kendi parçasını aynı anda kullanır.

Zamanda çoklama: tek CPUABCABCAzamanAlanda çoklama: bellekİSABCboşYazıcı → zamanda · disk blokları → alanda
Şekil 1.10: Üstte zamanda paylaştırma: tek CPU'yu A, B, C süreçleri sırayla kullanır. Altta alanda paylaştırma: bellek İS, A, B, C ve boş bölge olarak bölünmüştür. Yazıcı zamanda, disk blokları alanda paylaştırılır.

Hangi kaynağın hangi biçimde paylaştırılacağı, kaynağın bölünüp bölünemeyeceğine bağlıdır. İşlemci aynı anda yalnız bir komut akışını yürütür; bölünemez, zamanda paylaştırılır. Bellek ise bölünebilir: aynı anda birkaç program bellekte durur ve her biri kendi bölgesini kullanır. Disk alanı da bölünür, her dosyaya ayrı bloklar verilir; ama diske giden okuma ve yazma istekleri sırayla işlendiği için disk aynı zamanda zamanda da paylaştırılır. Yazıcı ise bölünemez; yarım sayfasını bir programa, yarısını ötekine vermenin anlamı yoktur.

Kaynak yöneticisi bakışı, işletim sistemi tasarımının temel sorularını doğurur. Birçok program aynı anda yazdırmak isterse ne olur? Her sürecin ne kadar kaynak kullandığı nasıl ölçülür? Kaynaklar çoğaldığında, örneğin çekirdek sayısı arttığında, adalet ve verimlilik nasıl sağlanır? Sonraki üç alt kesim bu soruları sırayla ele alır.

1.3.5Aynı anda yazdırma: biriktirme

Bir ofiste üç kişinin neredeyse aynı anda belge yazdırdığını düşünelim. Biri kelime işlemciden bir rapor, biri PDF görüntüleyiciden bir fotoğraf, biri de tarayıcıdan bir not gönderir. Yazıcı tektir. Üç program da yazıcıya doğrudan yazabilseydi, sayfalar satır satır birbirine karışırdı: raporun ilk satırının altında fotoğrafın bir parçası, onun altında notun ilk cümlesi basılırdı.

Kelime işlemciPDF görüntüleyiciTarayıcıYazıcıçıktılar karışır!
Şekil 1.11: Biriktirme olmadan: kelime işlemci, PDF görüntüleyici ve tarayıcı aynı anda tek yazıcıya yazmaya çalışır ve çıktılar karışır.

İşletim sisteminin çözümü, yazıcıyı tek bir sürecin tekeline vermektir.

Tanım 1.8

Biriktirme (spooling), yavaş ve paylaşılamayan bir aygıta giden işlerin önce diskteki bir kuyruğa yazılması, sonra tek bir hizmet sürecinin bu işleri aygıta sırayla göndermesidir.

Kelime işlemciPDF görüntüleyiciTarayıcıbiriktirme kuyruğu (disk)iş 1: rapor.pdfiş 2: foto.jpgiş 3: not.txtBiriktirmehizmetiCUPS / Print SpoolerYazıcışu an: foto.jpg
Şekil 1.12: Biriktirmeyle yazdırma. Üç program işlerini diskteki biriktirme kuyruğuna yazar (iş 1: rapor.pdf, iş 2: foto.jpg, iş 3: not.txt). Yazıcıyı yalnız biriktirme hizmeti (Linux'ta CUPS, Windows'ta Print Spooler) kullanır. İş 1 bitmiş, şu an iş 2 (foto.jpg) basılıyor.

Şekil 1.12 işleyişi gösterir. Programlar işlerini yazıcıya değil, işletim sisteminin diskteki biriktirme kuyruğuna yazar ve hemen kendi işlerine döner. Yazıcıyı kullanan tek süreç biriktirme hizmetidir: Linux'ta CUPS, Windows'ta Print Spooler. Karışıklık bu tekellikle önlenir, çünkü aynı anda yazıcıya yalnız bir iş gider. Hizmet işleri genellikle geliş sırasıyla (first-come, first-served, FCFS) gönderir. Yönetici bir işe öncelik verebilir ya da bir işi duraklatabilir.

Biriktirmenin ikinci kazancı zamandır. Yazıcı, işlemciye göre son derece yavaştır; bir sayfanın basılması saniyeler sürer. Programlar yazıcının bitirmesini beklemek zorunda kalsaydı, kullanıcı her yazdırmada dakikalarca donmuş bir pencereye bakardı. Biriktirme sayesinde yavaş yazıcı çalışırken programlar ve işlemci başka işler yapar. G/Ç ile hesaplama aynı anda ilerler, yani örtüşür. Bu fikir işletim sistemlerinin tarihinde tekrar tekrar karşımıza çıkar.

1.3.6Kod izleme: biriktirme kuyruğu

Biriktirmenin özü küçük bir C koduyla modellenebilir. Kuyruk 4 yuvalı bir dizidir; programlar ekle(), biriktirme hizmeti ise biriktirme_hizmeti() fonksiyonunu çağırır.

char *kuyruk[4];
int giris = 0, cikis = 0, adet = 0;
void ekle(char *dosya) {   /* programlar */
  kuyruk[giris] = dosya;
  giris = (giris + 1) % 4;
  adet++;
}
void biriktirme_hizmeti(void) {
  while (adet > 0) {
    yazici_gonder(kuyruk[cikis]);
    kuyruk[cikis] = NULL;   /* yuva boşalır */
    cikis = (cikis + 1) % 4;
    adet--;
  }
}

Kod satır satır şöyle okunur. İlk satır, dosya adlarını tutacak 4 elemanlı bir işaretçi dizisi tanımlar. İkinci satırdaki üç sayaç kuyruğun durumunu tutar: giris sıradaki boş yuvanın indisidir, cikis basılacak en eski işin indisidir, adet ise kuyruktaki iş sayısıdır. ekle() fonksiyonu dosya adını giris yuvasına yazar, giris'i bir ilerletir ve adet'i artırır. İlerletmede kullanılan % 4 işlemi kuyruğu dairesel yapar: 3. yuvadan sonra 0. yuvaya dönülür. biriktirme_hizmeti() ise kuyrukta iş kaldıkça en eski işi yazıcıya gönderir, gönderilen yuvaya NULL yazarak onu boşaltır, cikis'i aynı biçimde ilerletir ve adet'i azaltır.

Dört programın sırayla a.pdf, b.txt, c.doc ve d.png gönderdiğini, ardından biriktirme hizmetinin çalıştığını varsayalım. Tablo 1.2 her çağrıdan sonra değişkenlerin ve kuyruğun durumunu verir. Kuyruk sütununda “·” boş yuvayı gösterir.

Tablo 1.2: Biriktirme kuyruğunun izlemesi.
AdımİşlemgiriscikisadetKuyruk [0] [1] [2] [3]Basılanlar
0başlangıç000· · · ·—
1ekle("a.pdf")101a.pdf · · ·—
2ekle("b.txt")202a.pdf b.txt · ·—
3ekle("c.doc")303a.pdf b.txt c.doc ·—
4ekle("d.png")004a.pdf b.txt c.doc d.png—
5gönder (1. tur)013· b.txt c.doc d.pnga.pdf
6gönder (2. tur)022· · c.doc d.pnga.pdf, b.txt
7gönder (3. tur)031· · · d.pnga.pdf, b.txt, c.doc
8gönder (4. tur)000· · · ·a.pdf, b.txt, c.doc, d.png
Biriktirme kuyruğu (diskte)[0][1][2][3]giriscikisadet = 0YazıcıboştaBasılanlar: Biriktirme kuyruğu (diskte)a.pdf[0]b.txt[1]c.doc[2]d.png[3]giriscikisadet = 4YazıcıboştaBasılanlar:
Biriktirme kuyruğu (diskte)[0]b.txt[1]c.doc[2]d.png[3]giriscikisadet = 3Yazıcıa.pdfBasılanlar:a.pdf Biriktirme kuyruğu (diskte)[0][1][2][3]giriscikisadet = 0YazıcıboştaBasılanlar:a.pdfb.txtc.docd.png
Şekil 1.13: İzlemenin dört anı. (a) Kuyruk boş, giris ve cikis 0. yuvayı gösterir. (b) Dört ekle'den sonra kuyruk dolu, adet = 4; giris başa sarmıştır. (c) a.pdf yazıcıya gitmiş, cikis = 1, adet = 3. (d) Dört iş basılmış, kuyruk yeniden boş, yazıcı boşta.

Tablonun iki satırı özel dikkat ister. Dördüncü adımda giris = (3 + 1) % 4 = 0 olur: indis dizinin başına sarar. Bu anda giris ile cikis eşittir, ama kuyruk boş değil doludur. İki indisin eşitliği tek başına boş ile doluyu ayırt edemez; adet sayacı tam bu yüzden tutulur. Sekizinci adımda cikis da 0'a sarar ve adet = 0 olduğu için döngü biter. İşler geliş sırasıyla, yani FCFS düzeninde basılmıştır. Yazıcıyı yalnız hizmet kullandığı için çıktılar karışmamıştır.

Yuvayı boşaltan kuyruk[cikis] = NULL; satırı, kuyruğun doğru çalışması için zorunlu değildir: hangi yuvaların dolu olduğunu cikis ve adet sayaçları zaten belirler. Satır, izlemeyi okunur kılar ve basılmış bir işin adının dizide unutulup kalmasını önler. Tablonun beşinci satırından itibaren boşalan yuvaların “·” görünmesi bu satırın sonucudur.

Maliyet. ekle() üç atama yapar; kuyruktaki iş sayısından bağımsız olarak sabit sürede, yani O(1)'de biter. biriktirme_hizmeti() kuyrukta n iş varsa döngüyü n kez döner: O(n). Kuyruğun kendisi sabit, 4 yuvalık bir alan kaplar.

Sık yapılan hata

Bu kodu gerçek bir sistemde olduğu gibi kullanmak. İki eksik vardır. Birincisi, ekle() kuyruğun dolu olup olmadığını denetlemez; beşinci bir iş gelirse en eski işin üzerine yazar. İkincisi, programlar ve hizmet ayrı süreçlerdir ve aynı anda çalışabilir. adet++ ile adet-- tek bir komut değil, “oku, değiştir, yaz” dizisidir; araya öteki taraf girerse bir güncelleme kaybolur. Bu duruma yarış durumu denir. Gerçek biriktirme kuyrukları bu yüzden bir kilitle korunur.

1.3.7Kaynak kullanımını ölçmek

Kaynakları adil dağıtmak için önce kimin ne kadar kullandığını bilmek gerekir. İşletim sistemi bu bilgiyi her süreç için ayrı ayrı tutar. Tablo 1.3 üç temel kaynak için ölçütleri ve onları gösteren araçları özetler.

Tablo 1.3: Kaynak kullanımı ölçütleri ve araçları.
KaynakÖlçütLinuxWindows
CPUKullanıcı ve çekirdek modunda geçen CPU zamanı, kullanım yüzdesi/proc/[pid]/stat, top, timeGörev Yöneticisi, Performans İzleyicisi
BellekYerleşik küme (RSS), sanal boyut, çalışma kümesi; ayırma ve serbest bırakma örüntüsü/proc/[pid]/status, smem“Çalışma kümesi” sütunu
G/ÇOkunan ve yazılan bayt, sistem çağrısı ve G/Ç isteği sayısı/proc/[pid]/io, iotop, strace -cKaynak İzleyicisi

CPU zamanının kullanıcı ve çekirdek modu diye ikiye ayrılması anlamlıdır. Bir program hesap yaparken zamanını kullanıcı modunda geçirir. Çok sayıda küçük dosya okuyan bir program ise zamanının büyük kısmını çekirdekte, sistem çağrılarının içinde geçirir. Linux'ta time komutu bu iki değeri user ve sys adıyla ayrı ayrı yazdırır.

Bellek için en sık bakılan değer yerleşik küme boyutudur (resident set size, RSS): sürecin o anda fiziksel bellekte bulunan sayfalarının toplamı. Sanal boyut ise sürecin ayırdığı bütün adres alanıdır ve RSS'ten çok büyük olabilir, çünkü ayrılan her sayfa gerçekten belleğe getirilmez. Linux'ta bu değerler /proc dizinindeki dosyalardan okunur:

$ grep -E 'VmRSS|Threads' /proc/4211/status
VmRSS:    412360 kB
Threads:  57

Burada 4211 numaralı süreç (önceki terminal örneğindeki Firefox süreci) için iki satır seçilmiştir. VmRSS sürecin 412.360 kB'lık kısmının fiziksel bellekte olduğunu, Threads ise sürecin 57 iş parçacığı içerdiğini söyler. /proc diskte gerçekten var olan bir dizin değildir. Çekirdek bu dosyaların içeriğini her okumada o anki değerlerden üretir.

Bu ölçümlerin kaynağı çekirdektir. Çekirdek her bağlam değiştirmede, her sistem çağrısında ve her sayfa hatasında süreç başına tuttuğu sayaçları günceller. Görev Yöneticisi ya da top gibi araçlar bu sayaçları yalnız okur ve gösterir.

1.3.8Adalet ve verimlilik

Kaynakların çoğalması, yani çekirdek sayısının ve bellek miktarının artması, başarımı ve tepki süresini iyileştirir. Ama aynı zamanda kaynakları adil ve verimli paylaştırmayı zorlaştırır. Dört çekirdekli bir makinede hangi sürecin hangi çekirdekte çalışacağı, tek çekirdekli bir makinedeki “sıradaki kim?” sorusundan çok daha karmaşıktır. İşletim sistemleri bu sorunu dört araçla ele alır.

  • Çizelgeleme: hangi süreç ne zaman ve hangi çekirdekte çalışacak? Bu kararı veren çekirdek bileşenine çizelgeleyici (scheduler) denir. Linux'ta uzun yıllar CFS (Completely Fair Scheduler) kullanıldı; Linux 6.6 sürümüyle (2023) yerini EEVDF çizelgeleyicisi aldı.
  • Önceliklendirme: bazı süreçlere öteki süreçlerden daha çok pay verilir. Linux'ta nice değerleri, Windows'ta öncelik sınıfları ve her iki sistemde de gerçek zamanlı sınıflar bu iş içindir.
  • Kotalar ve sınırlar: bir sürecin ya da süreç grubunun kullanabileceği kaynağa üst sınır konur. Linux'ta cgroups v2 (konteynerlerin temeli), ulimit ve disk kotaları; Windows'ta Job nesneleri bu amaçla kullanılır.
  • Ödünleşim: verim, tepki süresi ve adalet aynı anda en iyi duruma getirilemez. Biri iyileşirken öteki kötüleşebilir.

Son madde üç kavrama dayanır. Verim (throughput), birim zamanda biten iş sayısıdır. Tepki süresi (response time), bir isteğin yapılmasından sistemin ilk yanıtına kadar geçen süredir. Adalet ise her sürecin hak ettiği payı alıp almadığıdır. Örneğin süreçler arasında sık geçiş yapmak tepki süresini kısaltır, çünkü hiçbir süreç uzun süre beklemez. Ama her geçişin bir maliyeti vardır; geçiş sayısı arttıkça işlemcinin asıl işe ayırdığı zaman, dolayısıyla verim azalır.

Doğru denge iş yüküne bağlıdır. Bir sunucuda öncelik verimdir: saatte işlenen istek sayısı en çok olmalıdır. Bir masaüstü bilgisayarda öncelik tepki süresidir: fare tıklamasına anında yanıt gelmelidir. Gerçek zamanlı bir sistemde ise öncelik, işlerin son tarihlerinden önce bitmesidir.

1.3.9Üç büyük fikir: dört küçük deney

Arpaci-Dusseau'ların Operating Systems: Three Easy Pieces (OSTEP) kitabı, işletim sistemi konularını üç büyük fikir çevresinde toplar: sanallaştırma, eş zamanlılık ve kalıcılık. Her fikir, Linux üzerinde çalıştırılan küçük bir deneyle somutlaştırılabilir. Sanallaştırma işlemci ve bellek için ayrı ayrı gösterildiğinden deney sayısı dörttür.

Tanım 1.9

Sanallaştırma (virtualization), tek bir fiziksel kaynağı her programa kendine ait, ayrı bir kaynak varmış gibi gösterme tekniğidir. İşletim sistemi işlemciyi ve belleği sanallaştırır.

Deney 1: işlemciyi sanallaştırma. yaz adlı program sonsuz bir döngüde kendisine verilen harfi ekrana yazar. Tek çekirdekli bir makinede üç kopyası arka planda başlatılır:

$./yaz X & ./yaz Y & ./yaz Z &[1] 3101 [2] 3102 [3] 3103XYZZXY…yaz X“CPU benim”yaz Y“CPU benim”yaz Z“CPU benim”gerçekte tek CPU, sırayla:XYZXYZXYZzamandilim başına birkaç ms; ne zaman kiminçalışacağına İS karar verir
Şekil 1.14: İşlemciyi sanallaştırma. ./yaz X & ./yaz Y & ./yaz Z & komutu 3101, 3102, 3103 numaralı üç süreç başlatır; çıktıda X, Y, Z harfleri karışık sırayla görünür. Her program “CPU benim” sanır; gerçekte tek CPU sırayla X, Y, Z süreçlerine verilir. Her dilim birkaç milisaniyedir ve kimin ne zaman çalışacağına İS karar verir.

Üç program da kendi işini kesintisiz yapıyormuş gibi görünür; çıktıda üç harf karışık sırayla belirir. Oysa makinede tek çekirdek vardır. İşletim sistemi işlemciyi birkaç milisaniyelik dilimler hâlinde sırayla üç sürece verir. Geçişler insan gözünün fark edemeyeceği kadar sık olduğu için her biri kendi işlemcisi varmış gibi davranır. Burada iki ayrı soru vardır. Sıradaki dilimin kime verileceği bir politika sorusudur ve çizelgeleme algoritmasıyla yanıtlanır. Geçişin nasıl yapılacağı ise bir mekanizma sorusudur ve bağlam değiştirmeyle yanıtlanır. İki kavramın ayrımı 1.8.1'de işlenir.

Deney 2: belleği sanallaştırma. sayac adlı program bir tamsayı için bellek ayırır, bu belleğin adresini yazdırır, sonra döngüde değeri artırıp yazdırır. İki kopyası aynı anda çalıştırılır:

$setarch -R ./sayac & setarch -R ./sayac &(4012) p = 0x5555555592a0(4013) p = 0x5555555592a0(4012) *p = 1(4013) *p = 1(4013) *p = 2(4012) *p = 2…süreç 4012sanal adres alanı0x5555555592a0süreç 4013sanal adres alanı0x5555555592a0fizikselbellekçerçeve 17çerçeve 42aynı sanal adres, farklı fiziksel çerçeve
Şekil 1.15: Belleği sanallaştırma. 4012 ve 4013 numaralı süreçler aynı adresi (0x5555555592a0) yazdırır, ama her birinin değeri birbirinden bağımsız 1, 2, … diye artar. İki süreç aynı sanal adresi kullanır; İS bu adresleri farklı fiziksel çerçevelere (17 ve 42) eşler.

İki süreç de aynı adresi yazdırır. Bellek gerçekten paylaşılsaydı, iki süreç aynı tamsayıyı artırır ve değerler 1, 2, 3, 4 diye ortak ilerlerdi. Oysa her süreçte değer 1'den başlayıp bağımsız artar. Açıklama şudur: programların gördüğü adres sanal adrestir. Her süreç kendi adres alanını görür ve işletim sistemi, donanımın yardımıyla, aynı sanal adresi her süreç için farklı bir fiziksel bellek çerçevesine eşler. Deneyde setarch -R komutu kullanılmıştır; bu komut adres alanı yerleşiminin rastgeleleştirilmesini (address space layout randomization, ASLR) kapatır. ASLR açık kalsaydı adresler her çalıştırmada değişir ve aynılık görülmezdi.

Deney 3: eş zamanlılık. artir programı iki iş parçacığı başlatır. Her iş parçacığı ortak bir sayac değişkenini N kez artırır. Beklenen sonuç 2N'dir.

$./artir 1000sonuç: 2000$./artir 1000000sonuç: 1318442$./artir 1000000sonuç: 1407715(beklenen: 2000000)T1: N kez sayac++T2: N kez sayac++sayacsayac++ aslında üç komut:okuartıryazaraya diğer iş parçacığı girerse bir artışkaybolur: sonuç 2N’den küçük olur
Şekil 1.16: Eş zamanlılık. ./artir 1000 doğru sonucu (2000) verir. ./artir 1000000 ise 2.000.000 yerine bir çalıştırmada 1.318.442, ötekinde 1.407.715 verir. T1 ve T2 aynı sayac'ı artırır; sayac++ aslında oku, artır, yaz biçiminde üç komuttur ve araya öteki iş parçacığı girerse bir artış kaybolur.

N = 1000 için sonuç doğru görünür, çünkü iş parçacıkları o kadar kısa sürer ki birbirleriyle pek karşılaşmazlar. N bir milyona çıkınca sonuç 2N'den küçük olur ve her çalıştırmada başka bir değer çıkar. Nedeni, sayac++ ifadesinin atomik olmamasıdır: makine düzeyinde değeri bellekten yazmaca okuma, yazmaçta artırma ve geri yazma diye üç ayrı komuttur. İki iş parçacığı aynı eski değeri okur, ikisi de bir artırır ve ikisi de aynı yeni değeri yazarsa iki artıştan biri kaybolur. Paylaşılan veriye eş zamanlı erişimin doğru yönetilmesi, işletim sistemlerinin en zor konularından biridir.

Tanım 1.10

Eş zamanlılık (concurrency), birden çok işin zaman içinde iç içe ilerlemesidir. İşler tek bir işlemcide sırayla dilimler alarak ilerleyebilir; birden çok çekirdekte gerçekten aynı anda çalışmaları ise paralellik (parallelism) diye adlandırılır.

Deney 4: kalıcılık. kaydet programı not.txt dosyasına “merhaba disk” yazar. Bilgisayar yeniden başlatıldıktan sonra dosya yine okunur:

$./kaydet$cat not.txtmerhaba disk$sudo reboot… (sistem kapanıp yeniden açılır)$cat not.txtmerhaba diskprogram: open, write, closeİS: dosya sistemiaygıt sürücüsü: G/Ç isteğiSSD / disk: kalıcıblok seçimi, meta veri, sıralama: hepsi İS’de
Şekil 1.17: Kalıcılık. ./kaydet çalıştırılır, cat not.txt “merhaba disk” yazar; sudo reboot ile sistem yeniden açıldıktan sonra da aynı içerik okunur. Program yalnız open, write, close çağırır; dosya sistemi, aygıt sürücüsü ve G/Ç isteği İS'nin içindedir; veri SSD ya da diskte kalıcıdır.

Ana bellek (DRAM) uçucudur: güç kesilince içeriği silinir. Verinin yeniden başlatmadan sonra da kalması isteniyorsa, dosya sistemi aracılığıyla kalıcı bir aygıta yazılır. Programın yaptığı tek şey open, write ve close çağırmaktır. Verinin hangi bloğa yazılacağı, dosyanın adı, boyutu ve izinleri gibi meta verilerin nerede tutulacağı ve aygıtla nasıl konuşulacağı işletim sisteminin işidir.

1.4Tarihçe

Bugünkü işletim sistemlerinde bulunan pek çok fikir, belirli bir dönemin belirli bir sorununa çözüm olarak doğmuştur. Biriktirme, çoklu programlama ve zaman paylaşımı birer moda değil, pahalı bir kaynağı boşa harcamamanın yollarıdır. Tarihi bilmek, bu fikirlerin neden var olduğunu anlamayı kolaylaştırır.

1.4.1Nesiller

Tanenbaum, bilgisayarların ve işletim sistemlerinin gelişimini donanım teknolojisindeki sıçramalara göre nesillere ayırır.

1945–55Vakum tüpleriİS yok; kablo panoları1. nesil1955–65TransistörlerToplu iş (batch)2. nesil1965–80TümleşikdevrelerÇoklu programlama,zaman paylaşımı3. nesil1980–Kişisel bilgisayarMS-DOS, Mac, Windows,Linux4. nesil1990–Mobil aygıtlarSymbian, iOS,Android5. nesil2000–Sanallaştırmave bulutHipervizör, konteyner,IoTgünümüz
Şekil 1.18: İşletim sistemlerinin nesilleri. 1. nesil (1945–55) vakum tüpleri, İS yok, kablo panoları; 2. nesil (1955–65) transistörler ve toplu iş; 3. nesil (1965–80) tümleşik devreler, çoklu programlama ve zaman paylaşımı; 4. nesil (1980–) kişisel bilgisayar, MS-DOS, Mac, Windows, Linux; 5. nesil (1990–) mobil aygıtlar, Symbian, iOS, Android. Son kutu (2000–, sanallaştırma ve bulut: hipervizör, konteyner, IoT) Tanenbaum'un nesillerinden biri değildir; bugünkü sistemleri anlamak için eklenmiştir.

Her nesil yeni bir elektronik teknolojiyle başlar: vakum tüpü, transistör, tümleşik devre, mikroişlemci. Teknoloji değiştikçe makinelerin boyu, fiyatı ve güvenilirliği değişir. Değişmeyen ise her nesilde yeniden sorulan şu sorudur: pahalı olan kaynağı nasıl daha iyi kullanırız? Bu kaynak ilk nesillerde işlemcinin kendisiydi. Fiyatlar düştükçe pahalı kaynak bellek ve en sonunda insan zamanı oldu.

1.4.21. ve 2. nesil

Birinci nesil (1945–55) vakum tüpleriyle yapılan makinelerin dönemidir. ENIAC (1945) gibi bu makineler bir odayı doldururdu; yavaştı ve tüpler sık yandığı için sık arızalanırdı. Aynı küçük mühendis grubu makineyi tasarlar, kurar, programlar ve bakımını yapardı. Programlama makine diliyle ya da kablo panolarındaki bağlantıları değiştirerek yapılırdı; 1950'lerin başında delikli kartlar kullanılmaya başlandı. Programlama dili de işletim sistemi de yoktu. Makineler çoğunlukla sayısal tablolar hesaplamak için kullanılırdı.

İkinci nesil (1955–65) transistörle başladı. Transistör tüpün yerini aldı; makineler küçüldü, güvenilir hâle geldi ve daha az enerji harcadı. Bu dönemin ana bilgisayarları klimalı odalarda dururdu ve fiyatları milyonlarca dolardı. Programcı ile makine arasına bir operatör girdi: programcı kart destesini operatöre teslim eder, operatör işi çalıştırır, sonucu basılı kâğıt olarak geri verirdi.

Bu düzenin büyük bir kaybı vardı. Bir iş bitince operatör sıradaki işin kartlarını okutup derleyiciyi yükleyene kadar milyonluk makine boş beklerdi. Çözüm toplu iş oldu.

Tanım 1.11

Toplu iş sistemi (batch system), işlerin önceden bir araya toplandığı ve küçük bir denetim programının, yani monitörün, bu işleri insan müdahalesi olmadan sırayla çalıştırdığı sistemdir.

Toplu işin amacı, işler arasındaki el ile geçiş süresini ortadan kaldırmaktı. İşler bir manyetik teybe toplanır, monitör onları sırayla ve otomatik olarak çalıştırırdı; operatör yalnız teypleri taşırdı. Ama iki sınır kaldı. Bellekte aynı anda tek iş bulunduğu için iş G/Ç yaparken işlemci boşta beklerdi. Programcı da işi teslim ettikten sonra saatler sonra sonuç alabiliyordu; etkileşim yoktu. Toplu iş, etkileşimli ve gerçek zamanlı kullanıma uygun değildi.

1.4.3Erken bir toplu iş sistemi

İkinci neslin tipik bir kurulumu, iki ayrı IBM makinesini birlikte kullanırdı: ucuz bir IBM 1401 ve pahalı bir IBM 7094.

Kartokuyucu1401Girişteybi7094Çıkışteybi1401YazıcıSistemteybioperatör taşıroperatör taşır(a)(b)(c)(d)(e)(f)
Şekil 1.19: Erken bir toplu iş sistemi. (a) Kart okuyucu, (b) 1401 kartları giriş teybine yazar, (c) operatör giriş teybini 7094'e taşır, (d) 7094 sistem teybindeki monitörle işleri hesaplar ve sonuçları çıkış teybine yazar, (e) operatör çıkış teybini 1401'e taşır, (f) 1401 sonuçları yazıcıdan basar.
  1. (a) Programcılar delikli kart destelerini 1401'in kart okuyucusuna getirir.
  2. (b) Ucuz 1401, bir grup işin kartlarını okuyup giriş teybine yazar. Yavaş kart okuma işi böylece pahalı makineden ayrılır.
  3. (c) Operatör dolan giriş teybini 7094'e taşır.
  4. (d) 7094, sistem teybindeki monitör programının denetiminde işleri sırayla hesaplar ve çıktıları çıkış teybine yazar.
  5. (e) Operatör çıkış teybini yeniden 1401'e taşır.
  6. (f) 1401 çıktıları yazıcıdan basar. Bu işe çevrim dışı yazdırma denirdi.

Bu düzenin mantığı açıktır: pahalı 7094 hiç kart okumak ya da yazdırmakla uğraşmaz. Teypten okumak karttan okumaktan çok daha hızlıdır, dolayısıyla pahalı makinenin zamanı hesaplamaya kalır. Yavaş G/Ç işleri ucuz makineye devredilmiştir. Aynı ilke, biraz farklı bir biçimde, bugünkü biriktirmede de yaşar.

1.4.4IBM 1401, 7094 ve System/360

Bu dönemin üç makinesi, işletim sistemlerinin evriminde ayrı birer basamaktır. Tablo 1.4 onları karşılaştırır.

Tablo 1.4: Üç IBM makinesi.
MakineYılÖzellik
IBM 14011959Ticari veri işleme; karakter tabanlı, görece ucuz. Kart okuma, teybe kopyalama ve yazdırma gibi G/Ç işlerinde de kullanıldı. 1960'ların en yaygın bilgisayarlarından biriydi.
IBM 70941962Bilimsel hesaplama; 36 bit sözcüklü ve 1401'den tamamen farklı bir mimari. IBSYS ve FMS altında toplu iş çalıştırdı. MIT'de değiştirilmiş bir 7094 üzerinde CTSS ile zaman paylaşımı denendi.
System/3601964Küçükten büyüğe aynı mimariyi paylaşan bir makine ailesi. İşletim sistemi OS/360: çoklu programlama ve biriktirme içeren, milyonlarca satırlık, hatalarla dolu dev bir proje.

Tablodaki iki ayrıntı sık karıştırılır. Birincisi, 1401 ve 7094 OS/360 altında çalışmazdı; OS/360, 1964'te duyurulan System/360 ailesi için yazılmıştı. İkincisi, 7094, 1401'in güçlü bir sürümü değildi; onu tamamlayan, bambaşka mimarili ayrı bir makineydi. İki makinenin birbirinin programını çalıştıramaması, IBM'i System/360'ta tek mimariye dayanan bir aile kurmaya itti. Bu aile fikri, bir programın küçük makinede yazılıp büyük makinede değiştirilmeden çalışabilmesi anlamına geliyordu ve bugünkü x86 ya da ARM ailelerinin de temelidir.

OS/360'ın geliştirilmesi yazılım mühendisliğinin klasik örneklerinden biri oldu. Projenin yöneticilerinden Fred Brooks, deneyimlerini The Mythical Man-Month kitabında anlattı. Kitaptaki en ünlü gözlem, geciken bir yazılım projesine yeni insan eklemenin projeyi daha da geciktirdiğidir.

1.4.5Tipik bir FMS işi

FMS, IBM 709 ve 7094 için yazılmış bir toplu iş monitörüdür; adı Fortran Monitor System kısaltmasıdır. Bir iş, monitöre ne yapacağını söyleyen özel kartlarla çevrelenmiş bir kart destesiydi.

$ENDProgram verisi$RUN$LOADFORTRAN programı$FORTRAN$JOB, 10,7710802, OGRENCI
Şekil 1.20: Tipik bir FMS işinin kart destesi. Öndeki ilk karttan arkaya doğru: $JOB (en çok çalışma süresi, hesap numarası, programcı adı), $FORTRAN, FORTRAN programı, $LOAD, $RUN, program verisi ve $END.

$ ile başlayan kartlara denetim kartları denir. Monitör desteyi sırayla okur ve her denetim kartında bir iş yapar. $JOB yeni bir işi başlatır ve hesabını açar. $FORTRAN derleyiciyi yükler ve ardından gelen kartları derletir. $LOAD derlenen programı belleğe yükler. $RUN programı çalıştırır ve sonraki kartları onun verisi olarak verir. $END işi bitirir. Bu kartlar, bugünkü kabuk betiklerinin ve IBM'in iş denetim dili JCL'nin atasıdır. Bir kabuk betiği de aynı biçimde “derle, bağla, çalıştır” adımlarını sırayla yazan bir komut listesidir.

Sık yapılan hata

FMS kısaltmasını fabrika otomasyonundaki Esnek Üretim Sistemi (Flexible Manufacturing System) ile karıştırmak. Buradaki FMS, FORTRAN Monitor System'dir; iki kavram arasında hiçbir bağ yoktur.

1.4.63. nesil: tümleşik devreler ve çoklu programlama

Üçüncü nesil (1965–80), tümleşik devrelerin dönemidir. System/360 gibi uyumlu aileler ortaya çıktı; makineler daha küçük, daha ucuz ve daha güvenilir oldu. Bu nesil, işletim sistemlerine üç kalıcı fikir kazandırdı.

Birinci fikir çoklu programlamadır.

Tanım 1.12

Çoklu programlama (multiprogramming), belleği bölümlere ayırıp her bölümde ayrı bir iş tutma ve bir iş G/Ç beklerken işlemciyi hemen bellekteki başka bir işe verme tekniğidir.

İş 3İş 2İş 1İşletim sistemibellek bölümleriBellekte üç iş
Şekil 1.21: Çoklu programlamada bellek dört bölüme ayrılmıştır: en altta işletim sistemi, üstünde iş 1, iş 2 ve iş 3.

İkinci nesilde bellekte tek iş bulunuyordu ve iş teypten okuma yaparken işlemci bekliyordu. Bilimsel hesaplamada bu bekleme küçüktü; ama ticari veri işlemede işlerin zamanının büyük kısmı G/Ç ile geçiyordu ve işlemci zamanının çoğu boşa gidiyordu. Çoklu programlamada ise Şekil 1.21'deki gibi bellekte birkaç iş birlikte durur. Biri G/Ç beklerken işlemci başka birini çalıştırır.

Tek işlemcili bir makinede işler gerçekten aynı anda çalışmaz; işlemci bir anda yalnız birini yürütür. İşler zaman içinde iç içe ilerler. Bu, gerçek paralellik değil, eş zamanlılıktır. Bellekte birden çok iş bulunması yeni bir sorun da getirir: bir iş, hatası yüzünden ya da kasten, ötekinin belleğine yazmamalıdır. Bu yüzden çoklu programlama, belleği koruyan donanım olmadan güvenle yapılamaz.

İkinci fikir biriktirmedir. Kartlar artık ayrı bir makine ve teyp aracılığıyla değil, doğrudan diske okunuyordu. Bir iş bitince sıradaki iş diskten belleğe yüklenirdi. 1401 ile teyp taşıma düzeni böylece ortadan kalktı.

Üçüncü fikir zaman paylaşımıdır.

Tanım 1.13

Zaman paylaşımı (time sharing), çok sayıda kullanıcının kendi terminalinden aynı makineyi etkileşimli olarak kullanabilmesi için işlemcinin kısa zaman dilimleriyle süreçler arasında döndürülmesidir. Bir sürecin dilimi dolunca, G/Ç yapmasa bile, zamanlayıcı kesmesiyle işlemci elinden alınır.

Çoklu programlama ile zaman paylaşımı arasındaki fark ince ama önemlidir. Çoklu programlamada işlemci bir işten ancak o iş beklemeye girince alınır; hiç G/Ç yapmayan bir iş işlemciyi saatlerce tutabilir. Zaman paylaşımında ise donanım zamanlayıcısı düzenli aralıklarla kesme üretir ve her sürecin dilimi sınırlıdır. Amaç verimden çok, her kullanıcıya kısa bir tepki süresi sağlamaktır. Programcı artık işini teslim edip saatlerce beklemez; komutunu yazar ve saniyeler içinde yanıt alır.

Zaman paylaşımının soyağacı bugünkü sistemlere kadar uzanır: MIT'de geliştirilen CTSS (1961), ardından çok iddialı MULTICS projesi, MULTICS'ten ayrılan Bell Labs araştırmacılarının yazdığı UNIX (1969). UNIX'ten Berkeley'deki BSD sürümleri doğdu. Farklı UNIX türevlerinin birbirleriyle uyumlu olması için POSIX standardı yazıldı. Andrew Tanenbaum öğretim amacıyla MINIX'i geliştirdi; Linus Torvalds da 1991'de MINIX'ten esinlenerek Linux çekirdeğini yazdı.

1.4.7Çoklu programlama zaman çizelgesi

Çoklu programlamanın kazancını sayıyla görmek için iki işi tek işlemcili bir makinede çalıştıralım. Süreler birim zaman cinsindendir:

  • A işi: 2 birim CPU, 4 birim G/Ç, 2 birim CPU.
  • B işi: 3 birim CPU, 3 birim G/Ç, 1 birim CPU.

İki işin toplam CPU ihtiyacı 2 + 2 + 3 + 1 = 8 birimdir. Her işin G/Ç'si kendi aygıtında yapılır, yani A'nın G/Ç'si ile B'nin G/Ç'si birbirini beklemez.

Tek programlıCPUG/ÇÇoklu programlıCPUG/Ç (A)G/Ç (B)AABBABboştaboştaABAABB0123456789101112131415bitiş t=15 · CPU %53bitiş t=9 · CPU %89
Şekil 1.22: Tek programlı ve çoklu programlı zaman çizelgeleri. Üstte tek programlı sistem: A 0–2, CPU boşta 2–6, A 6–8, B 8–11, CPU boşta 11–14, B 14–15; bitiş t = 15, CPU kullanımı %53. Altta çoklu programlı sistem: A 0–2, B 2–5, boşta 5–6, A 6–8, B 8–9; A'nın G/Ç'si 2–6, B'nin G/Ç'si 5–8; bitiş t = 9, CPU kullanımı %89.
Örnek 1.2

Tek programlı sistem. Bellekte tek iş bulunur; B, ancak A tamamen bitince başlar.

  1. t = 0–2: A CPU kullanır.
  2. t = 2–6: A G/Ç yapar. Bellekte başka iş olmadığı için CPU 4 birim boşta kalır.
  3. t = 6–8: A son CPU dilimini kullanır ve biter.
  4. t = 8–11: B CPU kullanır.
  5. t = 11–14: B G/Ç yapar; CPU 3 birim boşta.
  6. t = 14–15: B son CPU dilimini kullanır ve biter.

Toplam süre 15 birim, CPU'nun çalıştığı süre 8 birimdir. CPU kullanımı 8 / 15 ≈ 0,53, yani yaklaşık %53'tür. Boşta geçen 7 birim, tam olarak iki işin G/Ç sürelerinin toplamıdır (4 + 3).

Örnek 1.3

Çoklu programlı sistem. İki iş de bellektedir; CPU boşalınca hazır olan işe verilir.

  1. t = 0–2: A CPU kullanır; B hazır olarak bekler.
  2. t = 2: A G/Ç'ye gider (2–6). CPU boş kalmaz, hemen B'ye verilir: B t = 2–5 arasında çalışır. A'nın G/Ç'si bu sırada arka planda sürer.
  3. t = 5: B G/Ç'ye gider (5–8). A'nın G/Ç'si henüz bitmemiştir; hazır iş yoktur ve CPU yalnız t = 5–6 arasında boşta kalır.
  4. t = 6: A'nın G/Ç'si biter; A son CPU dilimini t = 6–8 arasında kullanır ve biter.
  5. t = 8: B'nin G/Ç'si tam bu anda biter; B son dilimini t = 8–9 arasında kullanır ve biter.

İki iş 15 yerine 9 birimde bitmiştir. CPU kullanımı 8 / 9 ≈ 0,89, yani yaklaşık %89'dur. Boşta geçen tek birim, iki işin aynı anda G/Ç beklediği t = 5–6 aralığıdır.

İki örnek arasındaki farkın kaynağı tektir: çoklu programlamada CPU ile G/Ç örtüşür. Tek programlı sistemde A'nın 4 birimlik G/Ç'si boyunca CPU boş beklerken, çoklu programlı sistemde bu sürenin 3 birimi B'nin hesabıyla doldurulmuştur. İşlerin toplam CPU ihtiyacı değişmemiş, yalnız beklemeler başka işlerin hesabıyla örtülmüştür. Bu yüzden çoklu programlamanın kazancı, işlerin G/Ç oranı yükseldikçe artar. Hiç G/Ç yapmayan iki iş için kazanç sıfırdır.

Sık yapılan hata

Çoklu programlamanın işleri “daha hızlı çalıştırdığını” düşünmek. Her işin kendi CPU süresi aynı kalır; A yine 4, B yine 4 birim CPU kullanır. Kısalan şey, bütün işlerin bitmesi için geçen toplam süredir. Örnekte A her iki sistemde de t = 8'de biter; kazanç tamamen B'nin 15 yerine 9'da bitmesinden gelir. A'nın G/Ç'si 2 birim sürseydi, t = 4'te bitecekti. CPU o sırada B'de olduğu için A t = 5'e kadar bekler ve t = 7'de biterdi; tek başına çalışsa t = 6'da bitecekti. Çoklu programlama toplam işi hızlandırır, tek bir işin bitişini ise geciktirebilir.

1.4.84. nesil: kişisel bilgisayarlar

Dördüncü nesil (1980–), binlerce transistörü tek bir yongaya sığdıran çok büyük ölçekli tümleşik devrelerle, yani mikroişlemcilerle başladı. Bilgisayar ilk kez bir kişinin masasına sığacak kadar küçüldü ve bir kişinin alabileceği kadar ucuzladı. Tablo 1.5 bu dönemin işletim sistemi kilometre taşlarını sıralar.

Tablo 1.5: Kişisel bilgisayar döneminin kilometre taşları.
YılGelişme
1974Gary Kildall'ın Intel 8080 için yazdığı CP/M: mikrobilgisayarlar için disk tabanlı ilk yaygın İS.
1981IBM PC ve 86-DOS'tan türetilen MS-DOS; BASIC yorumlayıcısı ROM'da.
1973–84Xerox PARC'ta geliştirilen GUI fikirleri, Apple Lisa (1983) ve Macintosh (1984) ile kullanıcı dostu arayüze dönüştü.
1985–2000Windows 1.0 … 3.x, MS-DOS üzerinde çalışan bir grafik ortamdı; Windows 95/98/ME de hâlâ MS-DOS tabanlıydı.
1993–Sıfırdan yazılan Windows NT; ardından 2000, XP, 7, 10 ve 11. Bugünkü Windows'un çekirdeği NT'dir.
1991–Linus Torvalds'ın Linux çekirdeği; 2001'de UNIX tabanlı Mac OS X (Darwin/XNU), bugünkü macOS'un atası.

Bu dönemde işletim sisteminin hedefi değişti. Önceki nesillerde makine pahalı, insan zamanı ucuzdu; işletim sistemi makineyi bir an bile boş bırakmamaya çalışırdı. Fiyatların hızla düşmesi bilgisayarı herkese açınca denge tersine döndü. Artık asıl değerli olan kullanıcının zamanıydı ve İS'nin hedefi “makineyi verimli kullan”dan “kullanıcıya kolaylık sağla”ya kaydı. GUI'nin yükselişi bu kaymanın sonucudur.

MS-DOS ile Windows NT arasındaki geçiş de öğreticidir. MS-DOS tek görevli bir sistemdi ve bellek koruması yoktu; bir programın hatası bütün sistemi çökertebilirdi. Windows 95/98/ME bu temelin üstüne kurulduğu için kararsızlıklarıyla tanındı. Windows NT ise baştan koruma, çoklu görev ve çok kullanıcılı çalışma düşünülerek yazıldı. 2001'de XP ile birlikte ev kullanıcıları da NT çekirdeğine geçti.

1.4.95. nesil: mobil aygıtlar ve günümüz

Beşinci nesil (1990–) mobil aygıtların dönemidir. 1990'larda PDA'lar ve Palm OS, 2000'lerde Symbian ve BlackBerry OS yaygındı. 2007'de iPhone ile iOS, 2008'de Linux çekirdeği üzerine kurulu Android çıktı. Microsoft'un Windows Phone'u 2010'da piyasaya girdi, 2017'de geliştirilmesi durduruldu. Bugün pazarı Android ve iOS paylaşır. Mobil işletim sistemlerinde dokunmatik arayüz, pil yönetimi ve uygulamaların hangi verilere erişebileceğini belirleyen izin modeli öne çıkar.

Mobil dünyanın yanında dört eğilim bugünkü işletim sistemlerini biçimlendirir:

  • Sanallaştırma ve bulut: hipervizörler (KVM, Hyper-V, Xen) tek bir fiziksel sunucuda çok sayıda sanal makine çalıştırır. Bulut hizmetlerinin temeli budur.
  • Konteynerler: Docker ve Kubernetes gibi araçlar, aynı çekirdeği paylaşan ama birbirinden yalıtılmış ortamlar kurar. Yalıtım Linux'un ad alanları (namespaces) ve cgroups özellikleriyle sağlanır.
  • Heterojen donanım: bir yongada CPU, GPU ve yapay zekâ işlemleri için NPU birlikte bulunur; işlemcilerde büyük ve küçük çekirdekler bir arada kullanılır.
  • Güvenlik: bellek güvenliğini dil düzeyinde sağlayan diller çekirdeğe giriyor. Linux 6.1'den beri çekirdekte Rust desteği vardır.

1.5Donanım

İşletim sistemi donanımı yönettiği için donanımın özellikleri İS tasarımını doğrudan biçimlendirir. Yazmaçların sayısı bağlam değiştirmenin maliyetini, bellek düzeyleri arasındaki hız farkı çizelgeleme kararlarını, çekirdek sayısı eş zamanlılık sorunlarının boyutunu belirler. Bu kesim, işletim sistemi açısından önemli olan donanım özelliklerini özetler.

1.5.1Kişisel bilgisayarın bileşenleri

CPUBellekEkrandenetleyicisiKlavyedenetleyicisiUSBdenetleyicisiDiskdenetleyicisiMMUVeri yoluMonitörKlavyeYazıcı (USB)SSD / diskAygıtlar →
Şekil 1.23: Kişisel bilgisayarın bileşenleri. CPU (içinde MMU), bellek ve ekran, klavye, USB ve disk denetleyicileri ortak bir veri yoluna bağlıdır. Denetleyicilerin altında monitör, klavye, USB yazıcı ve SSD/disk aygıtları bulunur.

Şekil 1.23 basitleştirilmiş bir modeldir. İşlemci, bellek ve G/Ç aygıtları bir veri yolu (bus) üzerinden haberleşir. Her aygıtın bir denetleyicisi vardır: monitörü ekran denetleyicisi, diski disk denetleyicisi sürer. İşlemci aygıtla doğrudan değil, onun denetleyicisiyle konuşur.

İşlemcinin içinde görülen MMU (memory management unit: bellek yönetim birimi), programların kullandığı sanal adresleri fiziksel adreslere çeviren donanımdır. 1.3.9'daki ikinci deneyde iki sürecin aynı adresi farklı fiziksel çerçevelere eşleyebilmesi bu birim sayesindedir. Çeviri kurallarını, yani sayfa tablolarını işletim sistemi yazar; çeviriyi her bellek erişiminde MMU yapar.

Gerçek sistemlerde tek bir ortak hat yerine PCIe gibi noktadan noktaya bağlantılar kullanılır; her aygıt işlemciye kendi hattıyla bağlanır. Ama kavramsal model aynı kalır: işlemci, bellek ve aygıt denetleyicileri birbirine bağlıdır ve işletim sistemi denetleyicilerle konuşarak aygıtları yönetir.

1.5.2İşlemci ve yazmaçlar

İşlemci bilgisayarın “beynidir”: bütün komutları o yürütür. Çalışması basit bir döngüye dayanır.

Tanım 1.14

İşlemcinin temel çalışma biçimi getir–çöz–yürüt döngüsüdür (fetch–decode–execute cycle): sıradaki komut bellekten getirilir, ne yapılacağı çözülür ve komut yürütülür. Ardından bir sonraki komuta geçilir.

Bellek işlemciye göre yavaş olduğu için değişkenler ve ara sonuçlar işlemcinin içindeki küçük ve çok hızlı saklama birimlerinde, yani yazmaçlarda (register) tutulur. Veriyi bellekten yazmaca taşıyan komuta yükle, yazmaçtan belleğe taşıyan komuta sakla denir. Genel amaçlı yazmaçların yanında, işlemcinin durumunu tutan özel yazmaçlar vardır.

Tablo 1.6: Başlıca yazmaçlar ve iki mimarideki adları.
Yazmaçx86-64ARM64
Program sayacı: sıradaki komutun adresiRIPPC
Yığın işaretçisi: yığının üstüRSPSP
PSW: koşul bitleri, mod, kesme izniRFLAGSPSTATE
Genel amaçlı yazmaç sayısı1631
PC0x1008SP0x7FF0PSWmod, koşul, kesmeR0 … R15ara sonuçlarBellek0x1004komut0x1008komut0x100Ckomut…0x7FF0yığın üstü0x7FF8yığın
Şekil 1.24: Yazmaçlar ve bellek. PC (0x1008) sıradaki komutu, SP (0x7FF0) yığının üstünü gösterir. PSW mod, koşul ve kesme bitlerini; R0 … R15 ara sonuçları tutar.

Program sayacı (program counter, PC) sıradaki komutun adresini tutar; her komuttan sonra ilerler, atlama komutlarında yeni adresi alır. Yığın işaretçisi (stack pointer, SP), fonksiyon çağrılarında yerel değişkenlerin ve dönüş adreslerinin tutulduğu yığının üstünü gösterir. Program durum sözcüğü (program status word, PSW) ise son karşılaştırmanın sonucunu tutan koşul bitlerini, işlemcinin modunu (çekirdek ya da kullanıcı) ve kesmelerin açık olup olmadığını içerir. 1.2.2'deki mod biti de bu yazmaçtadır.

Her işlemci ailesinin kendi komut kümesi vardır. x86-64 için derlenmiş bir program ARM işlemcide doğrudan çalışmaz; ya yeniden derlenmesi ya da bir çevirici katmandan geçmesi gerekir. İşlemcinin başarımı ise tek bir sayıyla ifade edilemez: saat hızı, çekirdek sayısı, önbellek boyutu, komutların iş hattında kaç aşamada işlendiği ve bellek bant genişliği birlikte etkilidir.

Yazmaçların işletim sistemi için asıl önemi bağlam değiştirmedir.

Tanım 1.15

Bağlam değiştirme (context switch), işlemcinin bir süreci bırakıp başka bir süreci çalıştırmaya geçmesidir. İşletim sistemi bunu yaparken bırakılan sürecin bütün yazmaçlarını belleğe kaydeder ve sıradaki sürecin daha önce kaydedilmiş yazmaçlarını geri yükler.

Kaydetmenin neden zorunlu olduğu kolayca görülür. İşlemcide tek bir yazmaç takımı vardır. Sıradaki süreç çalışmaya başlayınca bu yazmaçların hepsini kendi değerleriyle ezer. Kesilen süreç daha sonra kaldığı yerden sürecekse, aynı PC'ye, aynı SP'ye ve aynı ara değerlere dönmesi gerekir. Tek bir yazmaç bile eksik kaydedilse, süreç döndüğünde yanlış bir değerle hesaba devam eder ve hata nedeni bulunamayacak kadar gizli kalır.

1.5.3Çok çekirdekli yongalar

Saat hızını artırmak 2000'lerin ortasında ısı sınırına dayandı. Bunun üzerine üreticiler tek bir hızlı işlemci yerine aynı yongaya birden çok işlemci koymaya başladı. Bugün telefonlarda bile sekiz ya da daha çok çekirdek bulunur.

(a) Paylaşımlı L2Çekirdek 1L1Çekirdek 2L1Çekirdek 3L1Çekirdek 4L1Paylaşımlı L2(b) Çekirdeğe özel L2Çekirdek 1L2Çekirdek 2L2Çekirdek 3L2Çekirdek 4L2(c) Günümüz: özel L1+L2, ortak L3Çekirdek 1L1L2Çekirdek 2L1L2Çekirdek 3L1L2Çekirdek 4L1L2Paylaşımlı L3 (LLC)
Şekil 1.25: Dört çekirdekli yongalarda önbellek düzenleri. (a) Her çekirdeğin kendi L1'i ve paylaşımlı bir L2. (b) Her çekirdeğe özel L2. (c) Günümüzün yaygın düzeni: her çekirdeğe özel L1 ve L2, bütün çekirdeklerin paylaştığı L3 (son düzey önbellek, LLC).

Çok çekirdekli yongalarda üç ayrı fikir bir arada bulunur:

  • Çok çekirdek: bir yongada birden çok bağımsız işlemci bulunur. İşletim sistemi her birini ayrı bir işlemci olarak görür ve her birine ayrı süreç çizelgeler.
  • Eş zamanlı çoklu iş parçacığı (simultaneous multithreading, SMT; Intel'deki ticari adı Hyper-Threading): bir çekirdek iki donanım iş parçacığını aynı anda çalıştırır. İşletim sistemine iki “mantıksal CPU” görünür, ama bu ikisi aynı çekirdeğin yürütme birimlerini ve önbelleklerini paylaşır.
  • Hibrit çekirdekler: aynı yongada başarım (P) ve verimlilik (E) çekirdekleri bulunur. ARM'ın big.LITTLE tasarımı ve Intel'in 2021'den beri ürettiği işlemciler bu türdendir. Çizelgeleyici, hangi işin hangi tür çekirdekte çalışacağını bilmelidir: arka plandaki bir güncelleme E çekirdeğinde pil harcamadan ilerleyebilir, oyunun ana iş parçacığı ise P çekirdeğine aittir.
Örnek 1.4

Bir dizüstü işlemcide SMT destekli 6 başarım çekirdeği ve SMT desteği olmayan 8 verimlilik çekirdeği vardır. İşletim sistemi kaç mantıksal CPU görür?

Çözüm. Her P çekirdeği iki donanım iş parçacığı çalıştırdığı için 6 × 2 = 12 mantıksal CPU verir. E çekirdekleri birer mantıksal CPU sayılır: 8. Toplam 12 + 8 = 20 mantıksal CPU görünür. Bu 20 CPU'nun gücü eşit değildir. Aynı P çekirdeğini paylaşan iki mantıksal CPU, iki ayrı çekirdek kadar hızlı değildir; bu yüzden çizelgeleyici önce boştaki fiziksel çekirdekleri doldurmaya çalışır.

İşletim sistemi açısından çok çekirdeğin sonucu üç başlıkta toplanır. Paralellik: işler artık gerçekten aynı anda çalışır ve işletim sistemi onları çekirdeklere dengeli dağıtmalıdır. Senkronizasyon: iki çekirdek çekirdeğin aynı veri yapısını aynı anda değiştirmeye kalkabilir; 1.3.9'daki sayaç deneyinin sorunu artık İS'nin kendi içinde de vardır. Önbellek tutarlılığı: her çekirdeğin kendi önbelleği olduğu için bir çekirdeğin yazdığı değerin öteki çekirdeğin önbelleğindeki eski kopyayı geçersiz kılması gerekir. Bu son iş donanımca yapılır, ama maliyeti işletim sistemi tasarımını etkiler.

1.5.4Bellek hiyerarşisi

İdeal bellek hem çok hızlı, hem çok büyük, hem de çok ucuz olurdu. Böyle bir teknoloji yoktur. Hızlı bellek pahalıdır ve küçük üretilir; büyük ve ucuz bellek ise yavaştır. Bilgisayarlar bu yüzden farklı teknolojileri katmanlar hâlinde birlikte kullanır.

YazmaçlarÖnbellek L1/L2/L3Ana bellek (DRAM)SSD (NVMe)Sabit disk (HDD)Arşiv: teyp, buluthızlıpahalıbüyükucuz
Şekil 1.26: Bellek hiyerarşisi piramidi. Üstten alta yazmaçlar, L1/L2/L3 önbellekleri, ana bellek (DRAM), SSD (NVMe), sabit disk (HDD) ve arşiv (teyp, bulut). Yukarı çıktıkça bellek hızlanır ve pahalılaşır; aşağı indikçe büyür ve ucuzlar.
Tablo 1.7: Bellek düzeylerinin yaklaşık erişim süreleri ve tipik kapasiteleri (2026 itibarıyla büyüklük sırası).
DüzeyErişim (yakl.)Kapasite (tipik)
Yazmaçlar< 1 ns< 1 KB
L1 önbellek~1 ns32–64 KB
L2 / L33–20 ns1–100+ MB
DRAM60–100 ns8–64 GB
NVMe SSD20–100 µs0,5–4 TB
HDD5–10 ms2–24 TB
Tanım 1.16

Bellek hiyerarşisi (memory hierarchy), farklı hız, kapasite ve maliyetteki bellek teknolojilerinin, her düzey altındakinin önbelleği gibi çalışacak biçimde katmanlar hâlinde düzenlenmesidir.

Tablodaki iki sınır özel önem taşır. Birincisi uçuculuk sınırıdır: DRAM ve üstündeki düzeyler güç gidince içeriğini kaybeder, SSD ve HDD ise kalıcıdır. Kalıcılığın neden dosya sistemi gerektirdiği buradan anlaşılır. İkincisi büyüklük farkıdır: komşu düzeyler arasında hız bazen on kat, bazen bin kat değişir. DRAM'den SSD'ye geçerken erişim süresi nanosaniyelerden mikrosaniyelere, HDD'de milisaniyelere çıkar.

Hiyerarşinin işe yaramasının nedeni yerelliktir. Programlar kısa bir süre içinde genellikle aynı veriye ya da ona yakın adreslerdeki veriye tekrar tekrar erişir. Bir döngü aynı değişkenleri her turda kullanır; bir dizi baştan sona sırayla okunur. Bu yüzden üst düzeyde tutulan küçük bir kopya, erişimlerin büyük kısmını karşılar ve yavaş alt düzeye seyrek inilir. Sonuçta sistem, neredeyse üst düzeyin hızında ve alt düzeyin kapasitesinde bir bellek gibi davranır.

Örnek 1.5

2,5 GHz'de çalışan bir çekirdekte bir saat turu kaç ns sürer? 100 ns'lik bir DRAM erişimi ve 8 ms'lik bir HDD erişimi kaç saat turuna denk gelir?

Çözüm. 2,5 GHz, saniyede 2,5 × 109 tur demektir. Bir tur 1 / (2,5 × 109) s = 0,4 ns sürer; başka bir deyişle 1 ns'de 2,5 tur geçer. DRAM erişimi 100 × 2,5 = 250 tura denk gelir. HDD erişimi önce nanosaniyeye çevrilir: 8 ms = 8 × 106 ns; buradan 8 × 106 × 2,5 = 2 × 107, yani 20 milyon tur bulunur.

Bu sayılar işletim sisteminin davranışını açıklar. 250 turluk DRAM beklemesini donanım önbellekleri gizler; işletim sisteminin araya girip süreç değiştirmesine değmez, çünkü bağlam değiştirmenin kendisi bundan uzun sürer. 20 milyon turluk disk beklemesi ise çok büyüktür. İşletim sistemi bu sürede süreci bekletir ve işlemciyi başka bir sürece verir. Çoklu programlamanın temel gerekçesi de budur.

Sık yapılan hata

SSD'yi “belleğin hızlı bir türü” sanmak. NVMe SSD, HDD'den yaklaşık yüz kat hızlıdır, ama DRAM'den hâlâ yaklaşık bin kat yavaştır. İşletim sistemi için SSD de bir G/Ç aygıtıdır: okuma beklenirken süreç bekletilir ve işlemci başka işe verilir.

1.6G/Ç yöntemleri

İşlemcinin aygıtlarla konuşması, işletim sisteminin en sık yaptığı işlerden biridir. Bir tuşa basılması, bir ağ paketinin gelmesi, bir disk bloğunun okunması: hepsi bir G/Ç olayıdır. Bu kesim, işlemcinin bir G/Ç işinin bittiğini nasıl öğrendiğini ele alır.

1.6.1Denetleyici ve sürücü

Her G/Ç aygıtının bir aygıt denetleyicisi (device controller) vardır. Denetleyici, aygıtın üzerindeki küçük bir işlemci ya da devredir; aygıtı fiziksel olarak sürer ve işlemciye birkaç yazmaçtan oluşan bir arayüz sunar. Tipik olarak bir komut yazmacı (ne yapılacak), bir durum yazmacı (iş bitti mi, hata var mı) ve bir veri yazmacı ya da tamponu bulunur.

Çekirdekteki aygıt sürücüsü (device driver), belirli bir denetleyiciyle konuşmayı bilen yazılımdır. Sürücü denetleyicinin yazmaçlarına komut yazar ve durumunu okur. Her denetleyici farklı olduğu için her aygıt türünün kendi sürücüsü vardır; işletim sisteminin geri kalanı ise sürücülerle ortak bir arayüzden konuşur. 1.1.6'daki Aygıt Yöneticisi'nde listelenen sürücüler bunlardır.

1.6.2Yoklama, kesme ve DMA

Sürücü denetleyiciye bir komut verdikten sonra işin ne zaman bittiğini öğrenmesi gerekir. Bunun üç yolu vardır.

Tablo 1.8: Üç G/Ç yöntemi.
YöntemTemel fark
Yoklama (polling)CPU, aygıt hazır olana kadar durum yazmacını döngüde okur; beklerken başka iş yapamaz (meşgul bekleme).
Kesme (interrupt)Aygıt her iş biriminin sonunda CPU'ya sinyal gönderir; CPU arada başka süreci çalıştırır.
DMA (doğrudan bellek erişimi)Denetleyici bütün bloğu belleğe kendisi taşır; CPU yalnız başta (programlama) ve sonda (tek kesme) devreye girer.

Yoklamada sürecin mantığı basittir: işlemci bir döngüde durum yazmacını okur, “hazır” bitini görene kadar tekrar okur. Bu yönteme meşgul bekleme (busy waiting) de denir, çünkü işlemci beklerken tam güçle çalışır ama hiçbir yararlı iş yapmaz. Aygıt çok hızlı yanıt veriyorsa, yani bekleme birkaç turdan ibaretse, yoklama en ucuz yoldur. Bekleme uzunsa işlemci zamanı boşa gider.

Kesmede işlemci denetleyiciye komutu verdikten sonra başka bir süreci çalıştırmaya döner. Aygıt bir iş birimini, örneğin bir baytı ya da bir sözcüğü bitirince işlemciye bir kesme sinyali gönderir. İşlemci o anki komutu tamamlar, çekirdek moduna geçer ve kesme vektörü denen tablodan ilgili kesme işleyicisini bulup çalıştırır. İşleyici veriyi alır, aygıta sıradaki işi verir ve kesilen süreç kaldığı yerden devam eder. Böylece işlemci bekleme sürelerinde yararlı iş yapar. Ama her iş biriminin sonunda bir kesme geldiği için büyük aktarımlarda kesmelerin sayısı ve toplam maliyeti çok yükselir.

DMA (direct memory access: doğrudan bellek erişimi) bu son sorunu çözer. İşlemci denetleyiciye yalnız “şu adresten başlayarak şu kadar baytı belleğe yaz” bilgisini verir. Denetleyici bütün bloğu belleğe kendisi taşır ve aktarım bitince tek bir kesme üretir. İşlemci yalnız başta, DMA'yı programlarken, ve sonda, bu tek kesmeyi işlerken devreye girer.

1.6.3CPU yükünü hesaplamak

Örnek 1.6

Bir aygıttan 4 KiB'lık bir blok alınacaktır. Kesme güdümlü G/Ç'de her 4 bayt için bir kesme oluşur ve her kesme CPU'ya 2 µs'ye mal olur. DMA ile CPU denetleyiciyi 3 µs'de programlar ve aktarımın sonunda tek bir kesme (2 µs) gelir. İki yöntemde CPU'nun bu aktarıma harcadığı süre nedir?

  1. Önce blok bayta çevrilir. KiB ikili önektir: 4 KiB = 4 × 1024 = 4096 bayt.
  2. Kesme güdümlü G/Ç: kesme sayısı 4096 / 4 = 1024. CPU'nun harcadığı süre 1024 × 2 µs = 2048 µs ≈ 2,05 ms.
  3. DMA: 3 µs + 2 µs = 5 µs.
  4. Oran: 2048 / 5 ≈ 410. DMA, CPU yükünü yaklaşık 410 kat azaltır.

Aktarımın kendisi iki yöntemde de aygıtın hızına bağlıdır ve yaklaşık aynı sürer. Fark, işlemcinin bu süreyi nasıl geçirdiğindedir. DMA'da işlemci 5 µs dışında başka süreçleri çalıştırır. Klavye gibi saniyede birkaç olay üreten yavaş aygıtlarda bu fark önemsizdir; SSD ve ağ kartı gibi hızlı blok aygıtlarında ise DMA olmadan çalışmak düşünülemez.

Sık yapılan hata

DMA'nın güvenlik de sağladığını sanmak. DMA bir başarım mekanizmasıdır; işlemcinin kopyalama yükünü alır. Tersine, belleğe doğrudan yazabilen bir aygıt yanlış programlanırsa ya da kötü niyetliyse belleğin her yerini bozabilir. Aygıtların erişebileceği bellek bölgelerini sınırlamak için IOMMU denen ayrı bir donanım birimi gerekir.

1.6.4Kesme, tuzak ve istisna

Kesme sözcüğü iki farklı olay için kullanıldığından burada dikkatli olmak gerekir. Donanım kesmesi, işlemcinin dışındaki bir aygıttan gelir: zamanlayıcı süresini doldurmuştur, ağ kartına paket gelmiştir, kullanıcı bir tuşa basmıştır. Bu olaylar yürütülen komut akışından bağımsızdır ve her an gelebilir; bu yüzden eş zamansızdır.

Tuzak ya da istisna ise yürütülen komutun kendisinden doğar: sıfıra bölme, geçersiz bellek erişimi, sayfa hatası ya da bilerek yapılan bir sistem çağrısı. Belirli bir komutun sonucu olduğu için eş zamanlıdır: aynı program aynı girdiyle çalıştırılırsa tuzak her seferinde aynı noktada oluşur.

Kaynakları farklı olsa da iki olay çekirdeğe aynı yolla geçer. İşlemci çekirdek moduna girer, o anki durumu kaydeder ve olayın türüne göre bir tablodan (kesme vektörü ya da istisna tablosu) ilgili işleyiciye atlar. İşleyici işini bitirince ya kesilen komuttan sonrasına döner ya da, ölümcül bir hata varsa, süreci sonlandırır. Bu ortak yol, 1.2.2'deki “üç kapı” ile aynı şeydir: sistem çağrısı ve istisna komut akışının içinden, kesme dışarıdan gelir.

Örnek 1.7

Aşağıdaki olaylardan hangileri kesme, hangileri tuzaktır? (a) diskin bir okumayı tamamlaması, (b) bir programın NULL işaretçisinden okuma yapması, (c) fork() çağrısı, (d) fare hareketi, (e) sayfa hatası.

Çözüm. (a) ve (d) dış aygıtlardan gelir ve komut akışından bağımsızdır: kesme. (b) geçersiz bellek erişimidir ve belirli bir komutun sonucudur: tuzak (istisna). (c) programın bilerek yaptığı sistem çağrısıdır: tuzak. (e) erişilen sayfanın bellekte olmamasından doğar ve erişimi yapan komuta bağlıdır: tuzak (istisna). Program aynı girdiyle yeniden çalıştırıldığında (b) ve (c) aynı komutlarda tekrar oluşur; (e) de aynı türden bir komutta oluşur, ama yalnız sayfa o anda bellekte değilse. (a) ve (d)'nin zamanlaması ise her seferinde farklıdır.

1.7İşletim sistemi çeşitleri

“İşletim sistemi” adı, bir bankanın veri merkezindeki dev makineyi yöneten yazılım için de bir banka kartının yongasındaki küçük program için de kullanılır. Bu sistemlerin ortak yanı aynı iki rolü üstlenmeleridir; farkları ise hangi kaynağın kıt olduğunda ve neyin en önemli sayıldığındadır.

1.7.1Büyükten küçüğe sistemler

1 KB1 MB1 GB1 TBtipik RAM (logaritmik) →akıllı kartalgılayıcı düğümügömülümobilkişiselsunucuanabilgisayar
Şekil 1.27: İşletim sistemi çeşitleri, tipik RAM miktarına göre logaritmik bir eksende. Soldan sağa: akıllı kart ve algılayıcı düğümü (kilobaytlar), gömülü sistem (megabaytlar), mobil ve kişisel sistemler (gigabaytlar), sunucu ve anabilgisayar (yüzlerce gigabayttan terabaytlara).

Şekil 1.27 eksenini logaritmik tutar, çünkü bellek miktarları arasında milyar katlık fark vardır. Aşağıdaki liste en büyükten en küçüğe doğru ilerler.

  • Anabilgisayar (mainframe): çok yüksek G/Ç kapasitesi, güvenilirlik ve kesintisiz erişilebilirlik için tasarlanır. Toplu iş, saniyede binlerce küçük işlem (transaction, örneğin bir para transferi ya da uçak rezervasyonu) ve zaman paylaşımı hizmetleri verir. Bankalar ve havayolları bu sistemlere dayanır. Örnekler: IBM z/OS, z/VM, Linux on Z, Unisys MCP, Fujitsu BS2000.
  • Sunucu (server): ağ üzerinden çok sayıda kullanıcıya web, veritabanı, dosya ve e-posta hizmeti verir. Veri merkezleri, bulut, sanallaştırma (KVM, Hyper-V) ve konteynerler bu dünyaya aittir. Örnekler: Linux, Windows Server, FreeBSD.
  • Çok işlemcili (multiprocessor): birden çok çekirdek ya da yuvaya takılı birden çok işlemci kullanır, işleri paralel yürütür. Senkronizasyonla ve belleğin bazı bölgelerinin bazı işlemcilere daha yakın olduğu NUMA düzeniyle uğraşır. Bugün bütün genel amaçlı işletim sistemleri bu sınıftadır: Linux, Windows, macOS.
  • Kişisel (personal): tek kullanıcıya iyi bir GUI ve çoklu görev sunar; tarayıcı, ofis, oyun ve dosya yönetimi gibi işler için kullanılır. Örnekler: Windows 11, macOS, Linux dağıtımları, ChromeOS.
  • Mobil (handheld): dokunmatik arayüz, pil ve ısı yönetimi, algılayıcılar, uygulama mağazası ve izin modeli öne çıkar. Örnekler: Android, iOS/iPadOS, HarmonyOS. Windows Phone 2017'de sona erdi.
  • Gömülü (embedded): araçların, ev aletlerinin, ağ aygıtlarının ve uçakların içinde çalışır. Kullanıcı genellikle yazılım yüklemez; sistem flash bellekte sabittir. Örnekler: gömülü Linux (Yocto), Zephyr, FreeRTOS, QNX, VxWorks.
  • Algılayıcı düğümü (sensor node): birkaç on KB RAM ile, pil üzerinde yıllarca çalışacak biçimde tasarlanır. Olay güdümlüdür: çoğu zaman uyur, bir ölçüm ya da ileti geldiğinde uyanır. Düşük güçlü radyolarla (IEEE 802.15.4, BLE) haberleşir; IoT ve makineden makineye (M2M) ağlarda kullanılır. Örnekler: Contiki-NG, RIOT, Zephyr. TinyOS artık tarihsel bir örnektir.
  • Akıllı kart (smart card): işlemcisi ve belleği son derece kısıtlıdır. Kimlik doğrulama, ödeme, kriptografi ve güvenli saklama işleri yapar. Banka kartları, SIM ve eSIM, kimlik kartları bu sınıftadır. Örnekler: Java Card, MULTOS.

Listenin iki ucu arasında öncelikler tersine döner. Anabilgisayarda ve sunucuda kıt kaynak, binlerce kullanıcının isteğini aynı anda karşılayacak işlem gücü ve G/Ç kapasitesidir. Algılayıcı düğümünde ise kıt kaynak enerjidir; işletim sisteminin en önemli işi, işlemciyi ve radyoyu olabildiğince uzun süre kapalı tutmaktır.

1.7.2Gerçek zamanlı işletim sistemleri

Bazı sistemlerde bir hesabın doğru olması yetmez; zamanında bitmesi de gerekir. Bir aracın fren denetim yazılımı, tekerleğin kilitlendiğini birkaç milisaniye içinde fark edip frenin basıncını ayarlamalıdır. Doğru ama geç verilen yanıt, yanlış yanıt kadar tehlikelidir.

Tanım 1.17

Gerçek zamanlı işletim sistemi (real-time operating system, RTOS), işlerin belirli bir son tarihten (deadline) önce bitmesini güvence altına almaya çalışan işletim sistemidir. Önemli olan hız değil, öngörülebilirliktir: en kötü durumda bile yanıt süresinin bilinen bir sınırı aşmaması.

Katı: fren denetimiher 5 ms, 2 ms işYumuşak: video karesison tarih ~16,7 msgecikti → kare atlandıson tarih0 ms5 ms10 ms15 ms20 ms
Şekil 1.28: Katı ve yumuşak gerçek zamanlı görevler, 0–20 ms arasında. Üstte fren denetimi: her 5 ms'de bir 2 ms'lik iş, her iş kendi son tarihinden (dikey çizgiler) önce biter. Altta video karesi: son tarih yaklaşık 16,7 ms'dir; ikinci kare gecikmiş ve atlanmıştır.

Gerçek zamanlı sistemler son tarihin kaçırılmasının sonucuna göre ikiye ayrılır:

  • Katı (hard) gerçek zamanlı: son tarihi kaçırmak felakettir. Fren, hava yastığı, uçuş denetimi ve endüstriyel robotlar bu sınıftadır. Şekil 1.28'deki fren görevi her 5 ms'de bir gelir ve 2 ms'lik işini her seferinde son tarihinden önce bitirir.
  • Yumuşak (soft) gerçek zamanlı: son tarihi ara sıra kaçırmak kabul edilir; yalnız kalite düşer. Ses ve video akışı ile oyunlar bu sınıftadır. Saniyede 60 kare gösteren bir video için her kare 1000 / 60 ≈ 16,7 ms içinde hazır olmalıdır. Şekildeki ikinci kare bu süreyi aşmış ve atlanmıştır; izleyici kısa bir takılma görür, ama kimse zarar görmez.

Bilinen RTOS örnekleri arasında Mars gezginlerinde kullanılan VxWorks, araç sistemlerinde yaygın QNX, küçük mikrodenetleyiciler için FreeRTOS ve Zephyr sayılabilir. Linux da uzun süre ayrı tutulan PREEMPT_RT yamalarıyla gerçek zamanlı çalışabiliyordu. Bu yamalar 2024'te Linux 6.12 ile ana çekirdeğe katıldı; eski RTLinux projesi ise sona ermiştir. Gerçek zamanlı sistemler ses, video, hareket ve algılayıcı verisi işleyen uygulamalarda, tren denetiminde ve savunma sistemlerinde kullanılır.

Sık yapılan hata

“Gerçek zamanlı” sözünü “çok hızlı” diye anlamak. A sisteminin ortalama yanıt süresi 1 ms'dir, ama arada bir 500 ms'ye çıkar. B sisteminin ortalaması 5 ms'dir ve hiçbir zaman 8 ms'yi aşmaz. Ortalamada A daha hızlıdır. Yine de katı gerçek zamanlı bir iş için B uygundur, çünkü en kötü durumu bilinir ve sınırlıdır.

1.8Temel kavramlar

1.8.1Politika ve mekanizma

İşletim sistemi tasarımının en kullanışlı ilkelerinden biri, iki farklı soruyu birbirinden ayırmaktır: ne yapılacak ve nasıl yapılacak.

Tanım 1.18

Politika (policy), ne yapılacağına dair karardır. Mekanizma (mechanism) ise bu kararın nasıl uygulanacağını sağlayan altyapıdır.

Mekanizma (sabit çekirdek kodu)zamanlayıcı kesmesi → bağlam değiştirmedilim = 4 msdilim = 10 mspolitika takılırdeğiştirilebilir
Şekil 1.29: Politika, mekanizmaya takılan değiştirilebilir bir parametredir. Mekanizma (sabit çekirdek kodu): zamanlayıcı kesmesi ve bağlam değiştirme. Takılı politika “dilim = 4 ms”dir; yerine “dilim = 10 ms” takılabilir.

İkisini ayırmak en yüksek esnekliği sağlar: politika kararları sonradan değiştiğinde mekanizmayı yeniden yazmak gerekmez. Zamanlayıcı kesmesi ve bağlam değiştirme, işlemcinin süreçler arasında nasıl döndürüleceğini sağlayan mekanizmadır. Zaman diliminin 4 ms mi, 10 ms mi olacağı ise bir politikadır. Şekil 1.29'daki gibi dilim değeri mekanizmaya takılan bir parametre olarak tasarlanırsa, aynı çekirdek kodu masaüstünde kısa dilimle, sunucuda uzun dilimle çalışabilir. Linux'ta bir sürecin çizelgeleme sınıfı sched_setscheduler() çağrısıyla, bir diskin G/Ç çizelgeleyicisi ise /sys altındaki bir dosyaya yazılarak sistem çalışırken değiştirilebilir; çekirdek kodu değişmez.

Tablo 1.9: Mekanizma ve politika örnekleri.
MekanizmaPolitika
Zamanlayıcı kesmesiZaman dilimi kaç ms olmalı?
Öncelikli çizelgeleyiciHangi süreç yüksek öncelikli?
Dosya izin bitleri (rwx)Kime hangi izin verilecek?
Sayfa değiştirme altyapısıHangi sayfa dışarı atılacak?
cgroupsHangi konteynere ne kadar CPU ve bellek?

Tablodaki her satırda soldaki sütun bir “araç”, sağdaki sütun bu aracın nasıl kullanılacağına dair bir “karar”dır. Dosya izin bitleri, her erişimde izni denetleyen mekanizmayı oluşturur; hangi kullanıcıya okuma, hangisine yazma izni verileceği ise yöneticinin politikasıdır. Bir ayrımın politika mı, mekanizma mı olduğunu anlamanın kolay yolu şu soruyu sormaktır: “Bu karar değişirse kodun değişmesi gerekir mi?” Gerekmiyorsa o karar bir politikadır. İşletim sistemi tasarımı bu dengeleri kurmak için mühendislik kadar yaratıcılık da gerektirir.

1.8.2MS-DOS ve FreeBSD

Bir programın nasıl başlatıldığı, işletim sisteminin yapısı hakkında çok şey söyler. Tek görevli MS-DOS ile çok görevli bir UNIX türevi olan FreeBSD bu açıdan iyi bir karşıtlık oluşturur.

(a) MS-DOS açılıştaserbest bellekkomut yorumlayıcıçekirdek(b) MS-DOS program çalışırkenserbest belleksüreçyorumlayıcı (kalıcı kısım)çekirdek(c) FreeBSD: çoklu süreçsüreç Dserbest belleksüreç Cyorumlayıcısüreç Bçekirdek
Şekil 1.30: Bellek düzenleri. (a) MS-DOS açılışta: altta çekirdek, üstünde komut yorumlayıcı, en üstte serbest bellek. (b) MS-DOS program çalışırken: süreç, yorumlayıcının geçici kısmının üzerine yüklenmiş, yalnız kalıcı kısım kalmıştır. (c) FreeBSD: çekirdeğin üstünde B, C, D süreçleri ve yorumlayıcı aynı anda bellektedir.

MS-DOS tek görevli bir sistemdir ve tek bir bellek alanı kullanır. Açılışta kabuk, yani COMMAND.COM, belleğe yüklenir. Kullanıcı bir program çalıştırınca program, kabuğun geçici kısmının üzerine yazılarak yüklenir; kabuğun yalnız küçük kalıcı kısmı bellekte kalır. Program bitince kabuk kendini diskten yeniden yükler. Aynı anda tek program çalışır; çoklu görev ve bellek koruması yoktur.

FreeBSD çok görevlidir. Kullanıcı giriş yapınca bir kabuk başlar. Kullanıcı bir komut yazınca kabuk önce fork() sistem çağrısıyla kendisinin bir kopyası olan yeni bir süreç yaratır. Bu yeni süreç exec() ile istenen programı kendi belleğine yükler. Kabuk ya programın bitmesini bekler ya da komutun sonuna & konmuşsa programı arka planda çalıştırıp yeni komut almaya devam eder. Şekil 1.30'daki (c) düzeninde olduğu gibi pek çok süreç aynı anda bellekte bulunur ve her biri kendi bölgesinde korunur.

1.8.3Çalıştırılabilir dosya oluşturma

Bir C kaynak dosyasının çalışan bir sürece dönüşmesi birkaç ayrı programın sırayla çalışmasıyla olur. GCC derleyici sürücüsü bu adımların her birini ayrı ayrı yaptırmaya izin verir:

$ gcc -E kod.c -o kod.i
$ gcc -S kod.i -o kod.s
$ gcc -c kod.s -o kod.o
$ gcc kod.o diger.o -o prog
$ ./prog
kod.cÖnişlemcikod.iDerleyicikod.sAssemblerkod.oBağlayıcıprog (ELF)Yükleyicisüreçlibc.so, diğer.o
Şekil 1.31: C kaynağından çalışan sürece. kod.c önişlemciden geçip kod.i, derleyiciden geçip kod.s, assembler'dan geçip kod.o olur. Bağlayıcı kod.o'yu libc.so ve diger.o ile birleştirip prog (ELF) dosyasını üretir. Yükleyici bu dosyadan bir süreç yaratır.
  1. Kaynak dosya: kod.c, insanın okuyup yazdığı C kodudur.
  2. Önişlemci (gcc -E): #include satırlarındaki başlık dosyalarını metne ekler, makroları genişletir, #ifdef ile koşullu derleme bloklarını çözer. Sonuç yine C kodudur: kod.i.
  3. Derleyici (gcc -S): C kodunu hedef işlemcinin assembly diline çevirir ve eniyileme yapar: kod.s.
  4. Assembler (gcc -c): assembly'yi makine koduna çevirir ve bir nesne dosyası üretir: kod.o. Bu dosyada printf gibi dışarıdaki fonksiyonların adresleri henüz belli değildir; yalnız “burada printf çağrılıyor” diye not düşülmüştür.
  5. Bağlayıcı (gcc kod.o diger.o -o prog): nesne dosyalarını ve kütüphaneleri birleştirir, açık kalan adresleri çözer ve çalıştırılabilir dosyayı üretir. Linux'ta bu dosyanın biçimi ELF, Windows'ta PE'dir (.exe).
  6. Yükleme (./prog): kabuk fork ve exec yapar. Çekirdeğin yükleyicisi ELF dosyasını belleğe eşler, dinamik bağlayıcı libc.so gibi paylaşılan kütüphaneleri bağlar ve program bir süreç olarak çalışmaya başlar.

Bu zincirde işletim sisteminin payı yalnız son adımdadır. İlk beş adımı kullanıcı modunda çalışan sıradan programlar yapar: önişlemci, derleyici, assembler ve bağlayıcı. 1.2.4'te belirtildiği gibi programı makine diline derleyici çevirir. İşletim sistemi, ortaya çıkan dosyayı bir sürece dönüştürür: bellek ayırır, kodu ve veriyi bu belleğe eşler, yığını kurar ve işlemciyi programın ilk komutuna yönlendirir.

Sık yapılan hata

Program ile süreci aynı şey sanmak. Program, diskte duran edilgen bir dosyadır (prog). Süreç ise o programın çalışan bir örneğidir: kendi belleği, yazmaçları ve açık dosyaları vardır. Aynı prog dosyasından aynı anda üç süreç başlatılabilir; 1.3.9'daki yaz deneyi tam olarak bunu yapar.

1.8.4Metrik birimler

İşletim sistemi konularında hem çok küçük süreler (nanosaniyeler) hem de çok büyük boyutlar (terabaytlar) geçer. Tablo 1.10 ondalık önekleri, Tablo 1.11 ikili önekleri verir.

Tablo 1.10: Ondalık (SI) önekler.
ÜsAdSembolÜsAdSembol
10−3milim103kilok
10−6mikroµ106megaM
10−9nanon109gigaG
10−12pikop1012teraT
10−15femtof1015petaP
10−18attoa1018eksaE
Tablo 1.11: İkili (IEC) önekler.
BirimDeğer
1 KiB (kibibayt)210 = 1024 B
1 MiB (mebibayt)220 B
1 GiB (gibibayt)230 B
1 TiB (tebibayt)240 B

İki önek ailesinin bir arada kullanılması sık karışıklığa yol açar. Bellek genellikle ikili ölçülür: “16 GB RAM” denen bellek gerçekte 16 GiB, yani 16 × 230 = 17.179.869.184 bayttır. Disk ve ağ ise ondalık ölçülür: 1 TB = 1012 B ve 1 Gb/s = 109 bit/s. Bayt için büyük B, bit için küçük b kullanılır; 1 Gb/s'lik bir bağlantı saniyede en çok 109 / 8 = 125 milyon bayt taşır.

Örnek 1.8

“512 GB” etiketli bir SSD, boyutları ikili önekle gösteren bir işletim sisteminde kaç GiB görünür? 1 TB kaç TiB'dir?

Çözüm. Etiket ondalıktır: 512 GB = 512 × 109 B. Bir GiB 230 = 1.073.741.824 bayttır. Bölüm 512 × 109 / 230 ≈ 476,8 GiB'dir. Aynı yoldan 1 TB = 1012 / 240 ≈ 0,91 TiB bulunur. Diskte eksik bir şey yoktur; aynı bayt sayısı iki farklı birimle gösterilmiştir. Fark birim büyüdükçe artar: kilo düzeyinde %2,4 olan fark, tera düzeyinde yaklaşık %9'a çıkar.

1 ns1 µs1 ms1 sL1 isabetiDRAMbağlam değiştirmeNVMe okumaHDD erişimigöz kırpmaher çentik 1000 kat
Şekil 1.32: Logaritmik zaman ekseninde tipik gecikmeler; her çentik 1000 kattır. Soldan sağa: L1 isabeti (yaklaşık 1 ns), DRAM erişimi, bağlam değiştirme (mikrosaniyeler), NVMe okuma, HDD erişimi (milisaniyeler) ve göz kırpma (onlarca, yüzlerce milisaniye).

Şekil 1.32, bu notta geçen sürelerin ölçeğini tek bakışta gösterir. Bir L1 önbellek isabeti ile bir sabit disk erişimi arasında yaklaşık on milyon kat vardır. İnsan algısı ise bunların hepsinin ötesindedir: bir göz kırpma süresinde işlemci yüz milyonlarca komut yürütür. İşletim sisteminin bir kullanıcıya “aynı anda” çok sayıda program çalıştırıyormuş izlenimi verebilmesi, bu ölçek farkı sayesindedir.

1.9Özet

  • İşletim sistemi, donanımı yöneten ve uygulamalara basit, güvenli ve ortak bir arayüz sunan yazılım katmanıdır. Dar anlamda işletim sistemi, ayrıcalıklı modda çalışan çekirdektir; kabuk ve GUI sıradan kullanıcı programlarıdır.
  • Koruma, işlemcideki mod bitine dayanır. Kullanıcı modunda ayrıcalıklı komutlar yasaktır; çekirdeğe yalnız sistem çağrısı, kesme ve istisna kapılarından, önceden belirlenmiş giriş noktalarına geçilir.
  • Bir read() çağrısında libc sarmalayıcısı numarayı ve argümanları yazmaçlara koyar, syscall tuzak üretir, çekirdek tablodan sys_read()'i bulup argümanları denetler, sysret kullanıcı moduna döner.
  • İşletim sistemi yukarıdan bakınca genişletilmiş makinedir: çirkin donanımı süreç, adres alanı, dosya ve soket gibi soyutlamalara dönüştürür. Aşağıdan bakınca kaynak yöneticisidir: kaynakları zamanda ya da alanda paylaştırır, verimi ve adaleti dengeler.
  • Biriktirme, paylaşılamayan yavaş bir aygıtı tek bir hizmet sürecinin tekeline verir ve G/Ç ile hesaplamayı örtüştürür. Dairesel kuyrukta giris ile cikis'in eşitliği boş ile doluyu ayırmaz; bu yüzden adet sayacı tutulur.
  • Toplu iş, işler arasındaki el ile geçişi kaldırdı. Çoklu programlama, bir iş G/Ç beklerken CPU'yu başka bir işe vererek CPU kullanımını artırdı; örnekte iki iş 15 yerine 9 birimde bitti ve kullanım %53'ten %89'a çıktı. Zaman paylaşımı, zamanlayıcı kesmesiyle her kullanıcıya kısa tepki süresi sağladı.
  • Bağlam değiştirmede bütün yazmaçlar kaydedilir, çünkü işlemcide tek yazmaç takımı vardır. Çok çekirdekli yongalar paralellik, senkronizasyon ve önbellek tutarlılığı sorunları getirir; SMT her çekirdeği iki mantıksal CPU gibi gösterir.
  • Bellek hiyerarşisi, yerellik sayesinde neredeyse üst düzeyin hızında ve alt düzeyin kapasitesinde çalışır. Disk beklemesi milyonlarca saat turu sürdüğü için işletim sistemi bu sürede başka bir süreci çalıştırır.
  • Yoklamada CPU meşgul bekler; kesmede her iş birimi için bir kesme gelir; DMA'da denetleyici bütün bloğu taşır ve tek kesme üretir. Kesme dışarıdan ve eş zamansız, tuzak komutun kendisinden ve eş zamanlı doğar; ikisi de çekirdeğe aynı yolla geçer.
  • İşletim sistemleri anabilgisayardan akıllı karta kadar çok çeşitlidir. Gerçek zamanlı sistemlerde önemli olan hız değil, öngörülebilirliktir; katı sistemde son tarihi kaçırmak felakettir, yumuşak sistemde kaliteyi düşürür.
  • Politika ne yapılacağını, mekanizma nasıl yapılacağını belirler; ikisi ayrılırsa politika çekirdek kodu değişmeden değiştirilebilir.
  • C kaynağını önişlemci, derleyici, assembler ve bağlayıcı çalıştırılabilir dosyaya çevirir; işletim sistemi bu dosyayı yükleyip bir sürece dönüştürür. Bellek ikili (GiB), disk ve ağ ondalık (GB, Gb/s) ölçülür.

1.10Kavram listesi

1.11Kendini Dene

  1. Aşağıdaki olaylardan hangileri işlemcinin kullanıcı modundan çekirdek moduna geçmesine yol açar? (a) getpid() çağrısı, (b) i++, (c) sayfa hatası, (d) ağ kartına paket gelmesi, (e) kullanıcı belleğinde memcpy.

    Cevap

    (a), (c) ve (d) geçer. getpid() bir sistem çağrısıdır; libc sarmalayıcısı syscall ile tuzak üretir. Sayfa hatası, bellek erişimi yapan komuttan doğan bir istisnadır. Paket gelmesi, ağ kartından gelen bir donanım kesmesidir. i++ yazmaç ve bellek üzerinde sıradan bir işlem, memcpy ise kullanıcı belleğinde kopyalama yapan bir kütüphane fonksiyonudur; ikisi de kullanıcı modunda kalır.

  2. Tek CPU'lu bir sistemde A işi 1 birim CPU, 3 birim G/Ç, 1 birim CPU; B işi 2 birim CPU, 1 birim G/Ç, 2 birim CPU kullanır. Tek programlı ve çoklu programlı sistemde iki iş ne zaman biter, CPU kullanımı nedir? (Çoklu programlamada CPU boşalınca hazır işlerden önce A seçilir.)

    Cevap

    Tek programlı: A 0–1, boşta 1–4, A 4–5; B 5–7, boşta 7–8, B 8–10. Bitiş t = 10, CPU kullanımı 6 / 10 = %60. Çoklu programlı: A 0–1; A G/Ç'ye gidince B 1–3; B G/Ç'ye gider (3–4), A'nın G/Ç'si de 4'te bittiği için CPU 3–4 arasında boştadır; t = 4'te iki iş de hazırdır, A 4–5, B 5–7. Bitiş t = 7, CPU kullanımı 6 / 7 ≈ %85,7.

  3. Biriktirme kodunda kuyruk doluyken (a.pdf, b.txt, c.doc, d.png) bir program ekle("e.jpg") çağırırsa ne olur? Ardından biriktirme_hizmeti() çalışırsa hangi dosyalar hangi sırayla basılır?

    Cevap

    ekle() doluluk denetimi yapmaz. giris = 0 olduğu için e.jpg 0. yuvaya, a.pdf'in üzerine yazılır; giris = 1, adet = 5 olur. Hizmet döngüyü 5 kez döner: cikis 0, 1, 2, 3, 0 değerlerini alır. Sırayla e.jpg, b.txt, c.doc ve d.png basılır; her biri gönderildikten sonra yuvası NULL yapılır. Beşinci turda cikis yeniden 0'dır ve bu yuva artık boştur: yazıcıya NULL gönderilir. a.pdf kaybolmuş, sayaç ise var olmayan bir işi saymıştır. Doğru bir ekle(), adet == 4 iken işi reddetmeli ya da beklemelidir.

  4. 4 GHz'de çalışan bir çekirdekte 60 ns'lik bir DRAM erişimi ve 50 µs'lik bir NVMe okuması kaç saat turu sürer? İşletim sistemi bu iki bekleme için neden farklı davranır?

    Cevap

    Bir tur 0,25 ns'dir; 1 ns'de 4 tur geçer. DRAM: 60 × 4 = 240 tur. NVMe: 50 µs = 50.000 ns, 50.000 × 4 = 200.000 tur. DRAM beklemesi bağlam değiştirmeden kısadır ve önbelleklerle gizlenir; işletim sistemi araya girmez. 200.000 turluk SSD beklemesinde ise süreç bekletilir ve CPU başka bir sürece verilir.

  5. Bir aygıttan 16 KiB veri alınıyor. Kesme güdümlü G/Ç'de her 16 bayt için bir kesme oluşuyor ve her kesme CPU'ya 2 µs'ye mal oluyor. DMA ile programlama 5 µs, sondaki tek kesme 2 µs sürüyor. İki yöntemde CPU'nun harcadığı süre nedir?

    Cevap

    16 KiB = 16 × 1024 = 16.384 bayt. Kesme sayısı 16.384 / 16 = 1024; CPU süresi 1024 × 2 = 2048 µs. DMA: 5 + 2 = 7 µs. Oran yaklaşık 2048 / 7 ≈ 293'tür.

  6. Bir işlemcide SMT destekli 4 çekirdek ve SMT desteği olmayan 4 çekirdek vardır. İşletim sistemi kaç mantıksal CPU görür? Bu CPU'ların hepsi eşit güçte midir?

    Cevap

    4 × 2 + 4 = 12 mantıksal CPU görünür. Eşit güçte değildir: aynı çekirdeği paylaşan iki mantıksal CPU o çekirdeğin yürütme birimlerini ve önbelleğini paylaşır, bu yüzden iki ayrı çekirdek kadar hızlı çalışmaz.

  7. “256 GB” etiketli bir USB bellek, ikili önek kullanan bir işletim sisteminde yaklaşık kaç GiB görünür?

    Cevap

    256 × 109 / 230 ≈ 238,4 GiB. Etiket ondalık, işletim sisteminin gösterimi ikilidir; bayt sayısı aynıdır.

  8. Aşağıdakilerden hangileri mekanizma, hangileri politikadır? (a) Bağlam değiştirme kodu, (b) etkileşimli süreçlere daha kısa zaman dilimi verilmesi, (c) sayfa tablosu girdisindeki geçerli bitin donanımca denetlenmesi, (d) belleği dolunca en uzun süredir kullanılmayan sayfanın dışarı atılması.

    Cevap

    (a) ve (c) mekanizmadır: bir işin nasıl yapılacağını sağlarlar. (b) ve (d) politikadır: “kime ne kadar dilim?” ve “hangi sayfa atılacak?” sorularına verilen kararlardır. Aynı mekanizmalar başka politikalarla da kullanılabilir.

  9. Bir kalp pili, bir müzik akışı uygulaması ve bir web sunucusu: hangisi katı, hangisi yumuşak gerçek zamanlıdır, hangisi genel amaçlıdır?

    Cevap

    Kalp pili katı gerçek zamanlıdır: uyarının zamanında verilmemesi hayati sonuç doğurur. Müzik akışı yumuşak gerçek zamanlıdır: arada bir gecikme kısa bir kesintiye yol açar, ama tehlikeli değildir. Web sunucusu genel amaçlıdır; ortalama başarımı ve adaleti öne çıkarır, katı bir son tarihi yoktur.

  10. Zaman paylaşımı, çoklu programlamadan hangi donanım özelliği sayesinde ayrılır? Hiç G/Ç yapmayan bir iş iki sistemde nasıl davranır?

    Cevap

    Zamanlayıcı kesmesi sayesinde. Çoklu programlamada CPU bir işten ancak iş beklemeye girince alınır; hiç G/Ç yapmayan bir iş CPU'yu bitene kadar tutar. Zaman paylaşımında zamanlayıcı her dilimin sonunda kesme üretir ve çekirdek CPU'yu bu işten alıp sıradakine verir; böylece öteki kullanıcıların tepki süresi kısa kalır.

1.12İleri okuma

  • A. S. Tanenbaum ve H. Bos, Modern Operating Systems (5. baskı, 2023), Bölüm 1: işletim sisteminin iki rolünü, nesilleri, IBM 1401/7094 toplu iş düzenini ve İS çeşitlerini bu notla aynı sırayla ve bol tarihî ayrıntıyla anlatır.
  • R. H. Arpaci-Dusseau ve A. C. Arpaci-Dusseau, Operating Systems: Three Easy Pieces, Bölüm 2: sanallaştırma, eş zamanlılık ve kalıcılık deneylerinin C kodlarını verir; kitap çevrim içi olarak ücretsiz okunabilir.
  • A. Silberschatz, P. B. Galvin ve G. Gagne, Operating System Concepts (10. baskı, 2018), Bölüm 1: kesmeler, bellek hiyerarşisi, DMA ve çift modlu çalışmayı donanım açısından ayrıntılı işler.
  • F. P. Brooks, The Mythical Man-Month (1975; 20. yıl baskısı 1995): OS/360 projesinden çıkarılan yazılım mühendisliği derslerini anlatan klasik deneme kitabı.

1.13Kaynaklar

  1. R. H. Arpaci-Dusseau, A. C. Arpaci-Dusseau, Operating Systems: Three Easy Pieces, sürüm 1.10, pages.cs.wisc.edu/~remzi/OSTEP.
  2. F. P. Brooks, The Mythical Man-Month: Essays on Software Engineering, 20. yıl baskısı, Addison-Wesley, 1995.
  3. Linux kılavuz sayfaları: syscall(2), fork(2), execve(2), proc(5).
  4. A. Silberschatz, P. B. Galvin, G. Gagne, Operating System Concepts, 10. baskı, Wiley, 2018.
  5. A. S. Tanenbaum, H. Bos, Modern Operating Systems, 4. baskı, Pearson, 2014; 5. baskı, 2023.

İlgili sayfalar: Bölüm 1 sunumu · Çalışma soruları · Terim sözlüğü · © Sercan Külcü. Tüm hakları saklıdır.