7 Temmuz 2017 Cuma

x86 Interrupt Mekanizması

INTERRUPT Nedir ?
~~~~~~~~~~~~~~~~~~~~~~

Interrupt kelimesinin Türkçe karşılığı bilgisayar terminolojisinde kesmeye karşılık gelmektedir.  Interrupt mekanizmasına ihtiyaç duymamızın sebeplerinden birisi sahip olduğumuz çevresel cihazlar. Çevresel cihazlara örnek ; Klavye,Mouse,Printer.


Çevresel cihazlardan klavyeyi ele alalım. Klavyeden bir tuşa basılıp basılmadığını işletim sistemi nasıl anlayabilir ? Bunun için 2 yöntem var.  

İşletim sistemi , klavyeyi belirli aralıklarla dinleyerek, durumunu sorgulayarak bir tuşa basılıp basılmadığını anlayabilir. Bu yönteme polling denilmektedir.Bu yöntemin dezavantajlarından birisi belirli aralıklarla çevresel cihazların dinlenmesidir ve ayrıca bu yöntem hardware tabanlı bir çözüm değil software tabanlı bir çözümdür.

Klavyeden herhangi bir tuşa basıldığı takdirde   işlemciye “klavyeden bir tuşa basıldı”  uyarısı gönderilebilir. Bu yöntem sayesinde klavyenin belirli aralıklarla dinlenmesi gerekmez. Bu yönteme interrupt/kesme yöntemi denilmektedir.Bu çözüm hardware tabanlı bir çözümdür. Hardware tabanlı çözümden kastımız işlemciye sinyal gönderilmesidir.Bu  sinyal sayesinden dış cihaz tarafından kesme gerçekleştirilir. Bir Interrupt gerçekleştiği takdirde işlemci uğraşmış olduğu işi bırakarak kesmeye hizmet etmelidir.  Örneğin, Bir proses çalışırken kesme gerçekleştiği takdirde prosesi kaldığı yerden tekrar çalıştırabilmek için prosese ilişkin bilgilerin korunması gereklidir.

Interrupt gerçekleştiği takdirde kesmeyi gerçekleştiren olaya(yalnızca cihazlar değil başka sebeplerden dolayı da interruptlar gerçekleşebilir. Bu yüzden olay demek sanırım daha doğru olacaktır. Çok uzun bir parantez içi oldu farkındayım ) ilişkin programın çalıştırılması gerekir. Bu programlara  Interrupt Service Routine veya Interrupt handler denilmektedir.

INTERRUPT ve EXCEPTION
~~~~~~~~~~~~~~~~~~~~~~
Interruptlar  genel olarak 2 kısma ayrılmaktadır. Senkron Interruptlar ve Asenkron Interruptlar.

Senkron Interruptlar işlemcinin kendisi tarafından bazı emirler çalıştırılırken oluşan anormal durumlarda(sıfıra bölme,page faults )  meydana gelmektedir. Senkron interruptlar intel terminolojisinde  Exception olarak bilinmektedir.

Harici cihazlar  tarafından gerçekleştirilen ve kesmenin rastgele bir zaman içerisinde gelebileceği interruptlara Asenkron Interruptlar denilmekedir.

İntel terminolojisinde Senkron  Interruptlar Exception ve Asenkron Interruptlar ise Interrupt  olarak adlandırılmaktadır. Interrupt ve Exceptionlar  da kategorilere ayrılmaktadır.

Interruptlar, Maskable ve Non-Maskable olmak üzere ikiye ayrılır.

Maskable Interrupt , Maskelenebilir Interruptlar CPU tarafından reddedilebilir. EFLAGS registerı içerisindeki  IF(Interrupt Flag) Flagi STI emiri ile setlenir. Bu durumda maskelenebilir interruptlar etkindir. CLI instructionı ile IF(Interrupt Flag) flagi clear edilir. Bu durumda maskalenebilir interruptlar disable edilmiştir ve maskelenebilir interruptlar kesme gerçekleştirirse CPU tarafından kesmeler reddedilir. 

