> İşlemci Tasarımları: İşlemciler iki ana sınıfa ayırabiliriz. Bunlar "CISC" ve "RISC" isimli sınıflardır. Aslında bu sınıflar birer spekturumdur. Dolayısıyla işlemciler, iki ucu bu sınıf olan bir spekturumun herhangi bir yerinde olabilir. Pekiyi bu iki sınıfın arasındaki farkılıklar nelerdir? Şöyleki; >> "CISC" işlemciler çok sayıda makina kodu barındırır. Bu komutların bazıları bir takım karmaşık işlemleri gerçekleştirmektedir. Halbuki "RISC" işlemcileri az sayıda makina kodu barındırır, böylelikle daha etkin çalışabilsin. >> "CISC" işlemcilerin makina kodları değişik uzunluklarda olabilmektedir. Öte yandan "RISC" işlemcilerinki aynı uzunluktadır. Dolayısıyla "CISC" işlemcilerinin makina kodlarının çalışma süresi değişkenlik gösterirken, "RISC" işlemcilerinin komutları genellikle aynı çalışma sürelerine sahiptir. >> "CISC" işlemcilerde genel amaçlı az sayıda "register" içerir. Fakat "RISC" işlemciler genel amaçlı çok sayıda "register" içerir. >> "CISC" işlemcilerindeki "pipeline" işlemcileri, "RISC" işlemcilerindeki kadar etkin YAPILAMAMAKTADIR. >> "CISC" işlemcileri doğrudan bellek üzerinde işlem yapan makina kodlarına sahiptir. Fakat "RISC" işlemcileri ise "load/store" şeklinde çalışmaktadır. Yani bellek üzerinde doğrudan işlem yapılmaz, önce işlemcinin "register" larına çekilir. >> "CISC" işlemcilerinin makina komutlarında iki operand bulunur. Dolayısıyla işlem sonucunda, işleme giren operandlardan birisinin değeri bozulmaktadır. Halbuki "RISC" işlemcilerinin makine komutları 3 operand bulunur ki bu üçüncü operand işlemin sonucunun yazılacağı operanddır. >> "CISC" işlemcileri daha çok güç harcama eğilimindeyken, "RISC" işlemcileri daha az güç harcama eğilimindedir. Günümüzde "RISC" işlemcilerinin tasarımının daha iyi bir tasarım olduğu kabul edilmektedir. Fakat Intel gibi firmalar, çok yaygın işlemci kullandığı için hala "CISC" tasarımını devam ettirmektedir. > İşlemci Mimarileri: Bugün çok işlemcili ve çok çekirdekli sistemlerde, işlemci-bellek bağlantısı olarak, iki farklı mimari kullanılmaktadır. Bunlar "SMP" ve "NUMA" isimli mimarilerdir. >> "SMP" : "Symmetric Multiprocessor" olarak da geçer. Bu mimaride işlemciler ya da çekirdekler aynı belleğe erişmektedir. Dolayısıyla iletişim çakışmasını engellemek için, bir işlemci ya da çekirdek belleğe eriştiğinde diğerleri beklemede kalmaktadır. Bu yüzdendir ki bu mimaride işlemci ya da çekirdek sayısı arttıkça performans da düşmeye başlayacaktır. Tabii bu mimaride her işlemci ya da çekirdek kendine ait özel bir "cache" sistemine sahiptir. İlk önce bu "cache" sistemine, sonrasında belleğe başvurulmaktadır. Bu yüzdendir ki "Cache Consistancy" donanımsal olarak da sağlanmaktadır. Günümüzde yaygın olarak bu mimari kullanılmaktadır. >>> "Cache Consistancy" : Bir işlemci ya da çekirdek kendisine ait "cache" ye bir şey yazdığında, belleğin o bölgesi de "cache" içerisinde mevcutsa ve o bölge başka bir işlemci ya da çekirdeğin "cache" sinde de varsa, diğer işlemci ya da çekirdeğe ait olan "cache" de tazelenmektedir. >> "NUMA" : "Non-Unified Memory Access" olarak da geçer. Bu mimaride her çekirdek ya da işlemcinin, bellekte bağımsız olarak erişebileceği ayrı bir "bank" i vardır. Her işlemci ya da çekirdek, kendi "bank" bölümüne hızlı erişirken diğer işlemci ya da çekirdeklerin "bank" bölgelerine yavaş erişir. Yani herkesin tuttuğu kendinedir. Bu nedenle işlemci ve çekirdeklerin belleğe erişim süreleri eriştikleri lokasyona bağlı olarak değişiklik göstermektedir. Günümüzde daha az kesim tarafından bu mimari kullanılmaktadır. Örneğin, Intel firmasının sunuculara yönelik ürettiği "Xeon" marka işlemciler. > UNIX/Linux Sistemlerinde Sinyal İşlemleri: Sinyal işlemleri UNIX/Linux sistemlerinde sistem programlama faaliyetlerinde yoğun bir biçimde kullanılmaktadır. Dolayısıyla iyi bilinmesi gerekmektedir. "thread" ler konusu kadar kapsamlı olmasa bile yine de kapsamlı bir konudur. Pekiyi nedir bu sinyal kavramı? Sinyaller, kesme mekanizmalarına da benzetilebilir. UNIX/Linux sistemlerde asenkron işlem yapılmasına olanak sağlarlar. Sinyaller normalde proseslere gönderilmektedir fakat "thread" kavramının da işletim sistemine eklenmesiyle "thread" lere de sinyal gönderilebilir. Fakat burada dikkat etmemiz gereken husus ise sinyali sadece kendi "thread" imize gönderebiliyor oluşumuzdur. Çünkü "thread" lerin ID değerleri yalnızda ait oldukları proseslerde anlamlıdırlar. Bir prosese sinyal geldiğinde prosesin akışı o anda kesilir ve ismine "Sinyal Fonksiyonu (Signal Function)" denilen bir fonksiyon çalıştırılır. Daha sonra bu fonksiyon bittiğinde akış tekrardan kaldığı yere geri döner ve o noktadan çalışmaya devam eder. Böylelikle bir işi yaparken araya başka işlemlerin girebilmesine olanak sağlamış oluyoruz. Pekiyi bir sinyal nasıl oluşur? Burada çeşitli durumlar söz konusudur. Örneğin, programcının yaptığı çeşitli ihlallerde, işletim sistemi tarafından prosese sinyal gönderilmektedir. Öte yandan bazı sinyaller ise bazı aygıt sürücüleri tarafından proseslere gönderilir. Örneğin, terminal aygıt sürücüsü prosesin ilişkin olduğu terminale, programcının "CTRL+C" ya da "CTRL+Backspace" tuşlarına basmasından dolayı, bazı sinyaller gönderebilmektedir. Benzer şekilde bir program da başka bir programa açıkça sinyal gönderebilmektedir. Örneğin, bazı POSIX fonksiyonları belirli şartlar oluştuğunda sinyal gönderebilmektedir. Diğer yandan bir sinyal bir prosese gönderildiğinde o prosesteki hangi "thread" in bu sinyali alacağı, POSIX standartlarınca, ilgili işletim sistemini yazanların isteğine bırakılmıştır. Bütün bunlara ek olarak, her bir sinyal bir numaraya sahiptir. POSIX sistemlerinde sinyallere ilişkin numaraların ne olacağı yine işletim sisteminin yazanların isteğine bırakılmıştır ancak taşınabilirlik sağlamak için bu sinyal numaraları "signal.h" başlık dosyası içerisinde "SIGXXX" içerisinde sembolik sabitler ile "define" edilmişlerdir. Dolayısıyla bu sembolik sabitleri kullanmalıyız. Bir diğer yandan, yukarıda da açıklandığı üzere, bir prosese sinyal geldiğinde prosesin akışı o anda kesilip ismine sinyal fonksiyonu denilen fonksiyona geçmektedir. Aslında bu durum tam olarak böyle değildir. Şöyleki; prosese bir sinyal gelmesi halinde, eğer programcı bir sinyal fonksiyonu "set" etmişse, yukarıda anlatılan senaryo gerçekleşmektedir. Eğer böyle bir sinyal fonksiyonu "set" EDİLMEMİŞSE, bu durumda "Varsayılan Eylem (Default Action)" uygulanmaktadır. Bu ise sinyalin ne olduğuna bağlıdır. Bazı sinyaller için bu aksiyon ilgili sinyalin görmezden gelinmesiyken, bazı sinyallerde prosesin sonlandırılması biçimindedir. Bazı sinyallerde ise bu aksiyon hem prosesin sonlandırılması hem de "core file" oluşturulması biçimindedir. Buradaki "core file" aslında teşhis amacıyla oluşturulan ve "debugger" altında incelenebilen özel dosyalardır. Dolayısıyla programcı bu varsayılan aksiyonları sinyal temelinde öğrenmesi gerekmektedir. Bütün bunlara ek olarak, bir sinyal oluştuğunda ilk önce bu sinyal hedef prosese "deliver" edilir. Daha sonra iş bu proses gelen bu sinyali mümkün olan en kısa zamanda işlemek ister. Eğer bir sinyal fonksiyonu da "set" edilmişse, bu fonksiyonu en kısa zamanda çağırmak isteyecektir. İşte bir prosese sinyalin geldiği an ile ilgili sinyal fonksiyonunun çağrılması arasındaki bu zamana ise sinyalin askıda olması, yani "pending" olması denmektedir. Eğer programın akışı sinyalin geldiği anda bir sistem fonksiyonu içerisindeyse ve bu sistem fonksiyonu da uzun sürecek bir eylem başlatmışsa ya da açıkça bloke olmuşsa, ilgili sistem fonksiyon işletim sistemi tarafından başarısızlıkla sonlandırır ve tez zamanda ilgili sinyal fonksiyonunu çağırır. Eğer bir POSIX fonksiyonu da gelen sinyalden dolayı başarısız olmuşsa, "errno" değişkeninin değeri "EINTR" değerine çekilecektir. Buradan da görüleceği üzere bir sinyal geldiği anda programın akışınının aniden kesilmesi gerçekleşmez, bir takım ön kontrollerden geçmektedir. Örneğin, bizler bir "pipe" üzerinden "read" yapmak isteyelim ancak okunacak hiç "byte" bulunmasın. Bu durumda "read" fonksiyonu blokeye yol açacaktır. İşte bu sırada bir sinyal gelirse, "read" fonksiyonu başarısızlıkla geri döner ve "errno" değişkeni "EINTR" değerini alır. Bu tür durumlarda bizler ilgili fonksiyonun başarısız olma nedeninin gelen sinyalden olduğunu anlayıp, onu tekrardan çağırmak da isteyebiliriz. Şöyleki: while((result = read(...)) == -1 && errno == EINTR) ; if(result == -1) exit_sys("read"); Pek tabii programcı bu tür POSIX fonksiyonlarının otomatik olarak yeniden çağrılmasını da sağlayabilir. Eğer bu sağlanmışsa, ilgili fonksiyon hiç geri dönmez ancak ilgili sinyal fonksiyonu da çalıştırılır. Bu otomatik yeniden çağırma işlemi ise kütüphane tarafından değil, bizzat çekirdek tarafından gerçekleştirilir. Tabii bu durum her POSIX fonksiyonu için de geçerli değildir. Buradaki husus ilgili sistem fonksiyonunun ya da onu çağıran POSIX fonksiyonunun "yavaş bir fonksiyon" olması gerekmektedir. Yani blokeye yol açabilecek sistem fonksiyonlarıdır. Bir diğer deyişle programcı, çağırdığı sistem fonksiyonunun ya da POSIX fonksiyonunun bir sinyal karşısındaki davranışını da BİLMEK ZORUNDADIR. Tabii bir sinyal için sinyal fonksiyonu atanmamışsa ve varsayılan aksiyon da görmezden gelinmesiyse, bu durumda ilgili sistem fonksiyonunun ya da POSIX fonksiyonunun başarısız olmasının, o fonksiyonu yeniden çağırmanın da bir önemi kalmamaktadır.