İşletim Sistemi 101 – #5 (CPU Scheduling)

Giriş

Bir önceki yazımda process’lerden bahsetmiştim. Bu yazımda ise ister “multithreaded” ister “single-threaded” olsun, bir işin CPU’ya gönderilmesi için olmazsa olmaz bir konudan, CPU scheduling‘ten bahsedeceğim.

Hep tekrar ettiğimiz bir nokta: İşletim sistemi kaynakları doğru yönetmelidir. Ve kaynaklarımız sınırlı. Bir CPU çekirdeğinde aynı anda yalnızca bir iş yapılabilir ve daha bilgisayarımızı açar açmaz çalışan servisler, aslına bakarsanız ciddi iş yükü oluşturuyor.

Örneğin anti-malware yazılımınız çalışıyor. Güncellemelerini, günlük görevlerini yapmaya başlıyor. Wi-Fi kartınız, etrafındaki ağları aramaya başlıyor, otomatik bağlanmasını istediğiniz denk gelirse bağlanıyor. USB yollarına takılı olan cihazlar varsa ayağa kaldırılıyor. Ses, parlaklık, klavye aydınlatması, fonksiyon tuşları gibi işlevlerden sorumlu servisler ayağa kalkıyor. İşletim sistemi, kendi log’larını yazıyor. Kullanıcı oturumunuzu yöneten servisler ayağa kalkıyor. Daha neler neler…

Sonrasında kullanıcı, bilgisayarını kullanmaya başlıyor. Kullanıcının açtığı process’ler de dâhil oluyor curcunaya. Ama ortada – genelde – 1-8 arası CPU çekirdeği var. Bu CPU’yu en verimli şekilde kullanabilmek için geliştirilen bazı modeller var. Bunlardan biri, ilk paragrafta bahsi geçen “thread” kavramı.

Thread Nedir?

Process’lerin çalıştırılmasını “scheduler” yönetir (bu konuya değineceğiz). Thread’leri ise process’lerin içindeki alt process’ler olarak düşünebiliriz. Process’lerin bir birim zamanda birden fazla görevi eş zamanlı yürütebilmelerine olanak sağlayan yapıya “thread” diyebiliriz.

Olayı şöyle örnekleyelim. 1 CPU’muz var. 4 çekirdeği var. 4 çekirdeği de eş zamanlı eş yapabiliyor diyelim. Bizim de elimizde 4 birimlik iş olsun. Eğer single-threaded yapıda bir uygulamamız varsa, işini bitirebilmesi için 4 tura ihtiyaç duyar. Çünkü birim zamanda yalnızca bir çekirdeği kullanabilmekte, dolayısıyla bir iş yapabilmektedir. Fakat uygulamamız multithreaded yapıda ise, bir CPU turunda 4 çekirdeğe parçalı olarak yerleşerek işini “paralel^1 yapabilir ve 1 turda bitirebilir. Anlamadık mı?

Kabaca örneklemeyi deneyelim. Bir e-posta istemcisini düşünün. CPU’da oldukça kısıtlı olan zamanını single-threaded modda geçiriyorsa, her CPU turunda sadece bir iş yapabilir:

  1. Kimden, kime, konu gibi üst bilgileri çek. (Tur bitti, bekle.)
  2. E-posta içeriğini çek. (Tur bitti, bekle.)
  3. E-posta içerisinde yer alan imza, bir web sunucusundan görsel (kartvizit gibi) içeriyor. Onu getir. (Tur bitti, bekle.)
  4. Gönderen kişi “alındı onayı” istemiş. Onay verecek miyim diye sor. (Tur bitti, bekle.)
  5. Alındı onayını gönderdim. Karşıya ilet. (Tur bitti, bekle.)
  6. E-postayı okudum. Sunucuda da okunmuş işaretle.

Eğer aynı istemci, multithreaded yazılmış olsaydı ve elimizde kullanılabilir durumda 2 çekirdek olsaydı, bu işler eş zamanlı yapılabilir ve yukarıdaki adımlar 3 turda tamamlanabilirdi.

Başka bir örnek. Web tarayıcınızı açın. Sonra “Görev Yöneticisi” gibi bir yerden işlemlere bakın. Tarayıcıda 2 sekme açın. 2 farklı web sayfasına gidin. Process listesine tekrar bakın. Tarayıcınız yüksek ihtimalle, hem sekmeler için hem de bir sekme içinde farklı görevleri yerine getirmek için thread’ler açmış olacak. Hatta şansınız yaver gider ve doğru thread’i durdurmayı başarırsanız, sekmelerden birinin tamamen ya da kısmen çöktüğünü görebilirsiniz.

Kısa bir bilgi: Özellikle telefonlarda deli gibi çekirdek sayısı çıkıyor karşımıza. Soru: “Çift çekirdekli 2 GHz hıza sahip işlemci mi daha iyidir yoksa 4 çekirdekli 1.4 GHz hıza sahip işlemci mi?” Diğer tüm özelliklerinin aynı olduğunu kabul edelim. Bir tanesi 2×2’den 4 GHz hıza sahip teoride. Diğeri ise 4×1.4’ten 5.6 GHz. Dört çekirdekli olan daha iyi o zaman? Yok işte, öyle olmuyor. Kullanacağınız uygulama – diyelim ki sıradan bir mobil oyun – eğer multithreaded yazılmadıysa (geliştirilmediyse) sadece 1 çekirdek kullanabilir. Dolayısıyla o uygulama için çift çekirdekli 2 GHz işlemci daha iyi olur. Çünkü tek çekirdekteki hızı daha yüksektir. Öte yandan uygulamayı bu şekilde yazan kişilere de sormak lazım “Neden?” diye.

Giriş kısmında, henüz bilgisayar açılmışken yapılması gereken işlerden bahsetmiştim. İşletim sistemleri – kendileri de bir yazılım olduğu için – kaynakları doğru kullanacak şekilde hazırlanırlar. En azından bunu denerler. Bir işletim sistemi; aygıt yönetimi, bellek yönetimi, disk yönetimi gibi ayrı işleri için ayrı thread’lere sahip olabilir. Hatırlarsanız bir önceki yazımda “Kernel Thread Daemon”dan bahsetmiştim. Bütün process’ler ve dolayısıyla process’lerin thread’leri, bu arkadaşa bağlı çalışıyordu.

Process ve Thread Farkı

  • Process’ler birbirinden bağımsız yapılardır. Thread’ler ise, process’lere bağlı çalışan iş parçalarıdır. Bir process’in birden fazla thread’i olabilir.
  • Process’e dair tutulan bilgiler, thread’e kıyasla daha fazladır (Process Control Block’u hatırlayın.). Thread’ler ise; process’lerinin state’lerini, process’lerine atanan kaynakları miras alır.^2
Process içinde thread'ler. Thread'lerin code, data gibi segment'leri ortak kullanmalarına bir örnek. (Operating System Concepts Essentials kitabından alınmıştır.)
Process içinde thread’ler. Thread’lerin code, data gibi segment’leri ortak kullanmalarına bir örnek. (Operating System Concepts Essentials kitabından alınmıştır.)

Neden Thread?

  • E-posta istemcisi örneğine geri dönelim. Bir e-posta yazmak istediğinizde, uygulama sizi beklemek zorundadır. Klavyeden tuşlara basacaksınız, “Gönder” düğmesine tıklayacaksınız. Bu sırada process, sizinle meşgul. Sizden gelecek etkileşimleri bekliyor. Eğer single-threaded bir yapıda olsaydı, siz bu e-postayı yazma işini bitirene kadar başka hiçbir şeyle ilgilenemezdi. Fakat multithreaded çalışırsa, bir thread sizin e-posta yazmanız için beklerken başka bir thread arkaplanda yeni e-posta olup olmadığını kontrol etmeye devam edebilir.
  • Kaynakların process’ler arasında paylaştırılabilmesi için geliştirici tarafından ekstra bir çaba gerekir. Fakat multithreaded yazılmış bir uygulama, yapısı gereği, zaten kaynaklarını kendi içindeki thread’ler arasında paylaşabilir durumdadır.
  • Her iş için ayrı bir process demek, her iş için process oluşturma sürecinin tekrarlanması demek. Önceki yazımda değindiğim gibi, bu gerçekten maliyetli bir iş. Fakat bir process içinde birden fazla thread oluşturmak, onlarca kat hızlı olabilir. Ek olarak, thread’ler arasında “context switching” yapmak (buna da değineceğiz), process’ler arasındakine göre çok daha hızlıdır.
  • Yukarıda verdiğim mobil uygulama örneğinde olduğu gibi, donanımın hakkını vermek için thread’lerden faydalanırız. İster çift, ister dört, ister sekiz çekirdeğiniz olsun. Eğer uygulama, single-threaded çalışıyorsa, biri hariç diğer tüm çekirdekler hiçbir işe yaramaz. (Önemli bir nokta. İşletim sistemleri bazı çekirdekleri sabit ya da belirli görevlere atamış olabilir. Dolayısıyla CPU’nuzun diğer çekirdekleri de kullanılmaya devam edebilir fakat sizin kullandığınız uygulama tarafından değil.)

CPU Scheduling

Gelelim esas konumuza. Herhangi bir anda hangi process’in çalışacağına karar verilmesi gerekir. Tabii ki bu karar, işletim sisteminin sorumluluğundadır ve “scheduler” tarafından verilir (bu konuya değineceğiz dediklerimden ilki). Scheduler’ın bu kararı vermesinde rol alan algoritmalar vardır ki bu algoritmalara “scheduling algorithm” denir.

Geçmiş zamanda, batch system’lerde, scheduling daha kolaydı. Delikli kart bloklarını hangi sırada koyarsanız, o sırada çalışır. Siz Ahmet’in programını Mehmet’in programından önce yüklerseniz (evet, fiziken kartları makineye yüklerseniz) Ahmet’in programının önce çalışması kaçınılmazdır. Programların tape’lere kaydedildiği zamanı düşünelim. Eğer Mehmet’in programı, şerit üzerinde Ahmet’in programından daha önce yazıldıysa, önce Mehmet’in programı çalışır. Bu mekanizmalar “sıralı erişim” yapar. Telefonunuzda istediğiniz müziği açabiliyorsunuz ama kaset çalar kullanırken ileri sarmak zorundasınız, aynı mantık. Biri rastgele erişimi desteklerken diğeri sıralı erişimi destekler. Hemen bir hatırlatma: Random Access Memory‘e (Rastgele Erişimli Bellek, RAM) neden rastgele erişimli denildiğini anladınız mı? 🙂

Günümüz sistemlerinde ise birden fazla işi eş zamanlı yapabilmemizi sağlayan olay, CPU scheduling sayesinde CPU zamanının verimli kullanılmasıdır.

CPU Burst vs. I/O Burst

Çoook acil bir işim var. Hemen bir dilekçe yazıp çıktı almam lazım. Çabuk, acele. Tık, tık, tık, tık, tık… 8 tane metin editörü açtım. 7’sini kapattım, biri kaldı. İşletim sistemine bakın, nasıl hizmet veriyor bana. Editör önümde açık, bekliyor. CPU’da bekliyor beni iş yapmak için. Bütün kaynaklar önüme serilmiş. Saniyede milyarlarca işlemi hobi olarak yapan cihaz, beni bekliyor. “Bugün ayın kaçı? Tarihi sağ üste mi yazıyorduk? Başlıktan önce miydi sonra mıydı? Kuruma mı yazayım müdürlüğe falan mı?” Milyarlarca işlem kapasitesi, gigabyte’lar düzeyinde bellek, 100’ün üzerinde tuş tek yürek olmuş; keyfimi bekliyor.

İşte, ilk başta “hadi hadi hadi” diyerek başlattığımız süreçlere “CPU burst” diyebiliriz. Programımız, gerçekten açılmak istiyor. Process için ilgili kayıtlar oluşturuluyor. Görüntü önümüze geliyor. Bunlar hep CPU ile bağlantılı işler. Sonrasında ise benden bir girdi bekleniyor. Bu tarafa ise “I/O burst” diyebiliriz. Yazdık, bitirdik. Editör yine çalışıyor biz yazdıkça. Fakat bir birim çalışıyorsa belki 1000 birim bizi bekliyor. CPU’nun hızına yetişmemiz imkânsız. Sonrasında ise “kaydet” butonuna basıyoruz. Yazdıklarımız, CPU’ya kıyasla oldukça hantal kalan hard disk’e yazılmaya başlanıyor. Çıktı süreci başladı, yine I/O burst yapıyoruz. Yine yavaşız. Dosyayı yazıcıya gönderdik, yine yavaşız. Process’ler, herhangi bir dış kaynaktan etkileşim beklemeye başladılarsa, I/O sürecine girmiş olurlar. Bu süreç, CPU sürecine göre aşırı yavaştır ve beklemeye değmez. Bir başka deyişle, siz o editörü açıp boş boş bakarken, işletim sistemi sizi çoktan unutmuştur ve başka işlere dönmüştür bile. Neredeyse bütün programların girdi/çıktı ile uğraştığını düşünürsek, process’lerin de CPU burst ile I/O burst arasında gidip geldiğini söyleyebiliriz.

Web tarayıcımızı açtık. Önceki oturumdan kalan 24 sekme otomatik olarak yüklendi. CPU’muz çalışıyor. Bu oturumlara ait bilgiler getiriliyor, web adreslerine istekler gönderiliyor, gelen yanıtlar işleniyor, ekranda gösteriliyor. Peki ya sonra? Elimizi fareye uzatıyoruz. Tık, tık, kaydır, oku. Yine aynı durum. CPU burst oldu. I/O burst’e geçildi. Tak, bir input geldi bizden. Fare tekerleğini kaydırdık. Ekranda birtakım değişiklikler yaşandı. Sonra yine bekle, okuyorum.

İşleri Hangi Durumlarda Planlamalıyız?

Scheduler’dan bahsettik. Peki scheduler’a ne zaman, hangi durumlarda iş düşüyor? Ne zaman devreye girip yetkisini kullanması gerekiyor?

  • Yeni bir process oluşturuldu ve kuyruğa girdi. Ne zaman, ne sıklıkla, ne süreyle çalıştıralım?
  • Process durduruldu. Kuyruktan çıkarıldı. Bu process’in ortadan kaybolması, diğer process’lere ayırdığımız zamanı ve atadığımız önceliği nasıl etkileyecek?
  • Process bloklandı. Peki neden? Kullanıcıdan bir etkileşim bekliyorsa, sonlara atılabilir. Çünkü kullanıcı yavaştır. Peki bir “A” process’i, “B” process’inin işini bitirmesini beklediği için bloklandıysa ne olacak? Bir an önce “B”yi çalıştırıp bitirsek, bu sayede “A” da tekrar işine dönebilir hâle gelse olmaz mı? Peki scheduler bu bilgiye sahip mi?
  • Editör örneğine dönelim. Klavyeden bir tuşa basmam bekleniyordu. Bastım. Şimdi ne olacak? Tam ben tuşa bastığım sırada çalışmakta olan process çalışmaya devam mı etsin? Benim yazı yazmak için kullandığım process hemen devreye mi alınsın? Yoksa konudan bağımsız başka bir process’in daha öncelikli bir işi var ve herkes onu mu bekleyecek? Önceki yazılarımdan birinde, “a” ve “aaaaaaaaaaaaaaaaa” örneği vermiştim hatırlarsanız. Bunlar hep scheduling 🙂

Nonpreemptive vs. Preemptive

CPU scheduling algoritmalarını nonpreemptive ve preemptive olmak üzere ikiye ayırabiliriz.

Nonpreemptive” scheduling yapıyorsak, bir process’i CPU’ya göndeririz fakat durması için zorlamayız. Bu process; CPU ile işi bittiğinde, I/O beklediğinde, başka bir process’in işini bitirmesinde ihtiyaç duyduğunda CPU’dan gönüllü olarak ayrılır ve diğer process’lerin kullanabilmesi için CPU’yu serbest bırakır. Bu scheduling yöntemine “voluntary” ya da “cooperative” de denir.

Preemptive” scheduling’de ise, CPU’yu kullanmakta olan process’e “Tamam, yeter artık.” deme şansına sahip oluruz. Process’lerin çalışabilmek için önceden planlanmış süreleri olur. Bu süre, nanosaniyeler düzeyinde olabilir. Süresi dolan process, CPU’dan kaldırılır ve yerine – eğer varsa – bekleyen başka bir process konulur.

Bazı donanım platformlarında, preemptive scheduling için gerekli olan “zaman ölçme” mekanizmaları bulunmadığı için, mecburen nonpreemptive scheduling kullanılır. “Saat kaç oldu ki?” dediğinizde, bu durumu hatırlayın. 🙂

Bakıldığında, preemptive scheduling çok daha mantıklı görünüyor. Nonpreemptive bir yapıda, bir process CPU’yu kullanmaya başlar ve sürekli olarak bir işle meşgul olursa, diğer process’lere sıra gelmez değil mi? Fakat preemptive scheduling’in implementasyonu da gerek donanım gerekse yazılım tarafında ek gereksinimleri beraberinde getiriyor. Ayrıca, şöyle bir senaryo düşünelim. “A” process’i bir veri hazırlamakla yükümlü olsun. “B” process’i ise hazırlanan bu veri üzerinde işlem yapmakla yükümlü diyelim. Preemptive sistem. “A” process’i çalışıyor, veriyi hazırlıyor. “B” process’i devreye girip bu veriyi işliyor. Sonra “A” process’i yeni bir veri seti hazırlarken süresi doluyor ve veriyi doğru şekilde hazırlayamadan sıra “B” process’ine geliyor. “B” process’i bakıyor, veriyi işlemeye çalışıyor fakat bu veri tutarsız. O da savıyor sırasını. Şimdi ne olacak? 🙂

Dispatcher

CPU scheduling için kullanılan bir başka bileşen de “dispatcher“dır. Dispatcher’ın görevi, scheduler tarafından seçilen process ile ilgili aşağıdaki işlemleri yapmaktır:

  • Context switching
  • User mode’a geçiş
  • Yeni process’in çalışmaya devam edebilmesi için bir önceki turda kaldığı instruction’a geçiş yapmak

Context SwitchinG

“Bu konuya değineceğiz” dediklerimden ikincisi. Process control block’taki verileri neden tutuyorduk? Process hakkında bilgi sahibi olmak için. Bu bilgileri tutuyoruz ki, sıra tekrar bu process’e geldiğinde hiçbir şey olmamış gibi kaldığımız yerden devam edebilelim. İşte process’in durumunu PCB’ye kaydetme ve sıradaki process’in bilgilerini getirme işine “context switching” diyoruz.^3

User Mode vs. Kernel Mode

Kısaca açıklamaya çalışacağım. Bilgisayarı ben kullanıyorsam “user mode”, işletim sistemi kullanıyorsa “kernel mode” aktiftir diyebiliriz.^4

Bir sistemin, kullanıcının uygulamaları için çalıştığı duruma “user mode” denir. Ne zaman ki işletim sisteminin çekirdeği (kernel) bir iş yapmak durumunda kalır, sistem o zaman “kernel mode“a geçer. Bir medya oynatıcıyı çalıştırdım. Bu process “user mode”da çalışıyor. Bir müzik dosyasını açmak istedim. Uygulamam, diskten veri okumak zorunda. Bu durumda “open” ya da “read” gibi bir system call devreye giriyor. Kernel bu okuma işini devralıyor. Dolayısıyla sistem “kernel mode”a geçiyor. Bir metin editörünü açıyorum. User mode. Klavyemden birtakım girdiler gönderiyorum. Klavyem işletim sistemine çağrıda bulunuyor. Kernel mode. Yazımı yazıyorum, son kez göz gezdiriyorum. User mode. Kaydet tuşuna bastım. Uygulamam “write” system call’ı gönderdi. Kernel mode.

Sistemin hangi modda çalıştığını, donanım üzerinde “mode bit” dediğimiz bir bitle tutuyoruz. Mode bit 0 ise sistem kernel mode’dadır, 1 ise user mode’da. User mode, oldukça kısıtlı bir mod iken; kernel mode donanıma müdahale edebilen ayrıcalıklı bir moddur. Başka bir deyişle; sizin kullandığınız uygulama sırf siz istediğiniz diye ya da kendi kendine disk’le, RAM’le, ağ kartlarıyla vs. muhatap olamaz. Bu talepleri system call’lar ile işletim sistemine iletir. İşletim sistemi yapılması gereken işleri çekirdeğine yaptırır, sonucunu uygulamanıza iletir.

Neden Scheduling?

CPU scheduling yapılmasının belli başlı amaçları vardır.

  • CPU’yu mümkün mertebe kullanmak. Herhangi bir sistemde bir CPU kullanılmıyorsa, bu israf kabul edilir. Gerçekten. Madem bu kadar fazla işlem yapmayacaktık, neden bu işlemciyi satın aldık? Bu bilgisayar çalışıyor, peki neden iş yapmıyor? İşletim sistemi bu durumu önlemek için tüm process ve thread’leri doğru şekilde planlayıp çalıştırmak zorundadır.
  • Birim zamanda yapılan iş miktarını (throughput) arttırmak. Eğer bir process I/O bekliyorsa, neden diğer işleri yapmaya devam etmeyelim? Gerçekten tüm gün boyunca kullanıcının bir tuşa basmasını mı bekleyeceğiz?
  • Bir process’in başlangıcı ve bitişi arasında geçen süreye “turnaround time” denir ve scheduling bu süreyi minimumda tutmayı amaçlar. Aynı anda başlattığımız 2 işimiz var. Biri 10 saniyelik, diğeri 10 saatlik olsun. Bu işlerin birbirinden bağımsız olduğunu kabul edersek, 10 saniyelik işi erkene almak daha mantıklı olmaz mı? Diğer senaryoda 10 saniyelik işin bitmesi 10 saat + 10 saniye sürecektir. Markettesiniz, önünüzdeki müşteri arabayı doldurmuş. Siz de kola alıp çıkacaksınız. Let the games begin.
  • Bekleme sürelerini azaltmak. Process’lerin state’lerinden bahsetmiştik. Scheduling, bir process’i mümkün mertebe az süre bekletmek ister. Aksi durum devlet dairesi.
  • Tepki süresini (response time) mümkün mertebe kısa tutmak. Yukarıdaki “turnaround time” örneğinde, process’in işini bitirmesi için diske veri yazması gereken bir senaryo düşünün. Disk yavaş, CPU’nun suçu ne? Bu nedenle farklı bir kriter hayatımıza giriyor,”response time“. Process’in işimize yanıt verme süresi. Tamamlamasını geciktiren farklı etmenler olabilir, ancak işimizi yapmaya başladığını bilmek istiyoruz. Scheduling, response time’ı minimumda tutmayı amaçlar. Hastanede kayıt masasına geldiniz. Kimlik numaranızı verdiniz. Görevli bu bilgiyi girdi. “Bugün sistemler biraz yavaş da..” dedi. Olsun. Response verdi bize, sorun yok. Sistemin çalışmasını bekliyoruz şu an. Görevli, bizim işimizi aksatmıyor. Elinden geleni yaptı. Bekliyoruz. CPU gözünden düşünürsek de, I/O tarafı yavaş kaldı, sorun bizlik değil.

Scheduling Algoritmaları

Scheduling Kategorileri

Farklı amaçlar için kurulan farklı sistemlerin birbirinden farklı çalışması gayet doğaldır. Aynı durum, CPU scheduling için de geçerlidir. Farklı sistemlerde, farklı algoritmalar kullanılabilir. Kullanılan bu algoritmaları 3 temel gruba ayırabiliriz.

Batch

Her ne kadar eski gözükse de, batch sistemler günümüzde de bazı iş kollarında kullanılmaktadır. Bu sistemlerde, yapılması gereken bir ya da birkaç iş vardır ve bu işlerin tamamlanması gerekmektedir. Makine, bu işler için dedike çalışmaktadır. Bu tarz sistemlerin başında “Eee hadi ilgilen benimle.” diyen kullanıcılar beklemez. 🙂

Bu sistemlerde process’ler arası context switching bir hayli azalacağından performans artışı gözlemlenir. Batch sistemlerde kullanılan scheduling algoritmaları, gayet tabii farklı sistemlerde de kullanılabilir.

Interactive

Büyük çoğunluğumuzun kullandığı sistemler, interactive sistemler. Bu sistemlerde kullanıcılar ve kullanıcıların işleri var. Process’ler bir yanıt dönüyor, kullanıcılar bu yanıta göre gerekli adımları atıyor. Karşılıklı bir etkileşim söz konusu. Web tarayıcınızı açtınız. Bunun için bir etkileşim şart. Sistem size yanıt verdi. Açtı. Şimdi ne yapması gerekiyor? Tekrar kapatabilirsiniz, belirli bir web sayfasına gitmek isteyebilirsiniz, gizli moda geçebilirsiniz, tarayıcı geçmişinize bakabilirsiniz vs.

Böyle sistemlerde preemptive algoritmalar kaçınılmazdır. Benzer durum sunucular için de geçerlidir. Sunucular, birim zamanda mümkün mertebe fazla kullanıcının işini yapmak ister. Şu an dünya üzerinde popüler bir web sitesini ziyaret eden bütün insanları düşünün. Bu web sitesine ait sunucular, bu kullanıcıların her birine hizmet veriyor. Preemptive bir yapıda çalışmasalar, bir process bitene kadar diğer tüm ziyaretçilerin beklemesi gerekirdi.

Real Time

Real time (gerçek zamanlı) sistemlerde, yapılması gereken işin anında ve acilen yapılması gerekir. Bu tarz sistemler genelde birtakım olayları kontrol eder ve/veya bu olaylara karşı aksiyon alır.

Örneğin, bir uçağın irtifa takibini yapan process’i düşünün. Ya da kabin basıncı, motor arızası kontrolü gibi sistemlerini. Düşünsenize, sağ motoru kaybetmişiz ama o sırada işletim sisteminde başka bir process çalışmakta olduğu için haberi biraz geç gelmiş. Farkedince gerekli uyarı verip önlem alması gereken process de çalışamamış çünkü o sırada kaptanın anons process’i çalışıyormuş. Kabul edilebilir bir durum değil. Otonom araçlar, otopilotlar, üretim robotları gibi sistemleri düşünün. Bu tarz sistemlerde çalışan process’ler preemptive algoritmalara bile gerek duymayabilir. Çünkü kendileri, zaten “işini yap ve çık” mantığıyla hazırlanmış yazılımlardır. Takdir edersiniz ki bu tarz sistemlerdeki CPU scheduling de epey farklı olacaktır.

First-Come, First-Served Scheduling

En basit algoritmalardan biridir. First in first out (FIFO) olarak isimlendirildiğini de görebilirsiniz. FCFS algoritması, Ankara’nın EGO’su gibidir: Erken Gelen Oturur.

Process’ler FIFO kuyruğuna girer. Bir process çalışırken, diğerleri bekler. Bu process’in işi bitince, sıradaki process çalıştırılır. Olur da bu process, I/O bekleme gibi birtakım sebeplerden dolayı blocked durumuna düşerse, kuyruğun en sonuna atılır.

Klasik devlet hastanesi aslında (öncelikli hastaların olmadığı senaryoyu düşünelim tabii ki). Erken geldim, sıra aldım. Önce ben geldim. İlk ben muayene olacağım. Doktor beni tahlile gönderdi. İşim bitmedi aslında, ama blocked oldum. Sonraki hastanın muayenesi başladı. Ben ne oldum? Kuyruğun en arkasına atıldım. Sonuç bekliyorum. Bu process’lerin bazıları ilacını yazdırıp gidiyor. Bazıları sonuç sırasına giriyor. FIFO’nun en sonuna gönderilen process’ler tekrar aynı sıraya giriyor.

FCFS nonpreemptive bir algoritmadır. Process “Kolay gelsin hocam.” diyerek ayrılmadan ya da tahlile, görüntülemeye gönderilmeden CPU’yu bırakmaz. İmplementasyonu kolaydır. Fakat bekleme süreleri konusunda sıkıntılı senaryoları olabilir. Hastane örneğinden biraz kopacağız belki ama, muayene ve ameliyatların aynı yerde aynı kişi tarafından yapıldığını düşünelim. Bir hasta sadece reçetesini imzalatmak istiyor olabilir. Başka bir hasta sadece “Tahlillerin temiz, problem yok.” sözünü duymaya gelmiş olabilir. Diğer bir hasta, raporlu ilacını tekrar yazdırmak için gelmiştir. Bu işler kolaylıkla halledilebilecekken 18 saat sürecen bir kalp ameliyatını beklediklerini düşünün. Yapacak bir şey yok. FIFO kullanıyorsak, iş bitene ya da blocked olana kadar bekleyecekler.

Shortest-Job-First Scheduling

En kısa işin en önce yapıldığı algoritmadır. SJF algoritmasının verimli olabilmesi için bazı ön koşullar vardır. Bunlardan biri, sürekli aynı işleri yapan bir sistem olması. Çalışacak process’ler bellidir, process’lerin CPU yükü bellidir. Bu algoritma kullanılabilir. Fakat çoğu sistemde, kuyruğa giren process’in ne kadar çalışacağını önceden bilemeyiz. Bu durumda şöyle bir yaklaşım sergilenebilir. Process’lerin birim zamanda CPU burst’leri takip edilir ve bir noktadan sonra “tahmini” olarak hangi process’in daha kısa süreceği tespit edilebilir. İkinci konu ise, bu process’lerin kuyruğa birlikte girmesidir. Çünkü kuyruğa sonradan gelen process, önceden gelmiş ve çalışmaya başlamış process’lere kıyasla daha kısa sürecek olabilir.

Eğer çalışma zamanları aynıysa, ilk gelen process öncelikli çalıştırılır (FCFS). Bu algoritma nonpreemptive yapıdadır.

Shortest Remaining Time Next Scheduling

SJF algoritmasının preemptive implementasyonu diyebiliriz. Bu algoritmada, process’in toplam zamanından ziyade kalan zamanına bakılır. Örneğin, kuyrukta 1 , 5, 9, 11 birim zamanda tamamlanacak dört process olsun. Kuyruğa sonradan gelecek ve 3 birim zamana ihtiyaç duyan beşinci process, kuyruğun sonuna eklenmez, önceliklendirilir.

Tabii ki bu algoritmanın implementasyonunda da process’in ne kadar CPU burst yapacağının bilinmesi ya da en azından tahmin edilebilmesi gerekmektedir.

Round-Robin Scheduling

En sık kullanılan algoritmalardandır. Ayrıca oldukça “adil” çalışır. Her process için önceden belirlenmiş bir çalışma süresi, time quantum (time slice) vardır. Eğer bir process, quantum’un sonunda çalışmaya devam etmek istiyorsa, CPU’dan geri çağırılır. Eğer quantum dolmadan işi bittiyse ya da blocked duruma düştüyse de yine CPU’dan geri çağırılır ve diğer process’lerden biri çalıştırılır. Tahmin edeceğiniz üzere preemptive bir algoritmadır.

Quantum süresi, genelde 10 – 100 milisaniye arasındadır. Process kuyruğu FIFO’ya benzer fakat düz bir hat değil, dairesel gibi düşünebilirsiniz. Her process sırayla CPU’ya gönderiliyor. Zamanı dolunca ya da işi bitince bir sonraki process çalıştırılıyor. Daire tamamlanıyor.

RR algoritmasında bekleme süreleri genelde uzundur. Bütün işler birlikte ilerletilmeye çalışılır. Ek olarak, ayrı bir kararın da verilmesi lazım: time quantum. Ne kadar olsun? Her process 1 milisaniye çalışsın, böylece hiçbirinin durduğunu fark edemeyiz diyelim. Peki bu değişim için gerekli context switching ve dispatch süreleri ne olacak? Her milisaniyenin sonunda PCB güncelle, PCB’den veri oku, CPU’ya iş getir… Context switching burada ciddi bir iş yükü ve zaman kaybı oluşturur. O zaman her process 1000 ms çalışsın diyelim. Diyemeyiz, bu sefer de – işi olan – her process CPU’yu koca bir saniye boyunca meşgul eder. O sırada bilgisayarımızı kullanamadığımızı rahatlıkla fark edebiliriz. Bu durum da FCFS’ye yakın bir görüntü oluşturur.