Non-Maskable interruptlar hiçbir şekilde CPU tarafından reddedilemez eğer non-masksable bir interrupt gerçekleştirildiyse sistemin buna hizmet etmesi gerekir.

Exception olarak sık kullanılan iki exception durumunu açıklayalım.  Fault ve Trap.

Eğer exception olarak fault meydana geldiyse bu durumda ilgili fault handler çalışır ve fault handler işini bitirdikten sonra  faultlamaya sebep olan instructiona döner. Örnek olarak bir programın henüz memoryde bulunmayan bir sayfasına erişmek istediğini düşünelim bu durumda page fault exception meydana gelir . Page fault handler ilgili page eğer programa ait ise bu durumda memorye yükler ve fault handler faultlamaya sebep olan instructiona döner ve program tekrardan aynı emiri çalıştırarak akışına buradan devam eder.

Eğer exception olarak trap meydana geldiyse bu durumda ilgili trap handler çalışır fakat trap handlerdan dönüş olarak traplemeye sebep olan instructionı  değil  bir sonraki adrese döner. Fault exceptiondan farkı budur.

Basit Model
 ~~~~~~~~~~~~~~~~~~

Hardware tarafında bu işlerin nasıl gerçekleştirildiğini anlamak için basit model adını verdiğimiz bu başlık altında ilk x86 işlemcilerde  interrupt mekanizması nasıl kullanılıyordu buna göz atacağız.

İşlemcilerde INTR ve NMI adında iki pin-bacak bulunmaktadır. Kesmenin gerçekleştirilmesi için bu 2 bacaktan birine sinyal gönderilir.

 INTR
~~~~~~~~~~~~~~~~~
INTR pini , kesme bu bacak üzerinden gerçekleştirildiği zaman işlemci tarafından bu kesmeye ya cevap verilir ya da verilmez.  EFLAGS registerındaki IF flagı setlenerek veya clear edilerek kesmelere ya cevap verilebilir ya da kesmeye cevap verilmez.  x86 mimaride IF(Interrupt Flag) flaginin setlenmesi ve clear edilmesine ilişkin STI ve CLI emirleri mevcuttur.

Kritik işlerle ilgilenildiği zaman gelen interruptlar CPU tarafından reddedilebilir.

NMI(Non-Maskable Interrupt)
~~~~~~~~~~~~~~~~~

Bu  pin üzerinden kesme gerçekleşirse işlemci tarafından interrupt reddedilemez bu yüzden non-maskable interrupt olarak bilinir.

INTEL 8259A-PIC(Programmable Interrupt Controller)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Intel 8259A Programlanabilir Kesme Denetleyicisini intel x86 Uniprocessor  işlemcilerinde kullanılmıştır.

8259A kesme denetleyicisinin 8 adet IRQ(Interrupt ReQuest) bacağı vardır. Kesmeyi gerçekleştirecek olan cihazlar bu bacaklara bağlıdır. Herhangi bir kesme gerçekleştirmek istediklerinde bağlı bulundukları IRQ hattında bir sinyal yaratırlar.Eğer birden fazla kesme aynı anda gerçekleştirildiyse böyle bir durumda kesme denetleyici önceliğe göre kesmelere teker teker cevap verir. Kesme denetleyicisi tarafından  seçim gerçekleştirildikten  sonra interrupt için kesme denetleyicisinin INT bacağı aktif hale gelir. Bu bacak işlemcinin INTR bacağına bağlıdır.Eğer CPU bu kesmeyi onaylarsa Kesme denetleyicisinin INTA bacağına sinyal gönderir. Kesme denetleyici  kendisine gelen INTA sinyali ile Interruptın onaylandığını anlar ve bunun ardından işlemci bir INTA sinyali daha gönderir. Bu sinyalle birlikte 8259A ilgili interruptın  servis rutin adresini elde etmesi için işlemciye data bus üzerinden bir type number(8-bit) gönderir. Bu type number Interrupt Vektör Tablosuna göz atılırken kullanılan bir numaradır. Interrupt Vektör tablosu daha önceden hazırlanmış bir tablodur ve içerisinde interruptlara ilişkin servis programların adresini barındırır. Type Number ise bu tablo için bir index numarası olarak kullanılmaktadır.Bu tablodan ilgili interruptın adresi type number aracılığı ile elde edilir.

