歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux綜合 >> Linux內核 >> Linux內核分析方法談(3)

Linux內核分析方法談(3)

日期:2017/3/2 10:45:44   编辑:Linux內核

  方法之三:以數據結構為基點,觸類旁通

  結構化程序設計思想認為:程序 = 數據結構 + 算法。數據結構體現了整個系統的構架,所以數據結構通常都是代碼分析的很好的著手點,對Linux內核分析尤其如此。比如,把進程控制塊結構分析清楚了,就對進程有了基本的把握;再比如,把頁目錄結構和頁表結構弄懂了,兩級虛存映射和內存管理也就掌握得差不多了。為了體現循序漸進的思想,在這我就以Linux對中斷機制的處理來介紹這種方法。

  首先,必須指出的是:在此處,中斷指廣義的中斷概義,它指所有通過idt進行的控制轉移的機制和處理;它覆蓋以下幾個常用的概義:中斷、異常、可屏蔽中斷、不可屏蔽中斷、硬中斷、軟中斷 … … …

  I、硬件提供的中斷機制和約定

  一.中斷向量尋址:

  硬件提供可供256個服務程序中斷進入的入口,即中斷向量;

  中斷向量在保護模式下的實現機制是中斷描述符表idt,idt的位置由idtr確定,idtr是個48位的寄存器,高32位是idt的基址,低16位為idt的界限(通常為2k=256*8);

  idt中包含256個中斷描述符,對應256個中斷向量;每個中斷描述符8位,其結構如圖一:

  中斷進入過程如圖二所示。

  當中斷是由低特權級轉到高特權級(即當前特權級CPL>DPL)時,將進行堆棧的轉移;內層堆棧的選擇由當前tss的相應字段確定,而且內層堆棧將依次被壓入如下數據:外層SS,外層ESP,EFLAGS,外層CS,外層EIP; 中斷返回過程為一逆過程;

  二.異常處理機制:

  Intel公司保留0-31號中斷向量用來處理異常事件:當產生一個異常時,處理機就會自動把控制轉移到相應的處理程序的入口,異常的處理程序由操作系統提供,中斷向量和異常事件對應如表一:

  表一、中斷向量和異常事件對應表

