Interlude: Process API
Geçen hafta process’in ne olduğunu gördük. Bu hafta bir programın process’leri nasıl oluşturup yönettiğini inceleyeceğiz.
Bir program başka bir işi başlatmak isteyebilir. Başlattığı işin bitmesini bekleyebilir veya çalıştırdığı programı değiştirebilir. Unix, bu ihtiyaçlar için fork(), wait() ve exec() arayüzlerini sunar.
Program, process ve process durumları.
Yeni process oluşturma, bekleme ve program değiştirme.
İşletim sisteminin CPU kontrolünü nasıl koruduğu.
Interlude: Process API
Kısa hatırlatma: diskte duran program ile çalışmakta olan process farklıdır.
Program, yapılacak adımların ve verilerin saklandığı dosyadır. Process (işlem), o programın çalışmakta olan örneğidir. İşletim sistemi, her process’in belleğini ve çalışmasının hangi noktada kaldığını izler.
Interlude: Process API
Kısa hatırlatma: CPU’da çalışmak, CPU sırasını beklemek ve bir olay beklemek farklıdır.
| Durum | Process neden bu durumda? | Somut örnek |
|---|---|---|
| Running (çalışıyor) | Şu anda CPU komutlarını yürütüyor. | Hesaplama yapılıyor. |
| Ready (hazır) | Çalışabilir; CPU’nun kendisine verilmesini bekliyor. | Hesaplama yapacak, fakat sıra başka process’te. |
| Blocked (engellenmiş) | İlerlemek için başka bir olayın gerçekleşmesini bekliyor. | Dosyadan istenen veri henüz gelmedi. |
Interlude: Process API
Bir program, yeni process oluşturmak için işletim sisteminden hizmet ister.
API (uygulama programlama arayüzü), bir yazılımın sunulan hizmetleri nasıl isteyeceğini belirtir. System call (sistem çağrısı), programın işletim sistemine yaptığı hizmet isteğidir. İsteği, işletim sisteminin bu işleri yöneten temel bölümü olan kernel (çekirdek) işler.
Interlude: Process API
Üç farklı ihtiyaç için üç farklı işlem kullanacağız.
| Programın ihtiyacı | Kullanılan arayüz | Ne değişir? |
|---|---|---|
| “Ben çalışırken başka bir process de oluşsun.” | fork() | Yeni bir process eklenir. |
| “Başlattığım iş tamamlanmadan bu noktadan ilerlemeyeyim.” | wait() | Mevcut process gerekirse bekler. |
| “Bu process artık başka bir programı çalıştırsın.” | exec() | Process’in çalıştırdığı program değişir. |
5.1 The fork() System Call
fork(), çalışan bir process’ten yeni bir process oluşturur.
Yeni process’i oluşturan process’e parent process (ebeveyn işlem), yeni oluşturulana child process (çocuk işlem) denir. Başlangıçta ikisi aynı programın çalışma durumuna çok benzer. Sonrasında ayrı ayrı ilerleyebilirler.
5.1 The fork() System Call
İşletim sistemi, birbirine benzeyen parent ve child’ı ayrı kimliklerle izler.
Process ID / PID (işlem kimliği), işletim sisteminin process’e verdiği numaradır. Aynı programı çalıştırsalar da parent ve child’ın PID’leri farklıdır.
5.1 The fork() System Call
Child, programın en başından başlamaz; fork() yapılan noktadan devam eder.
Bir defterin beşinci satırına kadar yazdığımızı düşünelim. O noktada defteri kopyalarsak iki defter de aynı yerde kalmıştır. Parent ve child da başlangıçta aynı ilerleme noktasına sahiptir. İkisi de fork() çağrısının sonrasındaki adımları sürdürür.
5.1 The fork() System Call
Kopyanın başlangıcı aynı olsa da parent ve child’ın çalışma verileri ayrı ilerler.
Fork anında iki process’in özel belleğindeki değerler aynı başlayabilir. Bundan sonra parent’ın kendi değişkenini değiştirmesi, child’ın özel belleğindeki aynı değişkeni kendiliğinden değiştirmez.
Parent’ın belleği
Başlangıç değeri: 100
Parent kendi değerini değiştiriyor: 200
Child’ın belleği
Başlangıç değeri: 100
Parent’ın değişikliği sonrası kendi değeri: 100
5.1 The fork() System Call
Aynı noktadan devam eden iki process, hangisinin parent hangisinin child olduğunu nasıl anlar?
System call, sonucunu bir sayı olarak bildirir. Buna return value (dönüş değeri) denir. fork(), parent ve child’a farklı dönüş değerleri verir. Böylece program, iki process’e farklı işler yaptırabilir.
| Sonucu alan | fork() dönüş değeri | Program bu bilgiyi nasıl kullanır? |
|---|---|---|
| Parent | Child’ın PID’si; örneğin 121 | “Ben parent’ım; yeni child’ın kimliği 121.” |
| Child | 0 | “Ben yeni oluşturulan child’ım.” |
| Oluşturma başarısızsa | −1 | “Yeni child oluşmadı; hatayı ele almalıyım.” |
5.1 The fork() System Call
Parent ve child çalışmaya hazır olduğunda, ilk mesajı hangisinin yazacağı garanti edilmez.
Scheduler (zamanlayıcı), CPU’nun hangi ready process’e verileceğini seçer. fork(), parent ve child mesajları arasında zorunlu bir sıra kurmaz. Seçim rastgele veya iki process için eşit olasılıklı olmak zorunda değildir.
Parent: devam ediyorum. Child: devam ediyorum.
Child: devam ediyorum. Parent: devam ediyorum.
Interlude: Process API
Kod örneklerini okumak için önce birkaç C işaretini açıklayalım.
C kaynak kodu, programın yazılı adımlarıdır. Compiler (derleyici), bu metni çalıştırılabilir programa çevirir. Function (fonksiyon), belirli bir işi yapan ve adıyla çağrılabilen kod parçasıdır. “Derle ve çalıştır” düğmesi programı hazırlayıp başlatır.
| Kodda göreceğimiz ifade | Anlamı |
|---|---|
#include … | Hazır hizmetlerin C içinde kullanılabilmesi için gerekli bildirimleri ekler. |
main(…) | Bu örneklerde programın ana adımlarının bulunduğu bölümdür. |
printf(…) | Verilen mesajı programın çıktısına yazan hazır bir fonksiyondur. |
// açıklama | İnsanların okuması için yorumdur; program bu metni çalıştırmaz. |
5.1 The fork() System Call
fork() sonucu nasıl saklanır ve hangi dalın çalışacağı nasıl seçilir?
Variable (değişken), programın bir değeri bir ad altında sakladığı yerdir. int rc = fork(); ifadesinde int tam sayı türünü, rc saklama adını belirtir. =, çağrıdan gelen sonucu rc’ye koyar.
| Koşul | Nasıl okunur? | Hangi durum? |
|---|---|---|
rc < 0 | rc sıfırdan küçük mü? | Oluşturma hatası. |
rc == 0 | rc sıfıra eşit mi? | Child içindeyiz. |
else | Önceki koşullar sağlanmadıysa bu bölüm. | Bu örnekte parent içindeyiz. |
if koşulu denetler; süslü parantezler { } o bölümün adımlarını gruplar. = değer saklar; == eşitlik kontrolü yapar.5.1 The fork() System Call
p1.c: bir process’ten parent ve child oluşmasını gözlemleyin.
- printf → fork()
İlk mesaj yazılır. Fork başarılı olursa mevcut parent’a bir child eklenir.
- rc == 0
Child bu dala girer. getpid() ile kendi PID’sini yazdırır.
- else
Parent bu dala girer. rc child’ın PID’si, getpid() parent’ın kendi PID’sidir.
- return 0
Her process kendi yürütmesini normal biçimde bitirir. Mesajların sırası ayrıca garanti edilmez.
sleep(1); ekleyin. Sıfırlayıp aynı gecikmeyi child’a ekleyin. Üç çıktıyı karşılaştırın.#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
printf("hello (pid:%d)\n", (int) getpid());
int rc = fork();
if (rc < 0) {
// fork başarısız oldu.
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) {
// Yeni oluşturulan child process bu dalı yürütür.
printf("child (pid:%d)\n", (int) getpid());
} else {
// Parent process bu dalı yürütür.
printf("parent of %d (pid:%d)\n",
rc, (int) getpid());
}
return 0;
}
Linux hazır olduğunda kodu çalıştırın. Çıktı bu alanda görünecek.
5.2 The wait() System Call
Parent bazen child’ın bitirdiği işi kullanmak için beklemek zorundadır.
Örneğin parent, child’a bir dosyanın satır sayısını hesaplattıracak olsun. Parent sonucu kullanacaksa child tamamlanmadan sonraki işe geçmesi doğru olmaz. wait(), child’ın tamamlanmasını beklemek için kullanılır.
5.2 The wait() System Call
wait() içindeki parent, child tamamlanana kadar blocked olabilir.
Parent wait() isteğini yapar.
Child bitmediği için parent’ın yürütülmesi bekletilir.
Parent artık ilerleyebilir; ready olur.
Scheduler seçtiğinde parent wait() sonrasından devam eder.
5.2 The wait() System Call
Beklemek, CPU üzerinde sürekli “bitti mi?” diye sormak zorunda değildir.
Parent blocked durumdayken komutlarını yürütmez. CPU başka bir ready process için kullanılabilir. Child tamamlandığında işletim sistemi parent’ın devam edebileceğini kaydeder. Böylece parent, beklediği süre boyunca hesaplama yapmaz.
5.2 The wait() System Call
Belirli süre beklemek ile bir işin tamamlanmasını beklemek farklıdır.
| Yöntem | Beklenen koşul | Sonuç |
|---|---|---|
| sleep() (belirli süre bekleme) | Örneğin bir saniyenin geçmesi. | Child’ın tamamlandığına dair bir ilişki kurmaz. |
wait() | Child’ın tamamlanması. | Başarılı çağrıdan sonra ilgili child’ın tamamlandığını biliriz. |
5.2 The wait() System Call
p2.c: parent’ın son mesajı child tamamlandıktan sonra gelir.
- fork() ve child dalı
Child kendi mesajını yazar; sonra programı bitirir.
- wait(NULL)
Parent, child bitmediyse bekletilir. NULL çıkış durumunun ayrı saklanmadığını belirtir.
- rc_wait
Başarılı wait, tamamlanan child’ın PID’sini verir. Bu örnekte rc ile aynı kimliktir.
- Parent’ın printf’i
Wait sonrasında olduğundan, başarılı çağrıda child tamamlanmadan bu mesaj yazılamaz.
sleep(1); ekleyin. Parent’ın mesajı yine bekliyor mu? Sonra int rc_wait = wait(NULL); satırını int rc_wait = 0; ile değiştirip karşılaştırın.#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[]) {
printf("hello (pid:%d)\n", (int) getpid());
int rc = fork();
if (rc < 0) { // fork başarısız oldu; programı sonlandır.
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) { // Yeni oluşturulan child process bu dalı yürütür.
printf("child (pid:%d)\n", (int) getpid());
} else { // Parent process bu dalı yürütür.
int rc_wait = wait(NULL);
printf("parent of %d (rc_wait:%d) (pid:%d)\n",
rc, rc_wait, (int) getpid());
}
return 0;
}
Linux hazır olduğunda kodu çalıştırın. Çıktı bu alanda görünecek.
5.3 Finally, The exec() System Call
fork() sonrasında child başlangıçta aynı programı çalıştırır. Başka bir program çalıştırmak için exec() gerekir.
exec(), mevcut process’in belleğindeki programı başka bir programla değiştirir. Yeni programın komutları ve verileri yüklenir. Process artık bu yeni programın işini yapmaya başlar.
5.3 Finally, The exec() System Call
exec(), process’in kimliğini değil çalıştırdığı programı değiştirir.
Program image (program görüntüsü), process’in bellekteki program içeriğidir. exec() bu içeriği değiştirir; PID aynı kalır. Aynı çalışma masasında önce başka bir işin, sonra yeni bir işin talimatlarını açmak gibi düşünün.
5.3 Finally, The exec() System Call
Yeni program başarıyla yüklendiyse eski programın sonraki adımları çalışmaz.
| Durum | Ne olur? | Eski program devam eder mi? |
|---|---|---|
| exec() başarılı | Yeni program yüklenir ve yürütülür. | Hayır. Process artık yeni programı çalıştırır. |
| exec() başarısız | Örneğin istenen program bulunamaz; hata bildirilir. | Evet. Eski program hatayı ele alacak noktaya döner. |
5.3 Finally, The exec() System Call
p3.c: child artık wc programını çalıştırır.
- myargs[0] ve myargs[1]
Wc dosyanın satır, sözcük ve bayt sayılarını hesaplar. Listede program adı wc, okunacak dosya p3.c olarak hazırlanır.
- myargs[2] = NULL
Bu işaret argüman listesinin bittiğini belirtir.
- execvp(myargs[0], myargs)
Child’ın programı wc ile değiştirilir. PID aynı kalır.
- Exec sonrası mesaj
Başarılı yüklemede eski printf çalışmaz. Yükleme başarısızsa eski kod devam edebilir.
- Parent’ın wait’i
Parent, aynı child’ın artık wc olarak yürüttüğü işin bitmesini bekler.
"wc" yerine var olmayan bir ad yazın. Exec başarısız olduğunda eski programın sonraki mesajı görünüyor mu?#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char *argv[]) {
printf("hello (pid:%d)\n", (int) getpid());
int rc = fork();
if (rc < 0) { // fork başarısız oldu; programı sonlandır.
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) { // Yeni oluşturulan child process bu dalı yürütür.
printf("child (pid:%d)\n", (int) getpid());
char *myargs[3];
myargs[0] = strdup("wc"); // Çalıştırılacak programın adı: "wc".
myargs[1] = strdup("p3.c"); // Programa argüman olarak verilecek girdi dosyası.
myargs[2] = NULL; // NULL, argüman dizisinin sonunu belirtir.
execvp(myargs[0], myargs); // wc programını mevcut process içinde çalıştır.
printf("this shouldn’t print out");
} else { // Parent process bu dalı yürütür.
int rc_wait = wait(NULL);
printf("parent of %d (rc_wait:%d) (pid:%d)\n",
rc, rc_wait, (int) getpid());
}
return 0;
}
Linux hazır olduğunda kodu çalıştırın. Çıktı bu alanda görünecek.
5.4 Why? Motivating The API
Unix’te bir komut yazdığımızda onu çalıştıran program shell’dir.
Terminal (uçbirim), metinle giriş ve çıkış yaptığımız arayüzdür. Shell (kabuk), komutlarımızı okuyup gerekli programları başlatan yazılımdır. Shell de bir process olarak çalışır ve bunun için işletim sisteminden hizmet ister.
5.4 Why? Motivating The API
Shell, fork(), exec() ve wait() işlemlerini bir komut çalıştırmak için birleştirir.
wc rapor.txt komutu, rapor.txt dosyasının satır, sözcük ve bayt sayılarını hesaplayan wc programını ister. Shell, kullanıcıya yeni komut yazma fırsatı vermeden önce bu işin tamamlanmasını bekleyebilir.
Shell, programı ve dosya adını belirler.
Shell’in child’ı oluşturulur.
Child, wc programını yükleyip dosyayı sayar.
Parent shell, child’ın bitmesini bekler; sonra yeni komut alır.
5.4 Why? Motivating The API
Bir programın normal çıktısını ekranda görmek yerine dosyada saklamak isteyebiliriz.
Programın normal sonuçlarını yazdığı bağlantıya standard output / stdout (standart çıktı) denir. Redirection (yönlendirme), bu bağlantının hedefini değiştirmektir. Program stdout’a yazmaya devam eder; hedef dosya olabilir.
wc rapor.txt > sonuc.txt komutunda wc rapor.txt’yi sayar. > sonuc.txt bölümü, normal çıktının sonuc.txt dosyasına yazılmasını ister.5.4 Why? Motivating The API
File descriptor, process’in açık bir dosya veya veri bağlantısı için kullandığı numaradır.
File descriptor (dosya tanıtıcısı), bir bağlantının numaralı etiketi gibi düşünülebilir. Program bu numarayla okuma veya yazma ister. İşletim sistemi o numaranın hangi dosyaya ya da bağlantıya karşılık geldiğini bilir.
| Numara | Standart bağlantı | Tipik yön |
|---|---|---|
| 0 | Standard input / stdin (standart girdi) | Programın veri okuduğu bağlantı. |
| 1 | stdout | Programın normal sonucunu yazdığı bağlantı. |
| 2 | Standard error / stderr (standart hata) | Programın hata mesajlarını yazdığı bağlantı. |
5.4 Why? Motivating The API
Shell, child’ın stdout hedefini değiştirdikten sonra yeni programı yükler.
Shell fork() ile yeni process oluşturur.
Child’ın 1 numaralı bağlantısı sonuç dosyasına yönlendirilir.
Child exec() ile wc programını çalıştırır.
wc stdout’a yazar; veri sonuç dosyasına gider.
5.4 Why? Motivating The API
p4.c: stdout dosyaya bağlanır, sonra wc yüklenir.
- close(STDOUT_FILENO)
Child, stdout’un 1 numaralı bağlantısını kapatır.
- open("./p4.output", …)
Dosya yazmak için açılır. Bu örnekte en küçük boş numara 1 olduğundan stdout dosyaya bağlanır.
- O_CREAT | O_WRONLY | O_TRUNC
Dosya yoksa oluştur, yazmak için aç ve eski içeriği boşalt seçenekleri birlikte kullanılır.
- execvp() → wc
Yeni program stdout’a yazar. Korunan bağlantı nedeniyle çıktı p4.output dosyasına gider.
- cat p4.output
Laboratuvar bu ayrı komutla dosyayı okur; görünen sonuç wc’nin doğrudan terminal çıktısı değildir.
O_TRUNC yerine O_APPEND yazıp iki kez çalıştırın. O_APPEND, eski içeriği silmek yerine dosyanın sonuna ekler. Sonuçları karşılaştırın.#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>
int main(int argc, char *argv[]) {
int rc = fork();
if (rc < 0) {
// fork başarısız oldu.
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) {
// Child içinde stdout bağlantısını bir dosyaya yönlendir.
close(STDOUT_FILENO);
open("./p4.output", O_CREAT|O_WRONLY|O_TRUNC,
S_IRWXU);
// Şimdi exec ile wc programını yükle.
char *myargs[3];
myargs[0] = strdup("wc"); // Çalıştırılacak programın adı: wc.
myargs[1] = strdup("p4.c"); // Satır, sözcük ve bayt sayıları hesaplanacak dosya.
myargs[2] = NULL; // NULL, argüman dizisinin sonunu belirtir.
execvp(myargs[0], myargs); // wc programını mevcut process içinde çalıştır.
} else {
// Parent process bu dalı yürütür.
int rc_wait = wait(NULL);
}
return 0;
}
Linux hazır olduğunda kodu çalıştırın. Çıktı bu alanda görünecek.
5.4 Why? Motivating The API
Bir programın ürettiği veriyi başka bir programa doğrudan verebiliriz.
Pipe (boru), bir process’in çıktısını başka bir process’in girdisine taşıyan veri kanalıdır. İlk programın stdout’u ile ikinci programın stdin’i birbirine bağlanır.
5.4 Why? Motivating The API
Pipe üzerinden gelen satırları sayalım.
grep -o foo file, file dosyasındaki her foo eşleşmesi için bir satır üretir. wc -l, gelen satırları sayar. | işareti iki programı pipe ile bağlar.
file dosyasında “foo” ve “bar foo” satırları bulunur. Toplam iki foo eşleşmesi vardır.
5.5 Process Control And Users
Çalışan bir process’e durması, devam etmesi veya sonlanması için bildirim gönderilebilir.
Signal (sinyal), process’e gönderilen olay bildirimidir. kill komutu signal gönderir; adı, her durumda process’i sonlandırdığı anlamına gelmez.
| İstek | Tipik araç | Beklenen davranış |
|---|---|---|
| Çalışmayı kes | Ctrl+C → SIGINT | Varsayılan olarak process sonlanır. |
| Şimdilik duraklat | Ctrl+Z → SIGTSTP | Process durur; daha sonra devam ettirilebilir. |
| Yetkiyi denetle | User (kullanıcı) kimliği | Kernel, bu process’i kontrol etme iznini denetler. |
5.6 Useful Tools
İşletim sisteminin tuttuğu process bilgilerini araçlarla görebiliriz.
ps, o anda bulunan process’lerin listesini gösterir. top, process’leri ve kaynak kullanımlarını zaman içinde izlememizi sağlar. Geçen hafta gördüğümüz process kayıtlarının bir kısmı bu araçlarla görünür olur.
| Örnek PID | Örnek durum | Nasıl yorumlarız? |
|---|---|---|
| 410 | Running | Şu anda CPU üzerinde çalışıyor. |
| 411 | Ready | Çalışabilir; CPU sırasını bekliyor. |
| 412 | Blocked | İlerlemek için bir olay bekliyor. |
5.6 Useful Tools
Process kayıtlarını aynı Linux ortamında inceleyin.
Komutların anlamı
Önce p1.c örneğini derleyip çalıştırın. Terminal aynı Linux’taki programı yeniden çalıştırabilir.
5.7 Summary
Bir komutun çalıştırılmasını artık process’ler üzerinden açıklayabiliriz.
fork() parent’a ek olarak child oluşturur.
exec() mevcut process’in çalıştırdığı programı değiştirir.
wait() parent’ın child’ı beklemesini sağlar.
fork() ile exec() aynı işi mi yapar?
Hayır. fork() process sayısını artırır. exec() yeni process eklemeden mevcut process’in programını değiştirir.
Shell neden fork() ile exec() arasında hazırlık yapar?
Child’ın veri bağlantılarını düzenlemek için. Örneğin stdout’u dosyaya bağlayıp sonra yeni programı yükleyebilir.
Mechanism: Limited Direct Execution
Geçen hafta CPU’nun process’ler arasında paylaşıldığını gördük. Şimdi bunun nasıl yapılabildiğini inceleyeceğiz.
Mechanism (mekanizma), bir işi gerçekleştiren teknik yöntemdir. Policy (politika), hangi seçimin yapılacağını belirleyen kuraldır. CPU’yu başka bir process’e aktarabilmek mechanism; sıradaki process’i seçmek policy konusudur.
6.1 Basic Technique: Limited Direct Execution
Normal hesaplamaları programın kendisi CPU üzerinde yürütür.
Direct execution (doğrudan yürütme), programın komutlarının CPU tarafından çalıştırılmasıdır. Her toplama için kernel’in ayrı bir karar vermesi gerekmez. Bu yürütmeye yetki ve kontrol sınırları ekleyen yaklaşım limited direct execution (sınırlı doğrudan yürütme) olarak adlandırılır.
6.1 Basic Technique: Limited Direct Execution
Yalnızca programı başlatıp bitmesini beklemek, işletim sistemi için yeterli değildir.
Programın belleği ve process kaydı hazırlanır.
CPU programın komutlarını yürütür.
Program sonlanınca kaynakları temizlenir.
| Karşılaşabileceğimiz durum | Bu basit modelin eksikliği |
|---|---|
| Program izin verilmemiş bir kaynağa erişmek istiyor. | Neyi yapabileceğini sınırlandıran adım yok. |
| Program hiç bitmeden hesaplama yapmaya devam ediyor. | CPU’yu geri alıp başka process’e verme yolu yok. |
6.2 Problem #1: Restricted Operations
Bir program dosya okuyabilmeli; fakat bütün diski denetimsiz yönetememelidir.
Diskte farklı kullanıcıların dosyaları ve işletim sisteminin kendi verileri bulunabilir. Her programın bunların hepsine sınırsız erişmesi doğru olmaz. İşletim sistemi, istenen işlemi ve erişim iznini denetledikten sonra gerekli hizmeti yapar.
6.2 Problem #1: Restricted Operations
Aynı CPU, farklı yetki düzeyleriyle çalışabilir.
User mode (kullanıcı modu), uygulamaların sınırlı yetkiyle çalıştığı düzeydir. Kernel mode (çekirdek modu), kernel’in korunan sistem işlerini yapabildiği düzeydir. Bu ayrım CPU tarafından uygulanır.
User mode
Program kendi hesaplamalarını yapar.
Korunan hizmetler için kernel’den istekte bulunur.
Kernel mode
Kernel, isteği ve yetkiyi denetler.
İzin verilen sistem hizmetini gerçekleştirir.
6.2 Problem #1: Restricted Operations
User mode’daki program, yapamadığı sistem işi için system call kullanır.
“Bu dosyayı açmak istiyorum.”
Dosya var mı? Programın erişim izni var mı?
Uygunsa dosya açılır; uygun değilse hata verilir.
Sonucu kullanır veya hataya göre karar verir.
6.2 Problem #1: Restricted Operations
Programdan kernel’e geçiş, donanımın belirlediği kontrollü bir girişle yapılır.
Kitap bu girişi trap (denetimli çekirdek girişi) olarak adlandırır. Program hizmet istediğinde CPU, dönüş için gereken durumu korur ve kernel mode’a geçer. Kernel işi bitirdiğinde return-from-trap (trap’ten dönüş) adımıyla uygun kullanıcı durumuna dönülür.
6.2 Problem #1: Restricted Operations
Kernel’e nereden girileceğini kullanıcı programı seçemez.
Trap table (trap tablosu), giriş olayları için kullanılacak kernel adreslerini belirler. İlgili olayı ele alan kernel koduna trap handler (trap işleyicisi) denir. Bu giriş düzenini işletim sistemi başlangıçta kurar; kullanıcı programı kendi istediği kernel adresine atlayamaz.
6.2 Problem #1: Restricted Operations
Dosya hizmeti isteyen bir process’in user mode’dan kernel mode’a geçişini izleyelim.
Program
CPU
Kernel
1. Program bir dosya hizmeti ister.
6.3 Problem #2: Switching Between Processes
Program user mode’da çalışırken kernel aynı CPU’da sürekli yürütülmüyor.
Kernel’in başka bir process seçebilmesi için önce CPU kontrolünün ona geçmesi gerekir. Program system call yaparsa bu geçiş gerçekleşir. Peki program hiç hizmet istemeden hesaplamaya devam ederse kernel nasıl yeniden çalışacaktır?
6.3 Problem #2: Switching Between Processes
Cooperative approach (işbirlikçi yaklaşım), programın kontrolü geri vermesine güvenir.
Program dosya okumak için system call yapabilir veya kendi isteğiyle CPU’yu bırakabilir. Kernel bu girişlerde yeniden çalışır ve başka process seçebilir. Ancak bu model, programın kontrolü uygun aralıklarla bırakmasına dayanır.
6.3 Problem #2: Switching Between Processes
Timer interrupt, program istemese de CPU kontrolünün kernel’e geçmesini sağlar.
Timer interrupt (zamanlayıcı kesmesi), donanımın belirlenen zamanda oluşturduğu olaydır. CPU kullanıcı kodunun yürütülmesini keser ve kernel’in olay işleme koduna geçer. Kernel artık hangi process’in devam edeceğine karar verebilir.
İşletim sistemi donanımı önceden ayarlar.
Programın yürütülmesi kesilir; gerekli durum korunur.
Mevcut process veya başka bir ready process seçilir.
6.3 Problem #2: Switching Between Processes
A hiç system call yapmazsa iki yaklaşımda B’nin durumu ne olur?
Timer interrupt ile kernel çalışabilir ve B’yi seçebilir.
Bu öğretim modelinde seçim kuralı A ve B’yi sırayla seçmektir. Adımlar gerçek süre ölçmez.
6.3 Problem #2: Switching Between Processes
Aynı programın A ve B adlı iki process’i ilerliyor.
- argc ve argv[1]
Bir ad beklenir. ./cpu A çalıştırıldığında argv[1] A metnidir; diğer process’e B verilir.
- while (1) → printf()
Döngü sürekli tekrar eder; her turda o process’in A veya B adı yazılır.
- Spin(1)
Yaklaşık bir saniye hesaplama döngüsü yürütülür. Diğer process bu sırada CPU alabilir.
- Üç saniyelik deney
Laboratuvar ikisini sonra durdurur. A/B mesajları ilerlemeyi gösterir; geçişlerin tam sayısını göstermez.
Spin(1) ifadesini Spin(2) yapın. Mesajların aralığı nasıl değişiyor?#include <stdio.h>
#include <stdlib.h>
#include "common.h"
int main(int argc, char *argv[])
{
if (argc != 2) {
fprintf(stderr, "usage: cpu <string>\n");
exit(1);
}
char *str = argv[1];
while (1) {
printf("%s\n", str);
Spin(1);
}
return 0;
}
Linux hazır olduğunda kodu çalıştırın. Çıktı bu alanda görünecek.
6.3 Problem #2: Switching Between Processes
Bir process durdurulup sonra devam edecekse nerede ve hangi değerlerle kaldığını korumalıyız.
Context (bağlam), doğru noktadan devam etmek için gereken yürütme durumudur. Geçen haftanın machine state (makine durumu) fikri burada kullanılır: hangi komut sırada ve hesaplama hangi değerlerle devam edecek?
| Korunacak bilgi | Neyi gösterir? | Örnek |
|---|---|---|
| Program counter / PC (program sayacı) | Devam edilecek komutun yerini. | “Sırada 6. adım var.” |
| Register (yazmaç) | CPU’nun kullandığı küçük ve hızlı veri alanını. | “Ara hesaplama sonucu 30.” |
6.3 Problem #2: Switching Between Processes
Context switch, A’nın durumunu koruyup B’nin kendi durumuyla devam etmesidir.
Context switch (bağlam değiştirme), CPU’nun bir process’in yürütme durumundan diğerininkine geçmesidir. B, A’nın hesaplamasını devralmaz; kendi kaldığı yerden ilerler.
A: sıradaki adım 6, ara sonuç 30.
B: sıradaki adım 12, ara sonuç 200.
CPU artık B’nin 12. adımını onun değerleriyle yürütür.
6.3 Problem #2: Switching Between Processes
Kernel’e geçmek ile başka bir process’e geçmek aynı şey değildir.
Mode switch (mod geçişi), CPU’nun yetki düzeyinin değişmesidir. Context switch ise yürütülen process durumunun değişmesidir. Bazen ikisi aynı akışta gerçekleşir; bazen yalnızca mode değişir.
| Örnek | Yetki düzeyi değişir mi? | Process değişir mi? |
|---|---|---|
| A dosya hizmeti ister; kernel tamamlar ve A’ya döner. | Evet: user → kernel → user. | Hayır: A devam eder. |
| A’da timer interrupt olur; kernel yine A’yı seçer. | Evet. | Hayır. |
| A’da timer interrupt olur; kernel B’yi seçer. | Evet. | Evet: A → B. |
6.3 Problem #2: Switching Between Processes
A’dan B’ye geçişi, kontrolü alma ve durumu değiştirme adımlarıyla izleyelim.
Process’ler
CPU
Kernel
1. A çalışırken B CPU sırasını bekler.
6.3 Problem #2: Switching Between Processes
swtch.S, context switch’in düşük düzeydeki bir parçasıdır.
Assembly (makine komutlarına yakın dil), CPU işlemlerini ayrıntılı yazar. Stack (yığın) çağrı ve dönüş bilgilerini, stack pointer ise bu alanın konumunu tutar.
- old kaydı
Eski devam adresi, stack konumu ve register değerleri saklanır.
- new kaydı
Yüklenecek context’in saklanan register değerleri alınır.
- movl 4(%eax), %esp
Stack pointer değiştirilir. Sonraki stack işlemleri hedef context’in stack’inde yapılır.
- pushl → ret
Hedef dönüş adresi stack’e konur; yürütme bu context’te sürdürülür.
# void swtch(struct context *old, struct context *new); # # Mevcut context’in register değerlerini old kaydına sakla. # Ardından new kaydındaki context’in register değerlerini yükle. .globl swtch swtch: # Eski context’in register değerlerini kaydet. movl 4(%esp), %eax # old kaydının adresini EAX’a yükle. popl 0(%eax) # Eski dönüş adresini (IP) kaydet. movl %esp, 4(%eax) # Eski stack pointer değerini kaydet. movl %ebx, 8(%eax) # Diğer register değerlerini de kaydet. movl %ecx, 12(%eax) movl %edx, 16(%eax) movl %esi, 20(%eax) movl %edi, 24(%eax) movl %ebp, 28(%eax) # Yeni context’in register değerlerini yükle. movl 4(%esp), %eax # new kaydının adresini EAX’a yükle. movl 28(%eax), %ebp # Saklanan register değerlerini geri yükle. movl 24(%eax), %edi movl 20(%eax), %esi movl 16(%eax), %edx movl 12(%eax), %ecx movl 8(%eax), %ebx movl 4(%eax), %esp # Aktif stack bu komutla değiştirilir. pushl 0(%eax) # Yeni dönüş adresini stack’e yerleştir. ret # Yeni context’in dönüş adresinden yürütmeye devam et.
Kaynak kodu düzenlenebilir. İlk adımı görmek için “Satırları açıkla”ya basın.
6.4 Worried About Concurrency?
Kernel bir kayıt üzerinde çalışırken başka bir işin araya girmesi sorun oluşturabilir.
Concurrency (eşzamanlılık), birden fazla işin ilerleyişinin birbiriyle iç içe geçebilmesidir. Kernel de process kayıtlarını ve ortak verileri günceller. Bir kayıt henüz tamamlanmadan başka kod onu okursa yanlış veya eksik bilgi görebilir.
6.4 Worried About Concurrency?
Ortak kayıtlar, değişiklik tamamlanmadan başka işlerin yanlış kullanmasına karşı korunmalıdır.
Lock (kilit), ortak bir veriye erişirken işlerin birbirine uymasını sağlayan araçlardan biridir. Bir iş kaydı güncellerken başka bir iş aynı kaydı kullanmak için bekleyebilir. Tek CPU’da bazı kısa işlemlerde interrupt’ları geçici olarak kapatmak da araya girişi sınırlayabilir.
6.5 Summary
CPU’nun paylaşılabilmesi için hem yetki sınırı hem de kontrolü geri alma yolu gerekir.
| İhtiyaç | Bu derste öğrendiğimiz çözüm |
|---|---|
| Program normal hesaplamalarını hızlı yapsın. | Komutlar doğrudan CPU üzerinde yürür. |
| Korunan sistem işleri denetlensin. | User/kernel mode ve kontrollü system call girişi. |
| Program hiç bırakmasa da kernel yeniden çalışabilsin. | Timer interrupt. |
| Başka process kendi kaldığı yerden devam etsin. | Context switch. |
Homework (Code)
İsteğe bağlı çalışma · Önce kavramı açıklayın; ardından örnek kodla sınayın.
fork() sonrası kaç process var? PID’ler neden farklı?
Child işi uzun sürerse wait() içindeki parent hangi durumda olur?
exec() sonrası PID neden aynı kalır?
wc kodu değişmeden çıktı nasıl dosyaya gider?
Homework (Measurement)
İsteğe bağlı ileri çalışma · Bir işlemin maliyetini çok sayıda tekrar üzerinden ölçebiliriz.
Bir system call’ın süresi tek başına çok kısa olabilir. Onu N kez tekrarlayıp toplam süreyi ölçersek, toplamı N’ye bölerek ortalama hakkında fikir edinebiliriz. Döngünün ve saati okumanın süresi de bu ölçüme eklenir.
System call
Basit bir hizmet isteğini tekrar edin. Toplam süreyi ve tekrar sayısını kaydedin.
Context switch
Aynı CPU’daki iki process iki pipe üzerinden sırayla veri gönderip beklesin. Bekleme diğer process’in ilerlemesini sağlar.
Homework (Measurement)
Ölçümün birimi küçük olsa da saatin o kadar küçük farkları ayırabildiği kesin değildir.
Bir mikrosaniye, saniyenin milyonda biridir. Saatin bu birimde sayı vermesi, o kadar küçük her farkı güvenilir ölçebildiğini göstermez. Çok sayıda tekrar yapın ve sonuçların değişimini raporlayın. Context switch deneyi için iki process aynı CPU’da tutulmalıdır.
References
Bu ders, geçen haftaki Process sunumunun devamıdır. Bölüm adları OSTEP ile aynıdır.
The Abstraction: The Process
Program/process, durumlar ve çalışma bilgilerinin hatırlatıldığı temel.
BU DERS · BÖLÜM 5Interlude: Process API
fork(), wait(), exec(), shell ve bağlantıların düzenlenmesi.
DEVAM · BÖLÜM 6Mechanism: Limited Direct Execution
Yetki sınırı, kontrollü giriş, timer interrupt ve context switch.
KOD ÖRNEKLERİostep-code
Ek cpu.c/common.h örneği. p1–p4 ve swtch.S verilen PDF şekillerinden alınmıştır; yorumlar Türkçedir.
Remzi H. Arpaci-Dusseau ve Andrea C. Arpaci-Dusseau · Operating Systems: Three Easy Pieces. Başlangıç noktası: Isletim_Sistemleri_04_Process_Sunu.pptx. Sayfa referansları kullanıcı tarafından verilen Bölüm 5–6 PDF’lerine aittir.