Windows Debugging 203

Senkronizasyon genel anlamda yazilim akisini mümkün kilar ve en temel problemlerden biridir. Isletim sisteminde bu sorun herhangi bir yazilimdan pek farkli degildir, sadece eslenmesi gereken çok daha fazla senaryo vardir. Windows un bununla ilgili yapisi CPU da baslar. CPU un trap özelligi vardir, yani çalisan bir thread bir exception yarattiginda veya bir interrupt olustugunda bunu isletim sistemine aktarabilir. Windows tarafinda bu trap handler isimli yapiya aktarilmaktadir. Interruptlar öngörülemeyen dis etkenlerden olusurken (örnegin klavye tusuna bastigimizda bir interrupt göndermis oluruz, yani CPU dan hizmet bekleriz), exceptionlar bir kodun akisi sayesinde olusurlar. Yani exception yaratan bir kodu her çalistirdigimizda yine ayni durumda ayni exceptioni aliriz. Yazilim ve donanim her ikisini de olusturabilir. Trap handling sayesinde örnegin bir interruptdan sonra ‘interrupt’ edilmis thread kod akisina devam edebilir ( preemptive/reentrant ) veya bir exception sonucunda sunucu mavi ekrana düsebilir.

Interruptlari da aralarinda senkronize etmek gerekir, yani öncelik vermek gerekir. Bunu IRQ (interrupt request) lar ile yapariz. Msinfo32/Hardware Resources/IRQs dan görebileceginiz gibi sistemdeki farkli aygitlarin farkli IRQ seviyeleri vardir. System timer (0 olarak) en yüksek olandir ve klavye bir sonraki. CPU interrupt edildiginde kimin interrupt ettigini listesinden alip ilgili isletim sistemindeki koda yönlendirir; örnegin klavyenin sürücüsüne. Sadece system timer bir interrupt daysa klavye interrupt olusturamaz. Onun disinda klavyenin interrupt önceligi heg geçerlidir. Mümkün IRQ sayisini CPU daki interrupt controller belirler. Bu controller, örnegin advanced programmable interrupt controller da sözü geçen controllerdir; device manager da Computer altinda detayini görebilirsiniz. Interrupt mantigi disaridan öngörülmeyen gelen donanimsal IO lari hem aralarinda, hem de isletim sisteminin akisini optimize etmek için kullanilir. Örnegin bir diske bir IO baslatildiginda onun bitmesini sistem olarak beklemeyiz. IO bitince ilgili aygit sürücüsü yazilimsal bir interrupt olusturarak sisteme IO un bittigini bildirebilir.

Donanimsal interrupt mantigini anlamak daha kolaydir, ancak yazilimsal interrupt yapmak istedigimizde isletim sistemi tarafinda daha yetkin bir yapiya ihtiyacimiz olur. Yazilmsal interruptlari örnegin user mode dan kernel e geçerken kullaniriz. Ayrica interrupt controller da donanimsaldir, yani hizi isletim sisteminin hizi belirler. Windows da Kernel de IRQL (irqLevel) yapisi mevcuttur. Burada rakam yükseldiginde öncelik de yükselir. Trap handling mantigi aynidir. Yani bir interrupt olusur ve ilgili sürücünün ilgili fonksiyonu çagrilir. Bu semayi isletim sistemi hep uyarlar, yani donanimsal interruptlari da bu sekilde yönetir. Her CPU çekirdegin bir IRQL semasi vardir ve bu seklide prosessörler arasi interrupt da yapmamiz mümkündür (IPI). Belli bir düsük seviyenin üstündeki (DPC, örnegin thread ler ile lgili quantum süreleri gibi timer larin çalistigi seviye) interruptlar hep hizli olmali ve interrupta girdikten sonra beklememiz CPU yu bekletmek olabilir. Ondan mesela bu yapi sadece kernel da kullanilir ve ilskin objeler non paged pool da yani RAM de olmalidirlar. Ondan da mesela asla aygit sürücüleri page file i kullanmazlar. Farkli davranislarda IRQL ile ilgili mavi ekranlara düsebiliriz.
Bütün bunlarin mümkün oldugu bir altdaki APC seviyesi ve burada user mode seviyesinde de bundan dolayi çalisabilmekteyiz. Burada bir threadin kullandigi bütün objeleri bekleyebiliriz. Bundan dolayi File IO ile ilgili API lerden APC seviyesinde çalisirlar. Kernel bu düsük seviyede örnegin sonlanan thread islemlerini yapar. Düsük seviyeli APC ve DPC de interrupt yapmak isteyen threadleri her CPU için bir bekleme kuyruguna koyariz. Daha yukaridaki seviyelerde kuyruk degil de, daha karmasik esleme mekanizmalari devreye girer.
Interrupt yapabilmek için sürücüler farkli interrupt objelerini kullanabilirler. Bunlar ile bir interrupt service request, ISR, olusturabilirler.
Ayrica IRQL sanal bir IRQ yapisi gibi oldugu için lazy IRQL denilen kisa yollari mümkün kilar. Yani donanima gitmeden de IRQL ile yazilimsal interruptlari yönetebiliriz. Son olarak donanimsal IRQ lar ile Kerneldeki IRQL leri nasil eslestirdigimizi merak edebilirsiniz. Cevabi HAL . Interrupt controller ve isletim sistemi sürümüne göre eslestirmeleri yapar. Ancak burada degismeyen sabit kayitlarda vardir. Örnegin en yüksek seviye kernelindir. Mesela bir sürücü ciddi bir exception olusturmus ise ve isletim sisteminin geri dönülemez bilgi kirliligine maruz kalmasi söz konusu ise, hemen mavi ekrana düsmemiz gerekir. Ilk baska islerin bitmesini bekleyemeyiz. Bu her seyden daha önemlidir ve hemen yapilmalidir. Iste bunu mümkün kilan esleme mantigi yukarida özetlenmistir.

Exception larin yönetmeligi exception dispatcher dadir ve structured exception handling yapisinda uyarlanir. Her bir exception hatasi hard coded bir interrupt ile iliskilendirilmistir. Örnegin bir divide error sifir a vururken, bir sayfa erisim hatasi 14 dedir. Exception dispatcher in görevi basitçe exceptiondan kurtulmaktir. Örnegin çalisan kodun exception handlingi olabilir, ve belli hata da belli baska bir kod parçasina yönlendirilir. Ya da sistemin dr Watson gibi default debugger i devreye girebilir. Eger devreye alinabilir bir exception handling yoksa isletim sistemi durumu kendisi çözmeye çalisir ve bu da basarisiz olursa Kerneldeki exception mavi ekrana neden olabilir. User modeda da yazilim sonlandirilir ve crash olur, yani Windows error reporting, WER,e yönlendirilir.
Burasida First ve Second chance exception terimlerin kaynagidir. Exception olusur ve default debugger (örnegin adplus) devreye alinir. Debugger in durumu handle etmesi beklenir. Bu canli bir live debug da olabilir teoride. Eger debugger yoksa sistem kendisi sonlandirmayi dener ve yapamazsa Second a geçmis oluruz. Ayni sekilde debugger çözemezse de Second a geliriz. Eger bu da handle edilemezse Second chance e geçeriz ve en geç burada artik user mode thread ve prosesi sonlanir. Debugger varsa dumpini almis oluruz.

Kerneldeki esleme mekanizmalarinda Executive Objeler önem tasimaktadirlar. Object Manager bunlari (diger kernel objeleri gibi) yönetir. Burada hem Kernel in iç çekirdegi Executive in hem de Kernel dedigimiz dis çekirdegin kullandiklari alanlari mevcuttur. Bu kadar bilgi ile çok sey ifade etmez, ama örnegin bir File, Process, Thread, Token, Event vs., bunlarin hepsi Executive Objeleridir. Bunlarin kendileri ile ilgili bilgilerin tutuldugu bir obje yapisi vardir. Belli open/close gibi metot lar ile bu objeler ile interaksiyona girebiliriz. User mode da process ler herhangi bir objeye erismek istediklerinde, o obje ile olan iliskilerini belirleyen bir handle olusturmak zorundadirlar. Kernel mode da bu erisimi daha degisik yapilar ile senkronize edebiliriz. Son olarak objenin güvenlik ve sonlandirilma bakis açilari vardir.