中斷向量號 異常事件 Linux的處理程序 0 除法錯誤 Divide_error 1 調試異常 Debug 2 NMI中斷 Nmi 3 單字節,int 3 Int3 4 溢出 Overflow 5 邊界監測中斷 Bounds 6 無效操作碼 Invalid_op 7 設備不可用 Device_not_available 8 雙重故障 Double_fault 9 協處理器段溢出 Coprocessor_segment_overrun 10 無效TSS Incalid_tss 11 缺段中斷 Segment_not_present 12 堆棧異常 Stack_segment 13 一般保護異常 General_protection 14 頁異常 Page_fault 15 Spurious_interrupt_bug 16 協處理器出錯 Coprocessor_error 17 對齊檢查中斷 Alignment_check

  三.可編程中斷控制器8259A:

  為更好的處理外部設備,x86微機提供了兩片可編程中斷控制器,用來輔助cpu接受外部的中斷信號;對於中斷,cpu只提供兩個外接引線:NMI和INTR;

  NMI只能通過端口操作來屏蔽,它通常用於:電源掉電和物理存儲器奇偶驗錯;

  INTR可通過直接設置中斷屏蔽位來屏蔽,它可用來接受外部中斷信號,但只有一個引線,不夠用;所以它通過外接兩片級鏈了的8259A,以接受更多的外部中斷信號。8259A主要完成這樣一些任務:

  中斷優先級排隊管理,

  接受外部中斷請求

  向cpu提供中斷類型號

  外部設備產生的中斷信號在IRQ(中斷請求)管腳上首先由中斷控制器處理。中斷控制器可以響應多個中斷輸入,它的輸出連接到 CPU 的 INT 管腳,信號可通過INT 管腳,通知處理器產生了中斷。如果 CPU 這時可以處理中斷,CPU 會通過 INTA(中斷確認)管腳上的信號通知中斷控制器已接受中斷,這時,中斷控制器可將一個 8 位數據放置在數據總線上,這一 8 位數據也稱為中斷向量號,CPU 依據中斷向量號和中斷描述符表(IDT)中的信息自動調用相應的中斷服務程序。圖三中,兩個中斷控制器級聯了起來,從屬中斷控制器的輸出連接到了主中斷控制器的第 3 個中斷信號輸入,這樣,該系統可處理的外部中斷數量最多可達 15 個,圖的右邊是 i386 PC 中各中斷輸入管腳的一般分配。可通過對8259A的初始化,使這15個外接引腳對應256個中斷向量的任何15個連續的向量;由於intel公司保留0-31號中斷向量用來處理異常事件(而默認情況下,IBM bios把硬中斷設在0x08-0x0f),所以,硬中斷必須設在31以後,linux則在實模式下初始化時把其設在0x20-0x2F,對此下面還將具體說明。

  圖三、i386 PC 可編程中斷控制器8259A級鏈示意圖

  II、Linux的中斷處理

  硬件中斷機制提供了256個入口,即idt中包含的256個中斷描述符(對應256個中斷向量)。

  而0-31號中斷向量被intel公司保留用來處理異常事件,不能另作它用。對這0-31號中斷向量,操作系統只需提供異常的處理程序,當產生一個異常時,處理機就會自動把控制轉移到相應的處理程序的入口,運行相應的處理程序;而事實上,對於這32個處理異常的中斷向量,此版本(2.2.5)的Linux只提供了0-17號中斷向量的處理程序,其對應處理程序參見表一、中斷向量和異常事件對應表;也就是說,17-31號中斷向量是空著未用的。

  既然0-31號中斷向量已被保留,那麼,就是剩下32-255共224個中斷向量可用。這224個中斷向量又是怎麼分配的呢?在此版本(2.2.5)的Linux中,除了0x80 (SYSCALL_VECTOR)用作系統調用總入口之外,其他都用在外部硬件中斷源上,其中包括可編程中斷控制器8259A的15個irq;事實上,當沒有定義CONFIG_X86_IO_APIC時,其他223(除0x80外)個中斷向量,只利用了從32號開始的15個,其它208個空著未用。

  這些中斷服務程序入口的設置將在下面有詳細說明。

  一.相關數據結構

  中斷描述符表idt: 也就是中斷向量表,相當如一個數組,保存著各中斷服務例程的入口。(詳細描述參見圖一、中斷描述符格式)

  與硬中斷相關數據結構:

  與硬中斷相關數據結構主要有三個:

  一:定義在/arch/i386/kernel/irq.h中的

  

struct hw_interrupt_type {
const char * typename;
void (*startup)(unsigned int irq);
void (*shutdown)(unsigned int irq);
void (*handle)(unsigned int irq, struct pt_regs * regs);
void (*enable)(unsigned int irq);
void (*disable)(unsigned int irq);
};

  二:定義在/arch/i386/kernel/irq.h中的

  

typedef struct {
unsigned int status; /* IRQ status - IRQ_INPROGRESS, IRQ_DISABLED */
struct hw_interrupt_type *handler; /* handle/enable/disable functions */
struct irqaction *action; /* IRQ action list */
unsigned int depth; /* Disable depth for nested irq disables */
} irq_desc_t; 

  三:定義在include/linux/ interrupt.h中的

  