Burada Interrupt gerçekleştiğinde kesilmiş olan belirli bir iş olduğundan her servis rutin programına başlanmadan önce tüm cpu registerları korunur ve servis rutin programının sonunda tekrardan korunmuş olan  cpu registerları yüklenir ve interruptlama gerçekleştiğinde ana program nereden kesildiyse o adresden tekrar devam eder.



Yukarıdaki resim anlattığımız olay döngüsünü güzel bir şekilde açıklamakta. 2.INTA sinyali gösterilmese de yine de güzel bir çizim. : ) ( 2.INTA sinyalinin varlığına inanmanız için bu linke göz atabilirsiniz. https://pdos.csail.mit.edu/6.828/2005/readings/hardware/8259A.pdf )

(*) Resim Mohammed Amer Arafah adlı bir kişinin çizimi ve oldukça güzel bir çizim.

Buraya kadar x86 işlemcilerde ilk kullanılan interrupt mekanizmasından ve genel kavramlardan bahsettik. Interrupt mekanizmasının hardware tarafında uygulanış biçimi ile  interruptlar hakkında fikir sahibi olduk. Yazının bundan sonraki kısmı ise modern interrupt mekanizmasını anlatacaktır.

 Local APIC  ,   I/O APIC
~~~~~~~~~~~~~~~~~

Bu başlıkta PIC  anlatımındaki gibi belirli bir modeli(8259A) ele alarak anlatım yapmayacağız. Genel bir anlatım gerçekleştirilecektir.

8259A kesme denetleyicisi yalnızca uniprocessor(tek procesoor ve tek core) sistemlerde kesme isteklerini karşılayabilmektedir. Gelişmiş multiprocessor sistemler için gelişmiş kesme denetleyici gerektiğinden intel APIC( Advanced Programmable Interrupt Controller) denetleyicisini geliştirdi.  APIC genel olarak bir I/O APIC denetleyicisi ve her core veya processorün kendisine entegre edilmiş Local APIC denetleyicilerinden meydana gelmektedir.



I/O APIC Harici cihazların interruptlarını karşılamaktadır.  I/O APIC gelen bu interruptları sistemdeki processorlere/corelara dağıtmaktadır. I/O APIC ve LAPIC arasındaki iletişim yukarıdaki resimde  gördüğünüz gibi kimi processorler için  ya System bus üzerinden ya da APIC bus üzerinden gerçekleşmektedir. Günümüzde haberleşme system bus üzerinden gerçekleşmektedir.

APIC ile  interrupt handlerın adresi nasıl elde ediyor sorusundan önce Interrupt Descriptor Table anlatmamız gerekiyor.

 IDT-[Interrupt Descriptor Table]
~~~~~~~~~~~~~~~~~

İşlemci bir kesme gerçekleştiğinde ilgili kesmenin Interrupt handlerına ulaşmak için Interrupt Descriptor Tablosunu kullanmaktadır. Real Modda  bu tabloya Interrupt Vector Tablosu denilmektedir. x86 mimaride tablodaki her kayıt  8 bayt ve x64 mimaride 16 bayt  uzunluğundadır.(Bu yazıda anlatılanlar x86 mimari ve x64 mimari için geçerlidir fakat kısmi değişikliler olabilir tıpkı boyut farklılığı gibi) .

x86 İşlemcilerde bu tabloya erişim için emirler ve bir adet register bulunmaktadır.

IDTR registerı,  Interrupt Descriptor Tablosunun base adresini(bellekteki adresi)  ve Limit değerlerini tutar. ( Base Adress + Limit ) değeri  interrup descriptor tablosundaki son kayıtın adresini işaret eder.

LIDT(Load Interrupt Descriptor Table Register ) emiri ile IDTR registerının içeriği   setlenirken,  SIDT emiri ile IDTR registerının içeriği elde edilir.

IDT içerisindeki kayıtlar Gate  Descriptor olarak adlandırılmaktadır.

Gate descriptorlar type field ile birbirlerinden ayrılırlar. Üç tür gate descriptor vardır.  Bunlar ; Task Gate Descriptor, Interrupt Gate Descriptor , Trap Gate Descriptor.


Genel olarak  Exceptionlar için Trap Gateler ve Interruptlar için de Interrupt Gateler kullanılmaktadır( Örnek ; Linux). Eğer kesme  IDT içerisinde Interrupt Gate olarak tanımlandıysa, EFLAGS registerı içerisindeki IF flagi clear edilir(0 değerine setlenir) . Bu sayede bir kesmeye hizmet edilirken diğer Maskelenebilir Interruptlar kesme gerçekleştiremez. Trap Gateler ise IF flagini değiştirmezler.

Şimdi APIC ile Interrupt Vektörünün nasıl elde edileceğini anlatabiliriz.

I/O APIC  kendi içerisinde Redirection Tablosunu barındırır. Bu tablolarda tutulan kayıtlara ilişkin format ise şu şekildedir.


Redirection Tablosunun kayıtları System bus üzerinden Local Apiclere gönderilecek olan  Interrupt mesajının içeriğini belirler.

Buraya kadar anlatılanlar bir interrupt handlerın adresinin nasıl elde edildiğini anlamamız için yeterli. O halde bir interruptlama gerçekleşirken ilgili kesme için interrupt handler nasıl koşturuluyor buna göz atalım.

Harici cihazlar kesme gerçekleştirmek istedikleri zaman I/O APIC denetleyicisinin IRQ bacaklarına (INITIN bacağı da denilebilir ama biz I/O APIC denetleyicinin bacaklarına IRQ bacakları diyebiliriz : ) tıpkı PIC deki gibi ) sinyal gönderirler.

