Linux內核設計與實現 總結筆記(第七章)中斷和中斷處理

中斷和中斷處理

處理器的速度跟外圍硬件設備的速度每每再也不一個數量級上,所以,若是內核採起讓處理器向硬件發出一個請求。linux

而後專門等待迴應的辦法,若是專門等待迴應,明顯太慢。因此等待期間能夠處理其餘事務,等待完成了請求操做後,再回來進行處理。編程

因此內核提供了一種機制,讓內核在須要的時候再向內核發出信號。這就是中斷機制。dom

 

1、中斷

硬件中斷能夠隨時產生,所以,內核隨時可能由於新到來的中斷而被打斷。async

不一樣的設備對應的中斷不一樣,而每一箇中斷都經過一個惟一的數字標誌。ide

這些中斷值一般被稱爲中斷請求(IRQ)線。每一個IRQ線都會被關聯一個數值量。函數

 

異常:它產生時必須考慮處理器時鐘同步,經常也被稱爲同步中斷。this

在處理器執行到因爲編程失誤而致使的錯誤指令的時候,或者實在執行期間出現特殊狀況,必須靠內核來處理的時候,處理器就會產生一個異常。spa

 

2、中斷處理程序

在響應要給特定中斷的時候,內核會執行要給一個函數,該函數叫作中斷處理程序中斷服務例程(ISR)。線程

linux中斷程序是普通的C函數,不過必須按照特定的方式聲明,以便內核識別。指針

真正的區別在於:中斷處理程序是被內核調用來響應中斷的,而它們運行於中斷上下文的特殊上下文中。

中斷上下文偶爾也稱爲原子上下文,該上下文中的執行代碼不可阻塞。

中斷程序最好儘快反應,而且執行時間儘量短。

 

3、上半部與下半部的對比

通常把中斷程序分紅兩個部分,中斷處理程序是上半部(接收到一箇中斷,就當即執行,但只作有限的工做) 。

稍後完成的工做會推遲到下半部去,在適合的時機,下半部會被開中斷執行。

 

 

4、註冊中斷處理程序

驅動程序能夠經過request_irq()函數註冊終端處理程序,它被聲明再文件<linux/interrupt.h>中

/* request_irq:分配一條給定的中斷線 */
int request_irq(unsigned int irq,
                    irq_handler_t handler,
                    unsigned long flags,
                    const char *name,
                    void *dev)
/* 第一個參數irq表示要分配的中斷號 */
/* 第二個參數handler時一個指針,指向處理這個中斷的實際中斷處理程序 */

/* handler原型 */
typedef irqreturn_t (*irq_handler_t)(int, void *);
request_irq原型

 

4.1 中斷處理程序標誌

第三個參數flags能夠爲0,再文件<linux/interrupt.h>中定義了掩碼

IRQF_DISABLED:意味着內核在處理中斷程序自己期間,要禁止全部的其餘中斷

IRQF_SAMPLE_RANDOM:此標誌代表這個設備產生的中斷對內核熵池有貢獻

IRQF_TIMER:特別爲系統定時器的中斷處理而準備的

IRQF_SHARED:能夠在多箇中斷的處理程序之間的貢獻中斷線

第四個參數name是與中斷相關的設備的ASCII文本表示,在/proc/irq和/proc/interrupts

第五個參數dev用於共享中斷線,當一箇中斷處理程序釋放時,dev將提供標誌信息。若是不須要共享中斷線,設空(NULL)就行。

 

4.2 一箇中斷例程

request_irq();
if(request_irq(irqm, my_interrupt, IRQF_SHARED, "my_device", my_dev)) {
    printk(KERN_ERR "my_device: cannot register IRQ %d\n", irqn);
    return -EIO;
}
request_irq()
  • irqn是請求中斷線
  • my_interrupt是中斷處理程序
  • 設置IRQF_SHARED中斷能夠共享
  • 設備名爲"my_device"
  • 傳遞my_dev給dev形參

若是調用返回0,則說明成功。必須先初始化程序,而後再註冊中斷

 