struct irqaction {
void (*handler)(int, void *, struct pt_regs *);
unsigned long flags;
unsigned long mask;
const char *name;
void *dev_id;
struct irqaction *next;
};

  三者關系如下:

  圖四、與硬中斷相關的幾個數據結構各關系

  各結構成員詳述如下:

  struct irqaction結構,它包含了內核接收到特定IRQ之後應該采取的操作,其成員如下:

  handler:是一指向某個函數的指針。該函數就是所在結構對相應中斷的處理函數。

  flags:取值只有SA_INTERRUPT(中斷可嵌套),SA_SAMPLE_RANDOM(這個中斷是源於物理隨機性的),和SA_SHIRQ(這個IRQ和其它struct irqaction共享)。

  mask:在x86或者體系結構無關的代碼中不會使用(除非將其設置為0);只有在SPARC64的移植版本中要跟蹤有關軟盤的信息時才會使用它。

  name:產生中斷的硬件設備的名字。因為不止一個硬件可以共享一個IRQ。

  dev_id:標識硬件類型的一個唯一的ID。Linux支持的所有硬件設備的每一種類型,都有一個由制造廠商定義的在此成員中記錄的設備ID。

  next:如果IRQ是共享的,那麼這就是指向隊列中下一個struct irqaction結構的指針。通常情況下,IRQ不是共享的,因此這個成員就為空。

  struct hw_interrupt_type結構,它是一個抽象的中斷控制器。這包含一系列的指向函數的指針,這些函數處理控制器特有的操作:

  typename:控制器的名字。

  startup:允許從給定的控制器的IRQ所產生的事件。

  shutdown:禁止從給定的控制器的IRQ所產生的事件。

  handle:根據提供給該函數的IRQ,處理唯一的中斷。

  enable和disable:這兩個函數基本上和startup和shutdown相同;

  另外一個數據結構是irq_desc_t,它具有如下成員:

  status:一個整數。代表IRQ的狀態:IRQ是否被禁止了,有關IRQ的設備當前是否正被自動檢測,等等。

  handler:指向hw_interrupt_type的指針。

  action:指向irqaction結構組成的隊列的頭。正常情況下每個IRQ只有一個操作,因此鏈接列表的正常長度是1(或者0)。但是,如果IRQ被兩個或者多個設備所共享,那麼這個隊列中就有多個操作。

  depth:irq_desc_t的當前用戶的個數。主要是用來保證在中斷處理過程中IRQ不會被禁止。

  irq_desc是irq_desc_t 類型的數組。對於每一個IRQ都有一個數組入口,即數組把每一個IRQ映射到和它相關的處理程序和irq_desc_t中的其它信息。

  與Bottom_half相關的數據結構:

  圖五、底半處理數據結構示意圖

  bh_mask_count:計數器。對每個enable/disable請求嵌套對進行計數。這些請求通過調用enable_bh和disable_bh實現。每個禁止請求都增加計數器;每個使能請求都減小計數器。當計數器達到0時,所有未完成的禁止語句都已經被使能語句所匹配了,因此下半部分最終被重新使能。(定義在kernel/softirq.c中)

  bh_mask和bh_active:它們共同決定下半部分是否運行。它們兩個都有32位,而每一個下半部分都占用一位。當一個上半部分(或者一些其它代碼)決定其下半部分需要運行時,就通過設置bh_active中的一位來標記下半部分。不管是否做這樣的標記,下半部分都可以通過清空bh_mask中的相關位來使之失效。因此,對bh_mask和bh_active進行位AND運算就能夠表明應該運行哪一個下半部分。特別是如果位與運算的結果是0,就沒有下半部分需要運行。

  bh_base:是一組簡單的指向下半部分處理函數的指針。

  bh_base代表的指針數組中可包含 32 個不同的底半處理程序。bh_mask 和 bh_active 的數據位分別代表對應的底半處理過程是否安裝和激活。如果 bh_mask 的第 N 位為 1,則說明 bh_base 數組的第 N 個元素包含某個底半處理過程的地址;如果 bh_active 的第 N 位為 1,則說明必須由調度程序在適當的時候調用第 N 個底半處理過程。

Copyright © Linux教程網 All Rights Reserved