Thread yönetimi işletim sisteminin en temel ihtiyaçlarından biridir. Burada threadlerin farklı öncelikleri olur. Bu öncelikler yönetilirken hem var olan CPU kaynakları çok verimli kullanılmalıdır, hem de önceliği daha düşük threadlerin birikmesi ve farklı darboğazlara neden olabilmeleri önlenmelidir. Daha evvel örneğin interrup request level mantığı ile uyarlanan önecelik mekanizmalarını tartışmıştık. Ayrıca eşleme ihtiyaçlarının farklı mekanizmalar kullanarak nasıl karşılandığı da gündemdeydi. Şimdi daha yüksek bir abstraksiyon seviyesinde thread, process ve job yönetimlerin üzerinden geçeceğiz.


Her şeyden önce neyi yönetiğimizi bilmemiz gerekir ve böylece hangi bilgilere ne zaman ihtiyacımız olduğunu belirlememiz gerekir.
En basit işlem yapımız Process dir. Kernel tarafındaki data structure yapımıza executive process block, eprocess deriz. User mode adres bölgesinde process ile ilgili tutuğumuz yapımıza process environment block, peb deriz. Bunun amacı kernel bölgesini dâhil etmeden user mode tarafında process in kendi bilgileri ile ilgili basit işlemlerin yapılabilmesine izin verebilmektir. Ayrıca subsystem tarafında: client server run time subsystemi, csrss.exe, ve kernel mode device driver ı, win32k.sys, de her process ile ilgili veri yapıları oluşturup tutmaktadırlar. Bunun nedenini anlayabilmek için ilk windows subsystemi hatırlamamız gerekir.

Kitapın ikinci bölümü bu konuyu detaylı anlatmaktadır. Windows subsystem dediğimiz yapı öncelikle keyboard/mouse ve görüntü sistemlerini yönetir. Registry de HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems altında startup yapılandırma bilgisini bulabiliriz. Bu altyapı olmadan sistem boot edemez. Yani oldukça önemlidir.
Csrss.exe konsol/cmd ve process ve threadlerin başlatılması ve silinmesi gibi görevleri üstlenir. Win32k.sys de kernel tarafındaki kernelin aygıt sürücüsüdür. İşte burada mouse/keyboard ve görsel outputlardan sorumludur. Örneğin bir applikasyonun arayüzünde herhangi bir işlem yaptığınızda, arayüzde yapılmış olan bu işlemden applikasyonu haberdar eden win32k.sys dir. Kendisi ile graphics device interface, gdi, kütüphanesindeki fonksiyonları kullanarak çalışabiliriz. Kendisi directx sürücüsü olmasa da, onun üzerinden ilerlemek zorundayızdır. Ve elbette yazıcı ve tarayıcı işlemlerinde en kritik rolü oynamaktadır. Kitapta 50 sayfadaki grafiğe baktığınızda Kernel mode bölgesinin sağ tarafını executive den HAL a kadar win32k.sys in görev bölgesi olarak düşünebilirsiniz. Onun hemen üstünde user mode bölümünü de csrss.exe in çalıştığı bölge olarak düşünebiliriz. Yani bütün user mode/kernel mode yapısına daha yüksek abstraksiyon seviyesinde parallel çalışan windows subsystem yapısı vardır.
O zaman da her process ile ilgili bilgilerin neden windows subsystem de tutulması gerektiğini anlamışsınızdır. Her iki abstraksiyon seviyesinde de ilgili veriler tutulur. Subsystem thread yapılarını da aynı şekilde takip eder.


