Bir Yazılımcının Gözünden - "İşletim Sistemi"

Bu yazıda, hem lisans hem yüksek lisansta aldığım İşletim Sistemi dersinden gözlemlerim ve notlarımın, yazılımcı bakış açısı ile ele alınmaktadır. 
Yazılımcılara genel kültür olacağına inandığım ve temel seviyede bilgi vermesi amacıyla konular, basitçe ifade edilmiştir. 

Aşağıdaki başlıklara tıklayıp, ilgili konuya ulaşabilirsiniz.
  1. İşletim Sistemi Nedir?
  2. Program vs Process
  3. Thread Yapısı
  4. İşlem Senkronizasyonu
  5. CPU Scheduling
  6. Main Memory
  7. Deadlock - Ölümcül Kilitlenme
İşletim Sistemi Nedir?

İşletim Sistemi, son kullanıcıların kullandığı uygulamalarla donanım arasında haberleşmeyi sağlayan yazılımlardır. Bilgisayardaki işlemleri gerçekleştiren kaynakların - CPU(işlemci), Memory(bellek), I/O(Input-Output) - yönetimini sağlayan kontrol yazılımlarıdır. Bu kaynaklar, bir bilgisayarın temel işlevleri olan veriyi işleme, saklama, taşıma ve kontrol işlemlerinden sorumludur.

Buradan çıkarılabilecek temel sonuç; bilgisayar kaynaklarının etkili kullanımını sağlayıp (kaynak yöneticisi), kullanıcıya kullanım kolaylığı sağlamasıdır.  

Aşağıdaki görselde işletim sisteminin, bilgisayar sistemindeki yeri daha iyi anlaşılacaktır. 

İşletim sisteminin ne iş yaptığı, bakış açısına göre değişir. 

Normal kullanıcılar, kolay kullanım ve iyi performans bekler. Kaynak kullanımını önemsemezler. 
Yazılımcı bakış açısı ile düşünürsek, geliştirdiğimiz uygulamaların sistemi kilitlememesi, yerinde değişken kullanımı, işlemlerin hızlı cevap vermesi gibi konuları dikkate alarak, son kullanıcıya kesintisiz hizmet sağlamaya devam etmesini önemseriz.  

İşletim sistemleri interrupt driven (kesme yönenilmli) özelliğinden dolayı olmazsa olmazdır. Çalışma rutinin değiştirme yolu interruptlardan geçer. Yazılım tarafından üretien trap/exception, hata fırlatarak işlemi kesebilir. Programımızda çalışan kodlarda exception handling- hata yönetimi önemlidir.

Program vs Process

Program ve process kavramları sıkça karıştırılır. Kesin bilgi olarak; 

Program != Process

Program, pasif bir varlıktır. Program yazıldı, kaynak kod derlenerek makine koduna döndü. Process ise çalışan bir programdır. Bir process, "programı çalıştır" dediğimiz anda diskten RAM'e alınır ve CPU'da çalışması için sırasını bekler.

Process olması için aşağıdaki yapıların olması gerekir.
Text: Program kodu.
Data: Program kapsamında global değişkenlerin bulunmasıdır.
Stack: Geçici değişkenleri tutar.
Counter: Programın bir sonraki çalışacak adımını tutar.
Heap: Dinamik olarak yapılan memory allocation'dur.

Text, data sabittir. Stack, counter ve heap ise değişkendir.
Process Control Block (PCB) veri yapısı, processlerin takip edilebilmesi için kullanılır. İşlemin künyesi olarak da düşünülebilir. Temel bulunan kısımlar;
  • Process state - işlemin durumu
  • Process id - tekil bir id
  • Program Counter - Program çalışırken hangi satırda/ bir sonraki adımda ne çalışacak
  • Register - Processle ilgili bilgiler tutulur.
  • Memory Limit - Kaynak, kapasite tanımı
  • Açık dosyaların bilgisi - I/O bilgisi
Processlerin aşağıdaki gibi durumları vardır. Yaşam döngüsünü anlarsak, çalışma mantığını anlayabiliriz.