4.3 釋放中斷處理程序

調用free_irq註銷相應的中斷處理程序。

void free_irq(unsigned int irq, void *dev)
free_irq

若是指定的中斷線不是共享的,那麼函數刪除動做的同時會禁用此中斷線

若是設置了共享標誌,那麼函數刪除僅僅對dev對應的處理程序,除非刪除了最後一個處理程序後,中斷線纔會被禁用。

 

5、編寫中斷處理程序

/* irq:參數已經沒有太大意義了 */
/* dev:一個通用指針,它與中斷處理程序註冊時傳遞的必須一致 */
static irqreturn_t intr_handler(int irq, void *dev)
irqreturn_t intr_handler

返回值是一個特殊類型:irqreturn_t,可能返回兩個特殊的值:IRQ_NONE和IRQ_HANDLED

重入和中斷處理程序:中斷程序是不須要重入的,程序中斷時,其餘中斷會被屏蔽,並且也不會重複嵌套。

 

5.1 共享中斷處理程序

共享和非共享處理程序比較類似,可是差別主要有三處:

  • request_irq()參數flags必須設置IRQF_SHARED標誌
  • 對於每一個註冊的中斷程序,dev參數必須惟一,中斷處理程序會用到這個值
  • 中斷處理程序必須可以區分它的設備是否真的產生了中斷。不然沒法判斷哪一個設備發出的中斷請求。

任何一個設備沒有按規則進行共享,那麼中斷就沒法共享了。

 

5.2 中帶能處理程序實例

在driver/char/rtc.c中有RTC相關的例子。中斷髮生時,報警器或定時器就會啓動。

當RTC驅動程序裝載時,rtc_init()會被調用。

/* 對rtc_irq註冊rtc_interrupt */
if(request_irq(rtc_irq, rtc_interrupt, IRQF_SHARED, "rtc", (void *)&rtc_port)) {
    printk(KERN_ERR "rtc: cannot register IRQ %d\n", rtc_irq);
    return -EIO;
}
註冊rtc_interrupt

第一個參數說明RTC位於IRQ8,中斷程序爲rtc_interrupt,而且設置了共享中斷線,程序名爲"rtc"

static irqreturn_t rtc_interrupt(int irq, void *dev)
{
    /*
     * 能夠是報警中孤單、更新完成的中斷或週期性中斷
     * 咱們把裝填保存在rtc_irq_data的低字節中,
     * 而把從最後一次讀取以後所接收的中斷號保存在其他字節中
     */
    spin_lock(&rtc_lick);

    rtc_irq_data += 0x100;
    rtc_irq_data &= ~0xff;
    rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0);

    if(rtc_status & RTC_TIMER_ON)
        mod_timer(&rtc_irq_timer, jiffies + HZ/rtq_freq + 2*HZ/100);

    spin_unlock(&rtc_lock);

    /*
     * 如今執行其他的操做
     */
    spin_lock(&rtc_task_lock);
    if(rtc_callback)
        rtc_callback->func(rtc_callback->private_data);
    spin_unlock(&rtc_task_lock);
    wake_up_interruptible(&rtc_wait);

    kill_fasync(&rtc_async_queue, SIGIO, POLL_IN);

    return IRQ_HANDLED;
}
中斷處理程序

 使用自旋鎖,保證不會被其餘處理器同時訪問。

rtc_irq_data存放RTC有關的信息。

後面部分,則是可能運行預先設置好的回調函數。每一個RTC驅動程序容許註冊要給回調函數,在中斷到來時執行。

最後返回IRQ_HANDLED,代表已經正確完成對此設備的操做。

 

6、中斷上下文

當執行一箇中斷處理程序時,內核處於中斷上下文中。

中斷上下問和進程沒有什麼瓜葛。

中斷上下文具備較爲嚴格的時間限制,由於它打斷了其餘代碼。

中斷處理程序棧的設置是要給配置選項,32位8KB,64位16KB 。

總而言之,儘可能節約內核棧空間。

7、中斷處理機制的是實現