Priority Scheduling

RR algoritmasına göre her process aynı önceliğe sahiptir ve hepsi aynı süre çalışabilir. Fakat kullanıcıların düşüncesi daha farklı olabilir. Örneğin, arkada bir download yaparken film izlediğimizi düşünelim. İndirme işinin daha öncelikli olmasını istemezsiniz. Filminizi kesintisiz ve sorunsuz olarak izlemek istersiniz. İndirme arkaplanda olan ve zaten bir süre bekleyeceğiniz bir iştir. Bu tarz arkaplan işleri yapılırken bilgisayarınızı kullanmaya devam etmek istersiniz.

Farklı bir sistem düşünelim. Örneğin müzik yayını yapan bir sistem. Ücretli ve ücretsiz kullanıcıları olsun. Bu sistemde ücretli kullanıcılara ait işlemlerin daha hızlı gerçekleştirilmesinin istenmesi gayet mümkündür.

Priority scheduling’te her process’in bir öncelik numarası vardır. Öncelikler belirli bir sayı aralığında verilir. Örneğin 0 – 255 diyelim. Bazı sistemler öncelik numarası düşükse, öncelik de düşüktür diyebilir. Bazı sistemler de öncelik numarasının düşük olmasını “daha öncelikli” olarak yorumlayabilir. İmplementasyona göre farklılıklar olması normaldir.

Bu algoritmalardaki en büyük problem, belirsiz bloklanma süreleri (indefinite blocking), dolayısıyla CPU’ya duyulan açlıktır (starvation)^5. Yüklü çalışan bir sisteme katılan yeni bir process, önceliği çok düşük olduğu için günlerce bekleyebilir. BİR SÖYLENTİYE GÖRE, 1973 yılında MIT’deki IBM 7094 makinesi kapatıldığında, sisteme 1967 yılında eklenmiş düşük öncelikli bir process’in 6 yıldır hâlâ sırasını beklediği görülmüş^6. Zamanında yaptığım iş başvuruları gibi yani.

Bu sorunun çözümü olarak, Türk televizyonlarının vaz geçilmesi olan “yaşlandırma yönteminden” (aging)^7 faydalanılıyor. Process, kuyrukta bekledikçe önceliği kademeli olarak arttırılıyor. Bu sayede process’in yaşayacağı starvation süresi azaltılıyor.

Bazı sistemler RR algoritması ile priority algoritmasını birlikte kullanabilir. Şöyle ki, benzer önceliğe sahip process’ler gruplandırılır. Sonrasında Round-Robin devreye girer ve yüksek öncelikli process’leri gezerek tamamlamaya çalışır. Eğer yüksek öncelik grubunda bir process kalmadıysa, bir alt önceliğe geçilir ve oradaki process’ler çalıştırılır.

Multilevel Queue Scheduling

Process’lerin rahatlıkla gruplanabildiği sistemlerde kullanılmak için tasarlanmıştır. Etkileşim gerektiren process’ler, arkaplanda çalışması gereken process’ler, sistem için kritik düzeydeki process’ler gibi gruplar oluşturulur ve bu gruplar farklı process kuyrukları olarak işlenir.

Bu kuyrukların her biri için farklı scheduling algoritmaları kullanılabilir. Yüksek öncelik grubunda olan process’ler için Round-Robin tercih edilirken, düşük öncelikteki arkaplan işleri için FCFS görmemiz mümkündür. Bu yapıda, CPU zamanının büyük çoğunluğu RR ile çalışacak yüksek öncelikli gruplara atanabilirken, bir kısmı da FCFS ile çalışacak arkaplan işlerine ayrılabilir.

Yine MIT’de, IBM7094 üzerinde koşan Compatible TimeSharing System (CTSS) tasarımcıları bir sorunla karşılaşır. Sistem, belleğinde sadece bir process tutabildiği için process değişimi oldukça yavaştır. Yani her process değişiminde bellekteki process verileri diske yazılıyor ve sonraki process’in verileri diskten belleğe getiriliyordu. Bu nedenle time quantum’u arttırmayı düşündüler. Fakat bu da – yukarıda açıklamaya çalıştığım gibi – tepki süresini uzatabiliyordu. Bu nedenle çok katmanlı bir kuyruk oluşturdular ve öncelikleri kategorize ettiler. Yüksek önceliğe sahip process’ler daha sık context switching’e maruz kalırken, düşük önceliklerdeki process’ler daha uzun süreler çalışıyordu. Bu sayede hem yüksek öncelikli process’ler birlikte daha hızlı çalışıyor hem de düşük öncelikli process’ler için context switching daha az yapılıyor ve zaman kaybı azalıyordu.

Bir gün bir kullanıcı, kendi process’i çalışırken terminalinden “Enter” tuşuna basar. Sistem, bu process’in interactive bir process’e dönüşeceğini düşündüğü için öncelik grubunu yükseltir. Yüksek CPU kullanımına ihtiyaç duyan bu kullanıcı, birkaç saniyede bir enter tuşuna basarak sistemi yanıltmayı başarır ve aldığı tepki sürelerinde inanılmaz bir iyileşme görür. Sonra bu bilgi arkadaşlar arasında yayılır. 🙂

Multilevel Feedback Qeueue Scheduling

Multilevel Queue Scheduling’te, process’ler sisteme katıldığında bir kuyruğa dahil edilir ve orada kalırlar. Örneğin bir arkaplan process’i, arkaplan process’i olarak çalışmaya devam eder. Doğası budur.

Multilevel Feedback Queue Scheduling’te ise, process’lerin kuyruklar arasında geçişine izin verilir. Burada amaç, process’in karakterinden ziyade sistemi meşgul etme süresine göre değerlendirilmesidir. Yüksek önceliğe sahip bir process CPU zamanınını çok kullanıyorsa, önceliği düşürülür. Ek olarak, düşük öncelik grubunda uzun süre bekleyen process’ler de yüksek öncelik gruplarına taşınabilir.

Yüksek öncelikli process’ler, düşük time quantum değerine sahip kuyruklarda işlenirken, öncelik grubu düştükçe kullanılan algoritma FCFS bile olabilir. Bu yapı, gayet kullanışlı görünse de implementasyonu nispeten daha zordur.

Guaranteed Scheduling

Eğer n kullanıcı varsa, senin işlemlerin 1/n kadar CPU kullanır. Eğer p process’in varsa, CPU’nun 1/p zamanını alırsın. Herkese, her şeye aynı süre.

Bu sistemin doğru çalışabilmesi için, her process’in kullandığı CPU zamanları takip edilmelidir. Process’in gerçekten kullandığı ve aslında kullanması CPU zamanı oranlanır. Örneğin, bir process’in 10 birim çalışması lazımdı fakat 5 çalışmış. 5/10 = 0.5 oranı elde edilir. Farklı bir process’in ise 10 birim çalışması gerekirken 40 birim çalıştığını düşünelim. 40/10 = 4 oranı elde edilir. Oranı 0.5 olan process’e öncelik verilir ve denge sağlanmaya çalışılır.

Lottery Scheduling

Oldukça basit görünüyor aslında. Bütün process’lere sistem kaynakları için piyango bileti dağıtılıyor. Çekiliş yapılıyor ve kazanan process’ler ödül olarak kaynağı kullanıyor.

Burada da yine öncelikli process’lerin şansı var. Örneğin her process 10 bilet alabiliyorken, öncelikli process’lere 20 bilet veriliyor. Kazanma şansları kasten arttırılıyor. Dolayısıyla, biletlerin %n oranına sahip process, uzun vadede, kaynakların da %n’ine sahip oluyor.

Bu scheduling yöntemini ilginç kılan noktalardan biri, sisteme henüz katılmış bir process’in de piyangoyu kazanma ihtimalinin olması. Bu da scheduling’in, yeni katılan process’lere daha hızlı tepki verebilmesini sağlıyor. Birbiriyle ilintili işler yapan process’ler, kendi işlerini tamamladıklarında ilişkili oldukları process’e bütün biletlerini devredebilir ve o process’in kazanma şansını arttırabilir.

Bu yöntem, diğer yöntemlerle çözülmesi güç konularda kullanılabilir. Örneğin bir video server’ımız var ve farklı FPS oranlarında yayın dağıtan process’lere sahip. Process’lerin 10, 15, 25 FPS yayın yaptıklarını düşünelim. Bu durumda biletleri 10, 15, 25 oranında dağıtırız ve böylece kaynakları da bu oranda – iş yüküne göre – dağıtmış oluruz.

Fair-Share Scheduling

Bu zamana kadarki algoritmalarda, process’i kimin başlattığı konusunu düşünmedik. Çok kullanıcılı bir sistemde bir kullanıcının 400, diğer kullanıcının 100 process’i olduğunu düşünelim. RR gibi bir algoritma, tüm process’leri geziyordu hatırlarsanız. Bu durumda bir kullanıcının bir turda 400 parça işi yapılıyorken, diğer kullanıcının 100 parça işi yapılır.

Bu durumu engellemek için, CPU zamanı process’ler arasında değil, kaynaklar arasında dağıtılır. Yukarıdaki örnek için konuşursak, birinci kullanıcının birinci process’i çalıştıktan sonra, ikinci kullanıcının birinci process’i çalıştırılır (kaynakların eşit şekilde dağıtıldığını varsayarsak). Birinci kullanıcının 100. process’i sonrasında, ikinci kullanıcının 100. process’i çalıştırılır. Birinci kullanıcının 101’inden sonra ise ikinci kullanıcının 1’i, çalışmaya kaldığı yerden devam edebilir.

Real-Time Sistemlerde Scheduling

Burada da scheduling iki farklı kategoriye ayrılır. Hard real time, deadline’ın mutlaka yakalanmasının gerektiği, aksi takdirde çok ciddi sonuçlar doğurabilecek durumlardır. Soft real time ise, her ne kadar istenmese de deadline gecikmesinin kabul edilebileceği durumlardır.

Burada işler oldukça değişiyor. Process’in hard ya da soft kategoride olması, ihtiyaç duyduğu ya da ihtiyaç duyması muhtemel CPU zamanı gibi birçok etmen işin içine giriyor ve bu değerler formülize ediliyor.

Detaylı bilgi edinmek isteyenler şu iki makaleyi inceleyebilirler:

Sonuç Olarak

Process’lerin oluşturulması, çalıştırılması ve durdurulması – gördüğünüz gibi – oldukça detaylı bir süreç. Tabii ki işler burada anlatılanlarla kalmıyor. Burada anlattığım konu başlıkları, oldukça yüzeysel. İşin implementasyon tarafına geçildiğinde çok daha farklı ve beklenmedik sonuçlar çıkabilir, MIT örneğinde olduğu gibi.

Bir dahaki sefere “Bu bilgisayar niye yavaşladı ya kasıp duruyor!!!” derken, biraz da işletim sistemine hak verin derim. 🙂

Bağlantılar

1 , 2- Chapter 4. Threads and Processes, https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux_for_real_time/7/html/reference_guide/chap-threads_and_processes

3 – Context switch, https://en.wikipedia.org/wiki/Context_switch

4 – User mode and kernel mode, https://docs.microsoft.com/en-us/windows-hardware/drivers/gettingstarted/user-mode-and-kernel-mode

5 – Starvation (computer science), https://en.wikipedia.org/wiki/Starvation_(computer_science)

6, 7 – Starvation and Aging in Operating Systems, https://www.geeksforgeeks.org/starvation-and-aging-in-operating-systems/

Referans

1 – Operating System Concepts Essentials, Abraham SILBERSCHATZ (Yale University), Peter Baer GALVIN (Corporate Technologies, Inc.), Greg GAGNE (Westminster College)

2 – Modern Operating Systems, Andrew S. TANENBAUM, Herbert BOS (Vrije Universiteit)

İşletim Sistemi 101 – #4 (Process)

Program vs. Process

Görev Yöneticisi’ni açtığımızda ya da “ps”, “top” gibi uygulamaları çalıştırdığımızda karşımıza çıkan liste, bilgisayarımızda o an çalışmakta olan “şeylerin” bir kısmını bize gösterir. Bu “şeylerin” bazılarının isimleri bize tanıdık gelir.

Windows işletim sisteminde çalışan bazı process'ler.
Windows işletim sisteminde çalışan bazı process’ler.

İsimler gayet bilindik. Bunlar, bilgisayarımızdaki programlar. Ne olduğuna dair en ufak fikrimiz olmayan şeyler de görebiliriz bu listede. Onlar da bilgisayarımızdaki programlar. Değil mi? Değil. Herhangi bir program, çalıştırılma süreçlerine girdiyse artık bir “process” olmuştur.

ProgramProcess
Komut setiÇalıştırılan komut seti
Pasif durumdaAktif durumda
Diskten silinene kadar dururKapatılınca ölür
Diskte yer tutarCPU, RAM, disk, I/O aygıtları gibi kaynakları kullanır
Program ve Process karşılaştırması

Giriş yazımda, eski sistemlerin sadece bir program çalıştırabildiğini (job) belirtmiştim. Günümüzdeki sistemler ise Aynı anda birden fazla kullanıcıya hizmet edebilmekte ve bu kullanıcıların işlerini (task) ve hatta iş parçalarını (thread) çalıştırabilmektedir.

Process Nedir?

Yukarıda belirttiğim gibi, bir program çalıştırıldığında “process” olur. Process’ler çalışmaya başladığında beraberinde birçok kavramı da getirir. “text segment”, “data segment”, “heap”, “stack”. Bu kavramlara değineceğiz.

Bir programın çalışabilmesi için, tamamının ya da bir kısmının RAM’e getirilmesi gerekmektedir. RAM’de tutulan process verileri için aşağıdaki görseli inceleyebilirsiniz:

RAM’de process görüntüsü (Operating System Concepts Essentials kitabından alınmıştır.)

Şimdi bu arkadaşları tek tek ele alalım.

Text Segment

Code segment^1 ya da text segment, bir programın çalıştırılabilir kodlarıdır (obje kodları). Aşağıda örneklerde göreceğiniz kodlar, text segment’te değildir! O kodlar, derleyiciye girip derlendikten sonra çalıştırılabilir kodlara dönüştürülür. Bir paket hâline getirilir ve “program”a dönüşür. Programı çalıştırdığınız zaman derlenmiş kodlar RAM’e yüklenir. İşte o kodlar text segment’i oluşturur.

Data Segment

Herhangi bir fonksiyon içinde tanımlanmamış, global olarak her yerden erişilebilen değişkenler data segment‘te^2 yer alır. Aşağıdaki örnek kodda; “data_segment1”, “data_segment2” ve “ben_de_datayim” değişkenleri, data segment’e örnektir. Önemli not: “data_segment1” örneğinde olduğu gibi, herhangi bir ilk değer verilmemiş değişkenlerin “uninitialized data segment“te (diğer adıyla BSS) olduğunu belirten kaynaklar da mevcuttur.

#include <stdio.h>
#include <stdlib.h>

//Global tanımlı değişkenler, data segment'te yer alır:

int data_segment1;
int data_segment2 = 42;
char ben_de_datayim[] = "ali";

int main(void)
{
    //Data segment'te yer alan veriler:
    printf("Data segment üyeleri:\n");
    printf("%d\n", data_segment1);
    printf("%d\n", data_segment2);
    printf("%s\n", ben_de_datayim);
    return 0;
}

Stack

Fonksiyonlar içinde oluşturulan ve belirli bir kapsam içinde kalan, fonksiyonun çalışması bitince ise silinen değişkenlerimiz “stack^3 içerisinde tutulur.

Aşağıdaki örnekte, parametre olarak verilen bir değişkenin karesini hesaplayıp geri döndüren bir kod göreceksiniz. Burada “karesiniAl” fonksiyonu içindeki “kare” değişkeni, o fonksiyon çağırılana kadar bellekte herhangi bir yer işgal etmemektedir. Ne zaman ki “karesiniAl” fonksiyonu çağırılır, o zaman “kare” değişkeni de bellekte kendisine yer aramaya başlar.

Tipik bir FPS oyununda “sağlık” fonksiyonunu düşünelim. Bir mermi isabet ettiğinde, isabet eden yere göre canınız azalır. Bu durumda “vurulma” fonksiyonu içerisindeki değerler, siz vurulana kadar oluşturulmayacak. Ne zaman vurulursanız, vurulduğunuz yere göre, “hasar” değişkeni bir değer alacak. Yeni can değeriniz hesaplanacak, geri döndürülecek ve stack’ten tahsis edilen yer boşaltılacak. Benzer şekilde farklı bir oyunda, takımınızdaki “healer” canınızı yükseltebilir. Sonrasında item’larını ya da skill’lerini geliştirip tekrar geldiğinde, canınızı yükseltme oranı değişkenlik gösterecektir. Bu yeni değişkenler “heal” fonksiyonunda oluşturulabilir “healing” işi bittikten sonra stack’ten silinebilir. Tabii ki bunlar sadece örnek. Kodu yazan kişi, bambaşka bir tasarım yapmış olabilir.

#include <stdio.h>
#include <stdlib.h>

//Global tanımlı değişkenler, data segment'te yer alır:

int data_segment1;
int data_segment2 = 42;
char ben_de_datayim[] = "ali";

int main(void)
{
    //Data segment'te yer alan veriler:
    printf("Data segment üyeleri:\n");
    printf("%d\n", data_segment1);
    printf("%d\n", data_segment2);
    printf("%s\n", ben_de_datayim);

    //karesiniAl fonksiyonu çalıştırıldığı anda, "kare" adında yeni bir değişken ortaya çıkıyor.
    //Bu değişken, karesiniAl fonksiyonunun işi bittikten sonra kaybolacak.
    //Fonksiyonlar içindeki local değişkenler stack'te tutulur:
    //Stack'te yer alan veriler:
    printf("%d sayısının karesi: %d\n", data_segment2, karesiniAl(data_segment2));


    return 0;
}

int karesiniAl (int sayi) {
  int kare = sayi * sayi;
  return kare;
}

Stack Buffer Overflow

Arkadaşımız geldi, hoş geldin diyelim 🙂

Bir process, kendisine ayrılan stack alanına sığmayacak miktarda veriyi stack’e yazmaya çalışırsa, bir noktadan sonra kendisine tahsis edilen bellek alanı dolar ve stack buffer overflow ya da stack overflow dediğimiz olay yaşanır.^4

Örnek:

Aşağıdaki kod, komut satırından alacağı argümanı bir karakter dizisinin içine atmayı deniyor. Fakat oluşturulacak dizinin boyutu önceden belirlenmiş olmasına rağmen gelen parametrenin uzunluğu kontrol edilmiyor. Önce kodu, sonrasında çıktıyı paylaşıyorum:

#include <stdio.h>
#include <string.h>

void stackoverflow (char *metin)
{
   //12 karakter uzunluğunda bir metin oluşturuyoruz:
   char c[12];

   //Fonksiyona parametre olarak verilen veriyi, c içerisine kopyalamayı deniyoruz.
   //Fakat parametrenin uzunluğunu kontrol etmedik. Ya sığmazsa?

   strcpy(c, metin);  //Burada patlayacağız.
}

int main(int argc, char **argv)
{
   //stackoverflow metodunu, komut satırından gelen ilk argümanı göndererek çağırıyoruz.
   stackoverflow(argv[1]);
   return 0;
}
[root@localhost ~]# vi stack.c
[root@localhost ~]# gcc stack.c -o stack
[root@localhost ~]# ./stack ali
[root@localhost ~]# ./stack ABCDEFGHIJKLMNOP
Parçalama arızası

Önemli bir nokta. Sırf bu hatadan dolayı çok ciddi siber saldırılara maruz kalabilirsiniz. Örneğin Internet Download Manager yazılımında yer alan bir açığı incelemek isterseniz: Internet Download Manager 6.37.11.1 – Stack Buffer Overflow (PoC)

Heap

Process’in çalışırken dinamik olarak talep ettiği bellek alanları, heap^5 üzerinde tutulur. Büyük bir array ya da struct tutmak istediğimizde; bunu stack üzerinde oluşturmak yerine heap üzerinden kendimiz tanımlamayı ve işimiz bittiğinde bu alanı boşa çıkarmayı isteyebiliriz. Bu yaklaşım, hem bellek yönetimini daha etkili hâle getirebilir hem de stack overflow ihtimalimizi düşürebilir. Ayrıca, oluşturulacak değişkenleri uzun süre saklamak istiyorsak ve boyutlarının değişkenlik göstereceğini düşünüyorsak yine heap kullanırız.

Fakat ömrü kısa olacak, fonksiyon bitince ilişiğimizin kesileceği, nispeten küçük boyutlu değişkenler için stack tercih ederiz.

Bir Process’in Yaşam Döngüsü

Process’ler başlangıcından bitişine kadar farklı durumlar yaşayabilir. Bu durumların her birine “process state” diyoruz. Bir process’in state’i, işletim sistemi için oldukça önemlidir. State’ler, process’lerin yönetimi için elzem bir bilgidir.

Bazı sistemlerde, tüm process’ler cihaz boot edildiğinde otomatik olarak çalıştırılabilir. Bu tip sistemler; genelde az sayıda ve spesifik işler yapan sistemlerdir. Örnek olarak bir kahve makinesini ya da mikrodalga fırını verebiliriz. Kişisel bilgisayarlarımız, sunucular, oyun konsoları gibi cihazlarda ise durum daha farklıdır. Process’lerin bazıları, sistem boot edildiğinde çalışmaya başlar. Fakat kullanıcının taleplerine göre yeni process’ler oluşturulabilir. Bilgisayarınızı açtıktan sonra bir web sayfasını ziyaret etmek için Firefox yazılımını çalıştırmanız gibi.

Process’ler çalıştıkça, state’leri değişebilir. Bu state’lerin değişimi ve takibi, işletim sisteminin sorumluluğundadır. Aşağıda bu state’leri kısaca açıklamaya çalıştım:

  • New: Process ilk oluşturulduğunda sahip olduğu state’tir. Bazı kaynaklarda “created” olarak da geçer.
    • Bir process’in oluşturulması; verilerin diskten okunması, RAM’e yazılması (text, data, stack, heap atanması), process ID tanımlanması, Process Control Block’ta (PCB) ilgili kayıtların açılması gibi adımları içerir.
  • Ready: Process’in oluşturulma sürecinin tamamlanması, beklediği etkileşimin gerçekleşmesi gibi olaylardan sonra; çalışmaya hazır şekilde beklediği state’tir. Bazı kaynaklar “waiting” ile aynı tutsa da, bazıları waiting state’i ayrı bir başlık olarak inceler.
    • Bir process, çalışmaya devam ederken işletim sistemi tarafından beklemeye alınabilir. Zaten böyle olmasaydı, aynı anda sadece bir process çalışır ve o process kapatılana kadar başka hiçbir iş yapamazdık (Çok çekirdekli, çok CPU’lu sistemleri ayrı tutuyorum.). Dolayısıyla ready state’i, process’in yeni olduğunu garanti etmez. 5 aydır çalışmakta olan bir process’de ready state görmeye devam edebilir.
  • Running: Process, işletim sistemi tarafından CPU çekirdeğine getirilmiştir ve çalışmaktadır. Bir CPU çekirdeğinde yalnızca bir process çalışabilir. Bu çalışma süreci, milisaniye hatta nanosaniye düzeyinde olabilir. Müzik dinlediğiniz process, saniye’nin – örneğin! – 300’de 1’i kadar bir oranda çalışıp sonrasında duruyor, hemen ardından başka bir process çalışıyor, sonra bir başkası, belki yüzlercesi. Sonra yine sizin müzik çalıyor. CPU’ların saniyede milyarlarca işlem yaptığını düşününce, bu değişimleri fark etmememiz gayet normal. Fakat olay apayrı bir boyut değil mi sizce de?
  • Waiting: Process, herhangi bir I/O ya da etkileşim bekliyordur. Örneğin, bir metin editörü açtınız. Öylece bekliyorsunuz. Bu process’in iş yapabilmesi için kullanıcının klavyesinde bir şeylere basması gerekir ve sistemin bizi bekleyecek hâli yok. Bu process’i waiting state’e alır. Siz bir tuşa bastığınızda interrupt request^6 gönderirsiniz. Ya da process’in diskten bir veri okuması gerekiyordur. CPU hızı, HDD’ye göre yüzlerce kat fazladır. Dolayısıyla, process arkada sakin sakin bekletilir. İşi bitince tekrar ready state’e geçirilir. Zamanı gelince de çalıştırılır. Bu durum, bazı kaynaklarda “blocked” state olarak da geçer.
  • Terminated: Process’in çalıştırılması tamamlanmıştır. Bu state’e geçen process’lerle ilgili temizlik süreçleri başlatılır.

Process Ağacı

Process’ler arasında parent – child (ebeveyn – çocuk) ilişkisi vardır. Bir process, diğer process’leri doğurabilir. Örneğin, aşağıdaki örnekte “bash” process’inin “ps” process’ini doğurduğunu görüyoruz. Bash, bu işletim sisteminde kullandığım kabuk yazılımı. Makine boot edildikten sonra kendiliğinden çalışan process’lerden biri. ps ise, sistemde çalışan process’leri görüntülemeye yarayan bir araç. Aslında gayet mantıklı, ps çalıştırmak için bash’i kullandım. ps, bash’in child process’i oldu.

[root@localhost ~]# ps --forest
PID TTY TIME CMD
1240 pts/0 00:00:00 bash
1260 pts/0 00:00:00 \_ ps

Daha detaylı bir çıktıyı inceleyelim (bu çıktıda birçok satırı sildim):

[root@localhost ~]# ps aux --forest
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 2 0.0 0.0 0 0 ? S 23:07 0:00 [kthreadd]
root 4 0.0 0.0 0 0 ? S< 23:07 0:00 \_ [kworker/0:0H]
root 5 0.0 0.0 0 0 ? S 23:07 0:00 \_ [kworker/u2:0]
root 989 0.0 0.4 112924 4320 ? Ss 23:07 0:00 /usr/sbin/sshd -D
root 1236 0.0 0.5 158916 6060 ? Ss 23:10 0:00 \_ sshd: root@pts/0
root 1240 0.0 0.2 115544 2064 pts/0 Ss 23:10 0:00 \_ -bash
root 1261 0.0 0.1 155608 1948 pts/0 R+ 23:16 0:00 \_ ps aux --forest

Process ID’si (PID) 2 olan bir process görüyoruz. Kernel Thread Daemon (khreadd). Sistemde çalışan ilk process’lerden biri. Kendine bağlı bazı “worker“lar açmış. Bu process’ler, yeni açılacak process’leri çalıştırmakla yükümlü olan process’ler. Bir de Secure Shell Daemon (sshd) process’ini görüyoruz. “root” kullanıcısı ile bir SSH oturumu başlatılmış. O SSH oturumunda “bash” process’i çalıştırılmış. Bash’in altında ise “ps” çalıştırılmış.

Kısa bir bilgi: Eğer bir process’i doğuran parent process beklenmedik bir şekilde kapanırsa, child process’lerine kapanma sinyalini gönderemez. Bu durumda child process’ler, öksüz kalır (orphan process)^7. Çalışması tamamlanmış olmasına rağmen parent process’inden gerekli sinyalleri alamayan child process’ler ise, zombiye dönüşür (zombie process)^8. İşletim sistemi, bu durumlarda kalan process’i de yönetmek zorundadır.

Process Control Block (PCB)

Hadi düşünelim. Bir işletim sistemisiniz. Saniyede yüzlerce process çalıştırmak zorundasınız. Bu process’lerle ilgili neler bilmek, işinizi kolaylaştırırdı?

  • Process’lere özel bir isim vermek, process’leri birbirinden ayırma konusunda epey fayda sağlardı. Kimlik numaranız gibi düşünebilirsiniz. Buna Process ID (PID) deriz.
  • Her process’in state’ini tutmalıyız. Kim hazır, kim çalışıyor, kimler bekliyor, kimlerin işi tamamlandı? Bu konuyu da zaten bir önceki bölümde detaylandırdık.
  • Process çalışıyordu. Sonra duraklattık. Şimdi sıra tekrar ilgili process’e geldi. Eee? Baştan mı çalıştıracağız? Kaldığı yerden devam ettirmeliyiz değil mi? “Kaldığı yer”, program counter (PC) ya da instruction pointer (IP) olarak ifade edilir.
  • Process’in bilgileri (mesela kodları) diskten talep edildi. Disk buffer’ına geldi. Oradan RAM’e yazıldı. RAM’den işlemciye getirildi ve işlemcinin önbelleğine (cache memory) yazıldı. Son olarak, işlemci hızına en yakın bellek olan CPU register’ına getirildi. Çalıştırıldı. Derken süre doldu. Sıra tekrar bu process’e geldiğinde hangi verileri kullandığımızı hatırlamazsak, bütün bu süreç başa sarar. Dolayısıyla PCB üzerinde CPU register verilerini de saklamalıyız. Böylelikle bir sonraki turda, bu verileri doğrudan RAM’den ya da cache memory’den getirebiliriz. Disk’in yavaşlığı ile uğraşmak zorunda kalmayız. CPU seçiminde cache bellek önemini fark ediyor muyuz?
  • Peki bu process’in stack, heap gibi sınırları neresiydi? RAM’de hangi aralığı bu process’e ayırmıştık? Ne kadarlık alan ayırmıştık? Bu bilgileri de tutmamız gerekiyor. Şu soruyu sorabilirsiniz, “Process’in verileri zaten RAM’de duruyor. Neden aklımızda tutmak zorunda kalalım ki? Zaten belirli bir yerde duruyor.”. Peki ama o yer neresi? RAM üzerinde hangi adres aralığının hangi process’e ait olduğunu bilmek zorundayız. Ek olarak, işletim sistemleri ihtiyaç hâlinde bazı process’lerin verilerini RAM’den alır ve diske yazar. Yani process bir sonraki turunda kendini RAM’in bambaşka bir yerinde bulabilir. GNU/Linux sistemlerde sırf bu iş için ayrılmış özel disk partition’ları vardır ki buna “swap (takas) alanı” deriz. Hep verdiğim bir örnek. Bilgisayara gelince GB’ler düzeyinde RAM isteyen GTA V oyunu, Xbox 360 konsollarda da çalışıyor. Xbox 360’ın RAM miktarı ise 512 MB. Bu yazılımın, sırf Xbox 360 müşterilerine de satılabilmesi için nasıl kırpıldığını ve optimize edildiğini görebiliyor musunuz? O zaman biraz daha çaba sarf etseler, bilgisayarlarda da 2 – 4 GB RAM’de rahatlıkla çalışacak hâle getirirler değil mi? Getirmezler. RAM üreticileri RAM satacak çünkü 🙂

Yukarıda saydığım örnekler, PCB’de tutulan verilerin sadece bir kısmı. Bu process’i kim çalıştırdı? Önceliği nedir? Ne kadar kaynak tüketti? Ama sanırım olay anlaşılmıştır. Process’leri yönetebilmek için, process’lerle ilgili tuttuğumuz kayıtlar bütününe Process Control Block diyebiliriz. Bazı kaynaklar bu kayıtlara “Process Table” der.

Sonuç Olarak

Bütün bu yazının konusu “çift tıkladım açtım”. Ve daha hiçbir şey olmadı. Sadece çalışan şeyin ne olduğunu ve nasıl tutulduğunu anlamaya çalıştık. CPU’ya nasıl geldi, veriyollarını nasıl kullandı, diskte nasıl bulundu, RAM’e nasıl yazıldı, öncelikler neye göre ve nasıl belirlendi, içinden elektrik akımı geçen bir şey nasıl bu işleri başardı?

Bilgisayarları sevin, Bilgisayar Bilimi’ne emek veren insanları sayın. Umarım faydalı olabilmişimdir.

Bağlantılar

1 – Code segment, https://en.wikipedia.org/wiki/Code_segment

2 – Data segment, https://en.wikipedia.org/wiki/Data_segment

3 – Call stack, https://en.wikipedia.org/wiki/Call_stack