Bu sinyal örneğin IRQ8  bacağına gelsin. Bu durumda I/O APIC içerisinde bulunan Redirection Tablosunda 8. Kayıt dikkate alınır. I/O APIC bu kayıt içerisindeki destinationı  dikkate alarak system bus üzerinden Local APIC denetleyicisine Interrupt mesajını gönderir. Bu interrupt mesajı Interrupt Vektörünü barındırmaktadır. Interrupt vektörü, interrupt descriptor tablosunda ilgili kesmenin kayıtına ulaşmak için kullanılmaktadır.  Bu değer  0-255 arasında bir değer olabilir.

Bu arada Redirection tablosundaki kayıtların formatına dikkat edecek olursak yalnızca Destination bitleri kullanılarak(extended destination bitlerini dikkate almasak dahi)   I/O APIC tarafından bir interrupt mesajı  255 işlemciye veya çekirdeğe  gönderilebilir.

Local APIC sorunsuz bir şekilde interrupt mesajını aldıktan sonra ilgili core veya işlemci artık interrupt handlerı  koşturmak için gerekli tüm bilgilere sahip.


Yukarıdaki resimde bir interrupt handlerın(intel buna interrupt procedure demiş )  nasıl çağırılacağı görülmektedir.  Bu resmi özeteleyecek olursa  ilgili kesmenin interrupt vektör numarası IDTR içerisindeki IDT tablosunun adress ile toplanır. Buradaki Gate descriptor dan segment selector( bunun için koruma mekanizması adlı yazıya göz atabilirsiniz ) elde edilir . Bu segment selector bir segment descriptorı işaret eder. Koruma mekanizması adlı yazımızda da belirttiğimiz gibi  i386 ve sonrası  ve  x64 mimari buradaki base addres değerini 0 olarak ele alıyorlardı. İşlemcinin burada segment descriptora göz atmasının nedeni yine koruma amaçlı kontrollerdir. Bizi burada ilgilendiren Gate descriptorlardaki offset değeridir .Bu değer ile base adressin ( 0 değerinde ) toplanması sonucu Interrupt handlerın bellekteki adresi bulunur ve ilgili handler çalıştırılmaya başlanır.