Eprocess de örneğin processin bütün Handle bilgilerini ilişkilendiririz. Ayrıca processin işlemciye göndermek üzere hazırladığı kodu tuttuğumuz thread yapısı için aynı şekilde kernel adres bölgesinde bir executive thread blok, ethread veri yapısı oluştururuz. User mode tarafında da peb gibi bir thread environment block, teb, veri yapısı bulabiliriz.
Daha önceki chapterlarda sözü geçen (s.62), kernelin işlemci ile ilgili tuttuğu bilgilerin bulunduğu bölgeye kernel process block daki kernel processor control region, pcr, demiştik. Burada çalışan ve çalıştırılacak threadler hakkında da bilgiler tutulmaktadır. Eprocessde de buraya referans veririz. Eprocessin bu referansı verdiğimiz bölgesine kprocess denilir ve burası thread yönetimi açısından önemlidir. Eprocess de ayrıca örneğin Process id ve üvey/çocuk ilşkileri de tutulur. Process bir job un parçasıysa, onun ile gili referans bilgi ve handle table ile ilgili referans bilgi de tutulur. Kitap bu konularda çok detaylı bilgi vermektedir. Ayrıca Kernel Variables, performance counters ve protected processes (protected media rights vs. administrator) konularına da dayanmaktadır.
Process i oluşturduğumuzda öncelikle subsystem yine devreye girer. Burada güvenlik ile ilgili ayarlar ve hangi subsystem de çalışacağı belirlenir. Örneğin process posix için yaratılıyor olabilir. Ondan sonra çalıştırılacak .exe processin user mode adres bölgesine yüklenir ve eprocess yaratılır. .exe eğer Windows processi ise kendi adında beklediğiniz şekilde çalışır. Ancak örneğin posix ise, posix.exe olarak veya 16 bit ms-dos ise ntvdm.exe altında çalışır. Gözlemlemişsinizdir, bir .bat skript çalıştırdığınızda bu cmd.exe olarak çalışır. İşte bunun yönetimini yapan subsytemdir.
Bir processin tanımını hatırlarsanız, en az bir threadi vardır ve o thread işte bu aşamada yaratılır ve ethreadi oluşturulur. Bu thread in yapması gereken işlemler varsa ardından gerekli DLL ler de yüklenir. Eğer belirtilmemişse process yaratıldığında öncelik seviyesi ‘normal’ olur. Aynı şekilde desktop belirtilmemişse, processi çağıran processin desktopunda çalışır. Örneğin cmd açıp notepad yazarsanız, açılan notepad, cmd in desktopuna gelir. Son olarak kprocess ve peb yapıları oluşturulur. Process yaratılırken uyarlanan adımlar ve tutulan bilgileri kitap çok detaylı listeler.


Daha önceki bölümlerde anlaşılmıştır, ama yine de belirlemek gerekir: Windows multithreaded bir işletim sistemidir. Yani her thread işi bitene kadar bir işlemcide çalışmaz. İşlemcide o an çalışmak isteyen bütün threadler o işlemciyi her biri kısa süre çalışarak paylaşırlar. Thread in ilk özelliği processinden aldığı işlemci affinity si olabilir. Yani thread sadece bir veya bazı işlemcilerde çalışıyor olabilir. Mesela task manager da process e sağ tıklayıp affinity verebilirsiniz.

İkinci bir thread in maruz kaldığı temel ayar quantum süresidir. Yani bir işlemcide sıra bir thread e geldiğinde, o esnada onun çalışabileceği süresidir. Bunun ayarı performance options daki ‘Adjust for best performance of: programs/background services’ bölümündedir. ‘Programs’ bütün threadlerin quantum süresini azaltır ve ‘background services’ uzatır.
Quantum süresini belirleyen başka etkenlerde vardır. Process/job bazında quantum süreleri farklı olabilir. Örneğin bir processin kullanıcının odağında olması, yani foreground da olması, onun threadlerinin quantum sürelerinin daha uzun tutulmasına neden olabilir. Bu ancak ‘Programs’ seçeneğinde geçerlidir. Serverlar için tipik ‘background services’ ayarlıyken bu quantum boosting dediğimiz mekanizma normalde devreye girmez. Quantum boosting, quantum süresini normal ayarlarda üç kat artırır.
Daha granüler ayarlar direk registryde HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\PriorityControl altındaki Win32PrioritySeparation değeri ile yapılabilir.
Örneğin buradaki hex değer 26h ise, bu binary 100110b dir. Bunu da ikili gruplara bölerek 10b, 01b ve 10b olarak okuyabiliriz veya değiştirebiliriz.
İlk iki hane 10b, 2d kısa quantum demektir. 1d uzun olur. Yani 10b ‘programs’ demektir.
İkinci iki haneli grub 01b, 1d foreground a gelen processlerin quantum sürelerin boost edilmesini enable eder. 2d disable eder.
Son ikili hane de 10b, 2d. 10b ve 11b öne gelen processlerin quantum boostunu 3 katsayısı olarak belirler. Yani var olan quantum süreleri 3 kat uzatılır boost esnasında. 01b bu katmanı ikiye düşürür ve 00b de boost ayarı belki olmasına rağmen, katsayıyı1 de tutar ve böylece foreground boost olmaz. Her ne kadar burası açıklanmış olsada, bu değerleri değiştirmek sistemi işlevsel negatif etkileyebilir.


Yani affinity ve quantum öncelik mekanizmalarının bir parçasıdır. Threadlerin maruz kaldığı üçüncü öncelik mekanizması priorty level d'ir. Yani her threade bir öncelik rakamı veririz. 1 den 15 e kadar değişken seviyelerdir. 16 - 31 arasına real time levels deriz. Sıfır sistemindir ve bunu sadece zero page thread i kullanır. Bu özel threadi kitabın 9cu memory management bölümü açıklamaktadır.
Her thread in bir base priority değeri vardır ve bu thread yaratılırken processin önceliğine göre verilir. Ayrıca her threadin bir current priority değeri vardır.
Variable/dynamic/değişken aralıkta kalacak şekilde işletim sistemi bir threadin önceliğini geçici artırabilir. Sonra bu priority yine base seviyesine düşer. Yani bir processin bir öncelik seviyesi ve bir threadin iki öncelik seviyesi vardır.