4 – Stack buffer overflow, https://en.wikipedia.org/wiki/Stack_buffer_overflow

5 – Memory management #Dynamic memory management, https://en.wikipedia.org/wiki/Memory_management#HEAP

6 – Interrupt request (PC architecture), https://en.wikipedia.org/wiki/Interrupt_request_%28PC_architecture%29

7 – Orphan process, https://en.wikipedia.org/wiki/Orphan_process

8 – Zombie process, https://en.wikipedia.org/wiki/Zombie_process

Referans

1 – Operating System Concepts Essentials, Abraham SILBERSCHATZ (Yale University), Peter Baer GALVIN (Corporate Technologies, Inc.), Greg GAGNE (Westminster College)

2 – Modern Operating Systems, Andrew S. TANENBAUM, Herbert BOS (Vrije Universiteit)

İşletim Sistemi 101 – #3 (System Call)

Bilgisayarları kullanırken yaptığımız en basit işler bile, arkaplanda oldukça karmaşık süreçleri tetikleyebiliyor. En azından benim için karmaşık.

Çocukken Buca Şirinyer pazarından satın aldığımız, Atari dediğimiz ama aslında Nintendo oyunlarını çalıştıran, merdiven altı oyun konsolları mesela. Televizyona bağlıyorum. UHF kanalları tarıyorum. 99’a kaydediyorum bulduğum oyun yayınını. AV-1 AV-2 falan geziyorum. Joystick’ten bir düğmeye basıyorum, televizyondaki şey hareket ediyor. Büyük olay değil mi?

Peki işletim sistemleri, biz günlük işlerimizi yaparken, donanımlarla nasıl haberleşiyor? Sistem çağrıları (system call) sayesinde.

Uyarı: Sistem çağrılarını anlamlandırmaya çalıştığım kısımlara çok güvenmeyin. Lütfen ilgili sistem çağrısının dokümantasyonunu okuyun ve kesin bilgiye oradan ulaşın. Sistem çağrılarının listelerini “Bağlantılar” kısmına ekledim.

System Call (Syscall) Nedir?

System call, bir bilgisayar programının, üzerinde çalıştığı işletim sisteminin çekirdeğinden hizmet talep etmesidir.^1 Eee, yani?

Şöyle ki. Bir metin editörü açın ve içine bir şeyler yazın. Sonrasında kaydetmeyi deneyin. Çalıştırdığınız program, HDD ya da SSD üzerinden bir yer talep eder. Sonrasında ona atanan alana verileri yazabilir. Ya da web tarayıcınızı açın. Bir web sayfasına girin. Bu iki adımda da işletim sisteminden taleplerde bulunuyorsunuz. Programın çalıştırılması, ağ üzerinden bir verinin çekilmesi gibi. Sonrasında yeni bir sekme açın. Web tarayıcınız sizin için yeni bir process açılmasını talep eder. Çünkü bu iki sekme, birbirinden farklı olacaktır.

Daha da basitleştirelim. Bir dosyaya çift tıkladınız ve açtınız. Bu süreçte kullanılan sistem çağrılarından biri “open” çağrısıdır.^2

Biraz Örnekleyelim

Bilgisayarımda “yazı” adında bir dosya oluşturmak istiyorum. Bu dosyanın içine de “ali” yazmak istiyorum.

GNU/Linux ortamında “bash” kabuğuna yazmam gereken komut şu olacaktır:

echo ali > yazı

Bu komut, çalıştırıldığı dizinde “yazı” isminde bir dosya yoksa oluşturacak ve içine “ali” yazacak. Eğer “yazı” isminde bir dosya varsa, içerdiği her şeyi silecek ve sadece “ali” yazacak. Tabii dosyanın yeni hâli kaydedilecek. Oldukça kolay bir iş gibi görünüyor. İşin system call tarafına bakmak istediğimiz için “strace” isimli bir programdan yardım alacağız.

General Commands Manual
NAME
     strace - trace system calls and signals
DESCRIPTION
     In the simplest case strace runs the specified command until it exits. It intercepts and records the system calls which are called by a process and the signals which are received by a process. The name of each system call, its arguments and its return value are printed on standard error or to the file specified with the -o option.

Şimdi şu komutu çalıştıralım ve bize neler döndürdüğüne bakalım. Çıktıyı daha rahat inceleyebilmemiz için başına satır numaraları ekledim. Çıktının orijinalinde görünmüyorlar:

strace echo ali > yazı
0. execve("/usr/bin/echo", ["echo", "ali"], 0x7fff3b44ea18 /* 24 vars */) = 0
1. brk(NULL) = 0x1c77000
2. mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa2cf6c3000
3. access("/etc/ld.so.preload", R_OK) = -1 ENOENT (Böyle bir dosya ya da dizin yok)
4. open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
5. fstat(3, {st_mode=S_IFREG|0644, st_size=18708, …}) = 0
6. mmap(NULL, 18708, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa2cf6be000
7. close(3) = 0
8. open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
9. read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`&\2\0\0\0\0\0"…, 832) = 832
10. fstat(3, {st_mode=S_IFREG|0755, st_size=2156240, …}) = 0
11. mmap(NULL, 3985920, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fa2cf0d5000
12. mprotect(0x7fa2cf298000, 2097152, PROT_NONE) = 0
13. mmap(0x7fa2cf498000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c3000) = 0x7fa2cf498000
14. mmap(0x7fa2cf49e000, 16896, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fa2cf49e000
15. close(3) = 0
16. mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa2cf6bd000
17. mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa2cf6bb000
18. arch_prctl(ARCH_SET_FS, 0x7fa2cf6bb740) = 0
19. mprotect(0x7fa2cf498000, 16384, PROT_READ) = 0
20. mprotect(0x606000, 4096, PROT_READ) = 0
21. mprotect(0x7fa2cf6c4000, 4096, PROT_READ) = 0
22. munmap(0x7fa2cf6be000, 18708) = 0
23. brk(NULL) = 0x1c77000
24. brk(0x1c98000) = 0x1c98000
25. brk(NULL) = 0x1c98000
26. open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
27. fstat(3, {st_mode=S_IFREG|0644, st_size=106172832, …}) = 0
28. mmap(NULL, 106172832, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa2c8b93000
29. close(3) = 0
30. fstat(1, {st_mode=S_IFREG|0644, st_size=0, …}) = 0
31. mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa2cf6c2000
32. write(1, "ali\n", 4) = 4
33. close(1) = 0
34. munmap(0x7fa2cf6c2000, 4096) = 0
35. close(2) = 0
36. exit_group(0) = ?
37. +++ exited with 0 +++

Emin olun burada olan biten şeyleri anlayabilmeyi çok isterdim 🙂 Biraz kafa yoralım, en azından 3-5 satırını anlamayı deneyelim:

0. satırda, “/usr/bin/echo” adlı programın çalıştırıldığını görüyoruz. “execve” adlı syscall’ın görevi bu, program çalıştırmak. Köşeli parantezler içerisinde yer alan “echo” ve “ali” kelimelerine dikkat edin. execve, “echo” isimli programı “ali” parametresi ile çalıştıracak gibi görünüyor.

3. satırda ise “access” syscall’ını görüyoruz. Bu çağrının amacı, talebi yapan process’in bu dosyaya erişim durumunu kontrol etmektir. Process’in bu dosyaya erişmeye izni olmayabilir. Başka bir deyişle; A kullanıcısı kendisine ait bir dosyanın yalnızca kendisi tarafından açılmasını istemiştir. Buna rağmen B kullanıcısı bu dosyayı açmak istemiştir. Burada access devreye girer ve böyle bir yetkinin olup olmadığını kontrol eder. Sonucunu da talebi yapan programa döndürür. Siz de “Bu bıdı bıdıyı yapmak için yönetici yetkilerine sahip olmalısınız.” gibi uyarılar alabilirsiniz. Nasıl ama? Ya da bir programda önceden açılan dosyalardan birini sildiniz. Sonra “Recent Files” gibi bir menüden tekrar aynı dosyayı açmayı denediniz. Fakat dosya yok, bulunamadı gibi uyarılar aldınız. İşte, kullandığınız programın yaptığı access çağrısı (hatta belki de open çağrısı yaptı ve open çağrısı da access’i çağırdı), olumsuz sonuçlandı. Bu satırda da access çağrısı olumsuz sonuçlanmış. Türkçe bir hata görüyoruz hatta (OS dilinden kaynaklı): “(Böyle bir dosya ya da dizin yok)”. Fakat neden o dosyaya çağrı yapılmış, bilemiyorum Altan.

4. satırdan 32. satıra kadar olan biten hakkında hiçbir fikrim yok. “open” ve “close” syscall’larını görüyoruz. “mmap” ile bir şeylerin RAM’e gönderildiğini düşünebiliriz. “fstat” ile dosyanın özellikleri inceleniyor vs. Buraya kadar olan kısımda muhtemelen dosyamız, gerekli izin düzenlemeleri ile birlikte (kullanıcı, grup ve diğer herkes için okuma, yazma, çalıştırma izinleri) oluşturuldu.

32. satırdaki “write” syscall’ına bakalım. Birtakım parametreler almış, ne olduklarını bilmiyorum. Fakat içinde “ali” yazıyor. Bu syscall sayesinde oluşturulan dosyanın içine “ali” yazıldı.

33. – 35. satırlarda yazma işlemini sonlandırmak için dosya kapatılmış ve belleğe map edilen kısımlar unmap edilmiş (“munmap” syscall’ı) gibi görünüyor.

37. satır ise konuyu tamamen kapatıyor. “0 ile çıkıldı”. Yani herhangi bir hata ile karşılaşılmadı.

Hadi başka bir örnek daha inceleyelim. Yetkimiz olmayan bir dosyayı okumak istediğimizde neler oluyor:

strace cat /etc/shadow
0. execve("/usr/bin/cat", ["cat", "/etc/shadow"], 0x7ffd49c00dc8 /* 25 vars */) = 0
1. brk(NULL) = 0x10b1000
2. mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f979f6d2000
3. access("/etc/ld.so.preload", R_OK) = -1 ENOENT (Böyle bir dosya ya da dizin yok)
4. open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
5. fstat(3, {st_mode=S_IFREG|0644, st_size=18708, …}) = 0
6. mmap(NULL, 18708, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f979f6cd000
7. close(3) = 0
8. open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
9. read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`&\2\0\0\0\0\0"…, 832) = 832
10. fstat(3, {st_mode=S_IFREG|0755, st_size=2156240, …}) = 0
11. mmap(NULL, 3985920, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f979f0e4000
12. mprotect(0x7f979f2a7000, 2097152, PROT_NONE) = 0
13. mmap(0x7f979f4a7000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c3000) = 0x7f979f4a7000
14. mmap(0x7f979f4ad000, 16896, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f979f4ad000
15. close(3) = 0
16. mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f979f6cc000
17. mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f979f6ca000
18. arch_prctl(ARCH_SET_FS, 0x7f979f6ca740) = 0
19. mprotect(0x7f979f4a7000, 16384, PROT_READ) = 0
20. mprotect(0x60b000, 4096, PROT_READ) = 0
21. mprotect(0x7f979f6d3000, 4096, PROT_READ) = 0
22. munmap(0x7f979f6cd000, 18708) = 0
23. brk(NULL) = 0x10b1000
24. brk(0x10d2000) = 0x10d2000
25. brk(NULL) = 0x10d2000
26. open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
27. fstat(3, {st_mode=S_IFREG|0644, st_size=106172832, …}) = 0
28. mmap(NULL, 106172832, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f9798ba2000
29. close(3) = 0
30. fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), …}) = 0
31. open("/etc/shadow", O_RDONLY) = -1 EACCES (Erişim engellendi)
32. write(2, "cat: ", 5cat: ) = 5
33. write(2, "/etc/shadow", 11/etc/shadow) = 11
34. open("/usr/share/locale/locale.alias", O_RDONLY|O_CLOEXEC) = 3
35. fstat(3, {st_mode=S_IFREG|0644, st_size=2502, …}) = 0
36. mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f979f6d1000
37. read(3, "# Locale name alias data base.\n#"…, 4096) = 2502
38. read(3, "", 4096) = 0
39. close(3) = 0
40. munmap(0x7f979f6d1000, 4096) = 0
41. open("/usr/share/locale/tr_TR.UTF-8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (Böyle bir dosya ya da dizin yok)
42. open("/usr/share/locale/tr_TR.utf8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (Böyle bir dosya ya da dizin yok)
43. open("/usr/share/locale/tr_TR/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (Böyle bir dosya ya da dizin yok)
44. open("/usr/share/locale/tr.UTF-8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (Böyle bir dosya ya da dizin yok)
45. open("/usr/share/locale/tr.utf8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (Böyle bir dosya ya da dizin yok)
46. open("/usr/share/locale/tr/LC_MESSAGES/libc.mo", O_RDONLY) = 3
47. fstat(3, {st_mode=S_IFREG|0644, st_size=132344, …}) = 0
48. mmap(NULL, 132344, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f979f6a9000
49. close(3) = 0
50. open("/usr/lib64/gconv/gconv-modules.cache", O_RDONLY) = 3
51. fstat(3, {st_mode=S_IFREG|0644, st_size=26970, …}) = 0
52. mmap(NULL, 26970, PROT_READ, MAP_SHARED, 3, 0) = 0x7f979f6a2000
53. close(3) = 0
54. write(2, ": Eri\305\237im engellendi", 20: Erişim engellendi) = 20
55. write(2, "\n", 1
56. ) = 1
57. close(1) = 0
58. close(2) = 0
59. exit_group(1) = ?
60. +++ exited with 1 +++

31. satırda, /etc/shadow dosyası için open syscall’ını görüyoruz. Ve “Erişim engellendi” uyarısı bizi karşılıyor. 54. satırda ise “Erişim engellendi” yazısının ekranımıza yazılması için write syscall’ı çağrılmış. 60. satır ise “1 ile çıkıldı” diyor. Bu iş başarıyla tamamlanamadı.

Buraya kadar olan kısımda birçok Linux system call ismi geçti. Şuraya bırakayım.^3

System Call’ı Anlamaya Çalışalım

System call, genellikle C ya da C++ gibi düşük seviyeli (bu bir hakaret değil)^4 dillerle yazılır. Bazı çağrıların yazılması için assembly kullanımı da gerekebilir. Yani sistem çağrıları da aslında birer kod parçası. Linux’da 300’ün üzerinde syscall bulunuyor.^3 Bu sayı, Windows için 700’lere çıkıyor.^5 Buradan da anlaşılacağı üzere, sistem çağrıları işletim sistemlerine göre farklılıklar gösterebilir.

Peki böyle bir dünyada biz nasıl program yazacağız? Ekrana “Merhaba Dünya!” yazmak için bile binbir takla atmamız gerekmiyor mu? Hayır. Neden? API’ler (Application Programming Interface) sayesinde.^6

Örneğin, siz program yazarken “print (‘Hello World!’)” ifadesini kullanıyorsunuz. Arkaplanda bu komutlarınız, derleyici ya da yorumlayıcı tarafından, sistem çağrılarına dönüştürülüyor ve siz tek satır kodla ekrana bir yazı yazabiliyorsunuz. Detaylarla boğulmuyorsunuz. Derlenen dillere bir örnek olarak C# dilini ele alalım. Daha önce ilgilendiyseniz, derleme sırasında size birtakım sorular gelir: “Hangi işlemci mimarisi?, Hangi işletim sistemi?” gibi. Neden dersiniz? Derleyiciniz yazdığınız koda karşılık gelen sistem çağrılarını bu seçimlerinize göre belirliyor. Yorumlanan dillerden ise Python dilini ele alalım. GNU/Linux ortamında yazdığınız bir Python kodu, Windows ortamında da çalışır (Nispeten basit işler yapan kodlardan bahsediyorum. Yazılımcı değilim.). Bu nasıl mümkün olabiliyor? Gayet basit. Kodu siz derlemiyorsunuz. Python yorumlayıcısı yorumluyor. Peki aynı Python yorumlayıcısı, farklı işletim sistemlerinde çalışıyor mu? Hayır. Python indirme sayfasına gittiğinizde, hangi işletim sistemi için indirmek istediğinizi size sorduğunu göreceksiniz. Java için de benzer durum söz konusu. Java’nın klasikleşmiş “bir kere yaz, her yerde çalıştır” mottosu; çok da göründüğü gibi değil. Java kodlarınız bir Java Sanal Makinesi (JVM) üzerinde çalışıyor. Ve eğer bilgisayarınızda Java yüklü değilse, Java ile yazılmış bir programı çalıştıramıyorsunuz. Tabii ki Java’yı yüklerken de hangi işletim sistemini kullanıyorsanız ona göre yüklüyorsunuz. Dolayısıyla aynı Java kodunu GNU/Linux’a kurduğunuz JVM ile Windows’a kurduğunuz JVM farklı çalıştırıyor. Yani farklı sistem çağrılarına yönlendiriyor. Fakat aynı sonucu üretiyor.

Daha kolay bir örnek verelim. FIFA 2020 oyunu Windows için var, Xbox için var, PlayStation için var. Aynı oyun değil mi? Değil işte, farklı işletim sistemleri için hazırlandılar. Dolayısıyla kullandıkları sistem çağrıları değişiyor. Bu nedenle PlayStation için satın aldığınız FIFA’yı, Xbox’ınızda kullanamıyorsunuz. (Tek sebebi bu değil tabii ki. İşin içine dosya sistemlerinden tutun da ticari kaygılara kadar birçok etmen giriyor.)

Sonuç Olarak

Bilgisayarınızı kullanırken yaptığınız her iş, bir system call’a bağlı. Klavyenizden bir tuşa basmak ve ekranda karşılığını görmek, farenizi hareket ettirmek, Internet’ten dosya indirmek, oyun oynamak, müzik dinlemek… Eğer bir program çalışabiliyorsa, bunu sistem çağrıları sayesinde yapabilmektedir. Yazılım geliştiriciler, API’ler sayesinde bu sistem çağrılarını bilmek zorunda kalmazlar. Ayrıca derleyiciler ve yorumlayıcılar sayesinde de hangi ortamda hangi sistem çağrılarının kullanılacağı konusuyla da ilgilenmek zorunda değillerdir.

Bağlantılar

1 – System call, https://en.wikipedia.org/wiki/System_call

2 – open (system call), https://en.wikipedia.org/wiki/Open_(system_call)

3 – syscalls(2) – Linux manual page, https://man7.org/linux/man-pages/man2/syscalls.2.html

4 – Low-level programming language, https://en.wikipedia.org/wiki/Low-level_programming_language

5 – Windows WIN32K.SYS System Call Table (NT/2000/XP/2003/Vista/2008/7/8/10), https://j00ru.vexillium.org/syscalls/win32k/32/

6 – API, https://en.wikipedia.org/wiki/API

Referans

1 – Operating System Concepts Essentials, Abraham SILBERSCHATZ (Yale University), Peter Baer GALVIN (Corporate Technologies, Inc.), Greg GAGNE (Westminster College)

İşletim Sistemi 101 – #2 (Servisler)

Bir önceki yazımda, işletim sistemi hakkında genel bilgi vermiştim. Bu yazıda ise işletim sisteminin sunduğu hizmetleri anlamaya ve anlatmaya çalışacağım.

İşletim Sisteminin Sundukları

Ne demiştik? Kullanıcılar olarak aslında işletim sistemini, hatta onun kabuğunu (shell)^1 kullanıyoruz. Kalan her şey ise işletim sisteminin yönetiminde oluyor. Yani hem kullanıcı talepleri hem de programların talepleri, işletim sisteminin üzerinden geçiyor. Peki işletim sistemleri, kullanıcılarına (insanlar, programlar, diğer sistemler) neler sunuyor?

Kullanıcı Arayüzü

İşletim sistemi çalışmaya başladığında bizi karşılayan bir yüzü var. Bilgisayarın çalışmaya başlamasından kapanmasına kadar kullanım sırasında bize eşlik eden bu arayüze, kullanıcı arayüzü (user interface, UI) diyoruz.

Komut Satırı Arayüzü

Bu arayüzlerden aklıma ilk gelen, “Komut Satırı Arayüzü (Command Line Interface, CLI)“. Aşağıda GNU/Linux ve Windows Vista işletim sistemlerinden alınmış birer CLI örneğini görebilirsiniz.

GNU/Linux CLI örneği
GNU/Linux CLI örneği
Windows CLI örneği
Windows CLI örneği

Bu arayüzü kullanarak gireceğiniz birtakım komutlar, bilgisayarınızı kullanabilmenizi sağlar. Bu arayüzün kendisi de ayrı bir yazılımdır ve işletim sisteminin bileşenlerinden biridir. Yine hatırlatmak isterim, işletim sistemi tek bir yazılım değil, bir yazılımlar grubudur.

OpenWrt CLI
OpenWrt CLI

Ne yazık ki birçok insan CLI görünce korkuyor. Ya da bunun “aşırı ilkel!” olduğunu düşünüyor. Üzgünüm arkadaşlar fakat herkes bir gün CLI kullanmak zorunda kalacaktır. Bilgisayarınızda yapabildiğiniz herhangi bir şeyi, buradan da yapabilirsiniz. Tabii ki farklı arayüzlerde bazı işleri yapmak çok daha kolay olabilir. Fakat işlerin en başında ya da yanıp yıkıldığı, patladığı yerlerde CLI kullanmak zorunda kalacaksınız. Birçok depolama (storage) ve ağ (network) ürününün kurulumu ve yapılandırılması sırasında CLI kullanıldığından emin olabilirsiniz. İlerleyen süreçte ise cihazın doğru çalışmadığı durumlarda hata tespiti ve müdahale için yine CLI kullanılır. Yandaki örnekte, bir kablosuz router’a kurduğum OpenWrt’nin yönetim ekranını görebilirsiniz. Ve evet, router da bir bilgisayar.

Yığın Arayüzü

Yığın arayüzü (batch interface), eski zamanlarda kullanılan bir arayüzdü. Bu arayüzde tüm direktifler delikli kart (punched card), delikli şerit (paper tape) gibi ortamlara yazılır (işaretlenir) ve bilgisayara yüklenirdi. Bir önceki yazımda batch sistemlere kısaca değinmiştim.

Batch Interface (Birleşik Devletler Milli Arşiv ve Kayıt Yönetimi - 1940
Batch Interface (Birleşik Devletler Milli Arşiv ve Kayıt Yönetimi) – 1940

Grafik Arayüz

Xerox Star 8010 iş istasyonu (Piyasaya sürülen ilk GUI) - 1981
Xerox Star 8010 iş istasyonu (Piyasaya sürülen ilk GUI) – 1981

Geldik en tanıdık arayüze. Graphical User Interface (GUI). Üzerinde farenizi gezdirebildiğiniz ya da klavyenizdeki tuşlar ile bazı nesneleri gösterip onlarla etkileşime geçebildiğiniz, menüler görebildiğiniz arayüz.

Bu arayüzde “fare” sözcüğüne odaklanmamanızı öneririm. Dokunmatik telefonlarınızda gördüğünüz arayüz de bir GUI’dir. Keza oyun konsollarında gördüğünüz arayüz de GUI’dir. Birinde seçimlerinizi dokunarak yaparken diğerinde konrolör kullanırsınız.

TÜBİTAK UEKAE tarafından geliştirilen Pardus GNU/Linux dağıtımındaki Xfce GUI (https://commons.wikimedia.org/wiki/File:Pardus_19.0_Xfce_Desktop.jpg)
TÜBİTAK UEKAE tarafından geliştirilen Pardus GNU/Linux dağıtımındaki Xfce GUI (https://commons.wikimedia.org/wiki/File:Pardus_19.0_Xfce_Desktop.jpg)

Program Çalıştırma

İşletim sisteminin temel görevlerinden biri de, kullanıcının programlarını çalıştırmaktır. Çalıştırma sürecini; diskten okuma, RAM’e yazma, işlemciye gönderme, kapatma şeklinde özetleyebiliriz.

Kapatma sürecine dikkat çekmek isterim. İşletim sisteminin kaynakları doğru kullanabilmesi için, kapatılan programlara atadığı kaynakları geri alması gerekir. Bazı durumlarda bellek yöneticisi, atadığı kaynakların tamamını geri almaya ihtiyaç duymaz. Örneğin bir oyunu ilk açışınız 5 saniye sürerken, kapatıp tekrar açmayı denediğinizde bu süre 3 saniyeye düşebilir. Çünkü işletim sistemi, RAM’e yazdığı kısımların tamamını silmemiş olabilir. Akıllı telefonlarda uygulamalar arasında geçiş yaparken de bu durumu sıkça yaşarız. Fakat bazı durumlarda, işletim sistemi ipin ucunu kaçırabilir. İşte bu yüzden – özellikle Windows kullanırken – “Bu bilgisayara bir hâller oluyor. Çok yavaşlamaya başladı. Bir kapatıp açsak düzelir.” diyoruz. RAM, geçici (volatile) bir depolama birimidir. Yani gücü kesilince, üzerine yazılan veri silinir. İşletim sisteminin neyi silip neyi silmeyeceği konusunda afallamaya başladığı durumlarda kapatıp açmak güzel bir çözümdür. Gülmeyin!

Windows 10 işletim sisteminde RAM kullanımı. - 8 GB, %25'i Firefox tarafından kullanılmakta.
Windows 10 işletim sisteminde Firefox RAM kullanımı. – 8 GB, %68 doluluk oranı.

Girdi / Çıktı İşlemleri

Programların çalıştırılmasının bir amacı var. Çoğu zaman bu programlara bir girdi veririz ve bir çıktı bekleriz. Bu sürece Input / Output (I/O) deriz. Örneğin bir medya oynatıcıya ses dosyasını verirsiniz ve o verinin okunmasıyla bilgisayarınızdan ses duymak istersiniz. Benzer şekilde bu sayfayı okurken kullandığınız web tarayıcıya da birtakım girdiler veriyorsunuz. Sayfanın adresini yazıyorsunuz, linklere tıklıyorsunuz, ekranı kaydırıyorsunuz. Ve ekranınızdaki görüntüler değişiyor. Ya da bir metin editörüne, klavyeniz aracılığıyla birtakım girdiler gönderiyorsunuz. Sonrasında bu içeriği bir dosyaya kaydediyorsunuz. Tüm bu süreçleri yine işletim sistemi yönetiyor.

Örnek durum. Bazı zamanlar klavyenizde “a” tuşuna basarsınız ve hiçbir şey olmaz. Sonrasında birkaç kere daha basarsınız ve ekranda bir anda – ya da yavaş yavaş – “aaaaaaaaaaaaaaaaa” görürsünüz. Burada işletim sistemi sizin girdinizin önceliğini düşürmüş olabilir, çıktınızın önceliğini düşürmüş olabilir, kullandığınız programın – ki çalışmakta olduğu için process’in – önceliğini düşürmüş olabilir. Tekrar tekrar basmanız işleri olduğundan daha iyi hâle getrmeyecektir.

Dosya Sistemi İşlemleri

Bilgisayarlarımız veriyi depolayan ve gerektiğinde sunan cihazlar. Bu verileri kullanmak için de programlardan faydalanıyoruz. Peki bu süreç nasıl yönetiliyor? Manyetik olarak ya da elektriksel olarak verilerimiz depolama aygıtlarında saklanıyor. Nereye yazıldığına dair en ufak bir fikrimiz yok (İstersek olur).

Hard Disk silindiri üzerinde track ve sector
Hard Disk silindiri üzerinde track ve sector

Yukarıdaki görsel, sıradan bir hard diskin bir silindiri. Track ve sector’lere ayrılmış durumda. Okuma / yazma kafası sayesinde veriler bu alanlardan okunuyor ve bu alanlara yazılıyor. Harita üzerindeki koordinat sistemi gibi düşünebilirsiniz. Peki neresi boş? Neresi dolu? 5 KB boyutunda bir dosyayı yazmak için en uygun yer neresi? Peki 8 GB boyutunda bir filmi sığdırabileceğimiz bir alan var mı? Bu dosyayı tekrar açmak istersem nasıl açacağım? Bu dosyayı kimler açabilir? Benim oluşturduğum bir dosyayı başka bir kullanıcı gelip silmek ya da değiştirmek isterse ne olacak? Tüm bu soruların yanıtı işletim sisteminde.

Kısa bir bilgi. Çeşitli sebeplerle oluşan mekanik hasarlardan dolayı sector’ler kullanılmaz hâle gelebilir. Bu sector’e yazılan veri kaybedilir ve bu alan tekrar kullanılamaz. Disk üzerinde bu durumda bulunan sector’lere “bad sector”^2 denir.

İşletim sistemleri, depolama birimlerini belirli bir dosya sistemi ile kullanılmak üzere şekillendirir, biçimlendirir, FORMATLAR! Format atmak her ne kadar işletim sisteminin kaldırılıp yeniden kurulması olarak bilinse de, aslında formatlamak sadece bir depolama birimi üzerinde bir dosya sistemi oluşturulmasıdır. Dosya sistemi denildiğinde karşınıza çıkması oldukça muhtemel bazı örnekler: ext4^3, xfs^4, NTFS^5, FAT^6

Kabaca anlatmak gerekirse, işletim sistemleri dosyalarınız için bir tablo tutar. Siz dosyaların isimlerini ve yollarını (path) görürsünüz. İşletim sistemi ise fiziken sakladığı yeri de bilmek zorundadır.

PATHCHS^7
/home/ali/beat.flacCylinder 1 Head 2 Sector 3
/home/ali/readme.mdCHS 2/12/26
/var/log/apache.logCHS 3/28/62

Yukarıdaki örnek aşırı iyimser. Neden derseniz, 50 birimlik bir depolama aygıtı düşünelim. 5’lik parçalara ayrılmış. 5 x 10 şeklinde 50 birim depolama şansımız olsun. Bomboş olan diske 5’lik “A” parçasını yazıyorum:

A

Sonrasında 15’lik B ve 25’lik C yazalım:

ABBBCCCCC

Şimdi de B’leri silelim. Zaten elimizde 5 birim alan vardı. B’leri silince 15 birim daha gelecek. Dolayısıyla 20 birim boşluk var. Bu durumda 20 birimlik D’yi nasıl yazarız? Yer yok mu deriz? Yoksa parçalar mıyız? Bu sorunun yanıtı dosya sistemlerine göre değişebilir, emin değilim. Dosya sistemlerinin standartlarına göre tek dosyada minimum ve maksimum boyut, toplamda adreslenebilecek minimum ve maksimum boyut gibi değerleri var. Biz şu an kullandığımız dosya sisteminin 20 birimlik bir dosyayı yazarken sorun yaşamayacağını düşünerek ilerleyelim. Zaten az önce 25’lik C yazmıştık. Dolayısıyla D verisini parçalayarak yazacağız:

AD’D”D”’CCCCCD””

Hemen bir kısa bilgi daha. Yukarıda yaşadığımız durumu inceleyelim. D’nin 40 dakikalık bir film olduğunu düşünelim. İlk 30 dakikayı izlerken problem yaşamıyoruz ancak son 10 dakika yüklenirken bir yavaşlık geliyor. Bu durumun sebeplerinden biri, okuma işinin sırayla yapılamamasıdır. Mekanik bir diskte, okuma kafasının bambaşka bir noktaya gitmesini gerektiren bir durum yaşıyoruz. Bunu birden fazla silindir, on binlerce parçacık olarak düşünürseniz durumun önemini daha iyi anlayabilirsiniz. Yaşanan bu duruma da dosya sistemi parçalanması (file system fragmentation)^8 diyoruz. Eğer disk üzerinde yeterli alan varsa, verilerin yerleri değiştirilerek, ilgili parçalar bir araya ya da daha yakın konumlara getirilerek parçalanma azaltılabilir. Buna da defragmentasyon (defragmentation)^9 diyoruz. Ve bunu GNU/Linux’da Windows’a kıyasla aşırı az yaşıyoruz 🙂

Defragmentasyon örneği
Defragmentasyon örneği

İletişim

Aynı sistem üzerinde ya da farklı sistemlerde çalışan process’ler birbirleriyle iletişime geçme ihtiyacı hissedebilir. Bir process’in çıktısı, diğer bir process’in girdisi olabilir. Bu iletişim, shared memory^10, message passing^11 gibi yöntemlerle yapılabilir. Process’ler arası iletişimden işletim sistemi sorumludur.

Hata Tespiti

İşletim sistemi, bilgisayarımızda meydana gelebilecek hataları tespit eder. Hatta bazı hataları öngörerek gerekli önlemleri alır. Bu hatalar kritik olabileceği gibi, gayet sıradan ve çözümü oldukça basit hatalar olabilir.

Örneğin, anakartınızda ya da belleğinizde yaşanacak bir problemden dolayı, belleğinizin çektiği enerji miktarında dalgalanmalar olabilir. Yukarıda bahsettiğim gibi, hard diskinizde bad sector’ler oluşabilir. Bir process, yetkisi olmamasına rağmen başka bir process’in RAM’deki verilerine erişmeyi deneyebilir. Ya da CPU’yu sömürmeyi deneyebilir. Bunlar kritik hatalara örnek olarak verilebilir. İşletim sistemi, bu hataları tespit edip gerekli işlemleri yapmakla mükelleftir.

Daha basit hatalara örnek verelim. Kulaklığınız ile müzik dinliyorsunuz ve kulaklık bir yere takılıyor, çıkıyor. İşletim sisteminiz o anda hoparlör ve kulaklık ayrımını yapıp ses düzeyini ona göre düzenleyebilir ya da oynatılmakta olan medyayı duraklatabilir. Çıktı almak istersiniz fakat yazıcınızın mürekkebi bitmek üzeredir ya da kağıdı bitmiştir. “Bu bilgiler, yazıcının kendisi tarafından veriliyor ama?!?” diyebilirsiniz. Olabilir. Yazıcının işletim sistemi, bu durumu bağlı olduğu cihazın işletim sistemine iletir. Çıktı almakla sorumlu olan yazılım da gerekli kontrolleri yaparken bu verileri işletim sisteminden ister. İşin ucu hep işletim sistemine gidiyor.

Kaynakların Dağıtımı

Bir bilgisayar sisteminde birden fazla process koşabilir. Aynı sistemi aynı anda birden fazla kullanıcı kullanıyor olabilir. İşletim sistemi; CPU, RAM, I/O işlemleri gibi konularda efektif bir dağılım yapmakla sorumludur.

Daha önceden belirttiğim gibi, eğer sistem için kritik öneme sahip işler varsa, kullanıcının talepleri tamamen göz ardı edilebilir. Önemli olan, sistemin çalışmaya devam edebilmesidir. Örneğin, CPU’da hangi process’in ne zaman ve ne süreyle çalışacağını kararlaştırmak için “CPU Scheduling^12 algoritmalarından faydalanılır. Benzer modeller, sistemin diğer kaynakları için de kullanılmaktadır.

Saymanlık

Şaka mı? Değil. Hangi kullanıcının hangi kaynağı ne derece kullandığına dair detaylı istatistikler işletim sistemi tarafından tutulur. Bu veriler, bazı hataların çıkış noktalarını tespit etmek için kullanılabilir. Benzer şekilde, çok kullanıcılı herhangi bir sistemde kullanıcılardan birinin kaynak taleplerinde yaşanan aşırı dalgalanmalar, dikkatleri o noktaya çeker. Ayrıca, herhangi bir process’in beklenenden fazla kaynak talep etmesi de yine dikkat çeker. Bu tarz durumlar, zararlı yazılımların ve siber saldırıların tespitinde dahi kullanılabilir.

Bulut sağlayıcıları, işletim sisteminden aldıkları bu verileri kullanarak kullanım istatistiklerinizi tutabilir ve bu kullanımlara göre faturalandırma yapabilir.

Koruma ve Güvenlik

Bir bilgisayarı birden fazla kullanıcının kullandığı ya da birden fazla bilgisayarın aynı ağa bağlı olduğu bir senaryoyu düşünelim. Kullanıcılar birbirlerinin kaynaklarına müdahale edememeli, birbirlerinin hesabına giriş yapamamalıdır. Ağ üzerinden paylaşılabilecek dosyalarını seçebilmeli, hatta paylaşacakları dosyaları kimlerle, ne şartlar altında paylaşabileceklerini de belirleyebilmelidir.

İşletim sistemi, bu gibi olanakları sunarak sistemin güvenli çalışmasından sorumludur. Ayrıca, yetkisiz erişim denemelerini fark etmeli ve kayıt altına almalıdır.

Sonuç Olarak

İşletim sistemi, cihazınızda olan biten her şeyden sorumlu bir yazılımdır. Fakat kullanıcı olarak sizin de üzerinize düşen önemli görevler bulunmakta.

Zayıf bir parolanızın olması, işletim sisteminin değil, sizin probleminizdir. Şüpheli bir programı çalıştırmak, sizin probleminizdir. Elbette cihazlarımızın ve ağlarımızın güvenliği için birçok ürün mevcut. Fakat bunların hiçbiri tek başına yeterli değildir.

Ek olarak, eğer tüm kullanımımız kısıtlanacaksa, bilgisayar kullanmanın bir anlamı kalmaz. Kaldı ki, işletim sisteminin temel görevlerinden biri de, kullanıcılara kullanım kolaylığı sunmaktır. Dolayısıyla cihazlarınızı kullanırken ne yaptığınızı biliyor olmalısınız, en azından denemelisiniz. “Emin misin?”, “Yapayım mı?”, “Bak sorun olabilir diyorum, gerçekten devam ediyor muyuz?” uyarılarını dikkate alın. Eğer ısrarla “rm -rf /” yazmaya devam ederseniz, sonucuna katlanırsınız 🙂

Bağlantılar

1 – Shell (computing), https://en.wikipedia.org/wiki/Shell_(computing)

2 – Chapter 6. The Ext4 File System, https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/storage_administration_guide/ch-ext4

3 – Bad sector, https://en.wikipedia.org/wiki/Bad_sector

4 – Chapter 3. The XFS File System, https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/storage_administration_guide/ch-xfs

5, 6 – Overview of FAT, HPFS, and NTFS File Systems, https://support.microsoft.com/en-us/help/100108/overview-of-fat-hpfs-and-ntfs-file-systems

7 – Cylinder-head-sector, https://en.wikipedia.org/wiki/Cylinder-head-sector

8, 9 – Why Linux Doesn’t Need Defragmenting, https://www.howtogeek.com/115229/htg-explains-why-linux-doesnt-need-defragmenting/

10 – Shared memory, https://en.wikipedia.org/wiki/Shared_memory

11 – Message passing, https://en.wikipedia.org/wiki/Message_passing

12 – CPU Scheduling, https://www.cs.uic.edu/~jbell/CourseNotes/OperatingSystems/5_CPU_Scheduling.html

Referans

1 – Operating System Concepts Essentials, Abraham SILBERSCHATZ (Yale University), Peter Baer GALVIN (Corporate Technologies, Inc.), Greg GAGNE (Westminster College)

İşletim Sistemi 101 – #1 (Tanımlar)

Uyarı: İşletim sistemleri hakkında temel düzeyde bilgi edinmek isteyenlere, konunun güzelliğini fark ettirmek amacıyla bir yazı hazırladım. Teknik detaydan fazlasıyla uzak olduğunu düşünüyorum. Bu nedenle tekniğe dokunan noktalarda bariz eksiklikler olacaktır. Fakat yine de, lütfen eksik ve/veya yanlış gördüğünüz noktaları belirtin ve gerekli düzenlemeleri yapalım. Hayat boyu OS öğreneceğiz!

İnsan Bilgisayar (Human Computer)

Alan Turing - 1928 (16 yaşında)
Alan Turing – 1928 (16 yaşında)

Bilgisayar Bilimi’nin kurucusu Alan Mathison TURING, 1 Ekim 1950 tarihinde yayımlanan “Computing Machinery and Intelligence” (Hesaplama Makineleri ve Zeka) makalesinde, “Makineler düşünebilir mi? Bu konu, ‘makine’ ve ‘düşünme’ kavramlarının tanımıyla başlamalı.” ifadesine yer veriyor.^1 Makalesinde “The Imitation Game”e (Taklit Oyunu) yer veren Turing, bizlere “Turing Testi”ni anlatıyor. Yeri gelmişken 2014 yapımı “The Imitation Game” filmini de analım.^2 Bilgisayar Bilimi’ni öğrenmek, Alan Turing’i anlamak ve tanımak için film izlemekten daha fazlasına ihtiyacımız olduğunu hatırlatmama gerek yoktur sanırım.

Turing bu makalesinde, dijital bilgisayarların çıkış noktası olarak insan bilgisayarların görevlerini yerini getirme amacına işaret ediyor. İnsan bilgisayarı ise; “belirli kuralları takip eden, hiçbir ayrıntıdan sapma yetkisi olmayan kişiler” olarak tanımlıyor. (Burada yaptığım çeviriyi hiç beğenmedim. Olmadı burası.)

NACA (NASA) Yüksek Hızlı Uçuş İstasyonu "Computer" Odası (1949)
NACA (NASA) Yüksek Hızlı Uçuş İstasyonu “Computer” Odası (1949)

17. yy.’de “Computer” kelimesi, tam olarak buydu. “Hesaplama (compute) yapan kişi.”^3 Uzun soluklu hesaplama işlerinde kullanılan bir insan grubu düşünün. “Apollo” filmlerinde görmüştük sanki benzer sahneleri. Hata yapma ihtimalleri olduğu için, aynı hesabı yapan birden fazla grup var. Hatta hesaplamalar, aynı grup içerisindeki kişiler arasında paylaştırılıyor ve sonuçlar bir araya getirilerek nihai sonuca ulaşılıyor.^4 Multithreding? Bu konuya geleceğiz. Küçük bir not daha. Bu işlerde 1800’lü yıllar ve sonrasında ciddi miktarda kadının çalıştığı biliniyor. Ada Lovelace’ten beri – hatta belki daha önceden de – kadınlar bilgisayar dünyasında varlar. Olmaya da devam edecekler. Aksini iddia eden varsa şuradan Alt+F4 çekip gidebilir.

Dijital Bilgisayar

Şimdi gelelim günümüze. Hesaplama işleri artık birtakım aygıtlara devredilmiş durumda. Günümüzdeki karşılığıyla bilgisayar için şöyle bir tanım yapabiliriz: “Birtakım aritmetiksel ve mantıksal işlemi yürüterek veriyi işleyen, saklayan, gösteren aygıt.” Bildiğiniz gibi donanım ve yazılım olarak iki farklı birime ayrılabilir. Bu yazıda klişelere girip “oda kadar büyük, 1 MB hayaldi, 5 ton çekiyordu, Pascaline, ENIAC…” şeklinde devam etmeyeceğim. Bilgisayarın gelişim sürecini, tarihçesini mutlaka okuyup incelemelisiniz. Ayrıca adı anılması gereken onlarca bilim insanı var. Ve bu adı duyulmamış kahramanların, sosyal medyada şaklabanlık yapan tiplerden daha az tanınması beni inanılmaz yoruyor. O yüzden buralara hiç girmeyeceğim. Kendi okumalarımda yakaladığım ve saygı duyduğum insanlara değineceğim ayrı bir yazıyı, önümüzdeki 42 yıl içerisinde hazırlayabilirim.

“Compute (hesap)” kökünden gelen “computer (hesaplayıcı)” için muazzam bir öneri getiren ve “bilgisayar” kelimesini dilimize kazandıran kıymetli hocamız Prof. Dr. Aydın KÖKSAL’ı da anmadan geçmeyelim.^5 Bilgisayarın Türkiye’deki serüvenini anlamak için Aydın Hoca’yı mutlaka okumalısınız.

KKK Master Planı Projesi'nin başarıyla sonuçlanmasını kutlamak üzere Bilişim Ltd ve Aydın Köksal onuruna komutanlar yemek veriyor. - 1992 (Aydın Köksal hocanın web sitesinden alınmıştır.)
KKK Master Planı Projesi’nin başarıyla sonuçlanmasını kutlamak üzere Bilişim Ltd ve Aydın Köksal onuruna komutanlar yemek veriyor. – 1992 (Aydın Köksal hocanın web sitesinden alınmıştır.)

Peki bu tanıma göre düşünecek olursak, “Evinizde kaç tane bilgisayar var?” sorusunu yöneltmek isterim. 1? 3? 24? Sandığınızdan çok daha fazla olabilir. Örnekleyelim:

  • Masaüstü bilgisayar, dizüstü bilgisayar, tablet bilgisayar dediğimiz şeyler zaten bilgisayar.
  • Akıllı telefonunuz bir bilgisayar. Düşünsenize, ona da uygulamalar kuruyorsunuz. Birtakım verileri saklıyor, size bunları sunabiliyor.
  • Peki akıllı olmayan telefonlar? En azından bir hesap makinesi, bir takvimi var. Hatırlatıcıları, alarmları var. Hepsini kenara koyalım, bunlar nispeten basit kalır bu cihazlar için. Sesinizi alıp, dijitalleştirip, başka bir noktaya gönderebiliyor. Önemli bir iş bence. Kendi kendine bunu yapabilen var mı aramızda?
  • SIM kartlarınızın her biri kendi başına bir bilgisayar. Ciddiyim.
  • “Bilgisayar” olarak nitelendirdiğiniz makinelerin içinde birden fazla bilgisayar var. Grafik işlemciniz, merkezi işlemciniz, bunların çekirdekleri, Wi-Fi kartınız… Bu parçaların her biri de hesaplama yeteneğine sahip, veri işleyen cihazlar. Her birini ayrı bir bilgisayar olarak kabul edebiliriz.
  • Çamaşır makinesi, bulaşık makinesi, buzdolabı gibi eşyalar? Belirli durumları takip edip ona göre karar vermiyorlar mı? Ne zaman ısıtacak, ne zaman soğutacak, kaç dereceye kadar ısıtıp soğutacak, ne kadar süre iş yapacak, hangi temizlik maddesini sürece ne zaman dahil edecek? Bunlar önemli sorular ve bunların hepsine yanıt verebiliyorlar. Çoğunun üzerinde hazır programlar da geliyor. Hatta bu programları isteğinize göre düzenleme şansına da sahipsiniz.

Özetle, çok bilgisayar var.

Neyi, Nasıl Programlayalım?

Düşünün. İnsanların yaptığı birtakım hesaplama işlerini, aygıtlara devredeceksiniz. Onlara yeni bir dünyayı öğretmelisiniz. Sayı sistemlerini, veriyi saklamayı, verileri ilişkilendirmeyi, girdi almayı, çıktı vermeyi… Hayli uğraştırıcı bir süreç. Velev ki öğrettik. Süper. Şimdi ne olacak?

“Yoda mimarisine sahip, Death Star anakartlarla uyumlu, Jedi RAM’lere sahip bir bilgisayarı” programladık. Günümüz dünyası böyle mi? Amaca yönelik onlarca farklı parça, bu parçaların yüzlerce farklı modeli var. Fakat biz sadece belirli parçaların bir araya gelmesiyle oluşmuş bilgisayar ile konuşmayı öğrenmiştik ve işimiz daha yeni bitmişti. “CPU yeterli gelmiyor, yenisini ekleyelim ya da var olanı değiştirelim.” dediğinizde başınıza neler geleceğini kestirebiliyor musunuz? O artık apayrı bir cihaz oldu ve ne yapacağına dair en ufak bir fikri yok.

Daha bariz bir örnek verelim. Mobil uygulamalar. Gerçekten piyasadaki tüm telefonlar için oturup tek tek bir uygulama yazmamız gerekiyor aslında. Yoksa nasıl çalışabilirler ki? Neyse ki böyle bir derdimiz yok. Merak etmeyin, işletim sistemine geçiyoruz yavaş yavaş.

Hardware Abstraction (Donanım Soyutlaması?)

Hardware abstraction; programların, donanım kaynaklarına, programlama arabirimleri üzerinden erişebilmesini sağlayan rutinler kümesidir. Emin olun ben de hiçbir şey anlamadım.^6

Şöyle düşünelim. Bir donanıma iş yaptırmak istiyoruz. Ona birtakım komutlar (instruction) veriyoruz. Çalışıyor. Fakat birçok farklı mimaride birçok farklı modelde donanımlar çıkıyor ortaya. Bu durumda bir arabirimi, bir yardımcıyı kullanmak istiyoruz. “Tercüman”ları kullanıyoruz. Hardware abstraction donanımı biliyor ve bizim ilettiğimiz komutları, donanımın anlayacağı hâle getiriyor. Böylelikle her donanım için ayrı ayrı komut setleri bilmek ve yazmak durumunda kalmıyoruz.

Wikipedia’daki makalede joystick örneği verilmiş. Aklıma yattı. Neredeyse bütün kontrolörler birbirinden farklı ama neredeyse hepsi aynı. Birtakım düğmelere basıyoruz ve bir şeyleri kontrol etmek istiyoruz. Geliştiriciler, farklı joystick’ler için farklı girdiler beklemiyorlar. Ya da cihazın teknik detaylarında boğulmak zorunda kalmıyorlar.

Yine Wikipedia’dan farklı bir metafor var. Bisiklet kullanmak da araba kullanmak da bir ulaşım yöntemi. İkisinde de benzer şeyler var. Tekerlek, direksiyon, koltuk gibi. Hardware abstraction sayesinde “sürüş” odağının etrafında kendi uygulamalarımızı geliştirebiliyoruz.

Eminim sadece hardware abstraction için bir dönem ders verilir. Belki daha da fazla. O yüzden burayı havada bırakıp işletim sistemine geçiş yapıyorum.

Geçmeden önce, şu iki makaleyi okumanızı öneririm:

İşletim Sistemi Nedir?

İşletim sistemi (Operating System, OS) için, bir bilgisayarın kaynaklarının programlar arasında paylaştırılmasından ve bu programların çalıştırılmasından sorumlu “maestro” olarak bahsedebiliriz. Hatta dilerseniz sensei, Usta Splinter ya da Excal1bur_1337 bile diyebiliriz.

Bilgisayarınızda var olan çalıştırılabilir kod setleri birer programdır. Gerekli etkileşimleri sağlarsanız – çift tıklama, komut satırı arayüzü (CLI) üzerinden girdiler ya da zamanlanmış görevler gibi – bu programları çalıştırırsınız. Programlar çalıştırdıklarında birer “işlem”e (süreç, process) dönüşürler. Hemen bir özgür yazılım olan VLC Media Player’ı ele alalım. Programı kurdunuz. Diskinizde bir yer tutuyor. Sonrasında çalıştırdınız, bir process oldu. Bu process şunları yapabilir:

  • Diskinizden veri okuyabilir. Bunu yapmazsa zaten ne videoları ekranınızda gösterebilir, ne de sesleri çalabilir.
  • Kendi bileşenlerinin ve okuduğu verinin bir kısmını ya da tamamını RAM’e yazabilir. Bunu yapamazsa zaten çalışamaz. İsmini John von Neumann’dan alan “von Neumann” mimarisini inceleyiniz.^7
  • Veriyi işlemek için CPU’nuzu kullanabilir.
  • Görüntüyü ekrana verebilmek için GPU’nuzu kullanabilir.
  • Medya bilgilerini Internet üzerinden çekmek ya da kendi güncellemelerini denetlemek için ağ kartlarınızı kullanabilir.
  • Sesi dışarıya vermek için ses kartınızı kullanabilir.

Örnekleri artırabiliriz. Bu sadece bir video izlemek için yaşadığımız süreç. Günümüz bilgisayarlarında bir video klibi izlerken oyun oynamak, bu sırada oyunun görüntüsünü ve seslerini kaydetmek, aynı zamanda Internet üzerinden hem oyun görüntüsü hem de web cam’den alınan görüntüyü yayınlamak, tüm bunlar olurken de arkaplanda bazı uygulamaları güncellemek ya da bazı dokümanları indirmek sıradan bir etkinlik olarak görülüyor.

Hatırlarsanız, işletim sisteminin görevi olarak “kaynakların programlar arasında paylaştırılması” demiştik. Sanırım şimdi daha anlamlı olmuştur.

Şimdi kullandığınız herhangi bir yazılımı düşünün. Bir dosyayı hazırladınız ve kaydetmek istediniz. Kaydetme başarısız oldu. Çünkü diskiniz dolu. Ne kadar hoş bir yaklaşım. Bunu yapmak yerine rastgele bazı dosyaları silip kendine yer açabilirdi değil mi? Meh.

Ya da herhangi bir program, kendi hâlinde çok iyi çalışıyorken yanında farklı programlar çalıştırıldığında biraz zorlanmaya başlayabilir. “Kasıyor” değil mi? Halbuki madem bu kaynakları kullanabiliyor, neden hepsini kendine ayırmıyor?

Günümüzde geliştirilen programların donanıma doğrudan müdahale etmesi söz konusu değildir. Sistem yazılımlarını, sürücüleri vs. ayrı tutuyorum tabii ki. Sıradan bir kullanıcı olarak açıp çalıştırdığımız, “uygulama yazılımı” olarak anılan programlardan bahsediyorum. Eğer doğrudan müdahale edebiliyor olsaydık da, hardware abstraction’da değindiğimiz her donanıma ayrı yazılım mantığına girişmek zorunda kalacaktık. İşte işletim sistemi burada devreye giriyor.

İşletim sistemi, donanım ile yazılımlar arasında bir katman olarak düşünülebilir. Bilgisayarınızda koşan yazılımlar, donanım ile etkileşime geçmek istediklerinde bunu işletim sisteminden talep ederler. Ve eğer talep karşılanabilir bir durumdaysa, yerine getirilebilir. Hani bazen aynı anda 1-2 işi yürütmeye çalışırken izlediğiniz videonun sesi bir titrer, sonra toparlanır. Ya da ne bileyim, bir programı çalıştırırken bazen her zamankinden daha yavaş açılır ya. Alakası var mı dersiniz?

İşletim sisteminin katmanlı yapısı (https://en.wikipedia.org/wiki/File:Operating_system_placement.svg)
İşletim sisteminin katmanlı yapısı (https://en.wikipedia.org/wiki/File:Operating_system_placement.svg)

Eğlenceli Bilgi

Eskiden Dünya falan yoktu. Toz ve gaz bulutuydu vs. Sonrasında delikli kartlar hayatımıza girdi. Bir bilgisayara instruction’lar delikli kartlar sayesinde yükleniyordu. Bilgisayarlar bu delikleri ya da delik olmayan yerleri okuyup gerekli işlemleri yapıyordu. “Ya bilgisayar dediğin şey var ya 1’ler ve 0’lar işte.” mantığımız bir zamanlar da buydu: Delik ya da değil.^8 Tabii herhangi bir instruction’ı yanlış verdiyseniz, bütün sistem duruyordu. Çünkü programınız hatalı. Bazı hatalar için bütün kartı baştan oluşturmanız (delmeniz) gerekebilirdi. Bazen bir deliği unuttuğunuz fark eder, hemen delerdiniz. Bazen de “eyvah burayı yanlışlıkla delmişiz” durumuna düşer ve o deliğe “yama (patch)” yapardınız. Günümüzde kullanılan “yazılım yaması” kavramı, buradan geliyor.^9 Üzgünüm ama yamaların ilk olayı, hataları gidermekti. O yüzden bugün gördüğüm “Türkçe spiker yaması” ya da “GTA Kurtlar Vadisi yaması” bana komik geliyor. Madem fun fact paragrafına döndük, 9 Eylül 1947 15:45’te Harvard Mark II Aiken Relay Calculator cihazının F panelinin 70. rölesinde görülen bir “böcek (bug)”, programın çalışmasını durduğu gerekçesiyle kayıt altına alındı. Yani ilk bug, gerçekten de bir böcekti.^10 Edison’un bu kavramı daha önceden kullandığı biliniyor ama Edison’u sevmiyorum. Nikola TESLA!!!

Almanya'da öğrenciler delikli kartları kullanarak programlama yapıyor (1970) (Bundesarchiv, B 145 Bild-F031434-0006 / Gathmann, Jens / CC-BY-SA 3.0)
Almanya’da öğrenciler delikli kartları kullanarak programlama yapıyor. – 1970 (Bundesarchiv, B 145 Bild-F031434-0006 / Gathmann, Jens / CC-BY-SA 3.0)
Harvard Mark I makinesinde çalıştırılan kodlar. Yamalara dikkat edin. - 1944 (https://commons.wikimedia.org/wiki/File:Harvard_Mark_I_program_tape.agr.jpg)
Harvard Mark I makinesinde çalıştırılan kodlar. Yamalara dikkat edin. – 1944 (https://commons.wikimedia.org/wiki/File:Harvard_Mark_I_program_tape.agr.jpg)

Multiprogramming vs. Multitasking

İlerleyen zamanlarda bu bilgisayarlara birden fazla delikli kart seti yükleyebilir hâle geldik. Bu da bizi “batch system” dönemine getirdi. Bu dönemdeki bilgisayarlar “multiprogramming” çalışıyordu. Yani bir program çalışıyor, bitiyor. Sonrasında sıradaki program çalışıyordu. Günümüzde ise “multitasking (timesharing)” sistemler kullanıyoruz. Tüm kaynaklar, tüm işlemler arasında paylaştırılıyor – en azından deneniyor – ve her işlem, saniyenin belirli bir oranı kadar çalıştırılıp sonrasında bir sonraki işleme geçiliyor. Bu sayede aynı anda birden fazla işi yapabiliyoruz. Müzik dinlerken e-posta gönderebilmek, bunu yaparken arkaplanda bir şeyler indirmek, bir dosyayı başka bir yere kopyalamak, yazıcıdan çıktı almak gibi işleri düşünebilirsiniz. Tüm bu işler aynı anda yapılıyor gibi görünse de aslında birbirlerini bekleyerek ilerliyorlar. Sadece bunu fark etmiyoruz. Tüm bu süreci bize fark ettirmeden yöneten yazılım grubu ise işletim sistemidir.

Yazılım grubu diyorum, çünkü tipik bir işletim sistemi tek bir yazılım değildir. Çekirdeği (kernel), girdi/çıktı modülleri, bellek yönetim modülleri gibi farklı programları vardır. Daha kolay örnekler verelim. Kurduğunuz işletim sisteminin içerisinde notlarınızı yazabileceğiniz yazılımlar, medya oynatıcı, hesap makinesi, takvim, e-posta istemcisi, çeşitli ayarları yapabileceğiniz araçlar da geliyor. Bunların bütünü, işletim sistemini oluşturur. Bu nedenle bazı kaynaklarda işletim sistemi tanımı olarak “üreticinin size gönderdiği yazılım paketi” gibi ifadeler de görebilirsiniz.

İşletim Sistemini Özel Kılan Nedir?

İşletim sistemini diğer yazılımlardan ayıran bazı özellikleri şu şekilde sıralayabiliriz. Burada yazacağım özellikler, işletim sisteminin yalnızca bazı parçalarına ait olsa da, genele baktığımızda işletim sisteminin özelliği diyebiliriz:

  • Donanıma müdahale eder.
  • Bilgisayarın açılışından itibaren çalışmaya başlar. Bilgisayar kapanıncaya kadar çalışmaya devam eder (Bu tehlikeli bir cümle, bir bilgisayarın nasıl açıldığı ve kapandığı apayrı bir konu. Yeri ve zamanı geldiğinde çalışmaya başlar desek daha doğru olur.). Eğer çalışmaya devam edemezse, bilgisayarınızı kullanamaz hâle gelirsiniz. Sayın Windows kullanıcıları, mavi ekranı hatırladınız mı?
  • Donanımın büyük çoğunluğunu, kendini korumak için kullanır. Bilgisayarınızı kullanırken, şayet işletim sisteminin öncelikli bir işi varsa, önce bunu yapar. Size yanıt vermez. Başka bir deyişle, sistem kalp krizi geçiriyorken kolunu kaşımakla vakit harcamaz.
  • Kullanıcıdan ve diğer programlardan gelen istekleri alır ve uygun gördüğü takdirde işler. Yani aslında siz sadece işletim sistemini kullanıyorsunuz. Kalan bütün uygulamaları işletim sistemi kendisi kullanıyor.
  • İçerdiği – ya da sonradan eklenebilen – sürücüler sayesinde donanımınızı tanır ve kullanılabilir hâle getirir. Örneğin standart bir Android işletim sistemini alıp kişisel bilgisayarınıza kuramazsınız. Ya da kişisel bilgisayarınıza kurabildiğiniz Windows’u, Nokia 3310’a kuramazsınız. “Standart” kelimesini tekrar vurgulamak istiyorum.

Kamu spotu: Windows kullanmayın.

Cenevre Havalimanı'ndaki bir Windows cihazda görülen "Blue Screen of Death (BSoD)" - 1999 (https://en.wikipedia.org/wiki/File:Windows_NT_BSOD_at_GVA_baggage_claim,_1999-10-03.jpg)
Cenevre Havalimanı’ndaki bir Windows cihazda görülen “Blue Screen of Death (BSoD)” – 1999 (https://en.wikipedia.org/wiki/File:Windows_NT_BSOD_at_GVA_baggage_claim,_1999-10-03.jpg)

Bir Linux sistemden "Kernel Panic" mesajı. (https://commons.wikimedia.org/wiki/File:Kernel-panic.jpg)
Bir Linux sistemden “Kernel Panic” mesajı. (https://commons.wikimedia.org/wiki/File:Kernel-panic.jpg)

İşletim sisteminin görevleri konusunda da şu örnekleri verebiliriz:

  • Kullanıcı yazılımlarının çalıştırılması.
  • Yazılımlar arasında CPU, RAM gibi kaynakların paylaştırılması.
  • Yazılımların önceliklerinin ve çalıştırılma sıralarının belirlenmesi.
  • Hard disk, SSD gibi ortamlarda saklanan verinin yönetilmesi.
  • Çok çekirdekli, çok thread’li sistemlerde iş paylaşımının yapılması.
  • Klavye, fare, mikrofon, hoparlör, ekran gibi donanımlar üzerinden gelen girdilerin ya da bu donanımlar sayesinde verilen çıktıların yönetilmesi.

Sonuç Olarak

İşletim sistemleri apayrı bir uzmanlık alanı. Bu yazıda yer alan içerik aşırı büyük ölçüde eksik. Teknik detaydan da oldukça uzak. Sizlere tavsiyem, üniversitelerin İşletim Sistemi derslerinin kaynaklarını incelemeniz. Mümkünse bu konuda ders almanız. Sonrasında bir bilgisayarı kullanırken ona gerçekten daha farklı bakmaya başlayacağınızı düşünüyorum. Her ne kadar çok yüzeysel bilgiler olsa da, umarım birilerine faydalı olur.

Elimden geldiğince, bu konudaki alt başlıkları da inceleyen yazılar hazırlayacağım.

Bağlantılar

1 – A. M. TURING, I.—COMPUTING MACHINERY AND INTELLIGENCE, Mind, Volume LIX, Sayı 236, October 1950, Sayfa 433–460, https://doi.org/10.1093/mind/LIX.236.433

2 – The Imitation Game (2014) – https://www.imdb.com/title/tt2084970/

3 – Michael R. Swaine, Computer, https://www.britannica.com/technology/computer

4 – Computer (job description) https://en.wikipedia.org/wiki/Computer_(job_description)

5 – Prof. Dr. Aydın KÖKSAL, Yaşamöyküm, https://aydinkoksal.gen.tr/ozgecmis.html , https://aydinkoksal.gen.tr/gorseller/is-38.html

6 – Hardware Abstraction, https://en.wikipedia.org/wiki/Hardware_abstraction

7 – Von Neumann Architecture, https://en.wikipedia.org/wiki/Von_Neumann_architecture

8 – Computer Programming in the Punched Card Era, https://en.wikipedia.org/wiki/Computer_programming_in_the_punched_card_era

9 – Patch (Computing) https://en.wikipedia.org/wiki/Patch_(computing)#History

10 – The Origin of the Term Computer Bug, https://interestingengineering.com/the-origin-of-the-term-computer-bug