Kesmeler, işletim sisteminin önemli bir parçalarıdır. Hayati görevlerin gerektiği gibi tamamlanmasını sağlarlar ve iki şekilde ortaya çıkarlar: donanım ve yazılım. Bu Rehberde, ağırlıklı olarak donanım kesmelerine ve bunların işlemci tarafından nasıl ele alındığına odaklanacağız.
Donanım kesmeleri işletim sisteminde IRQ veya Kesme İstekleri ile temsil edilir. Bu istekler, anahtar kelime veya sabit disk gibi bir dizi donanım aygıtı tarafından verilir. Bir istek yapıldığında, işlemciye bir sinyal gönderilir, işlemci isteği alır ve isteği işleyebilecek uygun kesme işleyicisi için Interrupt Dispatch Table (IDT) adı verilen özel bir tabloyla iletişime girer.
IDT'ye bakmadan önce, sinyalin işlemciye nasıl ulaştığına ve işlemcinin kesme isteğini hangi cihazın gönderdiğini nasıl bildiğine bir göz atalım.
Aygıt Kesmelerinin İşlenmesi
Her isteğin kendi IRQ numarası vardır, bu numaralar Advanced Programmable Interrupt Controller veya APIC olarak bilinen farklı hatlarla ilişkilendirilir. Daha eski sistemlerde PIC'ler kullanılırdı ancak bunlar artık oldukça eski ve bu nedenle sadece APIC'lere odaklanmamız gerekiyor. APIC'ler her işlemciye bağlı yerel bir APIC ve donanım aygıtları ile yerel APIC'ler arasında yer alan bir I/O APIC'ten oluşur. Aslında, bu bilgiyi WindDbg'de !ioapic ve !apic kullanarak dökebiliriz.
Bir donanım cihazı I/O APIC'e bir IRQ verir, IRQ daha sonra IRQ'yu işlemcinin IDT'sine indekslemek için kullanılan bir kesme vektörüne çeviren yerel APIC'e gönderilir. Her işlemci kendi IDT'sine sahip olacaktır. IDT daha sonra kesme isteğini işlemek ve gerekirse işlemcinin Kesme İstek seviyesini (IRQL) yükseltmek için ilgili Interrupt Service Routine'ini (ISR) arayacaktır. IDT'nin adresi IDTR adı verilen bir kayıtta saklanır. Bu kayıt sırasıyla LIDT ve SIDT talimatları tarafından manipüle edilir.
Kesme başladıktan sonra, donanım kesmesine neden olan aygıt sürücüsü, kesmeye hizmet etmek için gereken verilerin diğer aygıt sürücülerinin iş parçacıkları tarafından bozulmamasını sağlamak için bir spinlock alır (KeSynchronizeExecution'ı çağırır). Bu spinlock, kesme nesnesinin SynchronizeIrql alanında belirtilen Kesme istek seviyesinde elde edilir. Uygun veriler okunduktan sonra spinlock serbest bırakılır.
İşlemciyi Donanımsal bir Kesme istek seviyesinde tutmak oldukça zararlı olabileceğinden, ISR kesmenin geri kalanını daha sonraki bir zamanda tamamlamak için bir DPC nesnesini kuyruğa alır (IoRequestDpc'yi çağırır). DPC nesnesi kuyruğa alındıktan sonra, işlemcinin DPC kuyruğuna eklenecek ve işlemcinin IRQL seviyesi DISPATCH_LEVEL seviyesine düştüğünde DPC'nin ISR'si çağrılacaktır.
APIC
Linti0 ve Linti1, yerel APIC ile ilişkili yerel kesme pinlerini ifade eder. Donanım aygıtları bu pinlere bağlanabilir ve yerel APIC'e kesmeler gönderebilir, bu da işlemciye iletilir. Bunlar yerel kesme olarak bilinen duruma bir örnektir. Yerel kesmeler daha sonra toplu olarak yerel vektör tablosu olarak bilinen özel kayıtlar koleksiyonuna yönlendirilir. LVT içindeki girişler, söz konusu girişin nasıl kullanılmak üzere yapılandırıldığına bağlı olarak IDT'ye iletilebilir. Vektör, IDT içindeki girişi ifade eder.
NMI bir kesme teslim çeşididir ve kesmenin nasıl teslim edilmesi gerektiğini belirtir. Teslim çeşidi NMI olarak ayarlanırsa, kesme vektör numarası göz ardı edilir. NMI daha öncelerden bahsettiğim gibi göz ardı edilemeyen kesmeler anlamına gelir. Öte yandan, Fixed bunun tam tersidir ve vektör numarası IDT'ye indekslemek ve ilgili kesmeyi işlemciye iletmek için kullanılacak daha sonralarda.
APIC Modu, APIC'in çalışma modunu ifade eder. x2APIC en yeni APIC mimarisidir ve eski xAPIC mimarisinin bir uzantısıdır. Modlar arasındaki temel fark, x2APIC'in daha iyi performans ve bazı ek özellikler sağlamasıdır.
APIC ile ilgili daha fazla bilgiyi Intel developer documentation belgelerinde bulabilirsiniz.
Interrupt Dispatch Table
IDT'yi !idt uzantısını kullanarak görebiliriz:
IDT'nin adresini vurguladım. Bu, IDTR kaydı içinde saklanan adresle eşleşir:
[CODE lang="rich" highlight="2"]5: kd> r idtr
idtr=fffff880030b06c0[/CODE]
Sol taraftaki sayılar, IDT'yi indekslemek ve ilgili ISR'yi bulmak için kullanılan kesme vektörleridir. Örneğin, page fault'lar için kesme vektörü 0e'dir. IDT'nin belirli bir girişini aşağıdakileri yaparak dökebiliriz:
[CODE highlight="5"]5: kd> !idt 0e
Dumping IDT: fffff880030b06c0
0e: fffff800034b9940 nt!KiPageFault[/CODE]
Şimdi, KINTERRUPT yapısının bazı girdiler için var olduğunu ancak hepsi için olmadığını fark etmiş olabilirsiniz, bunun nedeni page fault'un teknik olarak bir kesme değil bir istisna olmasıdır ve bu nedenle yukarıda belirtilen yapıya sahip olmamasıdır. Şimdi bu yapının ne anlama geldiğine bakalım:
Vektör alanı, IDT'ye indekslemek için kullanılan kesme vektör numarasını ifade eder. IRQL alanı, kesmenin çalışacağı IRQL seviyesini ifade eder. Bunu aşağıdakileri kullanarak bir IRQL numarasına çevirebiliriz:
IRQL seviyesine bakarak (10) bunun bir donanım kesmesi olduğunu görebiliriz. Bu, ServiceRoutine alanı kesmeyi işlemek için çağrılacak ISR'yi ifade eder.
Şimdi, bu beni IRQ'lar nedeniyle paylaşılan kesmelerle ilgili noktaya getiriyor. Bir işlemci için mevcut kesme hattı sayısından daha fazla cihaz olabileceğinden, IRQ'lar birden fazla cihaz arasında paylaşılabilir, bu da bir kesme vektörünün birden fazla cihaza hizmet vermek için kullanılabileceği anlamına gelir. Bunu InterruptListEntry alanı içinde inceleyebiliriz.
Birden fazla kesmenin hepsinin aynı kesme girişiyle nasıl ilişkilendirildiğine dikkat ettik mi?
Listedeki ilk kesmeyi atalım.
İlişkili iki kesmenin InterruptListEntry bağlantılı listesinde olduğunu görebiliriz.
[CODE lang="rich" highlight="2,5"]1: kd> ? ffffa18198314c80+8
Evaluate expression: -103897000489848 = ffffa181`98314c88
1: kd> ? ffffa18198314a00+8
Evaluate expression: -103897000490488 = ffffa181`98314a08[/CODE]
Şimdi, DispatchAddress alanına baktığımızda, kesmenin chained dispatch fonksiyonu kullanılarak gönderileceğini görebiliriz. Chained dispatch fonksiyonu, kendileriyle ilişkili başka kesmelerin olduğu, yani birden fazla kesmenin aynı IRQ'yu paylaştığı kesmelere kontrol aktarmak için kullanılır. Öte yandan, bu durumun söz konusu olmadığı kesmeler için kesme gönderme işlevi çağrılır.
Mod ve Polarity alanları da dikkat çekicidir ve bir kesmenin işlemciye nasıl iletileceğini belirlemek için birbirleriyle birlikte kullanılırlar. Bu alanların her ikisi de numaralandırmadır. Bunlar aşağıdaki komutlar kullanılarak incelenebilir:
Interrupt mode, işlemciye gönderilecek kesme türünü ifade eder. Şu anda iki mod vardır: Edge Triggering ve Level Triggering. Edge-triggered Interrupts, mesajlaşma iletim mekanizmasını kullanan kesmeler için kullanılırken, Level Triggering Interrupts, bir APIC için mevcut fiziksel hatların sayısını dikkate alan geleneksel hat tabanlı iletim mekanizmasını kullanır. İkisi arasındaki fark için...
[CODE lang="rich" highlight="1"]1: kd> dt _KINTERRUPT_POLARITY
nt!_KINTERRUPT_POLARITY
InterruptPolarityUnknown = 0n0
InterruptActiveHigh = 0n1
InterruptRisingEdge = 0n1
InterruptActiveLow = 0n2
InterruptFallingEdge = 0n2
InterruptActiveBoth = 0n3
InterruptActiveBothTriggerLow = 0n3
InterruptActiveBothTriggerHigh = 0n4[/CODE]
Polarity, hat tabanlı veya mesaj tabanlı bir kesmenin işlemciye nasıl iletileceğini açıklar. Yükselen ve alçalan kenarın, bir sinyalin 0'dan 1'e geçişini veya tam tersini tanımlayan sinyal kenarına atıfta bulunduğuna dikkat etmek önemlidir. 0'dan 1'e geçiş yapan bir sinyal yükselen bir kenara sahipken, düşen bir kenara sahip olan bir sinyal 1'den 0'a geçiş yapmaktadır.
Numaralandırma değerleri hakkında daha fazla ayrıntı burada bulunabilir - _KINTERRUPT_POLARITY (wdm.h) - Windows drivers
Line-based and Message-based interrupts
Belirli bir kesme pinini kullanmak ve ardından ilgili kesme hattı boyunca bir kesme sinyali göndermek yerine. Message-based interrupts (MSI'lar), tipik olarak adres alanının bellek eşlemeli bir I/O bölgesi olan belirli bir bellek adresine yazar.
Şimdi, Interrupt'a geri dönüp farklılıklara bakarsak...
Bu dosyada herhangi bir MSI görünmediğinden, daha önce olduğu gibi aynı kesmeyi kullandım, ancak örnek yeterli olacaktır. İlk önemli fark, gönderme işlevinin KiInterruptMessageDispatch olmasıdır. MessageServiceRoutine alanı, satır tabanlı kesme muadili ile aynı anlama sahiptir.
MessageIndex alanı _IO_INTERRUPT_MESSAGE_INFO yapısının bir parçası olan MessageInfo dizisinin indeksi olarak kullanılır. Bu, mesaj hizmet rutinine aktarılır. Daha fazla ayrıntı için lütfen buraya bakın - KMESSAGE_SERVICE_ROUTINE (wdm.h)
IDT içindeki her bir girdinin _KIDTENTRY64 (x86 sistemlerinde _KIDTENTRY) adlı bir yapı tarafından temsil edildiği de belirtilmelidir. Bu yapıyı inceleyebiliriz:
Şimdi, teknik olarak buradaki alanların bir segment tanımlayıcı biçimi olan IDT kapı tanımlayıcısının alanına karşılık geldiğini belirtmek önemlidir. Segment tanımlayıcıları kısaca; gerekli erişim hakları vb. dahil olmak üzere belleğin bir bölümünü tanımlamak için kullanılırlar.
OffsetLow, OffsetMiddle ve OffsetHigh alanları, kesme vektörünün bellek adresini oluşturmak için bir araya getirilir. Dpl alanı, bölüme erişilebilmesi için işlemcinin çalışması gereken halka seviyesini (ayrıcalık seviyesi) tanımlamak için kullanılır. Type alanı segment tanımlayıcısının türünü tanımlamak için kullanılır, bu durumda ya bir görev kapısı, tuzak kapısı ya da bir kesme kapısı olacaktır. Present bayrağı segmentin bellekte mevcut olup olmadığını belirlemek için kullanılır.
Interrupt Flag
Bundan diğer forumda da bahsetmiştim. Kesme bayrağı, işlemcinin göz ardı edilebilen kesmeleri ele alıp almayacağını belirtmek için kullanılır. Göz ardı edilebilen kesmeler, NMI'lar dışındaki tüm donanım kesmeleridir. Bu bayrak 0 olursa, işlemci göz ardı edilebilen kesmeleri yok sayacaktır. STI (Set interrupts) ve CLI (clear interrupts) komutları bu EFLAGS kaydında yapılan değişiklikleri işlemek için kullanılır.
İstek seviyeleri, önceliği belirten durumlardır; her IRQ HAL aracılığıyla belirli bir IRQL ile eşleştirilir. X64 sistemlerde Kesme istek seviyeleri 0'dan (en düşük) 15'e (en yüksek) kadar çalışır. X86 sistemlerde bu seviyeler 0'dan 31'e kadar çalışır.
Kesme seviyeleri, bir işlemcinin hangi kesmeler tarafından kesilebileceğini ve daha sonra işlenebileceğini belirlemek için kullanılır. Daha yüksek seviyeye sahip bir kesme daha düşük seviyeli bir kesmeye göre öncelikli olacaktır. Her işlemci, İşlemci Kontrol Bölgesi (PCR) içinde bulunabilen geçerli bir seviyeye sahip olacaktır. Bu yapıyı !pcr uzantısını kullanarak inceleyebiliriz:
Bir işlemci mevcut seviyesinden daha düşük bir kesme alırsa, kesme göz ardı edilir ve seviye düşürüldüğünde çalıştırılmak üzere programlanır ya da farklı bir işlemcide çalıştırılmak üzere programlanır.
İşlemcinin kesmeye hizmet verebilmesi durumunda, mevcut kesme seviyesi, mevcut thread bağlamıyla birlikte kaydedilecek, işlemcinin kesme istek seviyesi daha sonra kesmeyle ilişkili seviyeye yükseltilecek ve uygun ISR çağrılacaktır. Kesmelerin verimli bir şekilde servis edilebilmesini sağlamak için işlemcinin her zaman mümkün olan en kısa sürede mümkün olan en düşük kesme istek seviyesine dönmeye çalışacağı unutulmamalıdır.
İşlemci için kayıtlı Kesmek istek seviyesi değerini !irql uzantısını kullanarak görebiliyoruz:
[CODE lang="rich" highlight="2"]5: kd> !irql
Debugger saved IRQL for processor 0x5 -- 0 (LOW_LEVEL)[/CODE]
Alternatif olarak, işlem için kaydedilmiş Kesme istek seviyesi, PRCB içinde bulunabilir, !prcb kullanılarak incelenebilir:
Kaynaklar: CodeMachine, Micrososft Learn.
Donanım kesmeleri işletim sisteminde IRQ veya Kesme İstekleri ile temsil edilir. Bu istekler, anahtar kelime veya sabit disk gibi bir dizi donanım aygıtı tarafından verilir. Bir istek yapıldığında, işlemciye bir sinyal gönderilir, işlemci isteği alır ve isteği işleyebilecek uygun kesme işleyicisi için Interrupt Dispatch Table (IDT) adı verilen özel bir tabloyla iletişime girer.
IDT'ye bakmadan önce, sinyalin işlemciye nasıl ulaştığına ve işlemcinin kesme isteğini hangi cihazın gönderdiğini nasıl bildiğine bir göz atalım.
Aygıt Kesmelerinin İşlenmesi
Her isteğin kendi IRQ numarası vardır, bu numaralar Advanced Programmable Interrupt Controller veya APIC olarak bilinen farklı hatlarla ilişkilendirilir. Daha eski sistemlerde PIC'ler kullanılırdı ancak bunlar artık oldukça eski ve bu nedenle sadece APIC'lere odaklanmamız gerekiyor. APIC'ler her işlemciye bağlı yerel bir APIC ve donanım aygıtları ile yerel APIC'ler arasında yer alan bir I/O APIC'ten oluşur. Aslında, bu bilgiyi WindDbg'de !ioapic ve !apic kullanarak dökebiliriz.
Bir donanım cihazı I/O APIC'e bir IRQ verir, IRQ daha sonra IRQ'yu işlemcinin IDT'sine indekslemek için kullanılan bir kesme vektörüne çeviren yerel APIC'e gönderilir. Her işlemci kendi IDT'sine sahip olacaktır. IDT daha sonra kesme isteğini işlemek ve gerekirse işlemcinin Kesme İstek seviyesini (IRQL) yükseltmek için ilgili Interrupt Service Routine'ini (ISR) arayacaktır. IDT'nin adresi IDTR adı verilen bir kayıtta saklanır. Bu kayıt sırasıyla LIDT ve SIDT talimatları tarafından manipüle edilir.
Kesme başladıktan sonra, donanım kesmesine neden olan aygıt sürücüsü, kesmeye hizmet etmek için gereken verilerin diğer aygıt sürücülerinin iş parçacıkları tarafından bozulmamasını sağlamak için bir spinlock alır (KeSynchronizeExecution'ı çağırır). Bu spinlock, kesme nesnesinin SynchronizeIrql alanında belirtilen Kesme istek seviyesinde elde edilir. Uygun veriler okunduktan sonra spinlock serbest bırakılır.
İşlemciyi Donanımsal bir Kesme istek seviyesinde tutmak oldukça zararlı olabileceğinden, ISR kesmenin geri kalanını daha sonraki bir zamanda tamamlamak için bir DPC nesnesini kuyruğa alır (IoRequestDpc'yi çağırır). DPC nesnesi kuyruğa alındıktan sonra, işlemcinin DPC kuyruğuna eklenecek ve işlemcinin IRQL seviyesi DISPATCH_LEVEL seviyesine düştüğünde DPC'nin ISR'si çağrılacaktır.
APIC
C:
5: kd> !apic
Apic (x2Apic mode) ID:4fc73880 (4fc73880) LogDesc:4fc73880 TPR 4FC73880
TimeCnt: 4fc73880tmbase SpurVec:80 FaultVec:4fc73880 error:4fc73880 DISABLED
Ipi Cmd: 000000a6`f7afd4c8 Vec:C8 NMI Dest=Othrs-Pend lvl high rirr
Timer..: 00000000`4fc73880 Vec:80 FixedDel Dest=Self-Pend edg low m
Linti0.: 00000000`4fc73880 Vec:80 FixedDel Dest=Self-Pend edg low m
Linti1.: 00000000`4fc73880 Vec:80 FixedDel Dest=Self-Pend edg low m
TMR: 05-6, 0B-10, 12, 15-16, 1A-1c, 1F, 25-26, 2B-30, 32, 35-36, 3A-3c, 3F, 45-46, 4B-50, 52, 55-56, 5A-5c, 5F, 65-66, 6B-70, 72, 75-76, 7A-7c, 7F, 85-86, 8B-90, 92, 95-96, 9A-9c, 9F, A5-a6, AB-b0, B2, B5-b6, BA-bc, BF, C5-c6, CB-d0, D2, D5-d6, DA-dc, DF, E5-e6, EB-f0, F2, F5-f6, FA-fc, FF
IRR: 05-6, 0B-10, 12, 15-16, 1A-1c, 1F, 25-26, 2B-30, 32, 35-36, 3A-3c, 3F, 45-46, 4B-50, 52, 55-56, 5A-5c, 5F, 65-66, 6B-70, 72, 75-76, 7A-7c, 7F, 85-86, 8B-90, 92, 95-96, 9A-9c, 9F, A5-a6, AB-b0, B2, B5-b6, BA-bc, BF, C5-c6, CB-d0, D2, D5-d6, DA-dc, DF, E5-e6, EB-f0, F2, F5-f6, FA-fc, FF
ISR: 05-6, 0B-10, 12, 15-16, 1A-1c, 1F, 25-26, 2B-30, 32, 35-36, 3A-3c, 3F, 45-46,
Linti0 ve Linti1, yerel APIC ile ilişkili yerel kesme pinlerini ifade eder. Donanım aygıtları bu pinlere bağlanabilir ve yerel APIC'e kesmeler gönderebilir, bu da işlemciye iletilir. Bunlar yerel kesme olarak bilinen duruma bir örnektir. Yerel kesmeler daha sonra toplu olarak yerel vektör tablosu olarak bilinen özel kayıtlar koleksiyonuna yönlendirilir. LVT içindeki girişler, söz konusu girişin nasıl kullanılmak üzere yapılandırıldığına bağlı olarak IDT'ye iletilebilir. Vektör, IDT içindeki girişi ifade eder.
NMI bir kesme teslim çeşididir ve kesmenin nasıl teslim edilmesi gerektiğini belirtir. Teslim çeşidi NMI olarak ayarlanırsa, kesme vektör numarası göz ardı edilir. NMI daha öncelerden bahsettiğim gibi göz ardı edilemeyen kesmeler anlamına gelir. Öte yandan, Fixed bunun tam tersidir ve vektör numarası IDT'ye indekslemek ve ilgili kesmeyi işlemciye iletmek için kullanılacak daha sonralarda.
APIC Modu, APIC'in çalışma modunu ifade eder. x2APIC en yeni APIC mimarisidir ve eski xAPIC mimarisinin bir uzantısıdır. Modlar arasındaki temel fark, x2APIC'in daha iyi performans ve bazı ek özellikler sağlamasıdır.
APIC ile ilgili daha fazla bilgiyi Intel developer documentation belgelerinde bulabilirsiniz.
Interrupt Dispatch Table
IDT'yi !idt uzantısını kullanarak görebiliriz:
C++:
5: kd> !idt
Dumping IDT: fffff880030b06c0
00: fffff800034b8700 nt!KiDivideErrorFault
01: fffff800034b8800 nt!KiDebugTrapOrFault
02: fffff800034b89c0 nt!KiNmiInterruptStart Stack = 0xFFFFF880030B00C0
03: fffff800034b8d40 nt!KiBreakpointTrap
04: fffff800034b8e40 nt!KiOverflowTrap
05: fffff800034b8f40 nt!KiBoundFault
06: fffff800034b9040 nt!KiInvalidOpcodeFault
07: fffff800034b9280 nt!KiNpxNotAvailableFault
08: fffff800034b9340 nt!KiDoubleFaultAbort Stack = 0xFFFFF880030AC0C0
09: fffff800034b9400 nt!KiNpxSegmentOverrunAbort
0a: fffff800034b94c0 nt!KiInvalidTssFault
0b: fffff800034b9580 nt!KiSegmentNotPresentFault
0c: fffff800034b96c0 nt!KiStackFault
0d: fffff800034b9800 nt!KiGeneralProtectionFault
0e: fffff800034b9940 nt!KiPageFault
10: fffff800034b9d00 nt!KiFloatingErrorFault
11: fffff800034b9e80 nt!KiAlignmentFault
12: fffff800034b9f80 nt!KiMcheckAbort Stack = 0xFFFFF880030AE0C0
13: fffff800034ba300 nt!KiXmmException
1f: fffff800034af860 nt!KiApcInterrupt
2c: fffff800034ba4c0 nt!KiRaiseAssertion
2d: fffff800034ba5c0 nt!KiDebugServiceTrap
2f: fffff80003507510 nt!KiDpcInterrupt
37: fffffa800ca66ef0 hal!HalpApicSpuriousService (KINTERRUPT fffffa800ca66e60)
IDT'nin adresini vurguladım. Bu, IDTR kaydı içinde saklanan adresle eşleşir:
[CODE lang="rich" highlight="2"]5: kd> r idtr
idtr=fffff880030b06c0[/CODE]
Sol taraftaki sayılar, IDT'yi indekslemek ve ilgili ISR'yi bulmak için kullanılan kesme vektörleridir. Örneğin, page fault'lar için kesme vektörü 0e'dir. IDT'nin belirli bir girişini aşağıdakileri yaparak dökebiliriz:
[CODE highlight="5"]5: kd> !idt 0e
Dumping IDT: fffff880030b06c0
0e: fffff800034b9940 nt!KiPageFault[/CODE]
Şimdi, KINTERRUPT yapısının bazı girdiler için var olduğunu ancak hepsi için olmadığını fark etmiş olabilirsiniz, bunun nedeni page fault'un teknik olarak bir kesme değil bir istisna olmasıdır ve bu nedenle yukarıda belirtilen yapıya sahip olmamasıdır. Şimdi bu yapının ne anlama geldiğine bakalım:
Rich (BB code):
5: kd> dt nt!_KINTERRUPT fffffa800cb4fa80
+0x000 Type : 0n22
+0x002 Size : 0n160
+0x008 InterruptListEntry : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
+0x018 ServiceRoutine : 0xfffff800`0347da10 unsigned char nt!KiInterruptMessageDispatch+0
+0x020 MessageServiceRoutine : 0xfffff880`0482d62c unsigned char +0
+0x028 MessageIndex : 0
+0x030 ServiceContext : 0xfffffa80`0e4c4bb0 Void
+0x038 SpinLock : 0
+0x040 TickCount : 0
+0x048 ActualLock : 0xfffffa80`0e7b3230 -> 0
+0x050 DispatchAddress : 0xfffff800`034b7670 void nt!KiInterruptDispatch+0
+0x058 Vector : 0xa0
+0x05c Irql : 0xa ''
+0x05d SynchronizeIrql : 0xa ''
+0x05e FloatingSave : 0 ''
+0x05f Connected : 0x1 ''
+0x060 Number : 5
+0x064 ShareVector : 0x1 ''
+0x065 Pad : [3] ""
+0x068 Mode : 1 ( Latched )
+0x06c Polarity : 0 ( InterruptPolarityUnknown )
+0x070 ServiceCount : 0
+0x074 DispatchCount : 0
+0x078 Rsvd1 : 0
+0x080 TrapFrame : 0xfffff880`030cdab0 _KTRAP_FRAME
+0x088 Reserved : (null)
+0x090 DispatchCode : [4] 0x8d485550
Vektör alanı, IDT'ye indekslemek için kullanılan kesme vektör numarasını ifade eder. IRQL alanı, kesmenin çalışacağı IRQL seviyesini ifade eder. Bunu aşağıdakileri kullanarak bir IRQL numarasına çevirebiliriz:
Rich (BB code):
5: kd> ? 0xa
Evaluate expression: 10 = 00000000`0000000a
IRQL seviyesine bakarak (10) bunun bir donanım kesmesi olduğunu görebiliriz. Bu, ServiceRoutine alanı kesmeyi işlemek için çağrılacak ISR'yi ifade eder.
Şimdi, bu beni IRQ'lar nedeniyle paylaşılan kesmelerle ilgili noktaya getiriyor. Bir işlemci için mevcut kesme hattı sayısından daha fazla cihaz olabileceğinden, IRQ'lar birden fazla cihaz arasında paylaşılabilir, bu da bir kesme vektörünün birden fazla cihaza hizmet vermek için kullanılabileceği anlamına gelir. Bunu InterruptListEntry alanı içinde inceleyebiliriz.
Kod:
!idt -a
81: fffff802aad5d608 USBPORT!USBPORT_InterruptService (KINTERRUPT ffffa18196d53280)
1394ohci!Interrupt::WdfEvtInterruptIsr (KMDF) (KINTERRUPT ffffa18198314c80)
USBPORT!USBPORT_InterruptService (KINTERRUPT ffffa18198314a00)
Birden fazla kesmenin hepsinin aynı kesme girişiyle nasıl ilişkilendirildiğine dikkat ettik mi?
Listedeki ilk kesmeyi atalım.
Rich (BB code):
1: kd> dt nt!_KINTERRUPT ffffa18196d53280
+0x000 Type : 0n22
+0x002 Size : 0n256
+0x008 InterruptListEntry : _LIST_ENTRY [ 0xffffa181`98314c88 - 0xffffa181`98314a08 ]
+0x018 ServiceRoutine : 0xfffff807`25eb6470 unsigned char USBPORT!USBPORT_InterruptService+0
+0x020 MessageServiceRoutine : (null)
+0x028 MessageIndex : 0
+0x030 ServiceContext : 0xffffd607`5ade0050 Void
+0x038 SpinLock : 0
+0x040 TickCount : 0
+0x048 ActualLock : 0xffffd607`5af45df0 -> 0
+0x050 DispatchAddress : 0xfffff802`aac381d0 void nt!KiChainedDispatch+0
+0x058 Vector : 0x81
+0x05c Irql : 0x8 ''
+0x05d SynchronizeIrql : 0x8 ''
+0x05e FloatingSave : 0 ''
+0x05f Connected : 0x1 ''
+0x060 Number : 1
+0x064 ShareVector : 0x1 ''
+0x065 EmulateActiveBoth : 0 ''
+0x066 ActiveCount : 0
+0x068 InternalState : 0n4
+0x06c Mode : 0 ( LevelSensitive )
+0x070 Polarity : 0 ( InterruptPolarityUnknown )
+0x074 ServiceCount : 0
+0x078 DispatchCount : 0
+0x080 PassiveEvent : (null)
+0x088 TrapFrame : (null)
+0x090 DisconnectData : (null)
+0x098 ServiceThread : (null)
+0x0a0 ConnectionData : 0xffffd607`5af45e00 _INTERRUPT_CONNECTION_DATA
+0x0a8 IntTrackEntry : 0xffffd607`5afb2320 Void
+0x0b0 IsrDpcStats : _ISRDPCSTATS
+0x0f0 RedirectObject : (null)
+0x0f8 Padding : [8] ""
İlişkili iki kesmenin InterruptListEntry bağlantılı listesinde olduğunu görebiliriz.
[CODE lang="rich" highlight="2,5"]1: kd> ? ffffa18198314c80+8
Evaluate expression: -103897000489848 = ffffa181`98314c88
1: kd> ? ffffa18198314a00+8
Evaluate expression: -103897000490488 = ffffa181`98314a08[/CODE]
Şimdi, DispatchAddress alanına baktığımızda, kesmenin chained dispatch fonksiyonu kullanılarak gönderileceğini görebiliriz. Chained dispatch fonksiyonu, kendileriyle ilişkili başka kesmelerin olduğu, yani birden fazla kesmenin aynı IRQ'yu paylaştığı kesmelere kontrol aktarmak için kullanılır. Öte yandan, bu durumun söz konusu olmadığı kesmeler için kesme gönderme işlevi çağrılır.
Mod ve Polarity alanları da dikkat çekicidir ve bir kesmenin işlemciye nasıl iletileceğini belirlemek için birbirleriyle birlikte kullanılırlar. Bu alanların her ikisi de numaralandırmadır. Bunlar aşağıdaki komutlar kullanılarak incelenebilir:
Rich (BB code):
1: kd> dt _KINTERRUPT_MODE
nt!_KINTERRUPT_MODE
LevelSensitive = 0n0
Latched = 0n1
Interrupt mode, işlemciye gönderilecek kesme türünü ifade eder. Şu anda iki mod vardır: Edge Triggering ve Level Triggering. Edge-triggered Interrupts, mesajlaşma iletim mekanizmasını kullanan kesmeler için kullanılırken, Level Triggering Interrupts, bir APIC için mevcut fiziksel hatların sayısını dikkate alan geleneksel hat tabanlı iletim mekanizmasını kullanır. İkisi arasındaki fark için...
[CODE lang="rich" highlight="1"]1: kd> dt _KINTERRUPT_POLARITY
nt!_KINTERRUPT_POLARITY
InterruptPolarityUnknown = 0n0
InterruptActiveHigh = 0n1
InterruptRisingEdge = 0n1
InterruptActiveLow = 0n2
InterruptFallingEdge = 0n2
InterruptActiveBoth = 0n3
InterruptActiveBothTriggerLow = 0n3
InterruptActiveBothTriggerHigh = 0n4[/CODE]
Polarity, hat tabanlı veya mesaj tabanlı bir kesmenin işlemciye nasıl iletileceğini açıklar. Yükselen ve alçalan kenarın, bir sinyalin 0'dan 1'e geçişini veya tam tersini tanımlayan sinyal kenarına atıfta bulunduğuna dikkat etmek önemlidir. 0'dan 1'e geçiş yapan bir sinyal yükselen bir kenara sahipken, düşen bir kenara sahip olan bir sinyal 1'den 0'a geçiş yapmaktadır.
Numaralandırma değerleri hakkında daha fazla ayrıntı burada bulunabilir - _KINTERRUPT_POLARITY (wdm.h) - Windows drivers
Line-based and Message-based interrupts
Belirli bir kesme pinini kullanmak ve ardından ilgili kesme hattı boyunca bir kesme sinyali göndermek yerine. Message-based interrupts (MSI'lar), tipik olarak adres alanının bellek eşlemeli bir I/O bölgesi olan belirli bir bellek adresine yazar.
Şimdi, Interrupt'a geri dönüp farklılıklara bakarsak...
Kod:
1: kd> dt nt!_KINTERRUPT ffffa18196d53280
+0x000 Type : 0n22
+0x002 Size : 0n256
+0x008 InterruptListEntry : _LIST_ENTRY [ 0xffffa181`98314c88 - 0xffffa181`98314a08 ]
+0x018 ServiceRoutine : 0xfffff807`25eb6470 unsigned char USBPORT!USBPORT_InterruptService+0
+0x020 MessageServiceRoutine : (null)
+0x028 MessageIndex : 0
+0x030 ServiceContext : 0xffffd607`5ade0050 Void
+0x038 SpinLock : 0
+0x040 TickCount : 0
+0x048 ActualLock : 0xffffd607`5af45df0 -> 0
+0x050 DispatchAddress : 0xfffff802`aac381d0 void nt!KiChainedDispatch+0
+0x058 Vector : 0x81
+0x05c Irql : 0x8 ''
+0x05d SynchronizeIrql : 0x8 ''
+0x05e FloatingSave : 0 ''
+0x05f Connected : 0x1 ''
+0x060 Number : 1
+0x064 ShareVector : 0x1 ''
+0x065 EmulateActiveBoth : 0 ''
+0x066 ActiveCount : 0
+0x068 InternalState : 0n4
+0x06c Mode : 0 ( LevelSensitive )
+0x070 Polarity : 0 ( InterruptPolarityUnknown )
+0x074 ServiceCount : 0
+0x078 DispatchCount : 0
Bu dosyada herhangi bir MSI görünmediğinden, daha önce olduğu gibi aynı kesmeyi kullandım, ancak örnek yeterli olacaktır. İlk önemli fark, gönderme işlevinin KiInterruptMessageDispatch olmasıdır. MessageServiceRoutine alanı, satır tabanlı kesme muadili ile aynı anlama sahiptir.
MessageIndex alanı _IO_INTERRUPT_MESSAGE_INFO yapısının bir parçası olan MessageInfo dizisinin indeksi olarak kullanılır. Bu, mesaj hizmet rutinine aktarılır. Daha fazla ayrıntı için lütfen buraya bakın - KMESSAGE_SERVICE_ROUTINE (wdm.h)
IDT içindeki her bir girdinin _KIDTENTRY64 (x86 sistemlerinde _KIDTENTRY) adlı bir yapı tarafından temsil edildiği de belirtilmelidir. Bu yapıyı inceleyebiliriz:
Kod:
5: kd> dt nt!_KIDTENTRY64
+0x000 OffsetLow : Uint2B
+0x002 Selector : Uint2B
+0x004 IstIndex : Pos 0, 3 Bits
+0x004 Reserved0 : Pos 3, 5 Bits
+0x004 Type : Pos 8, 5 Bits
+0x004 Dpl : Pos 13, 2 Bits
+0x004 Present : Pos 15, 1 Bit
+0x006 OffsetMiddle : Uint2B
+0x008 OffsetHigh : Uint4B
+0x00c Reserved1 : Uint4B
+0x000 Alignment : Uint8B
Şimdi, teknik olarak buradaki alanların bir segment tanımlayıcı biçimi olan IDT kapı tanımlayıcısının alanına karşılık geldiğini belirtmek önemlidir. Segment tanımlayıcıları kısaca; gerekli erişim hakları vb. dahil olmak üzere belleğin bir bölümünü tanımlamak için kullanılırlar.
OffsetLow, OffsetMiddle ve OffsetHigh alanları, kesme vektörünün bellek adresini oluşturmak için bir araya getirilir. Dpl alanı, bölüme erişilebilmesi için işlemcinin çalışması gereken halka seviyesini (ayrıcalık seviyesi) tanımlamak için kullanılır. Type alanı segment tanımlayıcısının türünü tanımlamak için kullanılır, bu durumda ya bir görev kapısı, tuzak kapısı ya da bir kesme kapısı olacaktır. Present bayrağı segmentin bellekte mevcut olup olmadığını belirlemek için kullanılır.
Interrupt Flag
Bundan diğer forumda da bahsetmiştim. Kesme bayrağı, işlemcinin göz ardı edilebilen kesmeleri ele alıp almayacağını belirtmek için kullanılır. Göz ardı edilebilen kesmeler, NMI'lar dışındaki tüm donanım kesmeleridir. Bu bayrak 0 olursa, işlemci göz ardı edilebilen kesmeleri yok sayacaktır. STI (Set interrupts) ve CLI (clear interrupts) komutları bu EFLAGS kaydında yapılan değişiklikleri işlemek için kullanılır.
Kod:
3: kd> r if
if=1
İstek seviyeleri, önceliği belirten durumlardır; her IRQ HAL aracılığıyla belirli bir IRQL ile eşleştirilir. X64 sistemlerde Kesme istek seviyeleri 0'dan (en düşük) 15'e (en yüksek) kadar çalışır. X86 sistemlerde bu seviyeler 0'dan 31'e kadar çalışır.
Kesme seviyeleri, bir işlemcinin hangi kesmeler tarafından kesilebileceğini ve daha sonra işlenebileceğini belirlemek için kullanılır. Daha yüksek seviyeye sahip bir kesme daha düşük seviyeli bir kesmeye göre öncelikli olacaktır. Her işlemci, İşlemci Kontrol Bölgesi (PCR) içinde bulunabilen geçerli bir seviyeye sahip olacaktır. Bu yapıyı !pcr uzantısını kullanarak inceleyebiliriz:
Rich (BB code):
5: kd> !pcr
KPCR for Processor 5 at fffff880030a5000:
Major 1 Minor 1
NtTib.ExceptionList: fffff880030b0640
NtTib.StackBase: fffff880030aa040
NtTib.StackLimit: 00000000001ad328
NtTib.SubSystemTib: fffff880030a5000
NtTib.Version: 00000000030a5180
NtTib.UserPointer: fffff880030a57f0
NtTib.SelfTib: 000007fffffde000
SelfPcr: 0000000000000000
Prcb: fffff880030a5180
Irql: 0000000000000000
IRR: 0000000000000000
IDR: 0000000000000000
InterruptMode: 0000000000000000
IDT: 0000000000000000
GDT: 0000000000000000
TSS: 0000000000000000
CurrentThread: fffffa800d716060
NextThread: 0000000000000000
IdleThread: fffff880030b00c0
DpcQueue:
Bir işlemci mevcut seviyesinden daha düşük bir kesme alırsa, kesme göz ardı edilir ve seviye düşürüldüğünde çalıştırılmak üzere programlanır ya da farklı bir işlemcide çalıştırılmak üzere programlanır.
İşlemcinin kesmeye hizmet verebilmesi durumunda, mevcut kesme seviyesi, mevcut thread bağlamıyla birlikte kaydedilecek, işlemcinin kesme istek seviyesi daha sonra kesmeyle ilişkili seviyeye yükseltilecek ve uygun ISR çağrılacaktır. Kesmelerin verimli bir şekilde servis edilebilmesini sağlamak için işlemcinin her zaman mümkün olan en kısa sürede mümkün olan en düşük kesme istek seviyesine dönmeye çalışacağı unutulmamalıdır.
İşlemci için kayıtlı Kesmek istek seviyesi değerini !irql uzantısını kullanarak görebiliyoruz:
[CODE lang="rich" highlight="2"]5: kd> !irql
Debugger saved IRQL for processor 0x5 -- 0 (LOW_LEVEL)[/CODE]
Alternatif olarak, işlem için kaydedilmiş Kesme istek seviyesi, PRCB içinde bulunabilir, !prcb kullanılarak incelenebilir:
Rich (BB code):
5: kd> !prcb
PRCB for Processor 5 at fffff880030a5180:
Current IRQL -- 0
Threads-- Current fffffa800d716060 Next 0000000000000000 Idle fffff880030b00c0
Processor Index 5 Number (0, 5) GroupSetMember 20
Interrupt Count -- 001e9395
Times -- Dpc 00000218 Interrupt 00000839
Kernel 0000e9d7 User 000030aa
Kaynaklar: CodeMachine, Micrososft Learn.
Son düzenleme: