1. 程式人生 > >第7章中斷和中斷處理

第7章中斷和中斷處理

7.6 中斷上下文

當執行一箇中斷處理程式時,核心處於中斷上下文中。程序上下文是一種核心所處的操作模式,此時核心代表程序執行——例如,執行系統呼叫或允許核心執行緒。在程序上下文中,可以通過current巨集關聯當前程序。此外,因為程序是以程序上下文的形式連線到核心中的,因此,程序上下文可以睡眠,也可以呼叫排程程式。

中斷上下文和程序沒有瓜葛。與current巨集不相干。因為沒有後備程序,所以中斷上下文不可以睡眠,否則又怎能再對它重新排程呢?因此,不能從中斷上下文中呼叫某些函式。如果一個函式睡眠,就不能在中斷處理程式中使用它——這是對什麼樣的函式可以在中斷處理程式中使用的限制。

中斷上下文具有較為嚴格的時間限制,因為它打斷了其他程式碼。中斷上下文中的程式碼應當迅速、簡潔,儘量不要使用迴圈去處理繁重的工作。中斷處理程式打斷了其他的程式碼。正是因為這種非同步執行的特性,所以所有的中斷處理程式必須儘可能的迅速、簡潔。儘量把工作從中斷處理程式中分離出來,放在下半部執行,因為下半部可以在更合適的時間執行。

中斷處理程式棧的設定是一個配置選項。曾經,中斷處理程式並不具有自己的棧。相反,它們共享所中斷程序的核心棧。核心棧的大小是兩頁,在32位體系結構上是8KB,在64位體系結構上是16KB。因為在這種設定中,中斷處理程式共享別人的堆疊,所以它們在棧中獲取空間時必須非常節約。當然,核心棧本來就很有限,因此,所有的核心程式碼都應該謹慎利用它。

在2.6版本早期的核心中,增加了一個選項,把棧的大小從兩頁減到一頁,也就是在32位的系統上只提供4KB的棧。這就減輕了記憶體的壓力,因為系統中每個程序原先都需要兩頁連續,且不可換出的核心記憶體。為了應對棧大小的減少,中斷處理程式擁有了自己的棧,每個處理器一個,大小為一頁。這個棧稱為中斷棧,儘管中斷棧的大小是原先共享棧的一半,但平均可用棧空間大得多,因為中斷處理程式把這一整頁佔為己有。

中斷處理程式不必關心棧如何設定,或者核心棧的大小是多少。總之,儘量節約核心棧空間。

7.7 中斷處理機制的實現

中斷處理系統在Linux中的實現依賴於體系結構的。實現依賴於處理器、所使用的中斷控制器的型別、體系結構的設計及機器本身。

圖7-1是中斷從硬體到核心的路由。

裝置產生中斷,通過匯流排把電訊號傳送給中斷控制器。如果中斷線是啟用的,那麼中斷控制器就會把中斷髮往處理器。在大多數體系結構中,這個工作就是通過電訊號給處理器的特定管腳傳送一個訊號。除非在處理器上禁止該中斷,否則,處理器會立即停止它正在做的事,關閉中斷系統,然後跳到記憶體中預定義的位置開始執行那裡的程式碼。這個預定義的位置是由核心設定的,是中斷處理程式的入口點。

在核心中,中斷的旅程開始於預定義入口點,這類似於系統呼叫通過預定義的異常控制代碼進入核心。對於每條中斷線,處理器都會跳到對應的一個唯一的位置。這樣,核心就可知道所接收中斷的IRQ號了。初始入口點只是在棧中儲存這個號,並存放當前暫存器的值;然後,核心呼叫函式do_IRQ()。從這裡開始,大多數中斷處理程式碼是用C編寫的——但它們依然與體系結構相關。

do_IRQ()宣告如下:

unsigned int do_IRQ(struct pt_regs *regs);

因為C的呼叫慣例是要把函式引數放在棧的頂部,因此pt_regs結構包含原始暫存器的值,這些值是以前在彙編入口例程中儲存在棧中的。中斷的值也會得以儲存,所以,do_IRQ()可以將它提取出來。

計算出中斷號後,do_IRQ()對所接收的中斷進行應答,禁止這條線上的中斷傳遞。在普通的PC機上,這些操作由mask_and_ack_8259A()來完成的。

接下來,do_IRQ()需要確保在這條中斷線上有一個有效的處理程式,而且這個程式已經啟動,但是當前並沒有執行。如果是這樣的話,do_IRQ()就呼叫handle_IRQ_event()來執行為這條中斷線所安裝的中斷處理程式。handle_IRQ_event()方法被定義在kernel/irq/handle.c中。

/**  * handle_IRQ_event - irq action chain handler  * @irq:    the interrupt number  * @action:    the interrupt action chain for this irq  *  * Handles the action chain of an irq event  */ irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action) {     irqreturn_t ret, retval = IRQ_NONE;     unsigned int status = 0;

    if (!(action->flags & IRQF_DISABLED))         local_irq_enable_in_hardirq();

    do {         trace_irq_handler_entry(irq, action);         ret = action->handler(irq, action->dev_id);         trace_irq_handler_exit(irq, action, ret);

        switch (ret) {         case IRQ_WAKE_THREAD:             /*              * Set result to handled so the spurious check              * does not trigger.              */             ret = IRQ_HANDLED;

            /*              * Catch drivers which return WAKE_THREAD but              * did not set up a thread function              */             if (unlikely(!action->thread_fn)) {                 warn_no_thread(irq, action);                 break;             }

            /*              * Wake up the handler thread for this              * action. In case the thread crashed and was              * killed we just pretend that we handled the              * interrupt. The hardirq handler above has              * disabled the device interrupt, so no irq              * storm is lurking.              */             if (likely(!test_bit(IRQTF_DIED,                          &action->thread_flags))) {                 set_bit(IRQTF_RUNTHREAD, &action->thread_flags);                 wake_up_process(action->thread);             }

            /* Fall through to add to randomness */         case IRQ_HANDLED:             status |= action->flags;             break;

        default:             break;         }

        retval |= ret;         action = action->next;     } while (action);

    if (status & IRQF_SAMPLE_RANDOM)         add_interrupt_randomness(irq);     local_irq_disable();

    return retval; }

首先,因為處理器禁止中斷,這裡要把它們開啟,就必須在處理程式註冊期間指定IRQF_DISABLED標誌。IRQF_DISABLED表示處理程式必須在中斷禁止的情況下執行。接下來,每個潛在的處理程式在迴圈中依次執行。如果這條線不是共享的,第一次執行後就退出迴圈。否則,所有的處理程式都要被執行。之後,如果在註冊期間指定了IRQF_SAMPLE_RANDOM標誌,則還要呼叫函式add_interrupt_randomness()。這個函式使用中斷間隔時間為隨機數產生器產生熵。最後,再將中斷禁止,函式返回。回到do_IRQ(),該函式做清理工作並返回到初始入口點,然後再從這個入口點跳到函式ret_from_intr()。

ret_from_intr()例程類似於初始入口程式碼,以組合語言編寫。這個例程檢查重新排程是否正在掛起。如果重新排程正在掛起,而且核心正在返回使用者空間,那麼,schedule()被呼叫。如果核心正在返回核心空間,只有在preempt_count為0時,schedule()才會被呼叫,否則,搶佔核心是不安全的。在schedule()返回之後,或者如果沒有掛起的工作,那麼,原來的暫存器被恢復,核心恢復到曾經中斷的點。

在x86上,初始的彙編例程位於arch/x86/kernel/entry_64.S(檔案entry_32.S 對應32位的x86體系架構),C方法位於arch/x86/kernel/irq.c。

7.8 /proc/interrupts

procfs是一個虛擬檔案系統,存在於核心記憶體,一般安裝在/proc目錄。在procfs中讀寫檔案都要呼叫核心函式,這些函式模擬從真實檔案中讀寫。與此相關的例子是/proc/interrupts檔案,該檔案存放的是系統中與中斷相關的統計資訊。如下是從單處理器PC上輸出的資訊:

第1列是中斷號。在這個系統中,現有的中斷號為0~2、4、5、12及15。第2列是一個接收中斷數目的計數器。系統中的每個處理器都存在這樣的列,但是,這個機器只有一個處理器。看到,時鐘中斷已接收3602371次中斷,這裡,音效卡(EMU10K1)沒有接收一次中斷。第3列是處理這個中斷的中斷控制器。XT-PIC對應於標準的PC可程式設計中斷控制器。最後一列是與這個中斷相關的裝置名字。這個名字是通過引數devname提供給函式request_irq()的。如果中斷是共享的,則這條中斷線上註冊的所有裝置都會列出來。

procfs程式碼位於fs/proc中。提供/proc/interrupts的函式是與體系結構相關的,叫做show_interrupts()。

在x86體系結構下:

int show_interrupts(struct seq_file *p, void *v) {         unsigned long flags, any_count = 0;         int i = *(loff_t *) v, j, prec;         struct irqaction *action;         struct irq_desc *desc;

        if (i > nr_irqs)                 return 0;

        for (prec = 3, j = 1000; prec < 10 && j <= nr_irqs; ++prec)                 j *= 10;

        if (i == nr_irqs)                 return show_other_interrupts(p, prec);

        /* print header */         if (i == 0) {                 seq_printf(p, "%*s", prec + 8, "");                 for_each_online_cpu(j)                         seq_printf(p, "CPU%-8d", j);                 seq_putc(p, '\n');         }

        desc = irq_to_desc(i);         if (!desc)                 return 0;

        raw_spin_lock_irqsave(&desc->lock, flags);         for_each_online_cpu(j)                 any_count |= kstat_irqs_cpu(i, j);         action = desc->action;         if (!action && !any_count)                 goto out;

        seq_printf(p, "%*d: ", prec, i);         for_each_online_cpu(j)                 seq_printf(p, "%10u ", kstat_irqs_cpu(i, j));         seq_printf(p, " %8s", desc->chip->name);         seq_printf(p, "-%-8s", desc->name);

        if (action) {                 seq_printf(p, "  %s", action->name);                 while ((action = action->next) != NULL)                         seq_printf(p, ", %s", action->name);         }

        seq_putc(p, '\n'); out:         raw_spin_unlock_irqrestore(&desc->lock, flags);         return 0; }