中斷髮生,經過電信號傳遞給處理器,除非屏蔽了此中斷,不然處理器會當即執行。

中斷旅程開始於預約義入口,每條中斷線,處理器都會跳到對應的一個惟一的位置,這樣內核就知道接收的IRQ中斷號。

unsigned int do_IRQ(struct pt_regs regs)
do_IRQ()聲明

若是do_IRQ確保在中斷線上有一個有效的處理程序,並且程序已經啓動。do_IRQ()就調用handle_IRQ_event()來運行爲這條

中斷線所安裝的中斷處理程序。在文件kernel/irq/handler.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:
                /*
                 * 把返回值設置爲已處理,以即可疑的檢查再也不觸發
                 */
                ret = IRQ_HANDLED;

                /*
                 * 捕獲返回值爲WAKE_THREAD的驅動程序,可是並不建立一個線程函數
                 */
                if(unlikely(!action->thread_fn)) {
                    warn_no_thread(irq, action);
                    break;
                }

                /*
                 * 爲此次中斷喚醒處理線程。萬一線程崩潰且被殺死,咱們僅僅僞裝已經處理了該中斷。上述的硬件中斷(hardirq)處理程序已經進制設備中斷,所以杜絕irq產生
                 */
                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;
}
handle_IRQ_event()

 

8、/proc/interrupts

chen@chen-K42F:~/Downloads/4412/linux-4.14.12$ cat /proc/interrupts 
           CPU0       CPU1       
  0:         18          0   IO-APIC   2-edge      timer
  1:      25244          0   IO-APIC   1-edge      i8042
  9:         11         12   IO-APIC   9-fasteoi   acpi
 12:     207590      21487   IO-APIC  12-edge      i8042
 16:     197581     103440   IO-APIC  16-fasteoi   ehci_hcd:usb1
 17:    1087739     322896   IO-APIC  17-fasteoi   ath9k
 19:          0          0   IO-APIC  19-fasteoi   i801_smbus
 23:         15         26   IO-APIC  23-fasteoi   ehci_hcd:usb2
 24:      79350      28510   PCI-MSI 327680-edge      xhci_hcd
cat /proc/interrupt部分結果

第一列時中斷線,第二列時一個接收中斷的計數器,第三列時處理這個中斷的中斷控制器

 

9、中斷控制

相關文件在<asm/system.h> <asm/irq.h>中找到

9.1 禁止和激活中斷

用於禁止當前處理器上的本地中斷,隨後再就激活他們的語句。

local_irq_disable();
/* 禁止中斷 */
local_irq_enable();

 

9.2 進制指定中斷線

對中斷的狀態操做以前禁止設備中斷的傳遞,linux提供了四個接口

void disable_irq(unsigned int irq);
void disable_irq_nosync(unsigned int irq);
void enable_irq(unsigned int irq);
void synchronize_irq(unsigned int irq);
四種方法

 

9.3 中斷系統狀態

一般有必要了解中斷系統的狀態,或者你當前是否正處於中斷上下文的執行狀態中。

宏irqs_disable()定義在<asm/system.h>

若是本地處理器上的中斷系統被禁止,則返回非0,不然返回0

在<linux/hardirq.h>

local_irq_disable()  禁止本地中斷傳遞

local_irq_enable()  激活本地中斷傳遞

local_irq_save()  保存本地中斷傳遞的當前狀態,而後禁止本地中斷傳遞

local_irq_restore()  恢復本地中斷傳遞到給定的狀態

disable_irq()    禁止給定中斷線,並確保該函數返回以前在該中斷線上沒有處理程序再運行

disable_irq_nosync()  禁止給定中斷線

enable_irq()    激活給定中斷線

irqs_disabled()    若是本地中斷傳遞被禁止,則返回非0,不然返回0

in_interrupt()    若是在中斷上下文中,則返回非0,若是在進程上下文中,則返回0

in_irq()    若是當前正在執行中斷處理程序,則返回非0,不然返回0

相關文章
相關標籤/搜索