Bir process in öncelik seviyesi threadlerin base prioritysini bu şekilde etkiler:
örneğin task manager dan processlere base priority kolonunu ekleyebilirsiniz. Burada process normal ise, thread 6 ile 10 arasında bir base priority alabilir. Normalde ortadaki, bu örnekte 8 i alacaktır. Above normal 10, high 13, below normal 6 ve process idle ise, threadin base prioritysi 4 olacaktır. Bu kadar granularity yapılmasının nedeni de örneğin sistemin bazı threadlerin prioritysini az oranda da yükseltebilmesidir. Örneğin session manager normal de ürettiği bir threadin base priority si 8 değil de belki 9 da set edilir. Farklı boost mekanizmalarında örneğin hemen 5 level değil de, belki geçici bir puan artırmak yeterli olur. Bu yapı neticesinde öncelikleri ince bir şekilde ayarlayabiliriz ve farklı önemlilik taşıyan işler farklı öncelik sıralarına koyulabilirler.
İşletim sistemi asla 15 in üstüne base veya current i set etmez. Yani real time levelları vermez. Bunları kritik kernel threadleri için kullanır. En nihayetinde temel problem, öncelikleri darboğaz yaratmadan belirleyebilmektir. Bundan dolayı priority farkları çok olmamalıdırlar, yoksa yüksek priority grubunda olan threadlere hep sıra gelir ve düşük öncelikli threadler toplamda çok beklerler. Yani yüksek öncelikli thread grubu sistemin verimsiz çalışmasına neden olur. Ayrıca beklenmedik senkronizasyon sorunları da oluşabilir. Threadler farklı eşleme durumlarında birbirlerini beklediklerinden dolayı çok farklı sorunlar oluşabilir. Yani örneğin bir kilidi tutan bir thread işlemciyi çok beklerse, onu bekleyen belki çok daha kritik threadler de daha çok beklemek zorunda kalırlar. Ondan mesela bir processin içindeki bütün threadler genelde aralarında en çok eşleme yaptıklarından dolayı default da bunlar aynı base priority e sahiptirler.


Thread yönetimi, thread scheduling, kernelde uyarlanır. Her zaman bir işlemci bir thread den diğerine geçtiğinde bir context switch oluşur. Bunu örneğin performans monitör ile thread objesinin altında Context Switches/sec olarak takip edebiliriz. Örneğin bir processin bu değeri yüksek ise, threadlerinin çok beklediğine dair bir bulgu olabilir. Yani threadler quantum sürelerini bitirmiyor olabilirler. Her context switch de, çalışması kesilen thread in bütün bilgileri kendi alanına kayıt edilir ve bir sonra çalışacak threadin bilgileri okunur. Threadler bir sefer sıra onlara geldiğinde genelde işlemlerini tamamlayamadıkları için bu işlem çok kritiktir. Thread yönetimi her thread geçişinde bir sonraki threadi önceden belirlemekten sorumludur.
Thread yönetiminde tutulması, bilinmesi gereken bir bilgi de thread in kendi durumudur. Ready state tinde olan threadler bizi öncelikli ilgilendirir. Bunlar kernel in kendilerini çalıştırmasını bekler. Running deki bir thread quantum süresi bittiğinde ve en az kendisinin priority sinde başka bir thread olduğunda, veya başka bir thread preemption yapıldığında, çalışmasını sonlandırıp tekrar ready olur. Eğer bu durumlar yoksa thread çalışmaya devam edebilir. Bir thread bir gate i, yani örneğin bir guarded mutex i bekliyor olabilir. Bu senkronizasyon objelerini bekleyen threadleri Gate Waiting state ine alırız. Waiting state de vardır, ancak mutex i açmak için IRQL APC seviyesini beklemek zorunda olduğumuz için ilgili threadleri de ayrı bir durumda toplarız, çünkü burada artı yapılması gereken işler çoktur. Böylece waiting queuesundaki işlem yapısı sadeleştirilmiş olur. Mesela thread in stack i paged out ise ve geri yüklenmesini bekliyorsak, threadi transition state e alırız. Bütün bu bilgileri de dispatcher database inde tutarız. Her CPU un bir database i olur. Böylece threadleri organize ve katalogize etmiş oluruz. Kitap bütün state leri bütün detayları ile özetlemektedir.