new: Process yaratılmış demektir.
ready: Process çalışmak için hazırdır ve CPU'dan sıra bekliyor. Farklı bir processi veya kaynağı beklemektedir.
running: CPU tarafından  ready kuyruğundan işlemin çalıştırılmak üzere alınmasıdır.
waiting: I/O işlemi başladığında oluşacak bir durumdur. 
terminated: İşlemin bitmesidir.

Yukarıdaki görselde processin yaşam döngüsü üzerinden çalışma mantığına yer verilmiştir. Dikkat edilecek nokta, ready-running-waiting durumları arasında bir döngü olmasıdır.

ready-running arasında scheduler dispatch devreye girer. 
ready-running durumları arasındaki waiting durumunda I/O işlemi çalışıp bitmektedir. 
Waiting durumundan ready durumuna geçmesi, tekrar işlemcide çalışmaya hazır ve sırası geldiğinde runningde çalışabileceğini belirtir. 
Running'den ready durumuna geçmesi durumu herhangi bir interrupt oluşması ya da time quantum ile çalışan algoritmanın devreye girmesinden kaynaklı olabilir.

Thread Yapısı

Araştırma yaparken, "Processler kollarsa, threadler ise parmaklarıdır." analojisine denk geldim. Bu sayede Process ile thread arasındaki farkları daha iyi anladım. 

Bir thread'in var olması için process var olmalıdır. Bir processte birden fazla thread olabilir. Threadlerde amaç, işlemci zamanını efektif şekilde kullanmaktır. Bir kullanıcıya ait bir processler CPU zamanının tamamını kullanmak mümkün değildir. Bir kullanıcıya ait birden fazla processin aynı anda çalışmasına izin verilir. 

Faydaları; yanıt verilebilirlik artar, daha kolay kaynak paylaşımı ve daha az maliyetli kaynak kullanımı sağlar. Daha az yer kapladığı için ölçeklenebilirlik kolaydır.

Çok çekirdekli/işlemcili sistemlerde karşılaşılan zorluklar; etkinlikleri ve veriyi bölmek, denge, veri bağlılığı ve test/hata ayıklama sürecidir. Bu yüzden paralellik(parallelism) ve eş zamanlılık (concurrency) konuları gündemdedir. 

Paralellik: Sistemin aynı ana birden fazla iş yapabilmesi. Çoklu işlemcili ve çekirdekli sistemler paralel çalışmayı destekler.

Eş zamanlılık: Birden fazla işin ilerlemesini destekler. Zaman paylaşımlı sistemlerde, sistemin herkesin çalışmasına imkan verecek şekilde işlemleri yürütebilmesi için işlemler arasında kısa zamanda geçişler yaparak aynı anda çalışıyor izlenimi verilmesidir.
Single threaded ve multi process arasındaki en önemli fark her bir thread için register ve stacklerin ayrı olmadıdır. Kod ve data alanlarının sabit olduğunu processler bölümünde değinmiştim.

Amdahl Yasası'nda bilmemiz gereken konu, programın paralelleştirilmesiyle alakalıdır. Çekirdek sayısı iki katına çıktıkça performans iki katına çıkmaz. Paralel ve seri işlemlerin oranlarına göre değişir. 

Thread yönetiminde kernel ve kullanıcı threadlerinin eşleşmesi gerekir. Üç tip bulunur.

Many to One yapısında kernel engellenirse bütün kullanıcılar engellenmiş olur. Fakir işi bir yapı gibi akıllarda kalabilir. Çok kullanıcı threade karşı bir kernel threadin eşleşmesidir. 
One to One yapısında bir kullanıcı threadi için bir kernel threadi eşleşmiştir. Kaynakları bol bol tüketir ve bu yönden zengin işi şeklinde akıllarda kalabilir. Kernel yönetiminin sorun olması, dezavantaj yaratabilir.
Many to Many yapısında birden çok kullanıcı threadlerine karşılık birden fazla kernel thread ile eşleşmesidir. Bir kernelde engelleme varsa diğerleri çalışmaya devam edebilir. 
                                                       
Bir process birden fazla uygulamada olamaz ama bir programın birden fazla processi olabilir. 

İşlem Senkronizasyonu

Yazılımcının görevi işlemler arasında haberleşme ve senkronizasyonu sağlamasıdır. Bu noktada Interprocess communication (IPC) haberleşme için mekanizma sunmaktadır. 
Paylaşılan Bellek: Aynı bellekte birden fazla processin kullanılması
Message Passing: Mesaj kuyruğu vardır. Biri yazarken diğeri okuma işlemi yapabilir.

Bilinmesi gereken temel kavramlar
Race condition: Birden fazla process, aynı kaynağa erişmeye çalışıyor ama hangisinin önceden çalışacağı bilinmiyor. İşlemlerin farklı senaryolarda farklı sonuç elde etme durumudur.
Critical section: Programlarda eş zamanlı olarak çalışan processlerden korunan bölgedir. Korunmazsa hatalı sonuçlara sebebiyet verebilir. Kritik alanlar, girişine ve çıkışına yazılacak kodlarla korunur.

CPU Scheduling

Processleri nasıl sıraladığımız ve işlemcinin nasıl çalıştıracağına karar veren bir mekanizmadır. Birden fazla processin parallel olarak çalışması (concurrency) ve işlemler arası zaman paylaşımı bu işin temel konusudur. Bunun için değişik algoritmalar mevcuttur. İşletim sistemleri aynı zamanda bir process olduğundan process yönetimi olmadan olmaz.

Zamanlayıcı, işlemciyi çok iyi kullanmalı ve işlemciden maksimum performansı almasını sağlamalıdır.

İşlem davranışlarında işlemler; CPU burst veya I/O burst olabilir. Bunlar bir döngü halinde çalışırlar.

Algoritmalar iki temel gruba ayrılırlar.

  • Preemptive - Kesintili: İşlemcide işler çalışırken yarım bırakılıp başka bir işle değiştirilebilir. Zamanı dolunca farklı bir iş atanabilir. 
  • Non-preemptive - Kesintisiz: Bir iş atandı mı o iş bitene kadar işlemciden alınmıyor. 

Short-Term Scheduler

Çok uzun süre işlemcide olması istenmeyen ve CPU'ya az ihtiyaç duyan işlemler için kullanılır. Kullanıcı ile etkileşime giren veya hızlı cevap beklediğimiz uygulamalarda rastlarız.

Örnek olarak bir butona tıkladığımızda hemen cevabın gelmesidir. 

Long-Term Scheduler

Uzun sürecek işlemlerde kullanışlıdır. Kullanıcı ile etkileşime girmeyen işlerde tercih edilir. Örneğin, yedek alma işlemi, toplu işlemlerde kullanışlıdır. İş bitene kadar CPU da kalabilir. Genellikle, sunucu mimarisinde kullanılır. 

Zamanlayıcı kriterleri - işi iyileştirme kriterleri diyebiliriz.

  • CPU Utilization: İşlemciyi ne kadar yoğun tutarsak o kadar iyidir. 
  • Throughput: Birim zamanda tamamlanan, sistemi terk eden işlem adedi.
  • Turnaround Time: İşlemin sisteme girişi ve çıkışı arasında geçen süredir. İşlemin başlangıç ve bitiş süresi de denir.
  • Waiting Time: CPU'ya erişmek için ready kuyruğunda ne kadar bekledi.
  • Response Time: İşlemin cevap verme süresi. Bir işin işletim sistemine teslim edilmesinden, sonlanana(submit) kadar geçen süre de denilebilir.

Hedeflerimiz;

  • Maksimum CPU Kullanımı
  • Maksimum Throughput
  • Minimum Turnaround Time
  • Minimum Response Time
  • Minimum Waiting Time

Duruma göre değişen algoritmalar var. Her birinin avantajları ve dezavantajları vardır. İşlemlere göre aşağıdakiler uygulanabilir.