Linux x86  -[INT 0x80]-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


INT 0x80, Linux x86 işletim sisteminde user-space  den kernel-space tarafına geçiş için kullanılan yazılımsal kesmedir.Bu geçişin sebebi sistem çağrılarıdır. Sistem çağrıları ile User mod tarafındaki uygulamalar  işletim sisteminin kendisi ile irtibata geçerler.Örneğin ; Disk üzerindeki dosyaya yazma gerçekleştirmek istediğinizi varsayalım.Böyle bir işi ancak işletim sistemi yapabilir ve siz  kernel space tarafındaki  dosyaya yazma gerçekleştiren kodu tetiklemek için sistem çağrılarını kullanırsınız.

Burada fark edebileceğiniz gibi seviye değişiminin gerçekleşmesi gerekmektedir. İşletim sistemleri 0.seviyede çalıştıklarından, 3.seviyede bulunan uygulamalar 0.seviyedeki koda dallanma gerçekleştiremez. O halde önce seviye değişimi nasıl gerçekleşiyor buna göz atalım. 

1.Call Gate ile Seviye Değişimi
2.Interrupt Gate ile Seviye Değişimi


Call Gate ile Seviye Değişimi
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


Yukarıda  Code-Segment Descriptorının formatı görülmektedir. Burada Type field içerisindeki C bitinin setlenmesi durumunda bu segment  Conforming Code Segment olarak adlandırılmaktadır. Aksi durumda Nonconforming code segment olarak adlandırılmaktadır.


Call Gate ile seviye değişiminde 4 değere bağlı olarak bir seviye değişim kontrolü gerçekleştirilmektedir.



Burada CPL değeri o anki seviyeyi,  RPL ise dallanmak istenilen seviyeyi ,  Call Gate Descriptorındaki DPL ise Call Gate’in seviyesini , Destination Code-Segment Descriptordaki DPL  ise dallanmayı istediğimiz kodun seviyesini belirtmektedir.

İşlemci tarafından uygulanan kontrol ise şu şekildedir ;

 Call Gate DPL >= CPL  ve Call Gate DPL >= RPL  eğer bu 2 şart sağlanırsa işlemci olağan akışı bozmaz ve ardından hedef Code Segment Descriptorın Conforming bitini sorgular ve ardından son olarak Destination nonconforming / conforming code segment DPL <=C PL  şartına bakılır eğer bu durumda sağlanırsa  code segmentin nonconforming veya conforming olmasına bağlı olarak işlemler gerçekleştirilir.

Eğer nonconforming code segmente erişiliceksek bu durumda işlemci tarafından  CPL değeri  Destination nonconforming code segmentin DPL değerine setlenir ve ardından stack switch meydana gelir.

Eğer burada  dallanmayı gerçekleştirmek istediğimiz seviyedeki kod conforming code segment olsaydı bu durumda stack switch meydana gelmeyecek ve  CPL değeri değiştirilmeyecektir.

Linux  nonconforming code segmenti kullanmaktadır.


Yukarıdaki resim yukarıda bahsettiğimiz seviye değişimi için ilgili  güvenlik kontrollerin yapılmasının ardından  kodun icrası için ilgili adresin nasıl üretildiğini göstermektedir.

Interrupt Gate ile Seviye Değişimi
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Call gate ile seviye değişimini anlatmamızın sebebi, interrupt gate ile seviye değişiminin , call gate ile seviye değişiminden çok farklı olmamasıdır.