Her priority level ın kendi ready queueları, bekleme kuyrukları olduğundan engellenmesi gereken temel bir durum vardır. Hep yukarıdaki öncelik queuelarında bekleyen threadler çalışırsa belki daha düşük öncelikli threadlere sıra çok nadir veya hiç gelmeyebilir.
Ondan priority boost diye bir mekanizma vardır. Beklenen IO bittiğinde, executive event veya semaphore açıldığında, farklı nedenlerden dolayı threadler çok uzun çalışmamışsa veya uzun süredir bir kaynak bekliyorlarsa bu boosting devreye girer. Farklı senaryolarda farklı boost mekanizmaları devreye girer. Örneğin network üzerinden yapılan IO un bitmesini bekleyen thread, IO bittiğinde 2 priority level yükseltilir. Ama ses aygıtı ile ilgili bir IO bittiğinde bekleyen threadin boostu 8 level olur. Elbette bunlar hep current ile yapılan boostlardır. Base priority değeri burada boost edilmez. Ama örneğin bir semaphore u bekleyen bir thread in base prioritysini 15 i geçmeyecek şekilde geçici boost ederiz. Foreground da olan processler için, kullanıcıyı bekletmemek adına quantum boost artı kernel objesi beklendikten sonra ilgili threadlere current priority boost da yapılır.

Son olarak balance set manager yukarıdaki mekanizmalar ile korulamayan threadlerin de ‘cpu starvation’ a uğramamasını sağlar. Bu her saniye çalışıp ready queueları tarayan bir thread dir. Dört saniyedir herhangi bir priority ready queuesunda bekleyen threadlerin prioritysini 4 quantum süresi boyunca 15 de kalacak şekilde boost eder. Bu süre bitince thread yine eski değerlerine geri döner. Balance set manager ın bu çalışma mantığı her queue un sadece ilk öndeki 16 threadi taramasından kaynaklanır. Gelecek taramada son bıraktığı thread den (hala oradaysa) devam eder. Böylece kendiside bir thread olduğu için fazla işlemci kaynağı kullanmaz. Bunun dışında da multimedia için farklı boost mekanizmalrı devreye girebilir.
Yani farklı senaryolarda nasıl ve neye (current, base, quantum) boost yapılacağı net çizilmiştir.
Bu boost mekanizmaları ile gereksiz beklemeler ve darboğazlar engellenir.
Threadlerin prioritylerini performance monitörden takip edebiliriz.


İşlemci seçimi olarak processlerin threadleri hep bütün işlemcilere dağıtılmak istenir. İşlemcide de meşgul olmayan çekirdeğe verilmeye çalışılır. Oluşturulan bütün processlerin ideal processor oluştururken bir önceki processe göre verilir. Böylece dönemsel bir dağıtım oluşur. Eğer bütün işlemciler yoğunsa, priority e göre preemption yapılabilecek işlemci aranır. Yani her işlemcinin bir standby state de olan bir thread i vardır. Bu ‘queue’ ve thread özeldir, çünkü runningden sonra çalışacak thread dir. Eğer bu threadin prioritysi çalıştırmak istediğimiz yeni threadinkinden düşükse, o threadi standby dan ready e çekeriz ve bu threadi standby a koyarız. Preemption yapmış oluruz. Böylece daha öncelikli threadler de bütün işlemcilerde öncelik görebilirler.

Thread scheduling
, thread dispatching in üstüne önceliğini yazamaz. Yani yüksek öncelikli IRQL 0 da çalışan bir user mode thread, daha düşük öncelikte IRQL1 veya IRQL 2 de çalışan bir kernel threadten daha öncelikli değildir. Yani IRQL seviyesi threadin current ve base priority sini ezer. Yani normal applikasyon threadleri IRQL 0 da ve 1-15 priority level arasında çalışırlar.

Son olarak Job kavramımız vardır. Basitçe bu birden fazla processi ve bunların threadlerini toplamaktır. Örneğin run as administrator bir cmd açınız. Bunun içinden de notepad i başlatınız. İşte şimdi notepad de run as administror olarak çalışacaktır. Kısaca child process, parent processin güvenlik kontekstini alacak. Bu ortak nokta bu iki processi bağlayacak, işte bu gruplama da bir job olacak. Job sadece gruplanmış processlerdir.


Bu konuların hepsini detaylı bir şekilde Windows Internals 5th ed. Chapter 5 ‘Processes, Threads and Jobs’ altında bulabilirsiniz: http://technet.microsoft.com/en-us/sysinternals/bb963901
Eğer kendinizi Windows mimarisinde veya sistem/sürücü yazılımcısı yönünde geliştirmek istiyorsanız veya işletim sisteminin bir referans kitabına ihtiyacınız varsa bu kitabı incelemenizi öneririm.

Başar Güner
Sr. Support Engineer, Microsoft