Thursday 29 November 2007

LINUX Çekirdeğini Anlamak – Süreçler 1


Şekil 3-1. Linux süreç tanımlayıcısı Şekil 3-2. Süreç tanımlayıcı ve süreç çekirdek yığınının saklanışı

LINUX Çekirdeğini Anlamak – Süreçler 1

Ali Rıza SARAL(1)


(1) Daniel P. Bovet, Marco Cesati, Understanding the Linux Kernel ‘den faydalanarak derlenmiştir.
(2) Bu yazı biraz da okuyucunun teknik İngilizce’sini geliştirmek amacı ile yazılmıştır.
(3) Bir diğer amaç ta teknik metinlerin Türkçe’ye tercüme edilişi hakkında tartışmağa imkan sağlamaktır. Bu amaçla cümle cümle tercümeyi içeren bir sürümünü de bulmak mümkündür.


Bölüm 3. LINUX’ta Süreçler (Processes)
Süreç kavramı her bir çoğul-programlayıcı (multi-programming) işletim sistemi için temel bir kavramdır. Bir süreç genellikle bir programın çalıştırılmakta olan bir örneğidir (instance);
böylece, eğer 16 kullanıcı aynı anda vi programını çalıştırıyorsa, 16 tane ayrı süreç vardır
(aynı çalıştırılabilir kodu paylaşabildiklerine karşın). Süreçlere Linux kaynak kodunda “görev” (task) adı verilir.

3.1 Süreç Tanımlayıcı (Process Descriptor)
Süreçleri yönetmek için, çekirdek, her sürecin ne yaptığına ilişkin net bir resme sahip olmalıdır. Örneğin, süreç önceliğini, CPU üzerinde çalışır durumda ya da bir olay nedeni ile tıkanmış olduğunu, hangi adres alanının ona atanmış olduğunu, hangi dosyalara erişişine izin verildiğini, ve benzerşeyleri bilmek zorundadır. Bu süreç tanımlayıcı’nın, yani tek bir sürece ilişkin bütün bilgileri içeren görev_yapısı (task_struct) tipinde bir yapının rolüdür.
.
Şekil 3-1. Linux süreç tanımlayıcısı


3.1.1 Süreç Hali (Process State)
İsminden anlaşıldığı gibi, süreç tanımlayıcının hal (state) alanı süreç içinde o anda ne olduğunu tarif eder. Her biri süreç durumunu tarif eden bir dizi bayraktan oluşur.
Güncel Linux sürümünde bu haller karşılıklı olarak birbirlerini hariç tutar, ve böylece kümenin yalnız bir bayrağı kaldırılımış diğerleri indirilmiş olur. Aşağıdakiler mümkün süreç halleridir:

GÖREV_ÇALIŞIYOR (TASK_RUNNING) Süreç ya CPU üstünde çalışıyor ya da çalıştırılmak için bekliyor.

GÖREV_KESİNTİYE_UĞRAYABİLİR (TASK_INTERRUPTIBLE) Bir koşul gerçekleninceye kadar süreç askıda (uyuyor). Bir donanım kesintisini başlatmak, sürecin beklediği bir sistem kaynağını serbest bırakmak, veya bir işaret teslim etmek bir süreci uyandıran koşullara örnektir, yani sürecin halini geriye GÖREV_ÇALIŞIYOR (TASK_RUNNING) ’a getirirler.

GÖREV_KESİNTİYE_UĞRATILAMAZ (TASK_UNINTERRUPTIBLE) bir önceki hal gibidir, yalnız uyuyan sürece bir işaret teslim edilişi hali değiştirmez. Bu süreç nadir olark kullanılır. Yine de, bir sürecin öngörülmüş bir olay oluncaya kadar kesintiye uğramadan beklemesi gerektiği belirli özel koşullar altında değerlidir. Örneğin, bir süreç bir cihaz dosyasını açtığında ve denk düşen cihaz sürücüsü denk düşen donanım cihazını yoklamağa başladığında bu hal kullanılabilir. Cihaz sürücüsü yoklayış tamamlanıncaya kadar kesintiye uğramamak zorundadır, ya da donanım cihazı belirsiz bir halde kalabilir.

GÖREV_DURDU (TASK_STOPPED) Süreç çalıştırılışı durduruldu: süreç bu duruma bir SIGSTOP, SIGTSTP, SIGTTIN, veya SIGTTOU işareti aldıktan sonra girer. Bir süreç bir diğeri tarafından izlendiğinde (bir hata bulucu bir test programını denemek için bir ptrace( ) sistem çağrısı çalıştırdığında ki gibi), herhangi bir işaret süreci GÖREV_DURDU (TASK_STOPPED) haline koyar.

GÖREV_ZOMBİ (TASK_ZOMBIE) Süreç çalıştırışı bitirildi, fakat anne süreç ölü süreç hakkında bilgi geriye vermek için henüz wait( )- benzeri sistem çağrısı (wait( ), wait3( ), wait4( ), veya waitpid( )) çıkarmadı. wait( )- benzeri çağrı yapılmadan önce, çekirdek ölü süreç tanımlayıcının içerdiği veriyi çöpe atamaz, çünkü ebeveyn ona ihtiyaç duyabilir.


3.1.2 Süreci Belirleyiş (Identifying a Process)
Lunix süreçleri kendilerine ait çekirdek veri yapılarınının önemli bir kısmını paylaşabildikleri halde—hafif-sıklet süreçler(lightweight processes)—her süreç kendine ait süreç tanımlayıcısına sahiptir. Bağımsız olarak çalıştırım programına alınabilen her çalıştırış bağlamı kendine ait süreç tanımlayıcısı sahibi olmalıdır. Hafif-sıklet süreçler bir kullanıcı-seviyesinde kütüphane tarafından ele alınıp farklı bir çalıştırış akışı olan, kullanıcı-üslubu bağlar(user-mode threads)’la karıştırılmamalıdır. Süreç ile süreç tanımlayıcı arasında çok katı bire-bir denk düşüş 32-bit süreç tanımlayıcı adresini [1] süreci belirlemek için kullanışlı bir araç yapar. Bu adreslere süreç tanımlayıcı adresleri (process descriptor pointers) olarak değinilir. Çekirdeğin yarattığı süreçlere yaptığı değinişlerin çoğu süreç tanımlayıcı işaret-vericileri(pointer) aracılığı iledir.

[1] Teknik açıdan 32 bit mantıksal adresin(logical) yalnız öteleyiş(offset) bileşenidir. Yine de, Linux tek bir çekirdek veri kesimi(segment) kullandığı için, öteleyişin tam bir mantıksal adrese(whole logical address) eşdeğer kabul edebiliriz. Dahası, kodun ve veri kesimleri(data segments)’nin taban adres(base address)’i 0 yapıldığı için, öteleyişi(offset) doğrusal bir addres gibi kullanabiliriz.

Öte yandan, herhangi bir UNIX-benzeri sistem, kullanıcılarının süreçleri Süreç Kimlik Nosu(Process ID (or PID)) adı verilen bir sayı aracılığıyla belirleyişine müsaade eder.
Süreç Kimlik NO’su (PID) süreç tanımlayıcının pid alanında tutulan 32-bit işaretsiz bir sayıdır. PID’ler sıra ile sayılandırılmıştır:yeni yaratılan bir sürecin PID’i normalde bir öncekinin PID’inin bir ile arttırılmışıdır. Yine de, 16-bit donanım platformları için üretilmiş geleneksel UIX sistemleri ile uyumluluk için, LINUX üzerinde izin verilen en büyük PID sayısı 32767’dir. Çekirdek sistemdeki 32768’inci süreci yarattığı zaman, aşağıdaki kullanılmamış PID’ları yeniden kullanmaya başlamalıdır. Etkinlik önemlidir çünkü öldür() ( kill( )) gibi bir çok sistem çağrısı etkilenen süreci belirtmek için PID’ı kullanır.

3.1.2.1 Görev dizisi (The task array)
Süreçler sistem içinde yaşam süreleri birkaç milisaniyeden aylara kadar uzanan devinimsel (dynamic) varlıklardır (entities). Linux SY_GÖREV (NR_TASKS) kadar sürece muamele edebilir. Çekirdek kendi adress alanında görev (task) adı verilen SY_GÖREV (NR_TASKS) büyüklüğünde küresel sabit bi dizi (global static array) ayırır. Bu dizinin elemanları süreç tanımlayıcıya işaret edicilerdir (process descriptor pointers); boşluğa(null) işaret edici bir süreç tanımlayıcının o dizi elemanı ile ilişkilendirilmediğini belirtir.

3.1.2.2 Bir Süreç Tanımlayıcının Saklanışı (Storing a process descriptor)
görev (task) dizisi yalnız süreç tanımlayıcıların işaret edicilerini içine alır, çok yer kaplayan tanımlayıcıların kendilerini değil. Süreçler devinimsel varlıklar (dynamic entities) olduğu için, süreç tanımlayıcılar çekirdeğe kalıcı olarak atanan bellek alanına değil devinimsel belleğe depolanırlar. Linux her süreç için, 8KB bellek alanına, iki farklı veri yapısı saklar: süreç tanımlayıcı ve Çekirdek-üslubu (Kernel Mode) süreç yığını.


Şekil 3-2. Süreç tanımlayıcı ve süreç çekirdek yığınının saklanışı
Figure 3-2. Storing the process descriptor and the process kernel stack

esp kayıt-tutucusu(register) yığının en üst konum adresini tutan yığın eşaret-edicisidir. Intel sistemlerinde, yığın sonda başlar ve bellek alanının başlangıcına doğru genişler. Kullanıcı-Üslubundan Çekirdek-Üslubuna geçişten hemen sonra, bir sürecin çekirdek yığını her zaman boştur, ve bu yüzden esp kayıt-tutucusu bellek alanından hemen sonra gelen byte’a işaret eder.

3.1.2.3 Güncel Makrosu (The current macro)
Çekirdek-üslubu yığını ile süreç tanımlayıcı eşleştirişi etkinlik açısından anahtar bir fayda sağlar: çekirdek esp kayıt-tutucusunun değerinden CPU üzerinde çalışan sürecin süreç tanımlayıcısına işaret-ediciyi kolaylıkla elde edebilir. Aslında, bellek alanı 8 KB(213 byte) uzunluğunda olduğundan, süreç tanımlayıcının taban adresini elde etmek için, çekirdeğin bütün yapması gereken, esp ‘nin en az önemli 13 bitini maskeleyerek çıkarmaktır. Bu şuanki (current) makrosu tarafından yapılır.

Görev_yapısı-dizisi (task_struct_stack) arabölge (cache) içindeki süreç tanımlayıcılarının işaret edicilerini içinde bulundurur. İsmi süreç tanımlayıcı serbest bırakılışı ve istekte bulunuluşunun dizi üzerinde “it”(push) ve “çek”(pop) işlemleri ile yapılışından gelir:

Görev_yapısını_serbest_birak (free_task_struct( )) Bu fonksiyon 8 KB’lık görev_birimi (task_union) bellek alanlarını serbest bırakır ve onları eğer boş değilse arabölge(cache)’ye yerleştirir.
Görev_yapısına_yer_ata (alloc_task_struct( )) Bu fonksiyon görev_birimi (task_union) bellek alanları için 8 KB yer atar. alloc_task_struct( ). Bu fonksiyon eğer yarı-dolu ise ya da art arda gelen bir çift sayfa çerçevesi(page frame) yok ise arabölge(cache)’den bellek alanları alır.