Yazilimsal senkronizasyon daha yaygin senkronizasyon olarak bilinmektedir ve Windows a özgü degildir. Buradaki temel kavram mutual exclusion. Kisaca, sadece bir ve o çalisan kodun bir anda bir kaynagi modifiye edebilmesidir. Basitçe bu duruma Critical Section deriz. Yani bir paylasilmaz kaynak üzerinde bir anda sadece bir kodun çalismasi ve digerinin ilk kodun bitmesini beklemesi. Örnegin iki thread bir crtitical section a girebilirler. Bu SMP isletim sistemin en önemli temelidir. Daha önce sözü geçen mekanizmalar ile her senaryoda eslenme saglamak mümkün degildir.

Burada en yaygin mekanizma Spinlock. Mesela farkli CPU lar arasi senkronizasyonu saglayabilmek için spinlock ile DPC queue un erisimini almaya çalisiriz. Yani spinlock bir kilit gibidir. Spinlock u isteyen thread mesela bir Do loop u ile sürekli spinlocku digerionu birakanar kadar almaya çalisir. Queued Spinlock türünde spinlock u bekleyen thread sadece bir CPU da çalisir ve baska CPU daki Spinlock açildiginda, o diger CPU Spinlock u bekleyen threadin çalistigi CPU u haberdar eder.

Kernel dispatcher objeleri ile bir objeyi signaled veya non signaled yapabiliriz. Sinyalsiz durmdaysa obje, bir thread onu kullanmaktadir ve digerleri bekler. Sinyalli duruma düstügünde Kernel iliskin threadi beklemeden alir ve kaynaga erisimi saglar. Mesela bir baska threadi de bekliyor olabiliriz, o zaman o thread sonlandiginda sinyal aliriz ve bekleyen thread devam edebilir.
En önemli kullanilan kernel dispatcher objesi Mutex/Mutant dir. Örnegin kaynagi kullanan thread bunu 1 e set eder ve isi bitince sifira set eder. 0 signaled yapar ve baska threadler bu sifirken mutexin korudugu kaynaga erisebilirler. Fast mutex türevi dispatcher i devreye sokmadan çalisabilir. Bunun bir ilerisi Executive Resource dur. Pushlock da mutex e benzerdir ama semaphore gibi shared Access için kullanilabilir. Fast mutex gibi bekleyen bir thread kendisini belli edebilir ama ayrica exclusive ve shared seçme imkânini sunar.
Yaygin bir objede Semaphore dur. Burada belli bir sayida thread bir kaynaga ayni anda erisebilirler. Mutex e olan fark burada bir sayaç olmasidir. Örnegin bes thread ayni anda bir kaynagi okuyabilirlerse, o zaman ilk thread sifir i bire artirir ve besinci thread dört den bes e arttirir. Gelen altinci thread sayacin en az dörde düsmesini beklemek zorundadir.
Baska kernel dispatcher objeleri örnegin Event, Timer veya Threadin kendisidir. Burada signaled state e düsdüklerinde bekleyen threadler haberdar edilirler.

Bunlarin hepsi en temel sistem mekanizmalaridir. Yukarida sözü geçen konular hakkinda daha detayli bilgiyi Windows Internals 5th ed. Chapter 3 ‘System Mechanisms’ altinda bulabilirsiniz:

https://technet.microsoft.com/en-us/sysinternals/bb963901

Yukarida sözü geçmeyen detaylari bulabilirsniz. Ayrica ayni chapter Hyper-V konusunda da detayli bilgi vermektedir.

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