Interrupt Gate ile seviye değişimidnde RPL kontrolü gerçekleştirilmemekte. Bunun sebepi  Interrupt veya exception vektörleri herhangi bir RPL değerine sahip değillerdir.

Ayrıca Interrupt Gate ile seviye değişiminde sadece INT  n , INT3 ve INTO instructionları ile üretilen exceptionlar veya interruptlar için DPL değerinin kontrolü gerçekleştirilir.

Evet yazının buraya kadar olan kısmına kadar INT 0x80 kesmesinden bahsetmedik. Bilmemiz gereken tüm detayları öğrendiğimize göre INT 0x80 kesmesinin basit olarak nasıl gerçekleştiğini bakalım.

İşlemci Instructionları işlerken bir yandan da herhangi bir kesme gerçekleşmiş mi bunu kontrol eder.

0x80 vektörü kesme gerçekleştirdiyse öncelikle APIC konusunda anlattığımız gibi vektör numarasını elde eder.

Seviye değişimi gerçekleşeceğinden dolayı kernel stack geçişinin yapılması gerekir. Linux işletim sistemi bunun için Task State Segmentinden yararlanır. Linux işletim sisteminde yalnızca bir tane TSS vardır ve switchlemede bu TSS den yararlanılır.

Task State Segment Yapısı ; 



TR registerı Linux x86 işletim sisteminde 0x80 değerine sahiptir. 0x80 değerini incelediğimizde TR Segment Selectorünün index değerinin 16 olduğu görülmektedir.Bu değer GDT için index değeri olarak kullanılır ve GDT içerisindeki 16.kayıt TSS segmentinin memorydeki yerini belirten bilgiyi içerir.

İşlemci tarafından Kernel stackine geçiş yapılırken Task State Segmentinden SS0 ve ESP0 değerleri elde edilir. Burada SS0 ve ESP0 ile kernel stacke geçiş yapılır.

Ardından eski SS ve ESP değerlerini bu stack üzerine iter. EFLAGS, CS  ve EIP değerlerini yine bu stack üzerine iter.


Burada Interrupted Procedure's stackini User Mode Stack ve Handler's Stackini Kernel Mode Stack olarak düşünebilirsiniz.

Int 0x80 user space tarafından gerçekleşirken CPL değerimizin 3 olduğunu biliyoruz.
Aşağıda Int 0x80 ilişkin Gate Descriptorını görmektesiniz.



INT 0x80 kesmesinin DPL değerinin  3 olduğu görülmektedir. Bu durumda INT 0x80 kesmesini gerçekleştirdiğimizde  CPL <= Gate DPL olduğundan işlemci herhangi bir exception fırlatmayacaktır. 

CS registerını Gate Descriptor içerisinde 0x60 kernel code segment selectorüne ve EIP registerını ise 0xd58115e8 adresine setleyecektir. Bu şekilde Interrupt Handler çalışmaya başlayacaktır.

x64 mimari privilege level değiştirmek için  syscall & sysret emirlerini tanıtmıştır.

ayrıca yazının yukarı kısmından bahsetmeyi unuttuğumuz bir şey var . x86 veya x64 bazı interruptları önceden tanımlamıştır.x86 mimari 0 ile 31 ve arasındaki vektörleri kendisi için tanımlamıştır.

Örneğin ;  14 numaralı vektör page fault exception için önceden tanımlanmıştır. Page fault exception meydana geldiğinden 14 numaralı vektördeki interrupt handler çalıştırılmaya başlanacaktır. Bu yüzden işletim sistemi geliştiricisi Interrupt Descriptor Tablosundaki 14 numaralı vektör için mecburi olarak page fault handlerına ilişkin bilgileri yerleştirmelidir.


Umarım Faydalı olmuştur.

Kaynaklar :

Understanding The Linux Kernel
Intel® 64 and IA-32 Architectures  Software Developer’s Manual

EOF

Hiç yorum yok:

Yorum Gönder