First Come / First Served : İlk gelen ilk hizmeti alır. Non-preemptivedir. 
Shortest Job First: En kısa burst(çalışma zamanı) olan ilk hizmeti alır.
Priorty Scheduling: İşlemlerle ilişkili bir öncelik değeri vardır ve düşük sayıda olan ilk hizmeti alır. 
Round Robin: İşlemlerin time quantum süresi başına sırasıyla çalıştırılmasıdır. Preemptivedir.
Shortest Remaining Job First:  Shortest Job First algoritmasına preemption eklenmiş halidir. Sisteme varış süresine bakıp, en kısa işin öncelikli çalıştırılması durumudur.

Context switch: Bir processten diğerine geçme işlemidir. İki process arasındaki geçişler PCB yapısına kaydedilir. Context switchin çok fazla olması iyi değildir. CPU zamanı israf edilecektir ve her değişim maliyetlidir. Scheduling bölümünde değinme sebebim, işlerin paylaştırılması noktasında context switchlere dikkat edilmesi gerektiğidir.

Main Memory

Bir programın çalışması için data ve komutların bellekte olması gerekmektedir. Makine dilindeki bir programın diskten RAM'e yüklenmesi, processlerin çalışması için ilk şarttır. Diskte program çalışmamaktadır. 

Kaynak sınırlı olduğundan processin belleğe giriş çıkış zamanının yönetilmesi problemdir.  Bu yüzden bellek yapısını anlamak gerekir.

Programlar, kod ve datadan oluşur. Kaynak kodlarında satır sayıları var, derlenirken adres ortaya çıkıyor. Program çalışırken fiziksel adres üzerinde işlem yapar. 

Processlerin çalışırken bellekte konumlandırılabilmeleri için base ve limit adresleri vardır. 
Base register: Programın ilk kodu oradadır. Process çalışırken başlangıç noktası olarak adresini sıfır zanneder. 
Limit register: İşlemler, bu linit aralığında çalışır. Base adresleri eklenerek gerçek adreslerine dönüştürülür. Sonrasında process, bu aralıkta konumlanır. 
Logical adres, erişmek istenen adrestir ve işlemci üretir. Fiziksel adres ise bellekte görünen adrestir.

Yukarıdaki görselde görüleceği gibi; işlemin adresi base adress ile base ve limit adresinin toplamı arasında olmalıdır. Aralık dışındaysa hata dönecektir ve belleğe alınamayacaktır.

Üç önemli zaman bulunuyor.

Compile Time: Derleme zamanı. Daha önceden derlenmiş, önceden fiziksel adresi biliyoruz. 
Load Time: Yükleme zamanı. Program çalıştı, belleğe yüklenme işlemini işletim sistemi yapar. Bu zamanda sırasıyla linker vasıtasıyla 3. parti kütüphanelerin bağlanması, loader vasıtasıyla kütüphaneler ve kodların yüklenmesi sağlanır.
Execution Time: Çalıştırma zamanı. Bellek üzerinde işlemler için bazı değişiklikler yapılıyor.

Load ve Execution Time'de base register sürekli değişiyor. 

Swapping: Bellekteki işlemlerin diske alınması veya diskteki bir işlemin de bellekte boş bir yere alınmasıdır.
Swap in: Diskten belleğe geçiş
Swap out: Bellekten diske geçiş
Tabi her process bellekte aynı miktarda yer kaplamıyor. Bu geçişler sırasında boşluklar oluşabilir. 
Internal fragmentation: Processe ayrılan alan içerisindeki boşluklardır.
External fragmentation: Eklenen processler arasındaki boşluklardır.

Processlerin belleklere yerleştirilmesi sırasında üç farklı yaklaşım vardır.
First-fit: İlk bulduğu, sığabilen boşluğa processin yerleştirilmesidir. Daha hızlıdır ve boyutlara bakmaz. 
Best-fit: External fragmenti en küçük olanı bulur yani boşluklara bakıp en iyi yeri bulur.
Worst-fit: "Daha araya process eklenebilir mi?" sorusunun cevabını verir.

