> Kabuk üzerinde "pipe" işlemleri: Bu konunun detaylarına ileride değineceğiz fakat şimdilik sadece bir giriş yapmak gerekirse; kabuk üzerinden "a | b" şeklinde bir komut çalıştıralım. Burada "a" ve "b" iki ayrı program olsun. Kabuk, "a" programının dosya betimleyici tablosundaki bir indisli betimleyiciyi kullanarak yazdığı şeyleri, "b" programı sanki sıfır indisli betimleyiciyi kullanarak okumuş havası estirmektedir. Yani "a" nın ekrana yazdığı şeyleri, sanki "b" klavyeden okuyormuş gibi bir etki oluşturuyor. Burada ilgili "a" ve "b" programlarına argüman da geçebiliriz. O zaman "a aa aaa | b bb bbb" biçiminde bir komut girmemiz gerekiyor ki burada "aa" ve "aaa" argümanları "a" programına aitken "bb" ve "bbb" argümanları ise "b" programına ait olacaktır. * Örnek 1, /* a.c */ #include #include #include #include void exit_sys(const char* msg); int main() { /* # OUTPUT # */ for(int i = 0; i < 10; ++i) printf("%d\n", i); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } /* b.c */ #include #include #include #include void exit_sys(const char* msg); int main() { /* # OUTPUT # */ int val; while(scanf("%d", &val) == 1) printf("%d\n", val); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } Komuk Komutu >>> "./a | ./b" <<< Çıktı >>> 1 2 3 4 5 6 7 8 9 0 Öte yandan, "stdout" indisini kullanan iki programı, bu şekilde çalıştırdığımız zaman, her iki program da "pipe" mekanizmasından faydalanır fakat sadece bir tanesininki ekrana basılır. Örneğin, "ls -l | ps -e" komutunu çalıştırdığımız zaman sadece "ps" isimli programın ekrana bastığı görülür. Bu demek değildir ki "ls" programı çalıştırılmadı. O da çalıştırıldı fakat "ps" programı okuma yapmadı. Buradan da diyebiliriz ki gönderilen bilgileri kullanmak zorunda değiliz. Bir diğer örnek; "ps -e | wc sample.c". Bu komutun çıktısı "sample.c" dosyasının kelime adedi olacaktır. Bu durumda "ps -e" komutunun çıktısı yine "pipe" a yönlendirilir fakat "wc" komutu "stdin" dosyasından okuma yapmayacağı için bu bilgileri işleme sokmayacaktır. UNIX/Linux sistemlerindeki dosya yol ifadesi alan POSIX kabuk komutlarına bir yol ifadesi verilmezse, bu komutlar "stdin" dosyasından okuma yapacak biçimde tasarlanmışlardır. Örneğin, "cat" komutu bir dosyanın içeriğini "stdout" dosyasına yazdırır ancak bu komuta argümansız kullanılırsa, okumayı "stdin" dosyasından yapacaktır. Dolayısıyla klavyeden okunan her şey direkt olarak ekrana yazılacaktır. Benzer şekilde "wc" isimli program da aynı etkiyi gösterecektir. Argüman geçmediğimiz zaman, klavyeden okuduklarına "word count" işlemi uygulayacaktır. Eğer "ps -e | wc" biçiminde bir kabuk komutu çalıştırırsak; "ps" nin ekrana yazdıklarını "wc" sanki klavyeden okuyormuş gibi yapacak. Dolayısıyla bizler bu yöntem ile o an çalışan proseslerin adedini bu şekilde öğrenebiliriz. Benzer şekilde "ps -e | more" komutunu çalıştırdığımız zaman, "ps -e" komutunun ekrana yazdırdıkları sayfa sayfa görüntülenecektir. Boru işlemleri de yinelemeli olarak kullanabiliriz. Örneğin, "a | b | c". Burada "a" nın ekrana yazdıklarını "b" klavyeden okuyormuş gibi yapıyor, "b" nin ekrana yazdıklarını da "c" programı klavyeden okuyacaktır. Eğer "pipe" mekanizması işletilmeseydi, üçüncü bir dosyaya/dosyalara ihtiyacımız vardır. Örneğin, ps -e > temp.txt wc temp.txt rm temp.txt > İşlemcilerin Koruma Mekanizması: Özünde iki temel şeyden oluşmaktadır. Bilindiği üzere prosesler RAM üzerinde çalışmaktadırlar. Bir proses, başka bir prosesin alanına girdiği zaman, o alandakileri casusluk amacıyla izleyebilir ya da oradaki bilgileri değiştirebilir ki bu iki durum da felakete yol açacaktır. İşte işlemcileri tasarlayanlar bunlar yapılamasın diye bir koruma mekanizması geliştirmişlerdir. Bu mekanizma hem proseslerin birbirlerinin alanlarına geçmesini engellemekte hem de tehlikeli "assembly" komutların çalıştırılmasının önüne geçmektedir. Fakat her türlü işlemcide böyle bir koruma mekanizmasının varlığından söz edilemez. Ek olarak "multi-processing" işlemcilerde bu mekanizmaya ihtiyaç duyulmaktadır. Çünkü diğer türlü işlemci zaten o anda bir adet prosesi çalıştırdığı için, o prosesin etki alanını bilmesine gerek yoktur. Örneğin, gömülü sistemlerde eğer "multi-processing" yapmıyorsak işlemcilerin koruma mekanizmasına ihtiyacımız yoktur çünkü zaten bir adet proses çalışmaktadır. Windows, Linux ve macOS işletim sistemlerini yükleyebilmek için işlemcinin koruma mekanizması olması gerekmektedir. İş bu koruma mekanizması, işletim sisteminin kendisini de olaya dahil etmemelidir. Yani işletim sistemleri, bu koruma mekanizmasına takılı kalmaması gerekmektedir. Peki bu mekanizma nasıl çalışmaktadır? CPU bu tip ihlalleri fark ettiği anda olayı direkt olarak işletim sistemine bildiriyor ve cezayı işletim sistemi prosesi derhal sonlandırarak kesiyor. * Örnek 1, koruma mekanizmasının ihlal eden bir program: #include int main() { /* # OUTPUT # */ char* str = (char*)0x1fc345; // putchar(*str); // Segmentation Fault return 0; } İşlemcilerin koruma mekanizmasına takılmayan bir diğer etmenler de özel proseslerdir. Bu özel prosesler işletim sisteminin kendi kodları ve aygıt sürücülerinin kodlarıdır. İş bu özel proseslere de "kernel mode" prosesler denmektedir. Bu grup haricindeki diğer üçüncü parti prosesler ki bunlara "user mode" prosesler denir, işlemcilerin koruma mekanizmasına takılmaktadır. Proseslerin "mode" bilgisi, "sudo" komutundan bağımsızdır. "sudo" da hakeza "user mode" bir program olup, dosyalara erişim konusunda ilgili prosese avantaj sağlamaktadır. Yani bir prosesi "sudo" ile çalıştırdığımız zaman o proses "kernel mode" a dönüşmemektedir. Proseslerin sahip olduğu "kernel mode", "user mode" bilgileri işlemcileri tasarlayan kişiler tarafından tasarlanmıştır. Dolayısıyla bazı işlemcilerde bu "mode" sayısı dörde kadar çıkabilmektedir. Bizler kendi programlarımızı "kernel mode" olarak genel olarak çalıştıramayız. Bunu yapmanın tek yolu, yukarıda da belirtildiği üzere, aygıt sürücüsü biçiminde kod yazmaktır. Aygıt sürücülerin yüklenmesi için "root" şifresi gerektiğinden, yine koruma sağlanmış oluyor. Bu anlatılanlara ek olarak birde şöyle bir durum vardır: sistem fonksiyonlarını çağıran prosesler, sistem fonksiyonunun işi bitene kadar, "kernel mode" seviyesine çıkartılırlar. İlgili sistem fonksiyonlarının işi bittiğinde tekrardan "user mode" seviyesine çekilirler fakat bu işlem zaman alan bir işlemdir. O halde diyebiliriz ki prosesler ömürlerinin bazı kısımlarını "user mode", bazı kısımlarını "kernel mode" olarak geçirmektedir. Bunu hesaplamak için "time" komutunu kullanabiliriz. Bu komuta çalıştırılan prosesin adını geçmemiz yeterli olacaktır. Örneğin, "time ./sample" komutu çalıştırdığımız zaman aşağıdaki çıktıyı alacağız: real 0m0,134s user 0m0,001s sys 0m0,001s Buradaki "sys", "kernel mode"; "user", "user mode"; "real" ise total zamanı temsil etmektedir.