Windows Debugging 205
Thread yönetimi isletim sisteminin en temel ihtiyaçlarindan biridir. Burada threadlerin farkli öncelikleri olur. Bu öncelikler yönetilirken hem var olan CPU kaynaklari çok verimli kullanilmalidir, hem de önceligi daha düsük threadlerin birikmesi ve farkli darbogazlara neden olabilmeleri önlenmelidir. Daha evvel örnegin interrup request level mantigi ile uyarlanan önecelik mekanizmalarini tartismistik. Ayrica esleme ihtiyaçlarinin farkli mekanizmalar kullanarak nasil karsilandigi da gündemdeydi. Simdi daha yüksek bir abstraksiyon seviyesinde thread, process ve job yönetimlerin üzerinden geçecegiz.
Her seyden önce neyi yönetigimizi bilmemiz gerekir ve böylece hangi bilgilere ne zaman ihtiyacimiz oldugunu belirlememiz gerekir.
En basit islem yapimiz Process dir. Kernel tarafindaki data structure yapimiza executive process block, eprocess deriz. User mode adres bölgesinde process ile ilgili tutugumuz yapimiza process environment block, peb deriz. Bunun amaci kernel bölgesini dâhil etmeden user mode tarafinda process in kendi bilgileri ile ilgili basit islemlerin yapilabilmesine izin verebilmektir. Ayrica subsystem tarafinda: client server run time subsystemi, csrss.exe, ve kernel mode device driver i, win32k.sys, de her process ile ilgili veri yapilari olusturup tutmaktadirlar. Bunun nedenini anlayabilmek için ilk windows subsystemi hatirlamamiz gerekir.
Kitapin ikinci bölümü bu konuyu detayli anlatmaktadir. Windows subsystem dedigimiz yapi öncelikle keyboard/mouse ve görüntü sistemlerini yönetir. Registry de HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems altinda startup yapilandirma bilgisini bulabiliriz. Bu altyapi olmadan sistem boot edemez. Yani oldukça önemlidir.
Csrss.exe konsol/cmd ve process ve threadlerin baslatilmasi ve silinmesi gibi görevleri üstlenir. Win32k.sys de kernel tarafindaki kernelin aygit sürücüsüdür. Iste burada mouse/keyboard ve görsel outputlardan sorumludur. Örnegin bir applikasyonun arayüzünde herhangi bir islem yaptiginizda, arayüzde yapilmis olan bu islemden applikasyonu haberdar eden win32k.sys dir. Kendisi ile graphics device interface, gdi, kütüphanesindeki fonksiyonlari kullanarak çalisabiliriz. Kendisi directx sürücüsü olmasa da, onun üzerinden ilerlemek zorundayizdir. Ve elbette yazici ve tarayici islemlerinde en kritik rolü oynamaktadir. Kitapta 50 sayfadaki grafige baktiginizda Kernel mode bölgesinin sag tarafini executive den HAL a kadar win32k.sys in görev bölgesi olarak düsünebilirsiniz. Onun hemen üstünde user mode bölümünü de csrss.exe in çalistigi bölge olarak düsünebiliriz. Yani bütün user mode/kernel mode yapisina daha yüksek abstraksiyon seviyesinde parallel çalisan windows subsystem yapisi vardir.
O zaman da her process ile ilgili bilgilerin neden windows subsystem de tutulmasi gerektigini anlamissinizdir. Her iki abstraksiyon seviyesinde de ilgili veriler tutulur. Subsystem thread yapilarini da ayni sekilde takip eder.
Eprocess de örnegin processin bütün Handle bilgilerini iliskilendiririz. Ayrica processin islemciye göndermek üzere hazirladigi kodu tuttugumuz thread yapisi için ayni sekilde kernel adres bölgesinde bir executive thread blok, ethread veri yapisi olustururuz. User mode tarafinda da peb gibi bir thread environment block, teb, veri yapisi bulabiliriz.
Daha önceki chapterlarda sözü geçen (s.62), kernelin islemci ile ilgili tuttugu bilgilerin bulundugu bölgeye kernel process block daki kernel processor control region, pcr, demistik. Burada çalisan ve çalistirilacak threadler hakkinda da bilgiler tutulmaktadir. Eprocessde de buraya referans veririz. Eprocessin bu referansi verdigimiz bölgesine kprocess denilir ve burasi thread yönetimi açisindan önemlidir. Eprocess de ayrica örnegin Process id ve üvey/çocuk ilskileri de tutulur. Process bir job un parçasiysa, onun ile gili referans bilgi ve handle table ile ilgili referans bilgi de tutulur. Kitap bu konularda çok detayli bilgi vermektedir. Ayrica Kernel Variables, performance counters ve protected processes (protected media rights vs. administrator) konularina da dayanmaktadir.
Process i olusturdugumuzda öncelikle subsystem yine devreye girer. Burada güvenlik ile ilgili ayarlar ve hangi subsystem de çalisacagi belirlenir. Örnegin process posix için yaratiliyor olabilir. Ondan sonra çalistirilacak .exe processin user mode adres bölgesine yüklenir ve eprocess yaratilir. .exe eger Windows processi ise kendi adinda beklediginiz sekilde çalisir. Ancak örnegin posix ise, posix.exe olarak veya 16 bit ms-dos ise ntvdm.exe altinda çalisir. Gözlemlemissinizdir, bir .bat skript çalistirdiginizda bu cmd.exe olarak çalisir. Iste bunun yönetimini yapan subsytemdir.
Bir processin tanimini hatirlarsaniz, en az bir threadi vardir ve o thread iste bu asamada yaratilir ve ethreadi olusturulur. Bu thread in yapmasi gereken islemler varsa ardindan gerekli DLL ler de yüklenir. Eger belirtilmemisse process yaratildiginda öncelik seviyesi ‘normal’ olur. Ayni sekilde desktop belirtilmemisse, processi çagiran processin desktopunda çalisir. Örnegin cmd açip notepad yazarsaniz, açilan notepad, cmd in desktopuna gelir. Son olarak kprocess ve peb yapilari olusturulur. Process yaratilirken uyarlanan adimlar ve tutulan bilgileri kitap çok detayli listeler.
Daha önceki bölümlerde anlasilmistir, ama yine de belirlemek gerekir: Windows multithreaded bir isletim sistemidir. Yani her thread isi bitene kadar bir islemcide çalismaz. Islemcide o an çalismak isteyen bütün threadler o islemciyi her biri kisa süre çalisarak paylasirlar. Thread in ilk özelligi processinden aldigi islemci affinity si olabilir. Yani thread sadece bir veya bazi islemcilerde çalisiyor olabilir. Mesela task manager da process e sag tiklayip affinity verebilirsiniz.
Ikinci bir thread in maruz kaldigi temel ayar quantum süresidir. Yani bir islemcide sira bir thread e geldiginde, o esnada onun çalisabilecegi süresidir. Bunun ayari performance options daki ‘Adjust for best performance of: programs/background services’ bölümündedir. ‘Programs’ bütün threadlerin quantum süresini azaltir ve ‘background services’ uzatir.
Quantum süresini belirleyen baska etkenlerde vardir. Process/job bazinda quantum süreleri farkli olabilir. Örnegin bir processin kullanicinin odaginda olmasi, yani foreground da olmasi, onun threadlerinin quantum sürelerinin daha uzun tutulmasina neden olabilir. Bu ancak ‘Programs’ seçeneginde geçerlidir. Serverlar için tipik ‘background services’ ayarliyken bu quantum boosting dedigimiz mekanizma normalde devreye girmez. Quantum boosting, quantum süresini normal ayarlarda üç kat artirir.
Daha granüler ayarlar direk registryde HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\PriorityControl altindaki Win32PrioritySeparation degeri ile yapilabilir.
Örnegin buradaki hex deger 26h ise, bu binary 100110b dir. Bunu da ikili gruplara bölerek 10b, 01b ve 10b olarak okuyabiliriz veya degistirebiliriz.
Ilk iki hane 10b, 2d kisa quantum demektir. 1d uzun olur. Yani 10b ‘programs’ demektir.
Ikinci 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 katsayisi olarak belirler. Yani var olan quantum süreleri 3 kat uzatilir boost esnasinda. 01b bu katmani ikiye düsürür ve 00b de boost ayari belki olmasina ragmen, katsayiyi1 de tutar ve böylece foreground boost olmaz. Her ne kadar burasi açiklanmis olsada, bu degerleri degistirmek sistemi islevsel negatif etkileyebilir.
Yani affinity ve quantum öncelik mekanizmalarinin bir parçasidir. Threadlerin maruz kaldigi üçüncü öncelik mekanizmasi priorty level d'ir. Yani her threade bir öncelik rakami veririz. 1 den 15 e kadar degisken seviyelerdir. 16 - 31 arasina real time levels deriz. Sifir sistemindir ve bunu sadece zero page thread i kullanir. Bu özel threadi kitabin 9cu memory management bölümü açiklamaktadir.
Her thread in bir base priority degeri vardir ve bu thread yaratilirken processin önceligine göre verilir. Ayrica her threadin bir current priority degeri vardir.
Variable/dynamic/degisken aralikta kalacak sekilde isletim sistemi bir threadin önceligini geçici artirabilir. Sonra bu priority yine base seviyesine düser. Yani bir processin bir öncelik seviyesi ve bir threadin iki öncelik seviyesi vardir.
Bir process in öncelik seviyesi threadlerin base prioritysini bu sekilde etkiler:
örnegin task manager dan processlere base priority kolonunu ekleyebilirsiniz. Burada process normal ise, thread 6 ile 10 arasinda bir base priority alabilir. Normalde ortadaki, bu örnekte 8 i alacaktir. Above normal 10, high 13, below normal 6 ve process idle ise, threadin base prioritysi 4 olacaktir. Bu kadar granularity yapilmasinin nedeni de örnegin sistemin bazi threadlerin prioritysini az oranda da yükseltebilmesidir. Örnegin session manager normal de ürettigi bir threadin base priority si 8 degil de belki 9 da set edilir. Farkli boost mekanizmalarinda örnegin hemen 5 level degil de, belki geçici bir puan artirmak yeterli olur. Bu yapi neticesinde öncelikleri ince bir sekilde ayarlayabiliriz ve farkli önemlilik tasiyan isler farkli öncelik siralarina koyulabilirler.
Isletim sistemi asla 15 in üstüne base veya current i set etmez. Yani real time levellari vermez. Bunlari kritik kernel threadleri için kullanir. En nihayetinde temel problem, öncelikleri darbogaz yaratmadan belirleyebilmektir. Bundan dolayi priority farklari çok olmamalidirlar, yoksa yüksek priority grubunda olan threadlere hep sira gelir ve düsük öncelikli threadler toplamda çok beklerler. Yani yüksek öncelikli thread grubu sistemin verimsiz çalismasina neden olur. Ayrica beklenmedik senkronizasyon sorunlari da olusabilir. Threadler farkli esleme durumlarinda birbirlerini beklediklerinden dolayi çok farkli sorunlar olusabilir. Yani örnegin bir kilidi tutan bir thread islemciyi çok beklerse, onu bekleyen belki çok daha kritik threadler de daha çok beklemek zorunda kalirlar. Ondan mesela bir processin içindeki bütün threadler genelde aralarinda en çok esleme yaptiklarindan dolayi default da bunlar ayni base priority e sahiptirler.
Thread yönetimi, thread scheduling, kernelde uyarlanir. Her zaman bir islemci bir thread den digerine geçtiginde bir context switch olusur. Bunu örnegin performans monitör ile thread objesinin altinda Context Switches/sec olarak takip edebiliriz. Örnegin bir processin bu degeri yüksek ise, threadlerinin çok bekledigine dair bir bulgu olabilir. Yani threadler quantum sürelerini bitirmiyor olabilirler. Her context switch de, çalismasi kesilen thread in bütün bilgileri kendi alanina kayit edilir ve bir sonra çalisacak threadin bilgileri okunur. Threadler bir sefer sira onlara geldiginde genelde islemlerini tamamlayamadiklari için bu islem çok kritiktir. Thread yönetimi her thread geçisinde bir sonraki threadi önceden belirlemekten sorumludur.
Thread yönetiminde tutulmasi, bilinmesi gereken bir bilgi de thread in kendi durumudur. Ready state tinde olan threadler bizi öncelikli ilgilendirir. Bunlar kernel in kendilerini çalistirmasini bekler. Running deki bir thread quantum süresi bittiginde ve en az kendisinin priority sinde baska bir thread oldugunda, veya baska bir thread preemption yapildiginda, çalismasini sonlandirip tekrar ready olur. Eger bu durumlar yoksa thread çalismaya devam edebilir. Bir thread bir gate i, yani örnegin bir guarded mutex i bekliyor olabilir. Bu senkronizasyon objelerini bekleyen threadleri Gate Waiting state ine aliriz. Waiting state de vardir, ancak mutex i açmak için IRQL APC seviyesini beklemek zorunda oldugumuz için ilgili threadleri de ayri bir durumda toplariz, çünkü burada arti yapilmasi gereken isler çoktur. Böylece waiting queuesundaki islem yapisi sadelestirilmis olur. Mesela thread in stack i paged out ise ve geri yüklenmesini bekliyorsak, threadi transition state e aliriz. Bütün bu bilgileri de dispatcher database inde tutariz. Her CPU un bir database i olur. Böylece threadleri organize ve katalogize etmis oluruz. Kitap bütün state leri bütün detaylari ile özetlemektedir.
Her priority level in kendi ready queuelari, bekleme kuyruklari oldugundan engellenmesi gereken temel bir durum vardir. Hep yukaridaki öncelik queuelarinda bekleyen threadler çalisirsa belki daha düsük öncelikli threadlere sira çok nadir veya hiç gelmeyebilir.
Ondan priority boost diye bir mekanizma vardir. Beklenen IO bittiginde, executive event veya semaphore açildiginda, farkli nedenlerden dolayi threadler çok uzun çalismamissa veya uzun süredir bir kaynak bekliyorlarsa bu boosting devreye girer. Farkli senaryolarda farkli boost mekanizmalari devreye girer. Örnegin network üzerinden yapilan IO un bitmesini bekleyen thread, IO bittiginde 2 priority level yükseltilir. Ama ses aygiti ile ilgili bir IO bittiginde bekleyen threadin boostu 8 level olur. Elbette bunlar hep current ile yapilan boostlardir. Base priority degeri burada boost edilmez. Ama örnegin bir semaphore u bekleyen bir thread in base prioritysini 15 i geçmeyecek sekilde geçici boost ederiz. Foreground da olan processler için, kullaniciyi bekletmemek adina quantum boost arti kernel objesi beklendikten sonra ilgili threadlere current priority boost da yapilir.
Son olarak balance set manager yukaridaki mekanizmalar ile korulamayan threadlerin de ‘cpu starvation’ a ugramamasini saglar. Bu her saniye çalisip ready queuelari tarayan bir thread dir. Dört saniyedir herhangi bir priority ready queuesunda bekleyen threadlerin prioritysini 4 quantum süresi boyunca 15 de kalacak sekilde boost eder. Bu süre bitince thread yine eski degerlerine geri döner. Balance set manager in bu çalisma mantigi her queue un sadece ilk öndeki 16 threadi taramasindan kaynaklanir. Gelecek taramada son biraktigi thread den (hala oradaysa) devam eder. Böylece kendiside bir thread oldugu için fazla islemci kaynagi kullanmaz. Bunun disinda da multimedia için farkli boost mekanizmalri devreye girebilir.
Yani farkli senaryolarda nasil ve neye (current, base, quantum) boost yapilacagi net çizilmistir.
Bu boost mekanizmalari ile gereksiz beklemeler ve darbogazlar engellenir.
Threadlerin prioritylerini performance monitörden takip edebiliriz.
Islemci seçimi olarak processlerin threadleri hep bütün islemcilere dagitilmak istenir. Islemcide de mesgul olmayan çekirdege verilmeye çalisilir. Olusturulan bütün processlerin ideal processor olustururken bir önceki processe göre verilir. Böylece dönemsel bir dagitim olusur. Eger bütün islemciler yogunsa, priority e göre preemption yapilabilecek islemci aranir. Yani her islemcinin bir standby state de olan bir thread i vardir. Bu ‘queue’ ve thread özeldir, çünkü runningden sonra çalisacak thread dir. Eger bu threadin prioritysi çalistirmak istedigimiz yeni threadinkinden düsükse, o threadi standby dan ready e çekeriz ve bu threadi standby a koyariz. Preemption yapmis oluruz. Böylece daha öncelikli threadler de bütün islemcilerde öncelik görebilirler.
Thread scheduling, thread dispatching in üstüne önceligini yazamaz. Yani yüksek öncelikli IRQL 0 da çalisan bir user mode thread, daha düsük öncelikte IRQL1 veya IRQL 2 de çalisan bir kernel threadten daha öncelikli degildir. Yani IRQL seviyesi threadin current ve base priority sini ezer. Yani normal applikasyon threadleri IRQL 0 da ve 1-15 priority level arasinda çalisirlar.
Son olarak Job kavramimiz vardir. Basitçe bu birden fazla processi ve bunlarin threadlerini toplamaktir. Örnegin run as administrator bir cmd açiniz. Bunun içinden de notepad i baslatiniz. Iste simdi notepad de run as administror olarak çalisacaktir. Kisaca child process, parent processin güvenlik kontekstini alacak. Bu ortak nokta bu iki processi baglayacak, iste bu gruplama da bir job olacak. Job sadece gruplanmis processlerdir.
Bu konularin hepsini detayli bir sekilde Windows Internals 5th ed. Chapter 5 ‘Processes, Threads and Jobs’ altinda bulabilirsiniz: https://technet.microsoft.com/en-us/sysinternals/bb963901
Eger kendinizi Windows mimarisinde veya sistem/sürücü yazilimcisi yönünde gelistirmek istiyorsaniz veya isletim sisteminin bir referans kitabina ihtiyaciniz varsa bu kitabi incelemenizi öneririm.
Basar Güner
Sr. Support Engineer, Microsoft