Fragmentleri azaltmak için yapılan işlemlere Compaction denir. Processleri bellekte oynatarak boş alanları arttırmaya çalışır. Processler bölünmüyor fakat maliyet artacaktır.

Segmentation - Processleri ihtiyaca göre RAM'e dağıtıyor. "External fragmentation"a sebep olur.
Paging - Segmentation'den farklı olarak sabit boyutlu sayfalar oluşturuluyor. "Internal framentation"a sebep olur.

Deadlock - Ölümcül Kilitlenme

İşlemler çalışırken neyin ne zaman hangi sırada çalışacağını bilemeyiz. Kaynağa erişme isteğinden dolayı processlerin birbirini kilitlemesi sonucu hiç bir işlem çalışamaz ve deadlock oluşur. Yazılımcılar olarak hatalı kod yazmak, işletim sisteminin sunacağı kaynakları hatalı kullanma sonucunda deadlocka sebep olabiliriz. Örnek bir senaryoda multi thread kurgusu doğru kurgulanmazsa paylaşımlı olarak thread kullanılma durumu olabileceğinden deadlocka sebep olabilir.

Kaynakların kullanımı için aşağıdaki sıra takip edilmelidir.
  1. Kaynağı iste
  2. Kullan
  3. Kaynağı bırak
Deadlock oluşması için aşağıdaki dört şartın sağlanması gerekir. 
  • Mutual exclusion: İki processin birbirini bekleme durumudur. Herhangi bir T anında bir kaynakta sadece tek process çalışmalıdır. Çalışmayıp aynı kaynağa erişme isteği doğuyor. 
  • Hold and wait: Process kaynağı tutuyor ama başkasının da kaynağını tutup diğer işlemin beklemesine yol açıyor.
  • No preemption: Scheduling ile ilgili bir konudur. Bir process kaynağı aldığı zaman işi bitene kadar elinden alınmıyor.
  • Circular: Eğer kaynak ve işlem arasında aşağıdaki görseldeki gibi bir döngü varsa, deadlock oldu denilebilir.
Görselde, Process 1, Resource 1'i talep etmiştir. Aynı zamanda Process 2 Resource 1'i tutmaktadır. Döngünün devamında Process 2, Resource 2'i talep etmiştir. Aynı zamanda Process 1, Resource 2'yi tutmaktadır. Kaynağı serbest bırakan bir işlem olmadığı için de kilitlenme ortaya çıkacak ve deadlock kaçınılmaz olacaktır.

Özetle; 
  • Eğer döngü yoksa, deadlock oluşmaz.
  • Döngü varsa kaynağa bakılır. 
  • Eğer kaynak başına bir örnek varsa kesinlikle deadlock oluşur. 
  • Eğer birden fazla varsa olabilir de olmayabilir de incelenmesi gerekir.
Deadlock önlemek için yollar var ama gerçekleştirmek zordur.
Birden fazla processin aynı anda çalışıp kaynağa erişmesine izin verilmiyor.- Mutual exclusion
İstekleri tek tek işleyip hold and waiti kaldırmak. En başta istediği kaynakğı öğrenip varsa sağlamak yoksa bekletmek. 
Sistemi preemptive yapmak. Belirli bir sürede daha önceden verilen kaynaklar, zorla elinden alınamaz. 

Kaynak Önerisi
  • Üniversitelerde gösterilen ve dinazorlu kitap olarak da bilinen aşağıdaki kitap, kitabı okumak istemezseniz slayları da mevcuttur. 
"Operating System Concepts", 9. Edition, Abraham Silberschatz,  Peter B. Galvin, Greg Gagne 

Slaytları için tıklayınız.  
  • Türkçe Youtube Kaynağı olarak Şadi Evren Şeker hocanın Bilgisayar Kavramları kanalında, İşletim Sistemleri oynatma listesinde özet olarak anlatılmaktadır. 
Oynatma Listesi için tıklayınız. 

Keyifli ve faydalı olmasını umarak, çalışmalarınızda kolaylıklar diliyorum :) 

Yorumlar